#!/usr/bin/env python3 """ OpenStack project multicloud migrator Usage example: * ./project-migrator.py --source-openrc ~/c/prod-einfra_cz_migrator.sh.inc --destination-openrc ~/c/g2-prod-brno-einfra_cz_migrator.sh.inc --project-name meta-cloud-new-openstack --validation-a-source-server-id <> --ceph-migrator-sshkeyfile $HOME/.ssh/id_rsa.LenovoThinkCentreE73 * ./project-migrator.py --source-openrc ~/c/prod-einfra_cz_migrator.sh.inc --destination-openrc ~/c/g2-prod-brno-einfra_cz_migrator.sh.inc --project-name meta-cloud-new-openstack --validation-a-source-server-id <> --ceph-migrator-sshkeyfile $HOME/.ssh/id_rsa.LenovoThinkCentreE73 --explicit-server-names freznicek-rook-internal-external-20-worker-1 * """ import argparse import json import logging import math import os import os.path import pprint import re import sys import time import xmltodict import paramiko import keystoneauth1.session from keystoneauth1.identity import v3 from openstack import connection from keystoneauth1 import session def wait_for_keypress(msg="Press Enter to continue..."): """ """ return input("Press Enter to continue...") def normalize_servers(servers): """ list of server names/IDs separated by space of comma returned as list of strings or None """ if isinstance(servers, str) and servers: return servers.replace(","," ").split() return None def trim_dict(dict_data, allowed_keys=None, denied_keys=None): """ transform input dictionary and filter its keys with allowed_keys and denied_keys sequences """ int_allowed_keys = allowed_keys if allowed_keys else tuple() int_denied_keys = denied_keys if denied_keys else tuple() if int_allowed_keys: return {i_key: dict_data[i_key] for i_key in dict_data if i_key in int_allowed_keys} elif int_denied_keys: return {i_key: dict_data[i_key] for i_key in dict_data if i_key not in int_denied_keys} return dict_data def get_destination_network(source_network): """ LUT for networks """ network_mapping = { # shared "78-128-250-pers-proj-net" : "internal-ipv4-general-private", "147-251-115-pers-proj-net" : "internal-ipv4-general-private", "public-muni-v6-432" : "external-ipv6-general-public", # external "public-muni-147-251-21-GROUP": "external-ipv4-general-public", "public-cesnet-78-128-250-PERSONAL": "external-ipv4-general-public", "public-cesnet-78-128-251-GROUP": "external-ipv4-general-public", "provider-public-cerit-sc-147-251-253": "external-ipv4-general-public", "public-muni-147-251-115-PERSONAL": "external-ipv4-general-public", "public-muni-147-251-124-GROUP": "external-ipv4-general-public", "public-cesnet-195-113-167-GROUP": "external-ipv4-general-public", "public-muni-147-251-11-128-254": "external-ipv4-general-public", "public-muni-CERIT-FI-147-251-88-132-254": "external-ipv4-general-public", "public-muni-CSIRT-MU-217-69-96-64-240": "external-ipv4-general-public", "public-muni-csirt-147-251-125-16-31": "external-ipv4-general-public", "provider-public-cerit-sc-147-251-254": "external-ipv4-general-public", # group project internal network "group-project-network": "group-project-network" } if source_network in network_mapping.keys(): return network_mapping[source_network] return None def get_destination_subnet(source_subnet): """ LUT for networks """ subnet_mapping = { # TODO: shared # group project internal network "group-project-network-subnet": "group-project-network-subnet" } if source_subnet in subnet_mapping.keys(): return subnet_mapping[source_subnet] return None def get_destination_router(source_router): """ LUT for networks """ router_mapping = { # TODO: shared # group project internal network "router": "group-project-router" } if source_router in router_mapping.keys(): return router_mapping[source_router] return None def get_destination_flavor(source_flavor): """ LUT for flavors """ flavor_mapping = { #'eph.16cores-60ram' # nemusime resit neni pouzit u zadneho projektu v g1 #'eph.8cores-30ram': 'c2.8core-30ram' # nemusime resit neni pouzit u zadneho projektu v g1 #'eph.8cores-60ram': 'c3.8core-60ram' # nemusime resit neni pouzit u zadneho projektu v g1 'hdn.cerit.large-35ssd-ephem': 'p3.4core-8ram', # nesedi velikost disku v G2 je 80 misto 35 'hdn.cerit.large-ssd-ephem': 'p3.4core-8ram', # ok 'hdn.cerit.medium-35ssd-ephem': 'p3.2core-4ram', # nesedi velikost disku v G2 je 80 misto 35 'hdn.cerit.xxxlarge-ssd-ephem': 'p3.8core-60ram', # ok #'hdn.medium-ssd-ephem': # nemusime resit neni pouzit u zadneho projektu v g1 'hpc.12core-64ram-ssd-ephem-500': 'c3.12core-64ram-ssd-ephem-500', # neni v G2 a je potreba 'hpc.16core-128ram': 'c3.16core-128ram', # neni v G2 a je potreba 'hpc.16core-256ram': 'c3.16core-256ram', # neni v G2 a je potreba 'hpc.16core-32ram': 'c2.16core-30ram', # ok 'hpc.16core-32ram-100disk': 'c3.16core-32ram-100disk', # neni v G2 a je potreba 'hpc.16core-64ram-ssd-ephem': 'hpc.16core-64ram-ssd', # neni v G2 a je potreba 'hpc.16core-64ram-ssd-ephem-500': 'p3.16core-60ram', # ok 'hpc.18core-48ram': '', # neni v G2 a je potreba 'hpc.18core-64ram-dukan': 'c2.24core-60ram', # nemusime resit 'hpc.24core-96ram-ssd-ephem': 'hpc.24core-96ram-ssd', # nemusime resit 'hpc.30core-128ram-ssd-ephem-500': 'c3.30core-128ram-ssd-ephem-500', # neni v G2 a je potreba 'hpc.30core-256ram': 'c3.30core-256ram', # neni v G2 a je potreba 'hpc.30core-64ram': 'c3.32core-60ram', # v G2 je o 2 CPU vic 'hpc.4core-16ram-ssd-ephem': 'p3.4core-16ram', # ok 'hpc.4core-16ram-ssd-ephem-500': 'p3.4core-16ram', # ok 'hpc.4core-4ram': 'e1.medium', # nemusime resit 'hpc.8core-128ram': 'c3.8core-128ram', # neni v G2 a je potreba 'hpc.8core-16ram': 'c2.8core-16ram', # ok 'hpc.8core-16ram-ssd-ephem': 'p3.8core-16ram', # nemusime resit 'hpc.8core-256ram': None, # nemusime resit 'hpc.8core-32ram-dukan': 'c2.8core-30ram', # nemusime resit 'hpc.8core-32ram-ssd-ephem': 'p3.8core-30ram', # ok 'hpc.8core-32ram-ssd-rcx-ephem': 'p3.8core-30ram', # ok 'hpc.8core-64ram-ssd-ephem-500': 'p3.8core-60ram', # ok 'hpc.8core-8ram': 'e1.1xlarge', # v G2 je o 20 GB mensi disk 'hpc.hdh-ephem': 'hpc.hdh', # neni a je potreba 'hpc.hdn.30core-128ram-ssd-ephem-500': 'c3.hdn.30core-128ram-ssd-ephem-500', # neni potreba 'hpc.hdn.4core-16ram-ssd-ephem-500': 'p3.4core-16ram', # neni potreba #'hpc.ics-gladosag-full': 'c3.ics-gladosag-full', # neni potreba 'hpc.large': 'g2.3xlarge', # ok 'hpc.medium': 'c2.8core-30ram', # ok 'hpc.small': 'c2.4core-16ram', # ok 'hpc.xlarge': None, # neni v G2 'hpc.xlarge-memory': 'c3.xlarge-memory', # neni v G2 'standard.16core-32ram': 'g2.2xlarge', # ok 'standard.20core-128ram': 'e1.20core-128ram', # neni potreba 'standard.20core-256ram': 'e1.20core-256ram', # neni v G2 'standard.2core-16ram': 'c3.2core-16ram', # ok 'standard.large': 'e1.large', # ok pripadne jeste c3.4core-8ram 'standard.medium': 'e1.medium', # o 2 vice CPU 'standard.memory': 'c3.2core-30ram', # pripadne i c2.2core-30ram 'standard.one-to-many': 'c3.24core-60ram', # v G2 je o 4 vice CPU 'standard.small': 'e1.small', # 2x vice ram a CPU u G2 'standard.tiny': 'e1.tiny', # 2x vice ram a CPU u G2 'standard.xlarge': 'e1.2xlarge', # o 4 vice CPU G2 'standard.xlarge-cpu': 'e1.2xlarge', # ok 'standard.xxlarge': 'c2.8core-30ram', # ok 'standard.xxxlarge': 'c3.8core-60ram' # ok } assert source_flavor in flavor_mapping, "Source flavor can be mapped to destination one" assert flavor_mapping[source_flavor], "Source flavor mapping is not valid" return flavor_mapping[source_flavor] def normalize_table_data_field(data_field): """ normalize single data field (single data insert) """ int_dict = {} i_name_key = '@name' for i_data_field_item in data_field: i_value_key = [ i_k for i_k in i_data_field_item.keys() if i_k != i_name_key][0] int_dict[i_data_field_item[i_name_key]] = i_data_field_item[i_value_key] return int_dict def normalize_table_data(data): """ normalize whole table data """ int_list = [] for i_data_field in data: int_list.append(normalize_table_data_field(i_data_field['field'])) return int_list def get_openrc(file_handle): """ parse and return OpenRC file """ openrc_vars = {} for line in file_handle: match = re.match(r'^export (\w+)=(.+)$', line.strip()) if match: openrc_vars[match.group(1)] = match.group(2).strip('"') return openrc_vars def get_ostack_connection(openrc_vars): """ """ auth_args = { 'auth_url': openrc_vars.get('OS_AUTH_URL'), 'username': openrc_vars.get('OS_USERNAME'), 'password': openrc_vars.get('OS_PASSWORD'), 'project_name': openrc_vars.get('OS_PROJECT_NAME'), 'project_domain_name': openrc_vars.get('OS_PROJECT_DOMAIN_NAME'), 'user_domain_name': openrc_vars.get('OS_USER_DOMAIN_NAME'), 'project_domain_id': openrc_vars.get('OS_PROJECT_DOMAIN_ID'), 'user_domain_id': openrc_vars.get('OS_USER_DOMAIN_ID'), } connection_args = { 'compute_api_version': openrc_vars.get('OS_COMPUTE_API_VERSION'), 'identity_api_version': openrc_vars.get('OS_IDENTITY_API_VERSION'), 'volume_api_version': openrc_vars.get('OS_VOLUME_API_VERSION') } auth = v3.Password(**auth_args) ostack_sess = session.Session(auth=auth) ostack_conn = connection.Connection(session=ostack_sess, **connection_args) return ostack_conn def get_ostack_project(ostack_connection, project_name): project = None for i_project in ostack_connection.list_projects(): if i_project.name == project_name: project = i_project return project def get_ostack_project_type(ostack_connection, project): """ detect project type, return 'group' / 'personal' / 'other' """ if project.name in [ i_user.name for i_user in ostack_connection.list_users() ]: return "personal" return "group" def get_ostack_project_security_groups(ostack_connection, project=None): security_groups = [] if project: for i_security_group in ostack_connection.network.security_groups(): if i_security_group.tenant_id == project.id: security_groups.append(i_security_group) return security_groups else: return tuple(ostack_connection.network.security_groups()) def get_ostack_project_keypairs(ostack_connection, project=None): return ostack_connection.list_keypairs() def get_ostack_project_keypairs2(ostack_connection, project=None): return list(ostack_connection.compute.keypairs()) def get_ostack_project_servers(ostack_connection, project=None): return tuple(ostack_connection.compute.servers()) def get_ostack_project_volumes(ostack_connection, project=None): return ostack_connection.block_store.volumes() def get_ostack_project_flavors(ostack_connection, project=None): return tuple(ostack_connection.compute.flavors()) def get_resource_details(resources): """ inspect resources """ for i_resource in resources: print(i_resource) pprint.pprint(i_resource) def remote_cmd_exec(hostname, username, key_filename, command): """ executes remote command, returs stdout, stderr and exit-code or Exception """ # Create SSH client ssh_client = paramiko.SSHClient() # Automatically add untrusted hosts ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ecode = None try: # Connect to the remote host pkey = paramiko.RSAKey.from_private_key_file(key_filename) ssh_client.connect(hostname, username=username, pkey=pkey, look_for_keys=False) # Execute the command, read the output and close stdin, stdout, stderr = ssh_client.exec_command(command) output = stdout.read().decode().strip() error = stderr.read().decode().strip() ecode = stdout.channel.recv_exit_status() ssh_client.close() return output, error, ecode except Exception as e: print("Error:", e) return None, None, e def get_ceph_client_name(args, ceph_src_pool_name, ceph_dst_pool_name=None): int_pool_name = ceph_dst_pool_name if ceph_dst_pool_name else ceph_src_pool_name return "client.cinder" if int_pool_name in (args.source_ceph_cinder_pool_name, args.source_ceph_ephemeral_pool_name,) else "client.migrator" def ceph_rbd_images_list(args, pool_name): """ """ stdout, stderr, ecode = remote_cmd_exec(args.ceph_migrator_host, args.ceph_migrator_user, args.ceph_migrator_sshkeyfile.name, f"/root/migrator/ceph-rbd-images-list.sh {pool_name}") assert stdout, f"RBD pool ({pool_name}) images received successfully (non-empty RBD list)" assert ecode == 0, f"RBD pool ({pool_name}) images received successfully (ecode)" return stdout.splitlines() def ceph_rbd_image_info(args, pool_name, rbd_image_name): """ get ceph RBD image information """ ceph_client_name = get_ceph_client_name(args, pool_name) stdout, stderr, ecode = remote_cmd_exec(args.ceph_migrator_host, args.ceph_migrator_user, args.ceph_migrator_sshkeyfile.name, f"CEPH_USER={ceph_client_name} /root/migrator/ceph-rbd-image-info.sh {pool_name} {rbd_image_name}") return json.loads(stdout), stderr, ecode def ceph_rbd_image_exists(args, pool_name, rbd_image_name): """ detect whether RBD image {pool_name}/{rbd_image_name} exists """ ceph_client_name = get_ceph_client_name(args, pool_name) stdout, stderr, ecode = remote_cmd_exec(args.ceph_migrator_host, args.ceph_migrator_user, args.ceph_migrator_sshkeyfile.name, f"CEPH_USER={ceph_client_name} /root/migrator/ceph-rbd-image-exists.sh {pool_name} {rbd_image_name}") return stdout.splitlines(), stderr, ecode def ceph_rbd_image_delete(args, pool_name, rbd_image_name): """ delete RBD image {pool_name}/{rbd_image_name} """ ceph_client_name = get_ceph_client_name(args, pool_name) stdout, stderr, ecode = remote_cmd_exec(args.ceph_migrator_host, args.ceph_migrator_user, args.ceph_migrator_sshkeyfile.name, f"CEPH_USER={ceph_client_name} /root/migrator/ceph-rbd-image-delete.sh {pool_name} {rbd_image_name}") return stdout.splitlines(), stderr, ecode def ceph_rbd_image_flatten(args, pool_name, rbd_image_name): """ flatten RBD image {pool_name}/{rbd_image_name} """ ceph_client_name = get_ceph_client_name(args, pool_name) stdout, stderr, ecode = remote_cmd_exec(args.ceph_migrator_host, args.ceph_migrator_user, args.ceph_migrator_sshkeyfile.name, f"CEPH_USER={ceph_client_name} /root/migrator/ceph-rbd-image-flatten.sh {pool_name} {rbd_image_name}") return stdout.splitlines(), stderr, ecode def ceph_rbd_image_clone(args, src_pool_name, src_rbd_image_name, src_rbd_image_snapshot_name, dst_pool_name, dst_rbd_image_name): """ clone RBD image {src_pool_name}/{src_rbd_image_name}@{src_rbd_image_snapshot_name} -> {dst_pool_name}/{dst_rbd_image_name}""" ceph_client_name = get_ceph_client_name(args, src_pool_name, dst_pool_name) stdout, stderr, ecode = remote_cmd_exec(args.ceph_migrator_host, args.ceph_migrator_user, args.ceph_migrator_sshkeyfile.name, f"CEPH_USER={ceph_client_name} /root/migrator/ceph-rbd-image-clone.sh {src_pool_name} {src_rbd_image_name} {src_rbd_image_snapshot_name} {dst_pool_name} {dst_rbd_image_name}") return stdout.splitlines(), stderr, ecode def ceph_rbd_image_copy(args, src_pool_name, src_rbd_image_name, dst_pool_name, dst_rbd_image_name): """ copy RBD image {src_pool_name}/{src_rbd_image_name} -> {dst_pool_name}/{dst_rbd_image_name}""" ceph_client_name = get_ceph_client_name(args, src_pool_name, dst_pool_name) cmd = f"CEPH_USER={ceph_client_name} /root/migrator/ceph-rbd-image-copy.sh {src_pool_name} {src_rbd_image_name} {dst_pool_name} {dst_rbd_image_name}" stdout, stderr, ecode = remote_cmd_exec(args.ceph_migrator_host, args.ceph_migrator_user, args.ceph_migrator_sshkeyfile.name, cmd) return stdout.splitlines(), stderr, ecode def ceph_rbd_image_snapshot_exists(args, pool_name, rbd_image_name, rbd_image_snapshot_name): """ detect whether RBD image snapshot {pool_name}/{rbd_image_name}@{rbd_image_snapshot_name} exists """ ceph_client_name = get_ceph_client_name(args, pool_name) stdout, stderr, ecode = remote_cmd_exec(args.ceph_migrator_host, args.ceph_migrator_user, args.ceph_migrator_sshkeyfile.name, f"CEPH_USER={ceph_client_name} /root/migrator/ceph-rbd-image-snapshot-exists.sh {pool_name} {rbd_image_name} {rbd_image_snapshot_name}") return stdout.splitlines(), stderr, ecode def ceph_rbd_image_snapshot_create(args, pool_name, rbd_image_name, rbd_image_snapshot_name): """ create RBD image snapshot {pool_name}/{rbd_image_name}@{rbd_image_snapshot_name} """ ceph_client_name = get_ceph_client_name(args, pool_name) stdout, stderr, ecode = remote_cmd_exec(args.ceph_migrator_host, args.ceph_migrator_user, args.ceph_migrator_sshkeyfile.name, f"CEPH_USER={ceph_client_name} /root/migrator/ceph-rbd-image-snapshot-create.sh {pool_name} {rbd_image_name} {rbd_image_snapshot_name}") return stdout.splitlines(), stderr, ecode def ceph_rbd_image_snapshot_delete(args, pool_name, rbd_image_name, rbd_image_snapshot_name): """ delete RBD image snapshot {pool_name}/{rbd_image_name}@{rbd_image_snapshot_name} """ ceph_client_name = get_ceph_client_name(args, pool_name) stdout, stderr, ecode = remote_cmd_exec(args.ceph_migrator_host, args.ceph_migrator_user, args.ceph_migrator_sshkeyfile.name, f"CEPH_USER={ceph_client_name} /root/migrator/ceph-rbd-image-snapshot-delete.sh {pool_name} {rbd_image_name} {rbd_image_snapshot_name}") return stdout.splitlines(), stderr, ecode def assert_entity_ownership(entities, project): """ """ for i_entity in entities: assert i_entity.project_id == project.id, f"Entity belongs to expected project (name:{project.name}, id: {project.id})" def get_source_keypairs(args): """ """ reply_stdout, reply_stderr, reply_ecode = remote_cmd_exec(args.ceph_migrator_host, args.ceph_migrator_user, args.ceph_migrator_sshkeyfile.name, f"cat {args.source_keypair_xml_dump_file}") assert reply_ecode == 0, "Keypairs received" table_dictdata = xmltodict.parse(reply_stdout) table_data_dictdata = table_dictdata['mysqldump']['database']['table_data']['row'] return normalize_table_data(table_data_dictdata) def get_source_keypair(keypairs, keypair_name, user_id): """ """ keypairs_selected = [ i_keypair for i_keypair in keypairs if i_keypair.get("name", "") == keypair_name and i_keypair.get("user_id", "") == user_id ] if keypairs_selected: return keypairs_selected[0] return None def create_keypair(ostack_connection, keypair): """ """ return ostack_connection.compute.create_keypair(name=keypair['name'], public_key=keypair['public_key'], type=keypair['type']) def log_or_assert(args, msg, condition, trace_details=None): """ log and assert """ if not condition: with open(args.exception_trace_file, "w") as file: file.write(f"{msg}\n{pprint.pformat(trace_details)}\n\n{locals()}\n") assert condition, msg args.logger.info(msg) def wait_for_ostack_server_status(ostack_connection, server_name_or_id, server_status, timeout=120): """ """ int_start_timestamp = time.time() int_server = ostack_connection.compute.find_server(server_name_or_id) int_server_status = None while True: if time.time() > (int_start_timestamp + timeout): break int_server_status = ostack_connection.compute.find_server(int_server.id).status if int_server_status == server_status: break return int_server_status def wait_for_ostack_volume_status(ostack_connection, volume_name_or_id, volume_status, timeout=120): """ """ int_start_timestamp = time.time() int_volume = ostack_connection.block_storage.find_volume(volume_name_or_id) int_volume_status = None while True: if time.time() > (int_start_timestamp + timeout): break int_volume_status = ostack_connection.block_storage.find_volume(int_volume.id).status if int_volume_status == volume_status: break return int_volume_status def server_detect_floating_address(server): """ """ for i_address_network_name, i_ip_details in server.addresses.items(): for i_ip_detail in i_ip_details: if str(i_ip_detail.get('version')) == '4' and i_ip_detail.get('OS-EXT-IPS:type') == 'floating': return True return False def get_server_floating_ip_port(ostack_connection, server): """ """ for i_port in ostack_connection.network.ports(device_id=server.id): for i_port_ip in i_port.fixed_ips: for i_ip_prefix in ('192.', '10.', '172.'): if str(i_port_ip.get('ip_address')).startswith(i_ip_prefix): return i_port return None def main(args): """ """ # connect to source cloud source_migrator_openrc = get_openrc(args.source_openrc) source_migrator_conn = get_ostack_connection(source_migrator_openrc) args.logger.info("A.1 Source OpenStack cloud connected as migrator user") # connect to destination cloud destination_migrator_openrc = get_openrc(args.destination_openrc) destination_migrator_conn = get_ostack_connection(destination_migrator_openrc) args.logger.info("A.2 Destination OpenStack cloud connected as migrator user") # check project exists in source and destination source_project = get_ostack_project(source_migrator_conn, args.project_name) log_or_assert(args, f"B.1 Source OpenStack cloud project exists", source_project) source_project_type = get_ostack_project_type(source_migrator_conn, source_project) log_or_assert(args, f"B.2 Source OpenStack cloud project type is {source_project_type}", source_project_type) destination_project = get_ostack_project(destination_migrator_conn, args.project_name) log_or_assert(args, f"B.10 Destination OpenStack cloud project exists", destination_project) destination_project_type = get_ostack_project_type(destination_migrator_conn, destination_project) log_or_assert(args, f"B.11 Destination OpenStack cloud project type is {destination_project_type}", destination_project_type) log_or_assert(args, f"B.12 Source and destination project types match", source_project_type == destination_project_type) # check user context switching & quotas source_project_conn = get_ostack_connection(source_migrator_openrc | {'OS_PROJECT_NAME': source_project.name}) #source_project_quotas = source_project_conn.get_compute_quotas(source_project.id) #assert_msg = f"Context switching to source OpenStack cloud project {source_project.name} succeeded (id:{source_project.id})" #assert source_project_quotas and source_project_quotas.id == source_project.id, assert_msg #args.logger.info(f"C.2 {assert_msg}") destination_project_conn = get_ostack_connection(destination_migrator_openrc | {'OS_PROJECT_NAME': destination_project.name}) #destination_project_quotas = destination_project_conn.get_compute_quotas(destination_project.id) #assert_msg = f"Context switching to destination OpenStack cloud project {destination_project.name} succeeded (id:{destination_project.id})" #assert destination_project_quotas and destination_project_quotas.id == destination_project.id, assert_msg #args.logger.info(f"C.2 {assert_msg}") # connect to migrator node reply_stdout, reply_stderr, reply_ecode = remote_cmd_exec(args.ceph_migrator_host, args.ceph_migrator_user, args.ceph_migrator_sshkeyfile.name, 'uname -a') log_or_assert(args, f"D.1 Migrator host is reachable", 'Linux' in reply_stdout and reply_ecode == 0) reply_stdout, reply_stderr, reply_ecode = remote_cmd_exec(args.ceph_migrator_host, args.ceph_migrator_user, args.ceph_migrator_sshkeyfile.name, '/root/migrator/ceph-accessible.sh') log_or_assert(args, f"D.2 Ceph is available from the migrator host", reply_ecode == 0) source_rbd_images = {args.source_ceph_ephemeral_pool_name: None, args.source_ceph_cinder_pool_name: None} for i_pool_name in source_rbd_images.keys(): source_rbd_images[i_pool_name] = ceph_rbd_images_list(args, i_pool_name) log_or_assert(args, f"D.3 Source cloud RBD images are received ({i_pool_name}).", source_rbd_images[i_pool_name]) source_keypairs = get_source_keypairs(args) log_or_assert(args, f"D.4 Source OpenStack cloud keypairs received.", source_keypairs) # get source/destination entities in the project source_project_servers = get_ostack_project_servers(source_project_conn, source_project) args.logger.info(f"E.1 Source OpenStack cloud servers received") assert_entity_ownership(source_project_servers, source_project) args.logger.info(f"E.2 Source OpenStack cloud project {source_project.name} has {len(source_project_servers)} servers.") source_project_flavors = get_ostack_project_flavors(source_project_conn) log_or_assert(args, f"E.4 Source OpenStack flavor list received", source_project_flavors) destination_project_servers = get_ostack_project_servers(destination_project_conn, destination_project) args.logger.info(f"E.10 Destination OpenStack cloud servers received") assert_entity_ownership(destination_project_servers, destination_project) args.logger.info(f"E.11 Destination OpenStack cloud project {destination_project.name} has {len(destination_project_servers)} servers.") destination_project_flavors = get_ostack_project_flavors(destination_project_conn) log_or_assert(args, f"E.12 Destination OpenStack flavor list received", destination_project_flavors) log_or_assert(args, f"E.20 Source OpenStack VM ID validation succeeded", args.validation_a_source_server_id in [i_server.id for i_server in source_project_servers]) destination_image = destination_project_conn.image.find_image(args.destination_bootable_volume_image_name) log_or_assert(args, f"E.30 Destination image found and received", destination_image) destination_fip_network = destination_project_conn.network.find_network(args.destination_ipv4_external_network) log_or_assert(args, f"E.31 Destination cloud FIP network detected", destination_fip_network) args.logger.info(f"F.0 Main looping started") args.logger.info(f"F.0 Source VM servers: {[ i_source_server.name for i_source_server in source_project_servers]}") for i_source_server in source_project_servers: i_source_server_detail = source_project_conn.compute.find_server(i_source_server.id) i_source_server_has_fip = server_detect_floating_address(i_source_server_detail) if args.explicit_server_names and i_source_server.name not in args.explicit_server_names: args.logger.info(f"F.1 server migration skipped - name:{i_source_server_detail.name} due to --explicit-server-names={args.explicit_server_names}") continue args.logger.info(f"F.1 server migration started - name:{i_source_server_detail.name}, id:{i_source_server_detail.id}, keypair: {i_source_server_detail.key_name}, flavor: {i_source_server_detail.flavor}, sec-groups:{i_source_server_detail.security_groups}, root_device_name: {i_source_server_detail.root_device_name}, block_device_mapping: {i_source_server_detail.block_device_mapping}, attached-volumes: {i_source_server_detail.attached_volumes}") # network, subnet detection i_source_server_network_names = i_source_server_detail.addresses.keys() i_destination_server_networks = [] for i_source_network_name in i_source_server_network_names: i_destination_network_name = get_destination_network(i_source_network_name) log_or_assert(args, f"F.2 Source to Destination network mapping succeeeded ({i_source_network_name}->{i_destination_network_name})", i_destination_network_name) i_destination_network = destination_project_conn.network.find_network(i_destination_network_name) log_or_assert(args, f"F.3 Destination network exists ({i_destination_network})", i_destination_network) i_destination_server_networks.append(i_destination_network) # flavor detection i_source_server_flavor_name = i_source_server_detail.flavor.name i_destination_server_flavor_name = get_destination_flavor(i_source_server_flavor_name) log_or_assert(args, f"F.5 Source to Destination flavor mapping succeeeded ({i_source_server_flavor_name}->{i_destination_server_flavor_name})", i_destination_server_flavor_name) log_or_assert(args, f"F.6 Destination OpenStack flavor exists", [ i_flavor for i_flavor in destination_project_flavors if i_flavor.name == i_destination_server_flavor_name ]) # keypair detection / creation i_source_server_keypair = get_source_keypair(source_keypairs, i_source_server_detail.key_name, i_source_server_detail.user_id) log_or_assert(args, f"F.7 Source OpenStack server keypair found ({i_source_server_keypair})", i_source_server_keypair) i_destination_server_keypair = None if i_destination_server_keypairs := [i_keypair for i_keypair in destination_project_conn.list_keypairs() if i_keypair.name == i_source_server_detail.key_name]: i_destination_server_keypair = i_destination_server_keypairs[0] log_or_assert(args, f"F.8 Destination OpenStack server keypair found already ({i_destination_server_keypair})", i_destination_server_keypair) else: i_destination_server_keypair = create_keypair(destination_project_conn, i_source_server_keypair) args.logger.info("F.8 Destination OpenStack server keypair created") log_or_assert(args, f"F.9 Destination OpenStack server keypair exists ({i_destination_server_keypair})", i_destination_server_keypair) # security group source_project_security_groups = get_ostack_project_security_groups(source_project_conn, source_project) destination_project_security_groups = get_ostack_project_security_groups(destination_project_conn, destination_project) i_destination_server_security_groups=[] for i_source_server_security_group in i_source_server_detail.security_groups: i_destination_server_security_group = None if i_destination_server_security_group := destination_project_conn.network.find_security_group(i_source_server_security_group["name"], tenant_id=destination_project.id): log_or_assert(args, f"F.10 Destination OpenStack server security group found already ({i_destination_server_security_group})", i_destination_server_security_group) else: i_destination_server_security_group = create_security_group(destination_project_conn, i_source_server_security_group) log_or_assert(args, f"F.10 Destination OpenStack server security group created ({i_destination_server_security_group})", i_destination_server_security_group) log_or_assert(args, f"F.11 Destination OpenStack server security group exists ({i_destination_server_security_group})", i_destination_server_security_group) i_destination_server_security_groups.append(i_destination_server_security_group) log_or_assert(args, f"F.12 Destination OpenStack server - all security groups exist {i_destination_server_security_groups}", len(i_destination_server_security_groups) == len(i_source_server_detail.security_groups)) # volume detection i_server_block_device_mappings = [ ] # schema: [ {}, ... ] # where {} is following dict # { 'source': {'block_storage_type': 'openstack-volume-ceph-rbd-image', 'volume_attachment_id': <>, 'volume_id': <>, # 'ceph_pool_name': <pool-name>, 'ceph_rbd_image_name': <rbd-image-name>, 'ceph_rbd_image_size': <size-gb>} # OR # {'block_storage_type': 'ceph-rbd-image', 'ceph_pool_name': <pool-name>, 'ceph_rbd_image_name': <rbd-image-name>, 'ceph_rbd_image_size': <size-gb> } ] # 'destination': {'volume_size': <size-gb>, 'volume_id': <vol-id>, 'device_name': <dev-name>, 'volume_bootable': True/False} # } i_source_server_root_device_name = i_source_server_detail.root_device_name log_or_assert(args, f"F.20 Source OpenStack server - root device name received ({i_source_server_root_device_name})", i_source_server_root_device_name) i_source_server_volume_attachments = tuple(source_project_conn.compute.volume_attachments(i_source_server_detail.id)) assert_msg = f"F.21 Source OpenStack server - volume attachments received {i_source_server_volume_attachments}" pprint.pprint(i_source_server_volume_attachments) i_source_ceph_ephemeral_rbd_image = None if i_source_server_root_device_name in [ i_source_server_attachment.device for i_source_server_attachment in i_source_server_volume_attachments ]: args.logger.info(f"F.22 Source OpenStack server - one of attached volume is attached as the root partition") # populate i_server_block_device_mappings for i_source_server_volume_attachment in i_source_server_volume_attachments: i_server_volume = source_project_conn.block_storage.find_volume(i_source_server_volume_attachment.volume_id) i_server_block_device_mappings.append({'source': {'block_storage_type': 'openstack-volume-ceph-rbd-image', 'volume_attachment_id': i_source_server_volume_attachment.id, 'volume_id': i_server_volume.id, 'ceph_pool_name': args.source_ceph_cinder_pool_name, 'ceph_rbd_image_name': i_server_volume.id}, 'destination': {'volume_size': i_server_volume.size, 'volume_name': i_server_volume.name, 'volume_description': i_server_volume.description, 'volume_id': None, 'ceph_pool_name': args.destination_ceph_cinder_pool_name, 'device_name': os.path.basename(i_source_server_volume_attachment.device), 'volume_bootable': i_source_server_root_device_name == i_source_server_volume_attachment.device}}) else: args.logger.info(f"F.22 Source OpenStack server - none of attached volumes is attached as the root partition. Seeking for root partition RBD image") if f"{i_source_server_detail.id}_disk" in source_rbd_images[args.source_ceph_ephemeral_pool_name]: i_source_ceph_ephemeral_rbd_image = f"{i_source_server_detail.id}_disk" args.logger.info(f"F.23 Source OpenStack server - Root partition found as RBD image {args.source_ceph_ephemeral_pool_name}/{i_source_ceph_ephemeral_rbd_image}") # get rbd image info / size i_source_ceph_ephemeral_rbd_image_data = ceph_rbd_image_info(args, args.source_ceph_ephemeral_pool_name, i_source_ceph_ephemeral_rbd_image) log_or_assert(args, f"F.24 Source OpenStack ceph RBD image information received", i_source_ceph_ephemeral_rbd_image_data and 'size' in i_source_ceph_ephemeral_rbd_image_data) i_source_ceph_ephemeral_rbd_image_size = math.ceil(i_source_ceph_ephemeral_rbd_image_data['size'] / 1024 / 1024 / 1024) log_or_assert(args, f"F.25 Source OpenStack ceph RBD image size calculated", i_source_ceph_ephemeral_rbd_image_size) # populate i_server_block_device_mappings ## initial disk i_server_block_device_mappings.append({'source': {'block_storage_type': 'ceph-rbd-image', 'ceph_pool_name': args.source_ceph_ephemeral_pool_name, 'ceph_rbd_image_name': i_source_ceph_ephemeral_rbd_image, 'ceph_rbd_image_size': i_source_ceph_ephemeral_rbd_image_size}, 'destination': {'volume_size': i_source_ceph_ephemeral_rbd_image_size, 'volume_name': i_source_ceph_ephemeral_rbd_image, 'volume_description': f"RBD {args.source_ceph_ephemeral_pool_name}/{i_source_ceph_ephemeral_rbd_image}", 'volume_id': None, 'ceph_pool_name': args.destination_ceph_cinder_pool_name, 'device_name': os.path.basename(i_source_server_root_device_name), 'volume_bootable': True}}) ## other disks attached to VM for i_source_server_volume_attachment in i_source_server_volume_attachments: i_server_volume = source_project_conn.block_storage.find_volume(i_source_server_volume_attachment.volume_id) i_server_block_device_mappings.append({'source': {'block_storage_type': 'openstack-volume-ceph-rbd-image', 'volume_attachment_id': i_source_server_volume_attachment.id, 'volume_id': i_server_volume.id, 'ceph_pool_name': args.source_ceph_cinder_pool_name, 'ceph_rbd_image_name': i_server_volume.id}, 'destination': {'volume_size': i_server_volume.size, 'volume_name': i_server_volume.name, 'volume_description': i_server_volume.description, 'volume_id': None, 'ceph_pool_name': args.destination_ceph_cinder_pool_name, 'device_name': os.path.basename(i_source_server_volume_attachment.device), 'volume_bootable': i_source_server_root_device_name == i_source_server_volume_attachment.device}}) log_or_assert(args, f"F.26 Source OpenStack server - root partition detected", i_server_block_device_mappings and i_server_block_device_mappings[0] and i_server_block_device_mappings[0]['source']) log_or_assert(args, f"F.27 Destination OpenStack server - root partition details generated", i_server_block_device_mappings and i_server_block_device_mappings[0] and i_server_block_device_mappings[0]['destination']) pprint.pprint(i_server_block_device_mappings) #wait_for_keypress() # volume creation in destination cloud for i_destination_server_block_device_mapping in i_server_block_device_mappings: i_new_volume_args = {'name': i_destination_server_block_device_mapping['destination']['volume_name'], 'size': i_destination_server_block_device_mapping['destination']['volume_size'], 'description': f"{i_destination_server_block_device_mapping['destination']['volume_description']}, g1-to-g2-migrated"} # TO BE REVISED: this seems to be the only way how to create bootable volume using openstacksdk if i_destination_server_block_device_mapping['destination']['volume_bootable']: i_new_volume_args['imageRef'] = destination_image.id i_new_volume = destination_project_conn.block_storage.create_volume(**i_new_volume_args) log_or_assert(args, f"F.29 Destination OpenStack volume created (name:{i_new_volume.name}, id:{i_new_volume.id})", i_new_volume) wait_for_ostack_volume_status(destination_project_conn, i_new_volume.id, 'available') log_or_assert(args, f"F.30 Destination OpenStack volume available (name:{i_new_volume.name}, id:{i_new_volume.id})", wait_for_ostack_volume_status(destination_project_conn, i_new_volume.id, 'available') == 'available') # remember volume ID i_destination_server_block_device_mapping['destination']['volume_id'] = i_new_volume.id for i_destination_server_block_device_mapping in i_server_block_device_mappings: log_or_assert(args, f"F.31 Destination OpenStack volume IDs properly stored", i_destination_server_block_device_mapping['destination']['volume_id']) # VM stop, wait for SHUTOFF if i_source_server_detail.status != 'SHUTOFF': source_project_conn.compute.stop_server(i_source_server_detail) log_or_assert(args, f"F.33 Source OpenStack VM server stopped", wait_for_ostack_server_status(source_project_conn, i_source_server.id, 'SHUTOFF') == "SHUTOFF") # volume migration (browse i_server_block_device_mappings) for i_server_block_device_mapping in i_server_block_device_mappings: ## G1: detect existing G1 RBD image #CEPH_USER=client.cinder ~/migrator/ceph-rbd-image-exists.sh prod-ephemeral-vms 0069e95e-e805-44ff-bab5-872424312ff6 i_source_server_rbd_images, i_stderr, i_ecode = ceph_rbd_image_exists(args, i_server_block_device_mapping['source']['ceph_pool_name'], i_server_block_device_mapping['source']['ceph_rbd_image_name']) log_or_assert(args, f"F.41 Source OpenStack VM RBD image exists - query succeeded", i_ecode == 0, locals()) log_or_assert(args, f"F.41 Source OpenStack VM RBD image exists - single image returned", i_source_server_rbd_images and len(i_source_server_rbd_images) == 1, locals()) i_source_server_rbd_image = i_source_server_rbd_images[0] ## G2: find volume #CEPH_USER=client.migrator ~/migrator/ceph-rbd-image-exists.sh cloud-cinder-volumes-prod-brno <g2-rbd-image-name> i_destination_server_rbd_images, i_stderr, i_ecode = ceph_rbd_image_exists(args, i_server_block_device_mapping['destination']['ceph_pool_name'], i_server_block_device_mapping['destination']['volume_id']) log_or_assert(args, f"F.42 Destination OpenStack VM RBD image exists - query succeeded", i_ecode == 0, locals()) log_or_assert(args, f"F.42 Destination OpenStack VM RBD image exists - single image returned", i_destination_server_rbd_images and len(i_destination_server_rbd_images) == 1, locals()) i_destination_server_rbd_image = i_destination_server_rbd_images[0] ## G1: create RBD image protected snapshot #CEPH_USER=client.cinder ~/migrator/ceph-rbd-image-snapshot-exists.sh prod-ephemeral-vms 006e230e-df45-4f33-879b-19eada244489_disk migration-snap2 # 1 #CEPH_USER=client.cinder ~/migrator/ceph-rbd-image-snapshot-create.sh prod-ephemeral-vms 006e230e-df45-4f33-879b-19eada244489_disk migration-snap2 # 0 #CEPH_USER=client.cinder ~/migrator/ceph-rbd-image-snapshot-exists.sh prod-ephemeral-vms 006e230e-df45-4f33-879b-19eada244489_disk migration-snap2 # 0 i_source_rbd_image_snapshot_name = f"g1-g2-migration-{i_source_server_rbd_image}" i_stdout, i_stderr, i_ecode = ceph_rbd_image_snapshot_exists(args, i_server_block_device_mapping['source']['ceph_pool_name'], i_source_server_rbd_image, i_source_rbd_image_snapshot_name) log_or_assert(args, f"F.43 Source OpenStack VM RBD image has non-colliding snapshot", i_ecode != 0, locals()) i_stdout, i_stderr, i_ecode = ceph_rbd_image_snapshot_create(args, i_server_block_device_mapping['source']['ceph_pool_name'], i_source_server_rbd_image, i_source_rbd_image_snapshot_name) log_or_assert(args, f"F.44 Source OpenStack VM RBD image snapshot created", i_ecode == 0, locals()) i_stdout, i_stderr, i_ecode = ceph_rbd_image_snapshot_exists(args, i_server_block_device_mapping['source']['ceph_pool_name'], i_source_server_rbd_image, i_source_rbd_image_snapshot_name) log_or_assert(args, f"F.45 Source OpenStack VM RBD image snapshot exists", i_ecode == 0, locals()) ## G2: delete RBD image #CEPH_USER=client.migrator ~/migrator/ceph-rbd-image-delete.sh cloud-cinder-volumes-prod-brno <g2-rbd-image-name> ## G2: confirm volume is deleted #CEPH_USER=client.migrator ~/migrator/ceph-rbd-image-exists.sh cloud-cinder-volumes-prod-brno <g2-rbd-image-name> # 1 i_stdout, i_stderr, i_ecode = ceph_rbd_image_delete(args, i_server_block_device_mapping['destination']['ceph_pool_name'], i_destination_server_rbd_image) log_or_assert(args, f"F.46 Destination OpenStack VM RBD image deletion succeeded", i_ecode == 0, locals()) i_stdout, i_stderr, i_ecode = ceph_rbd_image_exists(args, i_server_block_device_mapping['destination']['ceph_pool_name'], i_destination_server_rbd_image) log_or_assert(args, f"F.47 Destination OpenStack VM RBD image does not exist", i_ecode != 0, locals()) ## G1: clone from snapshot #CEPH_USER=client.cinder ~/migrator/ceph-rbd-image-clone.sh prod-ephemeral-vms 006e230e-df45-4f33-879b-19eada244489_disk migration-snap2 prod-ephemeral-vms migrated-006e230e-df45-4f33-879b-19eada244489_disk #CEPH_USER=client.cinder ~/migrator/ceph-rbd-image-exists.sh prod-ephemeral-vms migrated-006e230e-df45-4f33-879b-19eada244489_disk i_source_rbd_cloned_image_name = f"g1-g2-migration-{i_source_server_rbd_image}" i_stdout, i_stderr, i_ecode = ceph_rbd_image_clone(args, i_server_block_device_mapping['source']['ceph_pool_name'], i_source_server_rbd_image, i_source_rbd_image_snapshot_name, i_server_block_device_mapping['source']['ceph_pool_name'], i_source_rbd_cloned_image_name) log_or_assert(args, f"F.48 Source OpenStack VM RBD image cloned succesfully", i_ecode == 0, locals()) i_stdout, i_stderr, i_ecode = ceph_rbd_image_exists(args, i_server_block_device_mapping['source']['ceph_pool_name'], i_source_rbd_cloned_image_name) log_or_assert(args, f"F.49 Source OpenStack VM cloned RBD image exists", i_ecode == 0, locals()) ## G1: flatten cloned RBD image #CEPH_USER=client.cinder ~/migrator/ceph-rbd-image-flatten.sh prod-ephemeral-vms migrated-006e230e-df45-4f33-879b-19eada244489_disk i_stdout, i_stderr, i_ecode = ceph_rbd_image_flatten(args, i_server_block_device_mapping['source']['ceph_pool_name'], i_source_rbd_cloned_image_name) log_or_assert(args, f"F.50 Source OpenStack VM cloned RBD image flatten successfully", i_ecode == 0, locals()) ## G1->G2: copy RBD image to target pool #CEPH_USER=client.migrator ~/migrator/ceph-rbd-image-copy.sh prod-ephemeral-vms migrated-006e230e-df45-4f33-879b-19eada244489_disk cloud-cinder-volumes-prod-brno <g2-rbd-image-name> #CEPH_USER=client.migrator ~/migrator/ceph-rbd-image-exists.sh cloud-cinder-volumes-prod-brno <g2-rbd-image-name> # 0 i_stdout, i_stderr, i_ecode = ceph_rbd_image_copy(args, i_server_block_device_mapping['source']['ceph_pool_name'], i_source_rbd_cloned_image_name, i_server_block_device_mapping['destination']['ceph_pool_name'], i_destination_server_rbd_image) log_or_assert(args, f"F.51 Source OpenStack VM RBD image copied G1 -> G2 succesfully", i_ecode == 0, locals()) i_stdout, i_stderr, i_ecode = ceph_rbd_image_exists(args, i_server_block_device_mapping['destination']['ceph_pool_name'], i_destination_server_rbd_image) log_or_assert(args, f"F.52 Destination OpenStack VM RBD image exists", i_ecode == 0, locals()) ## G1: delete cloned RBD image #CEPH_USER=client.cinder ~/migrator/ceph-rbd-image-delete.sh prod-ephemeral-vms migrated-006e230e-df45-4f33-879b-19eada244489_disk i_stdout, i_stderr, i_ecode = ceph_rbd_image_delete(args, i_server_block_device_mapping['source']['ceph_pool_name'], i_source_rbd_cloned_image_name) log_or_assert(args, f"F.53 Source OpenStack VM RBD cloned image deletion succeeded", i_ecode == 0, locals()) i_stdout, i_stderr, i_ecode = ceph_rbd_image_exists(args, i_server_block_device_mapping['source']['ceph_pool_name'], i_source_rbd_cloned_image_name) log_or_assert(args, f"F.54 Source OpenStack VM cloned RBD image does not exist anymore", i_ecode != 0, locals()) ## G1: remove created snapshot #CEPH_USER=client.cinder ~/migrator/ceph-rbd-image-snapshot-exists.sh prod-ephemeral-vms 006e230e-df45-4f33-879b-19eada244489_disk migration-snap2 # 0 #CEPH_USER=client.cinder ~/migrator/ceph-rbd-image-snapshot-delete.sh prod-ephemeral-vms 006e230e-df45-4f33-879b-19eada244489_disk migration-snap2 #CEPH_USER=client.cinder ~/migrator/ceph-rbd-image-snapshot-exists.sh prod-ephemeral-vms 006e230e-df45-4f33-879b-19eada244489_disk migration-snap2 # 1 i_stdout, i_stderr, i_ecode = ceph_rbd_image_snapshot_exists(args, i_server_block_device_mapping['source']['ceph_pool_name'], i_source_server_rbd_image, i_source_rbd_image_snapshot_name) log_or_assert(args, f"F.55 Source OpenStack VM RBD image snapshot still exists", i_ecode == 0, locals()) i_stdout, i_stderr, i_ecode = ceph_rbd_image_snapshot_delete(args, i_server_block_device_mapping['source']['ceph_pool_name'], i_source_server_rbd_image, i_source_rbd_image_snapshot_name) log_or_assert(args, f"F.56 Source OpenStack VM RBD image snapshot deletion succeeeded", i_ecode == 0, locals()) i_stdout, i_stderr, i_ecode = ceph_rbd_image_snapshot_exists(args, i_server_block_device_mapping['source']['ceph_pool_name'], i_source_server_rbd_image, i_source_rbd_image_snapshot_name) log_or_assert(args, f"F.57 Source OpenStack VM RBD image snapshot does not exist anymore", i_ecode != 0, locals()) # start server in source cloud, wait for back being 'ACTIVE' if i_source_server_detail.status != source_project_conn.compute.find_server(i_source_server.id).status: if i_source_server_detail.status == 'ACTIVE': source_project_conn.compute.start_server(i_source_server_detail) log_or_assert(args, f"F.49 Source OpenStack VM server started back", wait_for_ostack_server_status(source_project_conn, i_source_server.id, 'ACTIVE') == "ACTIVE", locals()) # start server in destination cloud i_destination_server_flavor = destination_project_conn.compute.find_flavor(i_destination_server_flavor_name) i_destination_server_args = {'name': i_source_server_detail.name, 'flavorRef': i_destination_server_flavor.id, 'block_device_mapping_v2': [ {'source_type': 'volume', 'destination_type': 'volume', 'uuid': i_server_block_device_mapping['destination']['volume_id'], 'device_name': i_server_block_device_mapping['destination']['device_name'], 'boot_index': 0 if i_server_block_device_mapping['destination']['volume_bootable'] else None} for i_server_block_device_mapping in i_server_block_device_mappings ], 'boot_volume': i_server_block_device_mappings[0]['destination']['volume_id'], 'key_name': i_destination_server_keypair["name"], 'networks': [ {'uuid': i_network.id} for i_network in i_destination_server_networks ]} log_or_assert(args, f"F.60 Destination OpenStack server arguments are generated with valid block-device-mapping", i_destination_server_args['block_device_mapping_v2'], locals()) log_or_assert(args, f"F.60 Destination OpenStack server arguments are generated with valid network configuration", i_destination_server_args['networks'], locals()) pprint.pprint(i_destination_server_args) i_destination_server = destination_project_conn.compute.create_server(**i_destination_server_args) log_or_assert(args, f"F.61 Destination OpenStack server is created", i_destination_server, locals()) i_destination_server = destination_project_conn.compute.wait_for_server(i_destination_server) log_or_assert(args, f"F.62 Destination OpenStack server got ACTIVE", i_destination_server.status == 'ACTIVE', locals()) # add security groups to the destination server (if missing) for i_destination_server_security_group_id, i_destination_server_security_group_name in set([(i_destination_server_security_group.id, i_destination_server_security_group.name) for i_destination_server_security_group in i_destination_server_security_groups]): if {'name': i_destination_server_security_group_name } not in i_destination_server.security_groups: destination_project_conn.add_server_security_groups(i_destination_server.id, i_destination_server_security_group_id) # add FIP as source VM has it if i_source_server_has_fip: i_destination_server_fip = destination_project_conn.network.create_ip(floating_network_id=destination_fip_network.id) log_or_assert(args, f"F.63 Destination OpenStack server FIP is created", i_destination_server_fip, locals()) i_destination_server_port = get_server_floating_ip_port(destination_project_conn, i_destination_server) log_or_assert(args, f"F.64 Destination OpenStack server FIP port is detected", i_destination_server_port, locals()) destination_project_conn.network.add_ip_to_port(i_destination_server_port, i_destination_server_fip) args.logger.info(f"F.66 Source OpenStack server name:{i_source_server_detail.name} migrated into destination one name:{i_destination_server.name} id:{i_destination_server.id}") # main() call (argument parsing) # --------------------------------------------------------------------------- if __name__ == "__main__": AP = argparse.ArgumentParser(epilog=globals().get('__doc__'), formatter_class=argparse.RawDescriptionHelpFormatter) AP.add_argument('--source-openrc', default=None, type=argparse.FileType('r'), required=True, help='Source cloud authentication (OpenRC file)') AP.add_argument('--destination-openrc', default=None, type=argparse.FileType('r'), required=True, help='Destination cloud authentication (OpenRC file)') AP.add_argument('--ceph-migrator-host', default='controller-ostack.stage.cloud.muni.cz', help='OpenStack migrator ceph node host') AP.add_argument('--ceph-migrator-user', default='root', help='OpenStack migrator ceph node username') AP.add_argument('--ceph-migrator-sshkeyfile', default=None, type=argparse.FileType('r'), help='OpenStack migrator SSH keyfile') AP.add_argument('--source-ceph-cinder-pool-name', default='prod-cinder-volumes', help='Source OpenStack/ceph cloud Cinder pool name') AP.add_argument('--source-ceph-ephemeral-pool-name', default='prod-ephemeral-vms', help='Source OpenStack/ceph cloud "ephemeral on ceph" or "libvirt ephemeral" pool name') AP.add_argument('--destination-ceph-cinder-pool-name', default='cloud-cinder-volumes-prod-brno', help='Destination OpenStack/ceph cloud Cinder pool name') AP.add_argument('--destination-ceph-ephemeral-pool-name', default='cloud-ephemeral-volumes-prod-brno', help='Destination OpenStack/ceph cloud "ephemeral on ceph" or "libvirt ephemeral" pool name') AP.add_argument('--source-keypair-xml-dump-file', default='/root/migrator/prod-nova_api_key_pairs.dump.xml', help='Source OpenStack cloud keypair SQL/XML dump file name') AP.add_argument('--destination-bootable-volume-image-name', default='cirros-0-x86_64', help='Destination cloud bootable volumes are made on top of public image. Name of destination cloud image.') AP.add_argument('--destination-ipv4-external-network', default='external-ipv4-general-public', help='Destination cloud IPV4 external network.') AP.add_argument('--project-name', default=None, required=True, help='OpenStack project name (identical name in both clouds required)') AP.add_argument('--entity-overwrite', default=False, action='store_true', help='Instruct migrator to overwrite already existing entities in destination cloud, TODO') AP.add_argument('--explicit-server-names', default=None, required=False, help='(Optional) List of explicit server names or IDs to be migrated. Delimiter comma or space.') AP.add_argument('--validation-a-source-server-id', default=None, required=True, help='For validation any server ID from source OpenStack project') AP.add_argument('--exception-trace-file', default="project-migrator.dump", required=False, help='Exception / assert dump state file') logging.basicConfig(level=logging.INFO, # Set the logging level format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') ARGS = AP.parse_args() ARGS.logger = logging.getLogger("project-migrator") ARGS.explicit_server_names = normalize_servers(ARGS.explicit_server_names) sys.exit(main(ARGS))