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