restore.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. # encoding: utf-8
  2. import os
  3. import yaml
  4. from .color import GB
  5. from . import ocboot
  6. from .db import DB
  7. from .cmd import run_bash_cmd, ensure_pv
  8. from .utils import to_yaml
  9. from .utils import print_title
  10. ROOT_PATH = os.path.dirname(os.path.abspath(os.path.dirname(__file__)))
  11. PV_ARGS = 'pv --rate --timer --eta --progress'
  12. EXTRA_CMDS = []
  13. def add_command(subparsers):
  14. parser = subparsers.add_parser(
  15. "restore", help="restore onecloud cluster")
  16. parser.add_argument('--backup-path', dest='backup_path', help="backup path, default: /opt/backup", default="/opt/backup")
  17. parser.add_argument('--install-db-to-localhost', action='store_true', help="use this option when install local db")
  18. parser.add_argument('primary_ip', help="primary node ip")
  19. parser.add_argument('--master-node-ips', help="master nodes ips, seperated by comma ','")
  20. parser.add_argument('--master-node-as-host', action='store_true', help="use this option when use master nodes as host")
  21. parser.add_argument('--worker-node-ips', help="worker nodes ips, seperated by comma ','")
  22. parser.add_argument('--worker-node-as-host', action='store_true', help="use this option when use worker nodes as host")
  23. parser.add_argument('--mysql-host', help="mysql host; not needed if set --install-db-to-localhost")
  24. parser.add_argument('--mysql-user', default='root', help="mysql user, default: root; not needed if set --install-db-to-localhost")
  25. parser.add_argument('--mysql-password', help="mysql password; not needed if set --install-db-to-localhost")
  26. parser.add_argument('--mysql-port', default=3306, help="mysql port, default: 3306; not needed if set --install-db-to-localhost", type=int)
  27. parser.set_defaults(func=do_restore)
  28. def get_backup_db_password(args):
  29. config_file = os.path.join(args.backup_path, 'config.yml')
  30. assert os.path.exists(config_file), "the config file " + config_file + " dose not exist!"
  31. config = ocboot.load_config(config_file)
  32. primary_master_config = getattr(config, 'primary_master_config', None)
  33. db_password = getattr(primary_master_config, 'db_password', None)
  34. assert db_password, "the backup database password must not be empty!"
  35. return db_password
  36. db_config = {}
  37. def ensure_db_config(args):
  38. global db_config
  39. if db_config:
  40. return db_config
  41. ret = {}
  42. ip = args.primary_ip
  43. if args.install_db_to_localhost:
  44. ret['host'] = ip
  45. elif args.mysql_host:
  46. ret['host'] = args.mysql_host
  47. else:
  48. raise Exception("please set --install-db-to-localhost or --mysql-host option.")
  49. ret['port'] = args.mysql_port or '3306'
  50. ret['user'] = args.mysql_user or 'root'
  51. ret['passwd'] = args.mysql_password
  52. if not args.mysql_password:
  53. ret['passwd'] = get_backup_db_password(args)
  54. if args.install_db_to_localhost:
  55. # install
  56. import time
  57. mycnf = os.path.join(ROOT_PATH, 'onecloud/roles/mariadb/files/my.cnf')
  58. timestamp = str(time.time())
  59. bak_cnf = '/etc/my.cnf.%(timestamp)s' % locals()
  60. assert os.path.exists(mycnf)
  61. run_bash_cmd('''systemctl is-active mariadb > /dev/null || yum install -y mariadb-server && systemctl enable --now mariadb''')
  62. run_bash_cmd('''
  63. mv -fv /etc/my.cnf %(bak_cnf)s && cp -fv %(mycnf)s /etc/my.cnf && sudo systemctl restart mariadb''' % locals())
  64. # set password
  65. sqls = [
  66. '''grant all privileges on *.* to `%(user)s`@`localhost` identified by "%(passwd)s" with grant option''' % ret,
  67. '''grant all privileges on *.* to `%(user)s`@`%%` identified by "%(passwd)s" with grant option''' % ret,
  68. '''FLUSH PRIVILEGES''',
  69. '''set global net_buffer_length=999424 ''',
  70. '''set global max_allowed_packet=999999488 ''',
  71. '''set global group_concat_max_len=999999488 ''',
  72. ]
  73. db = DB()
  74. for sql in sqls:
  75. db.cursor.execute(sql)
  76. print("mysql init done.")
  77. db_config.update(ret)
  78. return db_config
  79. def source_db_files(args, tgz, db='', dry_run=False):
  80. db_args = '-h "%(host)s" -u "%(user)s" -p"%(passwd)s" -P "%(port)s"' % ensure_db_config(args)
  81. cmd = '''%(PV_ARGS)s --name " Restoring %(tgz)s " "%(tgz)s" | gunzip | mysql %(db_args)s %(db)s''' % {
  82. 'PV_ARGS': PV_ARGS,
  83. 'tgz': tgz,
  84. 'db_args': db_args,
  85. 'db': db,
  86. }
  87. if dry_run:
  88. return cmd
  89. else:
  90. run_bash_cmd(cmd)
  91. def restore_db(args):
  92. global EXTRA_CMDS
  93. from glob import glob
  94. os.chdir(args.backup_path)
  95. db_file_tar = os.path.join(args.backup_path, 'onecloud.sql.gz')
  96. source_db_files(args, db_file_tar)
  97. extra_files = glob('%s/*.sql.gz' % args.backup_path)
  98. extra_files = [i for i in extra_files if not i.endswith('/onecloud.sql.gz')]
  99. if extra_files:
  100. for i in extra_files:
  101. EXTRA_CMDS += [source_db_files(args, i, i.split('/')[-1].split('.')[0], dry_run=1)]
  102. print_title("Restore DB OK.")
  103. def generate_nodes(args, role):
  104. '''
  105. master_nodes:
  106. hosts:
  107. - hostname: $host1
  108. user: root
  109. use_local: false
  110. controlplane_host: $pri
  111. controlplane_port: "6443"
  112. as_controller: true
  113. registry_mirrors:
  114. - https://lje6zxpk.mirror.aliyuncs.com
  115. '''
  116. assert role in ['master', 'worker']
  117. ips = getattr(args, '%s_node_ips' % role)
  118. if not ips:
  119. return {}
  120. as_host = getattr(args, '%s_node_as_host' % role)
  121. vars = {
  122. 'controlplane_host': args.primary_ip,
  123. 'controlplane_port': "6443",
  124. 'as_controller': True,
  125. 'as_host': as_host,
  126. 'registry_mirrors': ['https://lje6zxpk.mirror.aliyuncs.com'],
  127. }
  128. nodes = []
  129. for i in ips.split(','):
  130. node = {
  131. 'hostname': i,
  132. 'user': 'root',
  133. }
  134. nodes += [node]
  135. vars['hosts'] = nodes
  136. return vars
  137. def restore_config(args):
  138. config_file = os.path.join(args.backup_path, 'config.yml')
  139. config_json = None
  140. with open(config_file, 'r') as stream:
  141. try:
  142. config_json = yaml.safe_load(stream)
  143. except yaml.YAMLError as exc:
  144. print(exc)
  145. # databases
  146. db_config = ensure_db_config(args)
  147. config_json['primary_master_node'].update({
  148. 'db_host': db_config.get('host'),
  149. 'db_password': db_config.get('passwd'),
  150. 'db_port': db_config.get('port'),
  151. 'db_user': db_config.get('user'),
  152. })
  153. config_json['mariadb_node'] = {
  154. "db_password": db_config.get('passwd'),
  155. "db_user": db_config.get('user'),
  156. "hostname": db_config.get('host'),
  157. "user": 'root',
  158. }
  159. # primary node ip update
  160. # if config_json['primary_master_node'].get('controlplane_host') != config_json['primary_master_node'].get('node_ip'):
  161. # raise Exception("Primary controlplane_host and node_ip are different. Not supported backup mode!")
  162. config_json['primary_master_node'].update({
  163. 'controlplane_host': args.primary_ip,
  164. 'node_ip': args.primary_ip,
  165. 'ip_autodetection_method': 'can-reach=%s' % args.primary_ip,
  166. 'restore_mode': True # very important for restoring.
  167. })
  168. for i in ['keepalived_version_tag', 'host_networks', 'image_repository', 'insecure_registries']:
  169. if i in config_json['primary_master_node']:
  170. del config_json['primary_master_node'][i]
  171. for i in ['master_node_ips', 'worker_nodes']:
  172. if i in config_json:
  173. del config_json[i]
  174. if args.master_node_ips:
  175. config_json['master_nodes'] = generate_nodes(args, 'master')
  176. if args.worker_node_ips:
  177. config_json['worker_nodes'] = generate_nodes(args, 'worker')
  178. yaml_content = ''
  179. for i in ['mariadb_node', 'primary_master_node', 'master_nodes', 'worker_nodes']:
  180. if i in config_json:
  181. yaml_content += to_yaml({i: config_json[i]})
  182. config_output = os.path.join(ROOT_PATH, 'config.yml')
  183. os.chdir(ROOT_PATH)
  184. with open('config.yml', 'w') as f:
  185. try:
  186. f.write(yaml_content)
  187. except IOError as e:
  188. print('write yaml config to %s error: %s!' % (config_file, e))
  189. run_bash_cmd(""" sed -i -e 's@^ iso_install_mode: true@# iso_install_mode: true@' '%s' """ % config_file)
  190. def helper():
  191. print('\n')
  192. print(GB("All done! please restore the k8s system by running:"))
  193. print("%s/run.py %s/config.yml" % (ROOT_PATH, ROOT_PATH))
  194. print
  195. if EXTRA_CMDS:
  196. print(GB('After the k8s is restored, please source the extra db tables:'))
  197. print('\n'.join(EXTRA_CMDS))
  198. def do_restore(args):
  199. ensure_pv()
  200. print_title('Restore DB')
  201. restore_db(args)
  202. print_title('Restore Config')
  203. restore_config(args)
  204. helper()