From bd1375e57093767a4ff52aedf20ed5cfc828bdf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20=C5=98ezn=C3=AD=C4=8Dek?= <246254@mail.muni.cz> Date: Wed, 14 Feb 2024 10:37:50 +0100 Subject: [PATCH] feat: initial kick of the migration script and documentation --- README.md | 26 +++++++ ci/project-migrator.py | 162 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100755 ci/project-migrator.py diff --git a/README.md b/README.md index 3b871ca..b9e89fa 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,29 @@ e-INFRA CZ G1 to G2 OpenStack cloud workload migration. + +This is automation CI pipeline is core of the G1 to G2 migration process. +More details of the progress can be found [here](https://docs.e-infra.cz/compute/openstack/migration-to-g2-openstack-cloud/). + + +## Migration checklist + +* [ ] Define date of the workload migration +* [ ] Is the project personal or group? +* [ ] Are there any data in object store? +* [ ] Are there any DNS records on FIPs? + + + +## How to launch G1 to G2 migration + +Input argumenta are: + * `PROJECT_NAME` + * name of the project in G1 and in G2 OpenStack cloud + * `KEYPAIR_ID` + * id of any existing keypair in G1 or G2 cloud + * `SERVER_NAME` + * migrate just single VM instance with defined name + + + diff --git a/ci/project-migrator.py b/ci/project-migrator.py new file mode 100755 index 0000000..8c00f9c --- /dev/null +++ b/ci/project-migrator.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +""" +OpenStack project multicloud migrator +""" + +import argparse +import re +import sys + +import paramiko +import keystoneauth1.session +from keystoneauth1.identity import v3 +from openstack import connection +from keystoneauth1 import session + +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_session_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'), + } + auth = v3.Password(**auth_args) + ostack_sess = session.Session(auth=auth) + ostack_conn = connection.Connection(session=ostack_sess) + return ostack_sess, 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_keypairs(ostack_connection, project_name): + return ostack_connection.compute.keypairs() + +def get_ostack_project_security_groups(ostack_connection, project_name): + return ostack_connection.network.security_groups() + +def get_ostack_project_servers(ostack_connection, project_name): + return ostack_connection.compute.servers() + +def get_ostack_project_volumes(ostack_connection, project_name): + return ostack_connection.block_store.volumes() + + +def remote_cmd_exec(hostname, username, key_filename, command): + # Create SSH client + ssh_client = paramiko.SSHClient() + + # Automatically add untrusted hosts + ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + 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 + stdin, stdout, stderr = ssh_client.exec_command(command) + + # Read the output + output = stdout.read().decode().strip() + + # Close the SSH connection + ssh_client.close() + + return output + + except Exception as e: + print("Error:", e) + return None + +def main(args): + """ """ + # connect to source cloud + source_openrc = get_openrc(args.source_openrc) + source_sess, source_conn = get_ostack_session_connection(source_openrc) + + # connect to destination cloud + destination_openrc = get_openrc(args.destination_openrc) + destination_sess, destination_conn = get_ostack_session_connection(destination_openrc) + + # check project exists in source and destination + source_project = get_ostack_project(source_conn, args.project_name) + destination_project = get_ostack_project(destination_conn, args.project_name) + + # connect to migrator node + reply = remote_cmd_exec(args.ceph_migrator_host, args.ceph_migrator_user, + args.ceph_migrator_sshkeyfile.name, 'uname -a') + + # get source/destination keypairs + source_keypairs = get_ostack_project_keypairs(source_conn, args.project_name) + destination_keypairs = get_ostack_project_keypairs(destination_conn, args.project_name) + + + # get source/destination security groups + source_security_groups = get_ostack_project_security_groups(source_conn, args.project_name) + destination_security_groups = get_ostack_project_security_groups(destination_conn, args.project_name) + + # get source/destination servers in the project + source_project_servers = get_ostack_project_servers(source_conn, args.project_name) + destination_project_servers = get_ostack_project_servers(destination_conn, args.project_name) + + # get source/destination volumes in the project + source_project_volumes = get_ostack_project_volumes(source_conn, args.project_name) + destination_project_volumes = get_ostack_project_volumes(destination_conn, args.project_name) + + + + #print(dir(source_conn)) + print(locals()) + + # connect to source cloud + + # connect to ceph migrator node + + pass + + +# 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('--project-name', default=None, required=True, + help='OpenStack project name (identical in both clouds)') + AP.add_argument('--keypair-id', default=None, required=True, + help='Any keypair ID within selected OpenStack project') + + #AP.add_argument('--hide-skipped-actions', default=False, action='store_true', + # help="Hide skipped ansible actions") + + sys.exit(main(AP.parse_args())) -- GitLab