diff --git a/CHANGELOG.md b/CHANGELOG.md index cecf17cfcdfed7d82f26bc2efb8e166e493f1821..2d76bd4217ecef4423f62ef68e5a9275c5c19006 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.1.2] - 2024-05-30 +### Fixed +- attach FIP to correct virtual NIC +### Added +- dry-run mode +- command-line debugging with IPython console + ## [1.1.1] - 2024-05-30 ### Fixed - assure correct VM NIC order diff --git a/ci/lib.py b/ci/lib.py index c6efd533ff68415b54acd9b03c3c465641ea9830..473d38f0be827682f3c91177a26212fa4096193d 100644 --- a/ci/lib.py +++ b/ci/lib.py @@ -245,20 +245,3 @@ def wait_for_ostack_volume_status(ostack_connection, volume_name_or_id, volume_s return int_volume_status - -def server_detect_floating_address(server): - """ return True if server has attached floating IP address otherwise False """ - for _, 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): - """ set server's port where to put FIP, otherwise None """ - 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 diff --git a/ci/olib.py b/ci/olib.py index bda0126070c4d0f82643b4549282021d79e1bbb2..2271136812b1af6a4d1fea542450bfa2c5857e09 100644 --- a/ci/olib.py +++ b/ci/olib.py @@ -674,3 +674,41 @@ def find_ostack_port(ostack_connection, mac_address, ip_address, description_sub return [i_port for i_port in project_ports if i_port.mac_address==mac_address and \ description_substr in i_port.description and \ ip_address in [i_addr.get('ip_address') for i_addr in i_port.fixed_ips]] + +def server_detect_floating_address(server): + """ return True if server has attached floating IP address otherwise False """ + for _, 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): + """ set server's port where to put FIP, otherwise None """ + 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 get_server_floating_ip_properties(server): + """ return VM FIP properties (IP type, internal IP addr, FIP addr, MAC addr) """ + int_address_data = {} + for i_net_name, i_net_addresses in server.addresses.items(): + int_address_data.clear() + for i_addr_field in i_net_addresses: + int_ip_type = i_addr_field.get('OS-EXT-IPS:type', 'unknown') + for i_field_name in ('addr', 'version', 'OS-EXT-IPS-MAC:mac_addr',): + int_address_data[f"{int_ip_type}/{i_field_name}"] = i_addr_field.get(i_field_name, '?') + if "fixed/version" in int_address_data and \ + "floating/version" in int_address_data and \ + "fixed/OS-EXT-IPS-MAC:mac_addr" in int_address_data and \ + "floating/OS-EXT-IPS-MAC:mac_addr" in int_address_data and \ + int_address_data["fixed/version"] == int_address_data["floating/version"] and \ + int_address_data["fixed/OS-EXT-IPS-MAC:mac_addr"] == int_address_data["floating/OS-EXT-IPS-MAC:mac_addr"]: + int_address_data["fixed/network-name"] = i_net_name + return int_address_data + return {} + + diff --git a/ci/project-migrator.py b/ci/project-migrator.py index 77446d3219ffb786e88e9d43e4bc0bfd4e306b55..52caacd4bbd1b5d072e6dd162bf11c7854e1c733 100755 --- a/ci/project-migrator.py +++ b/ci/project-migrator.py @@ -128,6 +128,13 @@ 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) + if args.dry_run: + args.logger.info("Exiting before first cloud modification operation as in dry-run mode.") + if args.debugging: + import IPython # on-purpose lazy import + IPython.embed() + return + olib.duplicate_ostack_project_security_groups(args, source_project_conn, destination_project_conn, source_project, destination_project) @@ -137,7 +144,7 @@ def main(args): args.logger.info(f"F.00 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 = lib.server_detect_floating_address(i_source_server_detail) + i_source_server_fip_properties = olib.get_server_floating_ip_properties(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.01 server migration skipped - name:{i_source_server_detail.name} due to --explicit-server-names={args.explicit_server_names}") @@ -229,14 +236,21 @@ def main(args): 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 args.migrate_fip_addresses and i_source_server_has_fip: + if args.migrate_fip_addresses and i_source_server_fip_properties: # 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"F.39 Destination OpenStack server (name:{i_destination_server.name}) FIP is created ({i_destination_server_fip.floating_ip_address})", + 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"F.40 Destination OpenStack server (name:{i_destination_server.name}) FIP port is detected", - i_destination_server_port, locals()) + i_destination_server_ports = olib.find_ostack_port(destination_project_conn, + i_source_server_fip_properties['floating/OS-EXT-IPS-MAC:mac_addr'], + i_source_server_fip_properties['fixed/addr'], + project=destination_project) + lib.log_or_assert(args, f"F.40 Destination OpenStack server (name:{i_destination_server.name}) FIP port(s) are detected", + i_destination_server_ports, locals()) + lib.log_or_assert(args, f"F.40 Destination OpenStack server (name:{i_destination_server.name}) single FIP port is detected", + len(i_destination_server_ports) == 1, locals()) + i_destination_server_port = i_destination_server_ports[0] 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}") @@ -349,18 +363,23 @@ if __name__ == "__main__": AP.add_argument('--log-level', default="INFO", required=False, choices=[i_lvl for i_lvl in dir(logging) if i_lvl.isupper() and i_lvl.isalpha()], help='Executio log level (python logging)') - AP.add_argument('--debugging', default=False, required=False, action='store_true', - help='(Optional) Enter custom development debugging mode.') + AP.add_argument('--dry-run', default=False, required=False, + choices=["True", "true", "False", "false"], + help='Migration dry-run mode. Stop before first modification action.') + AP.add_argument('--debugging', default=False, required=False, + choices=["True", "true", "False", "false"], + help='(Optional) Enter development debugging mode.') ARGS = AP.parse_args() ARGS.logger = logging.getLogger("project-migrator") ARGS.explicit_server_names = lib.get_resource_names_ids(ARGS.explicit_server_names) ARGS.explicit_volume_names = lib.get_resource_names_ids(ARGS.explicit_volume_names) ARGS.migrate_fip_addresses = str(ARGS.migrate_fip_addresses).lower() == "true" + ARGS.dry_run = str(ARGS.dry_run).lower() == "true" + ARGS.debugging = str(ARGS.debugging).lower() == "true" + ARGS.migrate_reuse_already_migrated_volumes = str(ARGS.migrate_reuse_already_migrated_volumes).lower() == "true" + logging.basicConfig(level=getattr(logging, ARGS.log_level), format='%(asctime)s %(name)s %(levelname)s %(message)s') - if ARGS.debugging: - import IPython - #IPython.embed() sys.exit(main(ARGS))