diff --git a/ci/lib.py b/ci/lib.py index 0eab6d78e1df242da87a80ca104c151b68eee754..6f24132196aa0828c156acb3c020a1927fb57022 100644 --- a/ci/lib.py +++ b/ci/lib.py @@ -1,5 +1,6 @@ """ OpenStack project migrator library """ +import copy import json import re import pprint @@ -403,29 +404,58 @@ def create_keypair(args, ostack_connection, keypair): return ostack_connection.compute.create_keypair(name=get_migrated_resource_name(args, keypair['name']), public_key=keypair['public_key'], type=keypair['type']) -def create_security_group(args, ostack_connection, security_group, project): - """ create openstack security group """ - int_sg = ostack_connection.network.create_security_group(name=get_migrated_resource_name(args, security_group.name), - description=security_group.description, - project_id=project.id) - #pprint.pprint(int_sg) - - for i_rule in security_group.security_group_rules: - i_mod_rule = {i_k: i_rule[i_k] for i_k in i_rule if i_k not in ['id', 'project_id', 'tenant_id', 'revision_number', 'updated_at', 'created_at', 'tags', 'standard_attr_id', 'normalized_cidr']} +def create_security_groups(args, src_ostack_conn, dst_ostack_conn, src_security_group, dst_project, recursion_stack=None): + """ create openstack security group[s] """ + int_recursion_stack = {} if recursion_stack is None else recursion_stack + int_sg = dst_ostack_conn.network.create_security_group(name=get_migrated_resource_name(args, src_security_group.name), + description=f"{src_security_group.description}, g1-to-g2-migrated(g1-id:{src_security_group.id})", + project_id=dst_project.id) + int_recursion_stack[src_security_group.id] = int_sg.id + + for i_rule in src_security_group.security_group_rules: + # browse security group rules + i_mod_rule = trim_dict(i_rule, denied_keys=['id', 'project_id', 'tenant_id', 'revision_number', 'updated_at', 'created_at', 'tags', 'standard_attr_id', 'normalized_cidr']) i_mod_rule['security_group_id'] = int_sg.id - i_mod_rule['project_id'] = project.id + i_mod_rule['project_id'] = dst_project.id i_mod_rule = {i_k: i_mod_rule[i_k] for i_k in i_mod_rule if i_mod_rule[i_k] is not None} - #pprint.pprint(i_rule) - #pprint.pprint(i_mod_rule) + if i_mod_rule.get('remote_group_id') is not None: + if i_mod_rule['remote_group_id'] in int_recursion_stack: + # keep reference to itself or known (already created) SGs + i_mod_rule['remote_group_id'] = int_recursion_stack[i_mod_rule['remote_group_id']] + # get linked source SG + elif _src_sg := src_ostack_conn.network.find_security_group(i_mod_rule['remote_group_id']): + if _dst_sg := dst_ostack_conn.network.find_security_group(get_migrated_resource_name(args, _src_sg.name), + project_id=dst_project.id): + i_mod_rule['remote_group_id'] = _dst_sg.id + else: + int_linked_sg = create_security_groups(args, src_ostack_conn, dst_ostack_conn, + _src_sg, dst_project, + copy.deepcopy(int_recursion_stack)) + i_mod_rule['remote_group_id'] = int_linked_sg.id try: - ostack_connection.network.create_security_group_rule(**i_mod_rule) + dst_ostack_conn.network.create_security_group_rule(**i_mod_rule) except openstack.exceptions.ConflictException as ex: - # TODO: analyze whether Conflicts we have seen meat that security group role IS COMPLETELY identical - # Alternative solution would be to remove rules after creation and add specific ones pass return int_sg +def duplicate_ostack_project_security_groups(args, src_ostack_conn, dst_ostack_conn, src_project, dst_project): + """ duplicate all projects's openstack security group[s] """ + + src_project_security_groups = tuple(src_ostack_conn.network.security_groups(project_id=src_project.id)) + + for i_src_security_group in src_project_security_groups: + j_dst_security_group_found = False + for j_dst_security_group in tuple(dst_ostack_conn.network.security_groups(project_id=dst_project.id)): + if get_migrated_resource_name(args, i_src_security_group.name) == j_dst_security_group.name and \ + i_src_security_group.id in j_dst_security_group.description: + j_dst_security_group_found = True + if not j_dst_security_group_found: + create_security_groups(args, src_ostack_conn, dst_ostack_conn, i_src_security_group, dst_project) + + return src_project_security_groups, tuple(dst_ostack_conn.network.security_groups(project_id=dst_project.id)) + + def log_or_assert(args, msg, condition, trace_details=None): """ log, assert, dump state """ if not condition: @@ -666,5 +696,3 @@ def migrate_rbd_image(args, server_block_device_mapping): "G.17 Source OpenStack VM RBD image snapshot does not exist anymore " \ f"{server_block_device_mapping['source']['ceph_pool_name']}/{source_server_rbd_image}@{source_rbd_image_snapshot_name}", ecode != 0, locals()) - - diff --git a/ci/project-migrator.py b/ci/project-migrator.py index cdb1df434a390159e32dcdca7ade99c073904fbf..926cbea2e9496200912d88c82c60d09895a00813 100755 --- a/ci/project-migrator.py +++ b/ci/project-migrator.py @@ -2,15 +2,15 @@ """ OpenStack project multicloud migrator +Tool performs OpenStack workflow migration from single OpenStack cloud to another one. +Block storage is transferred on external node using ceph low-level commands. 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 - * + --ceph-migrator-sshkeyfile ~/.ssh/id_rsa.g1-g2-ostack-cloud-migration """ import argparse @@ -24,7 +24,7 @@ import sys import lib def main(args): - """ """ + """ main project migration loop """ # connect to source cloud source_migrator_openrc = lib.get_openrc(args.source_openrc) source_migrator_conn = lib.get_ostack_connection(source_migrator_openrc) @@ -106,6 +106,9 @@ def main(args): destination_fip_network = destination_project_conn.network.find_network(args.destination_ipv4_external_network) lib.log_or_assert(args, "E.31 Destination cloud FIP network detected", destination_fip_network) + lib.duplicate_ostack_project_security_groups(args, source_project_conn, destination_project_conn, + source_project, destination_project) + args.logger.info("E.40 Destination OpenStack project security groups duplicated") args.logger.info("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]}") @@ -126,7 +129,11 @@ def main(args): args.logger.info(f"F.1 server migration skipped - name:{i_source_server_detail.name} as equivalent VM exists in destination cloud (name: {i_destination_server_detail.name})") 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}") + args.logger.info(f"F.1 server migration started - name:{i_source_server_detail.name}, id:{i_source_server_detail.id}, " \ + f"keypair: {i_source_server_detail.key_name}, flavor: {i_source_server_detail.flavor}, " \ + f"sec-groups:{i_source_server_detail.security_groups}, root_device_name: {i_source_server_detail.root_device_name}, " \ + f"block_device_mapping: {i_source_server_detail.block_device_mapping}, " \ + f"attached-volumes: {i_source_server_detail.attached_volumes}") # network, subnet detection, TODO: better i_source_server_network_names = i_source_server_detail.addresses.keys() @@ -136,9 +143,12 @@ def main(args): if not i_destination_network_name and args.destination_group_project_network_name != "": # if network is not mapped use network provided from switch --destination-group-project-network-name i_destination_network_name = args.destination_group_project_network_name - lib.log_or_assert(args, f"F.2 Source to Destination network mapping succeeeded ({i_source_network_name}->{i_destination_network_name}). Read --destination-group-project-network-name description for more details", i_destination_network_name) + lib.log_or_assert(args, + f"F.2 Source to Destination network mapping succeeeded ({i_source_network_name}->{i_destination_network_name}). " \ + f"Read --destination-group-project-network-name description for more details", + i_destination_network_name) - i_destination_network = destination_project_conn.network.find_network(i_destination_network_name) + i_destination_network = destination_project_conn.network.find_network(i_destination_network_name, project_id=destination_project.id) lib.log_or_assert(args, f"F.3 Destination network exists ({i_destination_network})", i_destination_network) i_destination_server_networks.append(i_destination_network) @@ -156,7 +166,8 @@ def main(args): lib.log_or_assert(args, f"F.7 Source OpenStack server keypair found ({i_source_server_keypair['name']})", 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 == lib.get_migrated_resource_name(args, i_source_server_detail.key_name)]: + if i_destination_server_keypairs := [i_keypair for i_keypair in destination_project_conn.list_keypairs() + if i_keypair.name == lib.get_migrated_resource_name(args, i_source_server_detail.key_name)]: i_destination_server_keypair = i_destination_server_keypairs[0] lib.log_or_assert(args, f"F.8 Destination OpenStack server keypair found already ({i_destination_server_keypair.name})", i_destination_server_keypair) else: @@ -164,12 +175,9 @@ def main(args): args.logger.info("F.8 Destination OpenStack server keypair created") lib.log_or_assert(args, f"F.9 Destination OpenStack server keypair exists ({i_destination_server_keypair.name})", 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) + # server security group i_destination_server_security_groups=[] - - for i_source_server_security_group_name in set([i_sg['name'] for i_sg in i_source_server_detail.security_groups]): + for i_source_server_security_group_name in {i_sg['name'] for i_sg in i_source_server_detail.security_groups}: i_source_server_security_group = source_project_conn.network.find_security_group(i_source_server_security_group_name, project_id=source_project.id) i_destination_server_security_group = None if i_destination_server_security_group := destination_project_conn.network.find_security_group(lib.get_migrated_resource_name(args, i_source_server_security_group.name), @@ -177,8 +185,9 @@ def main(args): lib.log_or_assert(args, f"F.10 Destination OpenStack server security group found already ({i_destination_server_security_group.name})", i_destination_server_security_group) else: - args.logger.info("F.10 Destination OpenStack server matching security group not found, gets created.") - i_destination_server_security_group = lib.create_security_group(args, destination_project_conn, i_source_server_security_group, destination_project) + args.logger.info("F.10 Destination OpenStack server matching security group not found and gets created.") + i_destination_server_security_group = lib.create_security_groups(args, source_project_conn, destination_project_conn, + i_source_server_security_group, destination_project) lib.log_or_assert(args, f"F.10 Destination OpenStack server security group created ({i_destination_server_security_group.name})", i_destination_server_security_group) @@ -225,7 +234,7 @@ def main(args): # get rbd image info / size i_source_ceph_ephemeral_rbd_image_data = lib.ceph_rbd_image_info(args, args.source_ceph_ephemeral_pool_name, - i_source_ceph_ephemeral_rbd_image) + i_source_ceph_ephemeral_rbd_image) lib.log_or_assert(args, f"F.24 Source OpenStack ceph RBD image information received {i_source_ceph_ephemeral_rbd_image_data}", 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) @@ -264,7 +273,8 @@ def main(args): 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(g1-id:{i_destination_server_block_device_mapping['source']['volume_id']})"} + 'description': f"{i_destination_server_block_device_mapping['destination']['volume_description']}, " \ + f"g1-to-g2-migrated(g1-id:{i_destination_server_block_device_mapping['source']['volume_id']})"} # 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 @@ -320,21 +330,26 @@ def main(args): #pprint.pprint(i_destination_server_args) i_destination_server = destination_project_conn.compute.create_server(**i_destination_server_args) - lib.log_or_assert(args, "F.37 Destination OpenStack server is created", i_destination_server, locals()) + lib.log_or_assert(args, + f"F.37 Destination OpenStack server (name:{i_destination_server.name}) is created", + i_destination_server, locals()) i_destination_server = destination_project_conn.compute.wait_for_server(i_destination_server) - lib.log_or_assert(args, "F.38 Destination OpenStack server got ACTIVE", - i_destination_server.status == 'ACTIVE', locals()) + lib.log_or_assert(args, + f"F.38 Destination OpenStack server (name:{i_destination_server.name}) 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]): + for i_destination_server_security_group_id, i_destination_server_security_group_name in {(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) if i_source_server_has_fip: # add FIP as source VM has it i_destination_server_fip = destination_project_conn.network.create_ip(floating_network_id=destination_fip_network.id) - lib.log_or_assert(args, "F.39 Destination OpenStack server FIP is created", i_destination_server_fip, locals()) + lib.log_or_assert(args, f"F.39 Destination OpenStack server (name:{i_destination_server.name}) FIP is created ({i_destination_server_fip.floating_ip_address})", + i_destination_server_fip, locals()) i_destination_server_port = lib.get_server_floating_ip_port(destination_project_conn, i_destination_server) - lib.log_or_assert(args, "F.40 Destination OpenStack server FIP port is detected", i_destination_server_port, locals()) + lib.log_or_assert(args, f"F.40 Destination OpenStack server (name:{i_destination_server.name}) 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.41 Source OpenStack server name:{i_source_server_detail.name} migrated into destination one name:{i_destination_server.name} id:{i_destination_server.id}")