From c4ad2380f0b28fcad8d9b56e1e346618ec52d54e Mon Sep 17 00:00:00 2001 From: Neill Cox Date: Thu, 28 Sep 2023 11:50:07 +1000 Subject: [PATCH 01/19] Move common code to utils. Add option to run cmd over ssh --- src/tripleo_aio_helpers/os_migrate_setup.py | 37 +++------------------ src/tripleo_aio_helpers/utils.py | 31 +++++++++++++++++ 2 files changed, 36 insertions(+), 32 deletions(-) create mode 100644 src/tripleo_aio_helpers/utils.py diff --git a/src/tripleo_aio_helpers/os_migrate_setup.py b/src/tripleo_aio_helpers/os_migrate_setup.py index 45b71a0..0ed3357 100644 --- a/src/tripleo_aio_helpers/os_migrate_setup.py +++ b/src/tripleo_aio_helpers/os_migrate_setup.py @@ -4,23 +4,7 @@ Quick and dirty script to help setup project, flavors, networks, images import argparse import json -import os -import subprocess -import sys - - -def openstack_cmd(cmd, args): - """Utility function to run an openstack command agains the standalone cloud""" - cmd = "OS_CLOUD=standalone openstack " + cmd - - if args.dry_run: - print("dry-run specified. Not executing cmd. cmd was:") - print(cmd) - return - - result = subprocess.check_output(cmd, shell=True, universal_newlines=True) - return result - +from .utils import get_from_env,openstack_cmd def parse_args(): """Parse the command line arguments""" @@ -41,6 +25,7 @@ def parse_args(): parser.add_argument("--public-net-end") parser.add_argument("--dns-server") parser.add_argument("--dry-run", action="store_true") + parser.add_argument("--ssh", help="Connection string to run commands on a remote host.") # export OS_CLOUD=standalone # export STANDALONE_HOST=10.76.23.39 @@ -62,26 +47,14 @@ def parse_args(): if not args.dns_server: args.dns_server = get_from_env("--dns-server", "AIO_DNS_SERVER") + if not args.ssh: + args.ssh = get_from_env("--ssh", "AIO_SSH", required=False) + return args -def get_from_env(name, envvar): - value = os.environ.get(envvar) - - if value is None: - print( - f"You must specify {name}, either on the commandline or using " - f"the {envvar} environment varauble." - ) - sys.exit(1) - - return value - def create_project(args): """Create the project if it doesn't already exist""" - if args.dry_run: - print("Dry run specified. Not creating project") - return cmd = "project list -f json" project_exists = [ diff --git a/src/tripleo_aio_helpers/utils.py b/src/tripleo_aio_helpers/utils.py new file mode 100644 index 0000000..6906973 --- /dev/null +++ b/src/tripleo_aio_helpers/utils.py @@ -0,0 +1,31 @@ +import os +import subprocess +import sys + +def get_from_env(name, envvar, required=True): + value = os.environ.get(envvar) + + if value is None and required: + print( + f"You must specify {name}, either on the commandline or using " + f"the {envvar} environment varauble." + ) + sys.exit(1) + + return value + +def openstack_cmd(cmd, args): + """Utility function to run an openstack command agains the standalone cloud""" + cmd = "OS_CLOUD=standalone openstack " + cmd + + if args.ssh: + cmd = f"ssh {args.ssh} \"{cmd}\"" + + if args.dry_run: + print("dry-run specified. Not executing cmd. cmd was:") + print(cmd) + return + + result = subprocess.check_output(cmd, shell=True, universal_newlines=True) + return result + From 347d26aef7e50b45547ade899803e462676de2e5 Mon Sep 17 00:00:00 2001 From: Neill Cox Date: Wed, 18 Oct 2023 08:26:02 +1100 Subject: [PATCH 02/19] :closed_lock_with_key: Exclude secrets from git --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a6f2f85..9666b7f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ venv/ *.gen -*__pycache__* \ No newline at end of file +*__pycache__* +secrets.bash From 80830e5e6854c88c6b2a38652b5a390dc8bc3f52 Mon Sep 17 00:00:00 2001 From: Neill Cox Date: Wed, 18 Oct 2023 08:28:35 +1100 Subject: [PATCH 03/19] :bricks: Add pyproject.toml for packaging --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e3f6f07..f37bfe5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "tripleo_aio_helpers" -version = "0.0.1" +version = "0.2.1" authors = [ { name="Neill Cox", email="neill.cox@ingenious.com.au" }, ] @@ -25,4 +25,5 @@ classifiers = [ create_aio_vm = "tripleo_aio_helpers.create_aio_vm:main" os_migrate_setup = "tripleo_aio_helpers.os_migrate_setup:main" os_migrate_teardown = "tripleo_aio_helpers.os_migrate_teardown:main" +os_migrate_validate = "tripleo_aio_helpers.os_migrate_validate:main" prepare_deployment = "tripleo_aio_helpers.prepare_deployment:main" \ No newline at end of file From 3ed2e4a02903b5ea375121f7c7bf6ffe3bd8de41 Mon Sep 17 00:00:00 2001 From: Neill Cox Date: Wed, 18 Oct 2023 08:33:26 +1100 Subject: [PATCH 04/19] :adhesive_bandage: Update utility functions --- src/tripleo_aio_helpers/utils.py | 45 +++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/src/tripleo_aio_helpers/utils.py b/src/tripleo_aio_helpers/utils.py index 6906973..dfd5eab 100644 --- a/src/tripleo_aio_helpers/utils.py +++ b/src/tripleo_aio_helpers/utils.py @@ -1,31 +1,62 @@ +"""Utility functions""" +import json import os import subprocess import sys + def get_from_env(name, envvar, required=True): + """Get the value for a parameter from an environment variable""" value = os.environ.get(envvar) if value is None and required: print( f"You must specify {name}, either on the commandline or using " f"the {envvar} environment varauble." - ) + ) sys.exit(1) - + return value -def openstack_cmd(cmd, args): + +def openstack_cmd(cmd, args, as_json=False): """Utility function to run an openstack command agains the standalone cloud""" + + cmd = cmd.replace("\n", " ") cmd = "OS_CLOUD=standalone openstack " + cmd if args.ssh: - cmd = f"ssh {args.ssh} \"{cmd}\"" + cmd = f'ssh {args.ssh} "{cmd}"' if args.dry_run: print("dry-run specified. Not executing cmd. cmd was:") print(cmd) - return - - result = subprocess.check_output(cmd, shell=True, universal_newlines=True) + return None + + try: + result = subprocess.check_output(cmd, shell=True, universal_newlines=True) + + if as_json: + result = json.loads(result) + except subprocess.CalledProcessError as err: + print("Cmd failed") + print(f"cmd: {cmd}") + print(f"return code: {err.returncode}") + print(f"stderr: {err.stderr}") + print(f"stdout: {err.output}") + sys.exit(1) return result + +def test_user_openstack_cmd(cmd, args, as_json=False): + """Run an openstack command as the test user""" + cmd = ( + f"--os-project-id {args.project_id} " + f"--os-username {args.username} " + f"--os-password {args.password} " + ) + cmd + + if args.debug: + print(cmd) + + return openstack_cmd(cmd, args, as_json=as_json) From 283d0b51ebb305fe877842e0732a3936130b6a5f Mon Sep 17 00:00:00 2001 From: Neill Cox Date: Thu, 19 Oct 2023 16:15:37 +1100 Subject: [PATCH 05/19] :rotating_light: linting fixes for create_aio_vm.py --- src/tripleo_aio_helpers/create_aio_vm.py | 57 +++++++++++++++--------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/src/tripleo_aio_helpers/create_aio_vm.py b/src/tripleo_aio_helpers/create_aio_vm.py index 98ac977..47bf8c7 100644 --- a/src/tripleo_aio_helpers/create_aio_vm.py +++ b/src/tripleo_aio_helpers/create_aio_vm.py @@ -14,6 +14,8 @@ ND_PATH = "./network-config" def parse_args(): """Parse the command line arguments""" + # pylint: disable=too-many-statements + template_path = pathlib.Path(__file__).parent.parent / "virt-install" parser = argparse.ArgumentParser() parser.add_argument("--password", required=True) @@ -21,12 +23,10 @@ def parse_args(): parser.add_argument("--local-hostname", required=True) parser.add_argument("--user-data", default=template_path / "user-data.tpl") parser.add_argument("--meta-data", default=template_path / "meta-data.tpl") - parser.add_argument( - "--network-data", default=template_path / "network-config.tpl" - ) + parser.add_argument("--network-data", default=template_path / "network-config.tpl") parser.add_argument("--instance-id", required=True, help="Hostname for the new VM") parser.add_argument("--output-image", required=True) - parser.add_argument("--image-size", default="100G") + parser.add_argument("--image-size", default="800G") parser.add_argument("--input-image", required=True) parser.add_argument("--os-variant", required=True) parser.add_argument("--name", required=True) @@ -35,9 +35,11 @@ def parse_args(): parser.add_argument("--gateway") parser.add_argument("--cidr-1") parser.add_argument("--cidr-2") - parser.add_argument("--mac-1",default="RANDOM") - parser.add_argument("--mac-2",default="RANDOM") - parser.add_argument("--dns",) + parser.add_argument("--mac-1", default="RANDOM") + parser.add_argument("--mac-2", default="RANDOM") + parser.add_argument( + "--dns", + ) parser.add_argument("--search-domain") parser.add_argument("-v", "--verbose", action="store_true") parser.add_argument("--rhn-user", required=True) @@ -61,22 +63,27 @@ def parse_args(): print("You must specify a DNS server if you specify any addresses") sys.exit(1) - if not (args.cidr_1 or args.cidr_2) and (args.dns or args.gateway or args.search_domain): - print("There's no point specifying DNS, gateway or search_domain if yoou don't specify addresses") - sys.exit(1) + # if not (args.cidr_1 or args.cidr_2) and ( + # args.dns or args.gateway or args.search_domain + # ): + # print( + # "There's no point specifying DNS, gateway or search_domain if you" + # " don't specify addresses" + # ) + # sys.exit(1) args.public_key = args.public_key.read() - with open(args.user_data) as user_data: + with open(args.user_data, encoding="utf-8") as user_data: args.user_data = user_data.read() generate_boot_cmd(args) args.user_data = args.user_data.format(data=args) - with open(args.meta_data) as meta_data: + with open(args.meta_data, encoding="utf-8") as meta_data: args.meta_data = meta_data.read() args.meta_data = args.meta_data.format(data=args) - with open(args.network_data) as network_data: + with open(args.network_data, encoding="utf-8") as network_data: args.network_data = network_data.read() args.network_data = args.network_data.format(data=args) @@ -92,17 +99,24 @@ def parse_args(): return args + def generate_boot_cmd(data): - data.bootcmd="" + """Generate the bootcmd section of the user-data""" + data.bootcmd = "" if data.cidr_1: data.bootcmd = ( "bootcmd:\n" - f' - "nmcli con modify \'System eth0\' ipv4.address {data.cidr_1} ipv4.method static ipv4.gateway {data.gateway} ipv4.dns {data.dns}"\n' - f' - "nmcli con modify \'Wired connection 1\' ipv4.address {data.cidr_2} ipv4.method static ipv4.gateway {data.gateway} ipv4.dns {data.dns}"\n' + f" - \"nmcli con modify 'System eth0' ipv4.address {data.cidr_1}" + f" ipv4.method static ipv4.gateway {data.gateway} " + f'ipv4.dns {data.dns}"\n' + " - \"nmcli con modify 'Wired connection 1' ipv4.address " + f"{data.cidr_2} ipv4.method static ipv4.gateway {data.gateway} " + f'ipv4.dns {data.dns}"\n' ' - "nmcli networking off"\n' ' - "nmcli networking on"\n' ) + def write_user_data(data): """Write out a temporary user data file""" @@ -124,7 +138,7 @@ def write_network_data(network_data): def create_image(args): """ - Create a new image file bashed on the input image, resized to the + Create a new image file bashed on the input image, resized to the specified size. """ @@ -132,7 +146,7 @@ def create_image(args): cmd = ( "qemu-img create -f qcow2 -o preallocation=metadata " f"{args.output_image} {args.image_size}" - ) + ) result = subprocess.check_output(cmd, shell=True, universal_newlines=True) if args.verbose: @@ -148,6 +162,7 @@ def create_image(args): print("Image resized") + def virt_install_cmd(args): """Build and execute the virt-install command""" cmd = f""" @@ -190,16 +205,14 @@ def delete_user_data(): def install_packages(args): """Install the packages needed for virt-install to work""" cmd = ( - "sudo dnf install -y virt-install virt-viewer qemu-img " + "sudo dnf install -y virt-install virt-viewer qemu-img " "libguestfs.x86_64" ) print("installing needed packages...") if args.verbose: - print( - subprocess.check_output(cmd, shell=True, universal_newlines=True) - ) + print(subprocess.check_output(cmd, shell=True, universal_newlines=True)) def main(): From 96e88b00c18bd7dafd662c8b2e74d2d17f0de04d Mon Sep 17 00:00:00 2001 From: Neill Cox Date: Thu, 19 Oct 2023 16:21:52 +1100 Subject: [PATCH 06/19] :sparkles: finish basic functionality of os_migrate_setup.py --- src/tripleo_aio_helpers/os_migrate_setup.py | 433 ++++++++++++++++++-- 1 file changed, 389 insertions(+), 44 deletions(-) diff --git a/src/tripleo_aio_helpers/os_migrate_setup.py b/src/tripleo_aio_helpers/os_migrate_setup.py index 0ed3357..e085830 100644 --- a/src/tripleo_aio_helpers/os_migrate_setup.py +++ b/src/tripleo_aio_helpers/os_migrate_setup.py @@ -3,8 +3,22 @@ Quick and dirty script to help setup project, flavors, networks, images """ import argparse import json +import subprocess +import tempfile + +from .utils import get_from_env, openstack_cmd, test_user_openstack_cmd + + +def execute_cmd(cmd): + """Execute a command""" + return subprocess.check_output(cmd, shell=True, universal_newlines=True) + + +def execute_ssh_cmd(cmd, args): + """Execute a command on a remote host using ssh""" + cmd = f'ssh {args.ssh} "{cmd}"' + return subprocess.check_output(cmd, shell=True, universal_newlines=True) -from .utils import get_from_env,openstack_cmd def parse_args(): """Parse the command line arguments""" @@ -20,12 +34,28 @@ def parse_args(): parser.add_argument("-c", "--cloud", default="standalone") parser.add_argument("-g", "--gateway") parser.add_argument("-C", "--public-network-cidr") + parser.add_argument( + "--ssh-key", help="File containing a public key to inject into the instances" + ) parser.add_argument("--private-network-cidr", default="192.168.100.0/24") parser.add_argument("--public-net-start") parser.add_argument("--public-net-end") parser.add_argument("--dns-server") parser.add_argument("--dry-run", action="store_true") - parser.add_argument("--ssh", help="Connection string to run commands on a remote host.") + parser.add_argument( + "--ssh", help="Connection string to run commands on a remote host." + ) + parser.add_argument( + "--cirros-url", + help="a URL that a cirros image can be downloaded from. Required. " + "Can be set in AIO_CIRROS_URL", + ) + parser.add_argument( + "--rhel-url", + help="a URL that a RHEL image can be downloaded from. Required. Can " + "be set in AIO_RHEL_URL", + ) + parser.add_argument("--debug", action="store_true") # export OS_CLOUD=standalone # export STANDALONE_HOST=10.76.23.39 @@ -34,12 +64,16 @@ def parse_args(): if not args.gateway: args.gateway = get_from_env("--gateway", "AIO_GATEWAY") - + if not args.public_network_cidr: - args.public_network_cidr = get_from_env("--public-network-cidr", "AIO_PUBLIC_CIDR") - + args.public_network_cidr = get_from_env( + "--public-network-cidr", "AIO_PUBLIC_CIDR" + ) + if not args.public_net_start: - args.public_net_start = get_from_env("--public-net-start", "AIO_PUBLIC_NET_START") + args.public_net_start = get_from_env( + "--public-net-start", "AIO_PUBLIC_NET_START" + ) if not args.public_net_end: args.public_net_end = get_from_env("--public-net-end", "AIO_PUBLIC_NET_END") @@ -47,15 +81,24 @@ def parse_args(): if not args.dns_server: args.dns_server = get_from_env("--dns-server", "AIO_DNS_SERVER") + if not args.cirros_url: + args.dns_server = get_from_env("--dns-server", "AIO_CIRROS_URL") + + if not args.rhel_url: + args.dns_server = get_from_env("--dns-server", "AIO_RHEL_URL") + if not args.ssh: args.ssh = get_from_env("--ssh", "AIO_SSH", required=False) + if not args.ssh_key: + args.ssh_key = get_from_env("--ssh-key", "AIO_SSH_KEY") + return args + def create_project(args): """Create the project if it doesn't already exist""" - cmd = "project list -f json" project_exists = [ x @@ -84,7 +127,7 @@ def create_user(args): if args.dry_run: print("Dry run specified. Not creating user") return - + cmd = "user list -f json" user_exists = [ x for x in json.loads(openstack_cmd(cmd, args)) if x["Name"] == args.username @@ -116,7 +159,7 @@ def assign_member_role(args): if args.dry_run: print("Dry run specified. Not assigning role") return - + cmd = f"role add --user {args.username} --project {args.project_id} member" result = openstack_cmd(cmd, args) @@ -129,66 +172,362 @@ def assign_member_role(args): def create_public_network(args): - """Coming soon - create the public network""" - # pylint: disable=unused-argument,unused-variable - print("creating public network - NYI") - cmd = ( - "network create --external --provider-physical-network datacentre " - "--provider-network-type flat public" + """Create the public network""" + + network_exists = json.loads( + openstack_cmd("network list -f json --name public", args) ) - cmd = ( - f"subnet create public-net --subnet-range {args.public_network_cidr} " - f"--no-dhcp --gateway {args.gateway} --allocation-pool " - f"start={args.public_net_start},end={args.public_net_end} " - "--network public" + + if network_exists: + print("Public network already exists - skipping") + args.public_network_id = network_exists[0]["ID"] + else: + cmd = ( + "network create -f json --external --provider-physical-network datacentre " + "--provider-network-type flat public" + ) + try: + args.public_network_id = json.loads(openstack_cmd(cmd, args))["id"] + except json.decoder.JSONDecodeError: + print(cmd) + raise + print("Public network created.") + + subnet_exists = json.loads( + openstack_cmd("subnet list -f json --name public-net", args) ) + if subnet_exists: + print("Public subnet exists - skipping") + else: + cmd = ( + "subnet create public-net -f json " + f"--subnet-range {args.public_network_cidr} " + f"--gateway {args.gateway} " + f"--allocation-pool start={args.public_net_start},end={args.public_net_end} " + "--network public " + f"--host-route destination=0.0.0.0/0,gateway={args.gateway} " + f"--dns-nameserver {args.dns_server}" + ) + args.public_subnet_id = json.loads(openstack_cmd(cmd, args)) + print("Public subnet created.") def create_private_network(args): - """Coming soon - create the private network""" - # pylint: disable=unused-argument,unused-variable - cmd = "openstack network create --internal private" - cmd = ( - "openstack subnet create private-net " - f"--subnet-range {args.private_network_cidr} --network private" + """Create the private network and subnet""" + + network_exists = json.loads( + openstack_cmd( + f"network list -f json --project {args.project_id} --name private", args + ) ) - print("creating private network - NYI") + + if network_exists: + print("Private network already exists - skipping") + args.private_network_id = network_exists[0]["ID"] + else: + cmd = f"network create -f json --internal private --project {args.project_id}" + args.private_network_id = json.loads(openstack_cmd(cmd, args))["id"] + print("Private network created.") + + subnet_exists = json.loads( + openstack_cmd( + f"subnet list -f json --project {args.project_id} --name private-net", args + ) + ) + if subnet_exists: + print("Private subnet exists - skipping") + args.private_subnet_id = subnet_exists[0]["ID"] + else: + cmd = ( + f"subnet create private-net -f json --project {args.project_id} " + f"--subnet-range {args.private_network_cidr} --network {args.private_network_id}" + ) + args.private_subnet_id = json.loads(openstack_cmd(cmd, args)) + print("Private subnet created.") + + +def create_flavor(args, flavor_name, memory, disk, cpus): + """Create a flavor - DRY""" + cmd = "flavor list -f json --all" + # Note we are going to assume that there is only one cirros flavor. This + # will be more of a problem come deletion time. + result = json.loads(openstack_cmd(cmd, args)) + flavor_exists = [x for x in result if x["Name"] == flavor_name] + if flavor_exists: + print(f"{flavor_name} flavor already exists. Skipping creation") + args.__dict__[flavor_name + "_flavor"] = flavor_exists[0]["ID"] + return + + print("creating cirros flavor") + # Note can't add a description in RHOSP16, but perhaps should add for 17 + cmd = f""" + flavor create -f json + --ram {memory} + --disk {disk} + --vcpus {cpus} + --private + --project {args.project_id} + {flavor_name} + """.replace( + "\n", " " + ) + + result = openstack_cmd(cmd, args, as_json=True) + args.__dict__[flavor_name] = result["id"] + print(result) def create_cirros_flavor(args): - """Coming soon - create the cirros flavor""" - # pylint: disable=unused-argument - print("creating cirros flavor - NYI") + """create the cirros flavor""" + create_flavor(args, "cirros", 256, 20, 1) def create_rhel_flavor(args): - """Coming soon - create the rhel flavor""" - # pylint: disable=unused-argument - print("creating rhel flavor - NYI") + """create the rhel flavor""" + create_flavor(args, "rhel", 1536, 120, 2) + + +def create_image(image_name, image_url, disk_size, memory, args): + """Create an image - DRY""" + + cmd = "image list -f json" + image_exists = [ + image + for image in json.loads(openstack_cmd(cmd, args)) + if image["Name"] == image_name + ] + + if image_exists: + print(f"{image_name} already exists - not creating.") + args.__dict__[image_name] = image_exists[0]["ID"] + return + + with tempfile.TemporaryDirectory() as tmp_dir: + + # download image to tmpdir + fname = f"{tmp_dir}/{image_name}.img" + cmd = f"wget -O {fname} {image_url}" + _ = execute_cmd(cmd) + + if args.ssh: + # Copy img to remote host + cmd = f"scp {fname} {args.ssh}:{image_name}.img" + execute_cmd(cmd) + + # create image - note this will only work on a remote ssh host at the moment + cmd = f""" + image create -f json + --container-format bare + --disk-format qcow2 + --min-disk {disk_size} --min-ram {memory} + --file {image_name}.img + --private + --project {args.project_id} + {image_name} + """ + + result = openstack_cmd(cmd, args, as_json=True) + args.__dict__[image_name] = result["id"] + + if args.ssh: + # Delete image from remote host + cmd = f"rm {image_name}.img" + execute_ssh_cmd(cmd, args) def create_cirros_image(args): - """Coming soon - create the cirros image""" - # pylint: disable=unused-argument - print("creating cirros image - NYI") + """create the cirros image""" + + create_image("cirros_image", args.cirros_url, 20, 256, args) def create_rhel_image(args): - """Coming soon - create the rhel image""" - # pylint: disable=unused-argument - print("creating rhel image - NYI") + """create the rhel image""" + create_image("rhel_image", args.rhel_url, 120, 1536, args) + + +def create_instance(args, name, flavor, image, security_group, boot_size): + """Create an instance""" + # pylint:disable=too-many-arguments + instance_exists = [ + instance + for instance in test_user_openstack_cmd( + "server list -f json", args, as_json=True + ) + if instance["Name"] == name + ] + + if instance_exists: + print(f"{name} instance exists - skipping") + else: + cmd = ( + f"server create --flavor {flavor} " + f"--image {image} " + f"--key-name test_keypair " + f"--security-group {security_group} " + "--network private " + f"--boot-from-volume {boot_size} " + "-f json " + f"{name}" + ) + + server = test_user_openstack_cmd(cmd, args, as_json=True) + + print(f"{name} instance created") + # assign floating IP + fip = test_user_openstack_cmd( + "floating ip create -f json public", args, as_json=True + ) + _ = test_user_openstack_cmd( + f"server add floating ip {server['id']} {fip['floating_ip_address']}", args + ) def create_cirros_instance(args): - """Coming soon - create the cirros instance""" - # pylint: disable=unused-argument - print("creating cirros instance - NYI") + """Create the cirros instance""" + + create_instance( + args, + "cirros-test-instance", + args.cirros_flavor, + args.cirros_image, + args.sg_id, + 20, + ) def create_rhel_instance(args): """Coming soon - create the rhel instance""" - # pylint: disable=unused-argument - print("creating rhel instance - NYI") + create_instance( + args, + "rhel-test-instance", + args.rhel_flavor, + args.rhel_image, + args.sg_id, + 120 + ) + + +def create_conversion_instance(args): + """Coming soon - create the rhel instance""" + create_instance( + args, + "rhel-conversion", + args.rhel_flavor, + args.rhel_image, + args.sg_id, + 120 + ) + + +def create_keypair(args): + """Create a keypair to allow ssh later""" + + key_exists = [ + kp + for kp in json.loads(test_user_openstack_cmd("keypair list -f json ", args)) + if kp["Name"] == "test_keypair" + ] + + if key_exists: + args.keypair_name = key_exists[0]["Name"] + else: + if args.ssh: + fname = "temp_pub_key" + cmd = f"scp {args.ssh_key} {args.ssh}:{fname}" + + execute_cmd(cmd) + + args.keypair_name = json.loads( + test_user_openstack_cmd( + f"keypair create -f json --public-key {fname} test_keypair", args + ) + )[ + "name" + ] # a little inconsistent here... + + _ = execute_ssh_cmd(f"rm {fname}", args) + else: + raise NotImplementedError("Only works over ssh") + + +def create_router(args): + """Create a router""" + router_id = None + + try: + router_id = [ + router + for router in openstack_cmd("router list -f json", args, as_json=True) + if router["Name"] == "test-router" + ][0]["ID"] + except IndexError: + pass + + if not router_id: + router_id = test_user_openstack_cmd( + "router create test-router -f json", args, as_json=True + )["id"] + print("router created") + + router_info = openstack_cmd(f"router show -f json {router_id}", args, as_json=True) + + if router_info["external_gateway_info"]: + print("router gateway already set") + else: + openstack_cmd( + f"router set {router_id} --external-gateway {args.public_network_id}", args + ) + print("router gateway added") + + if [ + interface + for interface in router_info["interfaces_info"] + if interface["subnet_id"] == args.private_subnet_id + ]: + print("router already connected to private subnet") + else: + cmd = f"router add subnet {router_id} {args.private_subnet_id}" + print(cmd) + openstack_cmd(cmd, args) + print("router subnet added") + + args.router_id = router_id + + +def create_security_group(args): + """Create a security group that allows ssh and icmp""" + + sg_exists = [ + sg + for sg in json.loads( + test_user_openstack_cmd("security group list -f json", args) + ) + if sg["Name"] == "test-sg" + ] + + if sg_exists: + args.sg_id = sg_exists[0]["ID"] + else: + sec_grp = json.loads( + test_user_openstack_cmd( + "security group create test-sg -f json", + args + ) + ) + args.sg_id = sec_grp["id"] + + _ = test_user_openstack_cmd( + ( + "security group rule create --dst-port 22 " + f"--protocol tcp {args.sg_id}" + ), + args, + ) + + _ = test_user_openstack_cmd( + ("security group rule create --protocol icmp " f"{args.sg_id}"), args + ) def main(): @@ -202,6 +541,7 @@ def main(): create_public_network(args) create_private_network(args) + create_router(args) create_cirros_flavor(args) create_rhel_flavor(args) @@ -209,9 +549,14 @@ def main(): create_cirros_image(args) create_rhel_image(args) + create_keypair(args) + create_security_group(args) + create_cirros_instance(args) create_rhel_instance(args) + ## create_os_migrate host + if __name__ == "__main__": main() From d4c2c2c747c0f63d8b71edd2eed70055c5f7863d Mon Sep 17 00:00:00 2001 From: Neill Cox Date: Thu, 19 Oct 2023 16:23:54 +1100 Subject: [PATCH 07/19] :rotating_light: linting fixes for prepare_deployment.py --- src/tripleo_aio_helpers/prepare_deployment.py | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/tripleo_aio_helpers/prepare_deployment.py b/src/tripleo_aio_helpers/prepare_deployment.py index 993805a..4597e13 100644 --- a/src/tripleo_aio_helpers/prepare_deployment.py +++ b/src/tripleo_aio_helpers/prepare_deployment.py @@ -18,9 +18,7 @@ def parse_args(): parser.add_argument("-a", "--address", required=True) parser.add_argument("-i", "--interface", required=True) parser.add_argument("-m", "--netmask", default=24) - parser.add_argument( - "-d", "--dns", nargs="+", action="append", required=True - ) + parser.add_argument("-d", "--dns", nargs="+", action="append", required=True) parser.add_argument("-D", "--domain", default="aio") parser.add_argument("--using-multiple-nics", action="store_true") parser.add_argument("-U", "--deployment-user") @@ -37,6 +35,11 @@ def parse_args(): parser.add_argument( "--deploy-script-out", default=f"{home}/deploy.sh.gen", dest="deploy" ) + parser.add_argument( + "--cinder-pool-size", + default="500280", + help="Size of the loopback device allocated to cinder. " "Defaults to 500280", + ) args = parser.parse_args() @@ -69,6 +72,7 @@ def get_standalone_parameters(args): "StandaloneEnableRoutedNetworks": False, "StandaloneHomeDir": args.deployment_dir, "StandaloneLocalMtu": 1500, + "CinderLVMLoopDeviceSize": args.cinder_pool_size, } } @@ -128,22 +132,17 @@ def set_hostname(args): print("set_hostname is not yet implemented") - def main(): """main function""" args = parse_args() containers_yaml = build_containers_yaml(args) - with open( - args.containers_yaml_out, "w", encoding="utf-8" - ) as containers_out: + with open(args.containers_yaml_out, "w", encoding="utf-8") as containers_out: containers_out.write(yaml.dump(containers_yaml)) print(f"containers yaml written to {args.containers_yaml_out}") standalone_parameters = get_standalone_parameters(args) - with open( - args.standalone_yaml_out, "w", encoding="utf-8" - ) as parameters_out: + with open(args.standalone_yaml_out, "w", encoding="utf-8") as parameters_out: parameters_out.write(yaml.dump(standalone_parameters)) print(f"standalone parameters yaml written to {args.standalone_yaml_out}") @@ -151,7 +150,10 @@ def main(): deploy.write(deploy_sh(args)) print(f"deploy script written to {args.deploy}") - print("If you are running on a cloud image remenber to disable cloud-init before running the deploy.") + print( + "If you are running on a cloud image remenber to disable cloud-init " + "before running the deploy." + ) print("sudo systemctl stop cloud-init") print("sudo systemctl disable cloud-init") print("Make sure you have specifed the correct interface to use!") From 23de20b1e5776c62cf36793bbd148663553bffc2 Mon Sep 17 00:00:00 2001 From: Neill Cox Date: Thu, 19 Oct 2023 16:26:00 +1100 Subject: [PATCH 08/19] :sparkles: add functionality to os_migrate_teardown.py --- .../os_migrate_teardown.py | 214 +++++++++--------- 1 file changed, 113 insertions(+), 101 deletions(-) diff --git a/src/tripleo_aio_helpers/os_migrate_teardown.py b/src/tripleo_aio_helpers/os_migrate_teardown.py index 86239b1..f24cf3b 100644 --- a/src/tripleo_aio_helpers/os_migrate_teardown.py +++ b/src/tripleo_aio_helpers/os_migrate_teardown.py @@ -4,21 +4,7 @@ Quick and dirty script to help setup project, flavors, networks, images import argparse import json -# import os -import subprocess - - -def openstack_cmd(cmd, args): - """Utility function to run an openstack command agains the standalone cloud""" - cmd = "OS_CLOUD=standalone openstack " + cmd - - if args.dry_run: - print("dry-run specified. Not executing cmd. cmd was:") - print(cmd) - return - - result = subprocess.check_output(cmd, shell=True, universal_newlines=True) - return result +from .utils import openstack_cmd, test_user_openstack_cmd def parse_args(): @@ -27,23 +13,23 @@ def parse_args(): # home = os.environ.get('HOME') parser = argparse.ArgumentParser() - parser.add_argument("-d", "--project-domain", default="default") parser.add_argument("-n", "--project-name", default="test-project") - parser.add_argument("-D", "--project-description", default="Test project") parser.add_argument("-u", "--username", default="test-user") parser.add_argument("-p", "--password", default="secrete123") parser.add_argument("-c", "--cloud", default="standalone") - parser.add_argument("-g", "--gateway", default="10.76.23.254") - parser.add_argument( - "-C", "--public-network-cider", default="10.76.23.0/24" - ) - parser.add_argument("--private-network-cidr", default="192.168.100.0/24") - parser.add_argument("--publice-net-start", default="10.76.23.50") - parser.add_argument("--publice-net-end", default="10.76.23.52") - parser.add_argument("--dns-server", default="10.76.23.245") + parser.add_argument("--delete-all", action="store_true") + parser.add_argument("--delete-images", action="store_true") + parser.add_argument("--delete-flavors", action="store_true") + parser.add_argument("--delete-networks", action="store_true") + parser.add_argument("--delete-instances", action="store_true") + parser.add_argument("--delete-user", action="store_true") + parser.add_argument("--delete-project", action="store_true") - # export OS_CLOUD=standalone - # export STANDALONE_HOST=10.76.23.39 + parser.add_argument("--dry-run", action="store_true") + parser.add_argument( + "--ssh", help="Connection string to run commands on a remote host." + ) + parser.add_argument("--debug", action="store_true") args = parser.parse_args() @@ -63,13 +49,11 @@ def destroy_project(args): print(f"Project {args.project_name} exists. Will delete") args.project_id = project_exists[0]["ID"] - cmd = ( - f"project delete -f json --domain {args.project_domain} " - f"{args.project_id}" - ) + cmd = f"project delete {args.project_id}" + _ = openstack_cmd(cmd, args) print(f"Project {args.project_name} deleted") else: - print("Project {args.project_name} not found.") + print(f"Project {args.project_name} not found.") def destroy_user(args): @@ -80,64 +64,57 @@ def destroy_user(args): ] if user_exists: - print(f"User {args.username} already exists. Skipping creation") - args.user_id = user_exists[0]["ID"] - return + user_id = user_exists[0]["ID"] - cmd = ( - f"user create -f json --project {args.project_id} " - f"--password {args.password} {args.username}" - ) - - args.user_id = json.loads(openstack_cmd(cmd, args))["id"] - - print(f"User created - id: {args.user_id}") - - -def assign_member_role(args): - """ - Assign the member role to the user. - - Note: it doesn't matter if the role is assigned multiple times, so not - bothering to check. - """ - - cmd = f"role add --user {args.username} --project {args.project_id} member" - - result = openstack_cmd(cmd, args) - - cmd = f"role assignment list --user {args.user_id} --role member -f json" - result = json.loads(openstack_cmd(cmd, args)) - - if result: - print("User has member role") + openstack_cmd(f"user delete {user_id}", args) + print(f"User deleted - id: {user_id}") + else: + print(f"User {args.username} not found.") def destroy_public_network(args): - """Coming soon - create the public network""" - # pylint: disable=unused-argument,unused-variable - print("creating public network - NYI") - cmd = ( - "network create --external --provider-physical-network datacentre " - "--provider-network-type flat public" - ) - cmd = ( - f"subnet create public-net --subnet-range {args.publice_network_cidr} " - f"--no-dhcp --gateway {args.gateway} --allocation-pool " - f"start={args.public_net_start},end={args.public_net_end} " - "--network public" - ) + """Delete the public network""" + print("deleting public network") + + public_network_exists = [ + network + for network in openstack_cmd("network list -f json", args, as_json=True) + if network["Name"] == "public" + ] + + if public_network_exists: + routers = test_user_openstack_cmd( + "router list -f json", args, as_json=True + ) + + for router in routers: + + router_details = test_user_openstack_cmd( + f"router show -f json {router['ID']}", args, as_json=True + ) + + for interface in router_details["interfaces_info"]: + test_user_openstack_cmd( + f"router remove port {router['ID']} {interface['port_id']}", + args, + ) + + test_user_openstack_cmd(f"router delete {router['ID']}", args) + + openstack_cmd("network delete public", args) def destroy_private_network(args): - """Coming soon - create the private network""" - # pylint: disable=unused-argument,unused-variable - cmd = "openstack network create --internal private" - cmd = ( - "openstack subnet create private-net " - f"--subnet-range {args.private_network_cidr} --network private" - ) - print("creating private network - NYI") + """Delete the private network""" + private_network_exists = [ + network + for network in openstack_cmd("network list -f json", args, as_json=True) + if network["Name"] == "private" + ] + + if private_network_exists: + print("deleting private network") + test_user_openstack_cmd("network delete private", args) def destroy_cirros_flavor(args): @@ -153,9 +130,9 @@ def destroy_rhel_flavor(args): def destroy_cirros_image(args): - """Coming soon - create the cirros image""" + """Coming soon - destroy the cirros image""" # pylint: disable=unused-argument - print("creating cirros image - NYI") + print("destroying cirros image - NYI") def destroy_rhel_image(args): @@ -165,35 +142,70 @@ def destroy_rhel_image(args): def destroy_cirros_instance(args): - """Coming soon - create the cirros instance""" - # pylint: disable=unused-argument - print("creating cirros instance - NYI") + """Delete the cirros instance""" + try: + cirros_instance = [ + instance + for instance in test_user_openstack_cmd( + "server list -f json", args, as_json=True + ) + if instance["Name"] == "cirros-test-instance" + ][0]["ID"] + + test_user_openstack_cmd(f"server delete {cirros_instance}", args) + except IndexError: + print("No cirros instance found") def destroy_rhel_instance(args): - """Coming soon - create the rhel instance""" - # pylint: disable=unused-argument - print("creating rhel instance - NYI") + """Delete the cirros instance""" + try: + rhel_instance = [ + instance + for instance in test_user_openstack_cmd( + "server list -f json", args, as_json=True + ) + if instance["Name"] == "rhel-test-instance" + ][0]["ID"] + + test_user_openstack_cmd(f"server delete {rhel_instance}", args) + except IndexError: + print("No RHEL instance found") + + +def get_project_id(args): + """Get the id of the test project""" + result = openstack_cmd("project list -f json", args, as_json=True) + args.project_id = [r for r in result if r["Name"] == args.project_name][0]["ID"] def main(): """main function""" args = parse_args() - # destroy_cirros_instance(args) - # destroy_rhel_instance(args) + get_project_id(args) - # destroy_cirros_image(args) - # destroy_rhel_image(args) + if args.delete_instances or args.delete_all: + destroy_cirros_instance(args) + destroy_rhel_instance(args) - # destroy_cirros_flavor(args) - # destroy_rhel_flavor(args) + if args.delete_images or args.delete_all: + destroy_cirros_image(args) + destroy_rhel_image(args) - # destroy_public_network(args) - # destroy_private_network(args) + if args.delete_flavors or args.delete_all: + destroy_cirros_flavor(args) + destroy_rhel_flavor(args) - destroy_user(args) - destroy_project(args) + if args.delete_networks or args.delete_all: + destroy_public_network(args) + destroy_private_network(args) + + if args.delete_user or args.delete_all: + destroy_user(args) + + if args.delete_project or args.delete_all: + destroy_project(args) if __name__ == "__main__": From f930b5f7462a926205f37af59c0cc10807d3bb96 Mon Sep 17 00:00:00 2001 From: Neill Cox Date: Thu, 19 Oct 2023 16:26:55 +1100 Subject: [PATCH 09/19] :memo: Update documentation --- README.md | 30 ++++++++++++---- TODO.md | 21 +++++++++++ docs/create_aio_vm.md | 5 +++ docs/notes.md | 84 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 7 deletions(-) create mode 100644 TODO.md create mode 100644 docs/create_aio_vm.md create mode 100644 docs/notes.md diff --git a/README.md b/README.md index f68261e..53c81e4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -A quick and dirty script to help set up a RHOSP AIO - -This has grown a bit. At some point I will make this a proper readme, but for now a few notes: +A set of quick and dirty scripts to help set up a RHOSP AIO The goal is to build a VM (although you could use baremetal) and deploy a RHOSP AIO to it. Then to add a project, flavors, images, networks to support testing os-migrate. @@ -8,20 +6,38 @@ After that create a VM to migrate, a conversion host to do the migration. This may well be useful for other things, but this is the current aim. +There are/will be example scripts in the examples directory + +Everything is a bit of a mess but here's a brief overview: + +## create_aio_vm.py + +Uses virt-install to build a VM to deploy a RHOSP 16 AIO on. + I am primarily targetting RHEL8.4 and RHOSP16.2. As a result I'm tied to virt-install v3.2.0 which makes using cloud-init for configuring network on the AIO VM a little annoying. Later versions allow you to pass in a network config, but 3.2.0 means I have to use nmcli and bootcmd to get the network configure. There is a vestigial network-config.tpl in the virt-install directory but it is not actually used yet. -EVerything is a bit of a mess but here's a brief overview: +## os_migrate_setup.py -main.py - original script. Generates the yaml files and a dploy script to deploy a RHOSP AIO. Assumes a VM to run on. Don't run this on your laptop. You'll be sorry if you do. +build the OSP infrastructure to allow testing os-migrate. -os_migrate_setup.py - build the OSP infrastructure to allow testing os-migrate. +## os_migrate_teardown.py -os_migrate_teardown.py - remove that infrastructure (to allow for refinement/iteration) +remove that infrastructure (to allow for refinement/iteration) virt-install/create_aio_vm.py - use virt-install to create and configure a VM to run the other scripts on. +## prepare-deployment + +Original script. Generates the yaml files and a dploy script to deploy a RHOSP AIO. Assumes a VM to run on. Don't run this on your laptop. You'll be sorry if you do. + +## os_migrate_validate.py + +Check that the necessary infrastructure has been deployed. + +# Remember to mask and disable cloud-init if installing on a VM + ``` usage: main.py [-h] -u USERNAME -p PASSWORD -a ADDRESS [-i INTERFACE] [-m NETMASK] -d DNS [DNS ...] [-D DOMAIN] diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..3919cfc --- /dev/null +++ b/TODO.md @@ -0,0 +1,21 @@ +# Tests + +There aren't any which. At least basic unit tests would be good. + +# Documentation + +There's a README, but it should be expanded. + +# Error handling. + +There's very litle and this is a complex process involving talking to other systems over the net. + +# Convert to Ansible + +Maybe this should all have been an ansible role. Maybe that will happen yet. + +# Ideas for specific scripts: + +## create_aio_vm + +Quite a few compromises to do with the versions of virt_install / libvirt available to me. \ No newline at end of file diff --git a/docs/create_aio_vm.md b/docs/create_aio_vm.md new file mode 100644 index 0000000..7c933a3 --- /dev/null +++ b/docs/create_aio_vm.md @@ -0,0 +1,5 @@ +# Documentation for create_aio_vm + +## Networking + +Networking has been a real pain. The RHOSP AIO deployment seems to force the vr-ctlplane interface to use dhcp. Originally I was trying to use static IPs configured with cloud-init (using bootcmd because of the version) but the deploy would die. To fix this I now speciiy MACs for the interfaces and then use an external DHCP server. Maybe this is configurable in the RHOSP standalone deploy. If so it would be better to use that. It would also be nice to do some feature switches so that if the version of virt-install allows I could use network-config instead of bootcmd \ No newline at end of file diff --git a/docs/notes.md b/docs/notes.md new file mode 100644 index 0000000..6149f3b --- /dev/null +++ b/docs/notes.md @@ -0,0 +1,84 @@ +# Installing os-migrate + +I now have two RHOSP-16.2 AIOs running on my homelab so it's time to install os-migrate. + +The intallation instructions are at: https://os-migrate.github.io/os-migrate/user/install-from-galaxy.html + +I setup a new Fedora 38 VM to run os-migrate from: + +```bash +virt-install -n os-migrate \ + --osinfo fedora37 \ + --memory 2048 \ + --vcpus 2 \ + --import \ + --disk /data/os-migrate.qcow2 \ + --graphics vnc \ + --cloud-init disable=on,clouduser-ssh-key=ncox.pub,root-password-file=rpw \ + --network bridge=br0 + ``` + + This VM is running at 172.23.0.53, on test-04(173.23.0.25) + + RHEL9 doesn't know about fedora38 + + Lots of stuffing around to get the bridge working + + ## Install and run os-miograte + +`dnf install ansible ` + +`vi os-migrate-vars.yml` + +Largely copied from the ~/.config/openstack/clouds.yaml files + + +Hints from os-migrate doco: + + +```bash +export OSM_DIR=/root/.ansible/collections/ansible_collections/os_migrate/os_migrate +export OSM_CMD="ansible-playbook -v -i $OSM_DIR/localhost_inventory.yml -e @os-migrate-vars.yml" +``` + +Despite what the os-migrate docs say, do not install python3-openstacksdk because on f38 the version is too recent. + +```bash +dnf install iputils python3-openstackclient +``` + +Let's try networks: + +```$OSM_CMD $OSM_DIR/playbooks/export_networks.yml``` + +Hmm, problems with versions. + +Going to need devel tools for openstacksdk + +``` +dnf install python3-devel +dnf group install "C Development Tools and Libraries" +``` + +We're going to need ansible-core in the venv or it will pick up system versions of things. +```bash +python3 -m venv venv +source venv/bin/activate +pip install --upgrade pip +python3 -m pip install --upgrade 'openstacksdk>=0.36,<0.99' +python3 -m pip install --upgrade 'ansible-core' +``` + + +Try again: + +``` +OSM_CMD $OSM_DIR/playbooks/export_networks.yml +``` + +```bash + 60 less os-migrate-data-test-1/clouds.yaml + 61 less os-migrate-data-test-1/networks.yml + 67 $OSM_CMD $OSM_DIR/playbooks/export_subnets.yml + 68 less os-migrate-data-test-1/subnets.yml +``` \ No newline at end of file From 8b2dcd66710aebc5a5f7ade7cdb5e124fd2bb370 Mon Sep 17 00:00:00 2001 From: Neill Cox Date: Thu, 19 Oct 2023 16:29:22 +1100 Subject: [PATCH 10/19] :technologist: Add examples --- examples/create_aio.bash | 21 +++++++++++++++++++++ examples/install_remote_venv.bash | 19 +++++++++++++++++++ examples/prepare_deployment.bash | 7 +++++++ examples/setup.bash | 16 ++++++++++++++++ examples/teardown.bash | 11 +++++++++++ 5 files changed, 74 insertions(+) create mode 100644 examples/create_aio.bash create mode 100644 examples/install_remote_venv.bash create mode 100644 examples/prepare_deployment.bash create mode 100644 examples/setup.bash create mode 100644 examples/teardown.bash diff --git a/examples/create_aio.bash b/examples/create_aio.bash new file mode 100644 index 0000000..7a0c5b7 --- /dev/null +++ b/examples/create_aio.bash @@ -0,0 +1,21 @@ +AIO_NAME=test5 + + +create_aio_vm \ + --password secrete123 \ + --public-key ~/.ssh/id_rsa.pub \ + --output-image /data/${AIO_NAME}.qcow2 \ + --input-image ~/rhel-guest-image-8.4-1269.x86_64.qcow2 \ + --image-size "800G" \ + --os-variant rhel8.4 \ + --name ${AIO_NAME} \ + --local-hostname ${AIO_NAME} \ + --instance-id ${AIO_NAME} \ + --gateway 172.23.0.1 \ + --dns 172.23.0.14 \ + --mac-1 52:54:00:a5:48:03 \ + --mac-2 52:54:00:a5:48:04 \ + --search-domain evatt.ingenious.com.au \ + --rhn-user $AIO_RHN_USER \ + --rhn-password $AIO_RHN_PASSWORD + diff --git a/examples/install_remote_venv.bash b/examples/install_remote_venv.bash new file mode 100644 index 0000000..c1b61a7 --- /dev/null +++ b/examples/install_remote_venv.bash @@ -0,0 +1,19 @@ +REMOTE=172.23.0.34 +AIOH_ENV="/home/stack/tripleo-aio-helpers" +AIO_SRC=$HOME/Projects +VENV=$AIOH_ENV/venv +rsync -avz --exclude venv $AIO_SRC/tripleo-aio-helpers stack@$REMOTE: +ssh stack@$REMOTE "sudo dnf install -y python3-tripleoclient" +ssh stack@$REMOTE "cd $AIOH_ENV && python3 -m venv venv" +ssh stack@$REMOTE "$VENV/bin/pip install --upgrade pip" +ssh stack@$REMOTE "cd $AIOH_ENV && source venv/bin/activate && pip install -r requirements.txt" +ssh stack@$REMOTE "cd $AIOH_ENV && source venv/bin/activate && pip install --editable ." +ssh stack@$REMOTE "cd $AIOH_ENV && source venv/bin/activate && prepare_deployment -u $AIO_RHN_USER --password $AIO_RHN_PASSWORD --address 172.23.0.34 --interface eth1 --dns 172.23.0.14 --gateway 172.23.0.1" +ssh stack@$REMOTE "sudo systemctl stop cloud-init" +ssh stack@$REMOTE "sudo systemctl disable cloud-init" +# ssh stack@$REMOTE "cp containers-prepare-parameters.yaml.gen containers-prepare-parameters.yaml" +# ssh stack@$REMOTE "cp standalone_parameters.yaml.gen standalone_parameters.yaml" +# ssh stack@$REMOTE "cp deploy.sh.gen deploy.sh" +ssh stack@$REMOTE "sudo dnf install -y tmux" +ssh stack@$REMOTE "echo \"tmux new -d \; setw remain-on-exit on \; respawnw -k bash deploy.sh\"" +# ssh stack@$REMOTE "OS_CLOUD=standalone openstack endpoint list" diff --git a/examples/prepare_deployment.bash b/examples/prepare_deployment.bash new file mode 100644 index 0000000..3f04d69 --- /dev/null +++ b/examples/prepare_deployment.bash @@ -0,0 +1,7 @@ +prepare_deployment \ + -u $AIO_RHN_USER \ + --password $AIO_RHN_PASSWORD \ + --address 172.23.0.35/24 \ + --interface eth1 \ + --dns 172.23.0.14 \ + --gateway 172.23.0.1 diff --git a/examples/setup.bash b/examples/setup.bash new file mode 100644 index 0000000..9a3731f --- /dev/null +++ b/examples/setup.bash @@ -0,0 +1,16 @@ +FIP_START=117 +FIP_END=129 +AIO_HOST=test-5 + +echo "Setting up $AIO_HOST" +os_migrate_setup \ + --gateway 172.23.0.1 \ + --public-network-cidr=172.23.0.1/24 \ + --public-net-start 172.23.0.$FIP_START \ + --public-net-end 172.23.0.$FIP_END \ + --dns-server 172.23.0.14 \ + --ssh stack@$AIO_HOST \ + --cirros-url https://isos.evatt.ingenious.com.au/cirros-0.6.2-x86_64-disk.img \ + --rhel-url https://isos.evatt.ingenious.com.au/rhel-guest-image-8.4-1269.x86_64.qcow2 \ + --ssh-key ~/.ssh/id_rsa.pub \ + --debug \ No newline at end of file diff --git a/examples/teardown.bash b/examples/teardown.bash new file mode 100644 index 0000000..2a424ad --- /dev/null +++ b/examples/teardown.bash @@ -0,0 +1,11 @@ +AIO_HOST=test-5 + +echo "Tearing down $AIO_HOST" + +os_migrate_teardown \ + --project-name test-project \ + --username test-user \ + --cloud standalone \ + --ssh stack@$AIO_HOST \ + --delete-instances \ + --delete-networks From f85e69e08fc11cc66172d37294d676a5b1121700 Mon Sep 17 00:00:00 2001 From: Neill Cox Date: Thu, 19 Oct 2023 16:30:57 +1100 Subject: [PATCH 11/19] :sparkles: add os_migrate_validate.py --- .../os_migrate_validate.py | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 src/tripleo_aio_helpers/os_migrate_validate.py diff --git a/src/tripleo_aio_helpers/os_migrate_validate.py b/src/tripleo_aio_helpers/os_migrate_validate.py new file mode 100644 index 0000000..aee7ed3 --- /dev/null +++ b/src/tripleo_aio_helpers/os_migrate_validate.py @@ -0,0 +1,170 @@ +""" +Quick and dirty script to validate that the necessary project, user,roles, +flavors, networks, images have been created +""" +import argparse +import json + +from .utils import openstack_cmd + + +def parse_args(): + """Parse the command line arguments""" + + # home = os.environ.get('HOME') + + parser = argparse.ArgumentParser() + # parser.add_argument("-d", "--project-domain", default="default") + parser.add_argument("-n", "--project-name", default="test-project") + # parser.add_argument("-D", "--project-description", default="Test project") + # parser.add_argument("-u", "--username", default="test-user") + # parser.add_argument("-p", "--password", default="secrete123") + # parser.add_argument("-c", "--cloud", default="standalone") + # parser.add_argument("-g", "--gateway", default="10.76.23.254") + # parser.add_argument( + # "-C", "--public-network-cider", default="10.76.23.0/24" + # ) + # parser.add_argument("--private-network-cidr", default="192.168.100.0/24") + # parser.add_argument("--publice-net-start", default="10.76.23.50") + # parser.add_argument("--publice-net-end", default="10.76.23.52") + # parser.add_argument("--dns-server", default="10.76.23.245") + + # parser.add_argument("--dry-run", action="store_true") + parser.add_argument( + "--ssh", help="Connection string to run commands on a remote host." + ) + + # export OS_CLOUD=standalone + # export STANDALONE_HOST=10.76.23.39 + + args = parser.parse_args() + + return args + + +def validate_project(args): + """Validate that the project exists""" + cmd = "project list -f json" + project_exists = [ + x + for x in json.loads(openstack_cmd(cmd, args)) + if x["Name"] == args.project_name + ] + + if project_exists: + print(project_exists) + else: + print("Project {args.project_name} not found.") + + +def validate_user(args): + """Validate that the user exists""" + cmd = "user list -f json" + user_exists = [ + x for x in json.loads(openstack_cmd(cmd, args)) if x["Name"] == args.username + ] + + if user_exists: + print(user_exists) + else: + print("User not found") + + +def validate_member_role(args): + """ + Validate that the member role has been assigned to the user. + """ + print(args) + # cmd = f"role add --user {args.username} --project {args.project_id} member" + + # result = openstack_cmd(cmd, args) + + # cmd = f"role assignment list --user {args.user_id} --role member -f json" + # result = json.loads(openstack_cmd(cmd, args)) + + # if result: + # print("User has member role") + + +def validate_public_network(args): + """Coming soon - validate the public network""" + # pylint: disable=unused-argument,unused-variable + print("Validate public network - NYI") + cmd = ( + "network create --external --provider-physical-network datacentre " + "--provider-network-type flat public" + ) + cmd = ( + f"subnet create public-net --subnet-range {args.publice_network_cidr} " + f"--no-dhcp --gateway {args.gateway} --allocation-pool " + f"start={args.public_net_start},end={args.public_net_end} " + "--network public" + ) + + +def validate_private_network(args): + """Coming soon - validate the private network""" + # pylint: disable=unused-argument,unused-variable + cmd = "openstack network create --internal private" + cmd = ( + "openstack subnet create private-net " + f"--subnet-range {args.private_network_cidr} --network private" + ) + print("validate private network - NYI") + + +def validate_cirros_flavor(args): + """Coming soon - create the cirros flavor""" + # pylint: disable=unused-argument + print("validate cirros flavor - NYI") + + +def validate_rhel_flavor(args): + """Coming soon - validate the rhel flavor""" + # pylint: disable=unused-argument + print("validate rhel flavor - NYI") + + +def validate_cirros_image(args): + """Coming soon - validate the cirros image""" + # pylint: disable=unused-argument + print("validate cirros image - NYI") + + +def validate_rhel_image(args): + """Coming soon - validate the rhel image""" + # pylint: disable=unused-argument + print("validate rhel image - NYI") + + +def validate_cirros_instance(args): + """Coming soon - validate the cirros instance""" + # pylint: disable=unused-argument + print("validate cirros instance - NYI") + + +def validate_rhel_instance(args): + """Coming soon - create the rhel instance""" + # pylint: disable=unused-argument + print("validate rhel instance - NYI") + + +def main(): + """main function""" + args = parse_args() + + validate_user(args) + validate_project(args) + validate_member_role(args) + validate_public_network(args) + validate_private_network(args) + validate_cirros_flavor(args) + validate_rhel_flavor(args) + validate_cirros_image(args) + validate_rhel_image(args) + validate_cirros_instance(args) + validate_rhel_instance(args) + + +if __name__ == "__main__": + main() From e62f806e8639408491d7f290779aba1692bb07e0 Mon Sep 17 00:00:00 2001 From: Neill Cox Date: Thu, 19 Oct 2023 17:57:12 +1100 Subject: [PATCH 12/19] :rotating_light: Add pre-commit --- .pre-commit-config.yaml | 25 ++++++ README.md | 10 +-- TODO.md | 2 +- dev_requirements.txt | 1 + docs/create_aio_vm.md | 2 +- docs/notes.md | 10 +-- examples/create_aio.bash | 1 - examples/setup.bash | 2 +- pyproject.toml | 2 +- src/tripleo_aio_helpers/create_aio_vm.py | 30 +++---- src/tripleo_aio_helpers/os_migrate_setup.py | 82 ++++++++++++------- .../os_migrate_teardown.py | 24 ++++-- .../os_migrate_validate.py | 20 +++-- src/tripleo_aio_helpers/prepare_deployment.py | 16 +++- src/tripleo_aio_helpers/utils.py | 8 +- src/virt-install/network-config.tpl | 2 +- src/virt-install/user-data.tpl | 1 - 17 files changed, 158 insertions(+), 80 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..8d63dcb --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,25 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files +- repo: https://github.com/psf/black + rev: 23.10.0 + hooks: + - id: black + args: ["--line-length=79"] +- repo: https://github.com/PyCQA/isort + rev: 5.12.0 + hooks: + - id: isort + args: ["--profile", "black"] +- repo: https://github.com/PyCQA/flake8 + rev: 6.1.0 + hooks: + - id: flake8 + exclude: migrations/ diff --git a/README.md b/README.md index 53c81e4..36abf84 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,11 @@ I am primarily targetting RHEL8.4 and RHOSP16.2. As a result I'm tied to virt-in There is a vestigial network-config.tpl in the virt-install directory but it is not actually used yet. -## os_migrate_setup.py +## os_migrate_setup.py build the OSP infrastructure to allow testing os-migrate. -## os_migrate_teardown.py +## os_migrate_teardown.py remove that infrastructure (to allow for refinement/iteration) @@ -32,11 +32,11 @@ virt-install/create_aio_vm.py - use virt-install to create and configure a VM to Original script. Generates the yaml files and a dploy script to deploy a RHOSP AIO. Assumes a VM to run on. Don't run this on your laptop. You'll be sorry if you do. -## os_migrate_validate.py +## os_migrate_validate.py Check that the necessary infrastructure has been deployed. -# Remember to mask and disable cloud-init if installing on a VM +# Remember to mask and disable cloud-init if installing on a VM ``` usage: main.py [-h] -u USERNAME -p PASSWORD -a ADDRESS [-i INTERFACE] @@ -69,4 +69,4 @@ By default the files will be placed in the home directory of the user running th When happy with the contents rename them to remove the .gen suffix and run the deploy script. -The doco for the AIO deply says to run the deploy using sudo - I don't think this is correct. If you use sudo you will get a warning about files ending up /root. \ No newline at end of file +The doco for the AIO deply says to run the deploy using sudo - I don't think this is correct. If you use sudo you will get a warning about files ending up /root. diff --git a/TODO.md b/TODO.md index 3919cfc..009aa37 100644 --- a/TODO.md +++ b/TODO.md @@ -18,4 +18,4 @@ Maybe this should all have been an ansible role. Maybe that will happen yet. ## create_aio_vm -Quite a few compromises to do with the versions of virt_install / libvirt available to me. \ No newline at end of file +Quite a few compromises to do with the versions of virt_install / libvirt available to me. diff --git a/dev_requirements.txt b/dev_requirements.txt index d2c4c65..f61ddb9 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -4,3 +4,4 @@ flake8==5.0.4 pyflakes==2.5.0 pylint==2.13.9 PyYAML==6.0.1 +pre-commit diff --git a/docs/create_aio_vm.md b/docs/create_aio_vm.md index 7c933a3..917d7eb 100644 --- a/docs/create_aio_vm.md +++ b/docs/create_aio_vm.md @@ -2,4 +2,4 @@ ## Networking -Networking has been a real pain. The RHOSP AIO deployment seems to force the vr-ctlplane interface to use dhcp. Originally I was trying to use static IPs configured with cloud-init (using bootcmd because of the version) but the deploy would die. To fix this I now speciiy MACs for the interfaces and then use an external DHCP server. Maybe this is configurable in the RHOSP standalone deploy. If so it would be better to use that. It would also be nice to do some feature switches so that if the version of virt-install allows I could use network-config instead of bootcmd \ No newline at end of file +Networking has been a real pain. The RHOSP AIO deployment seems to force the vr-ctlplane interface to use dhcp. Originally I was trying to use static IPs configured with cloud-init (using bootcmd because of the version) but the deploy would die. To fix this I now speciiy MACs for the interfaces and then use an external DHCP server. Maybe this is configurable in the RHOSP standalone deploy. If so it would be better to use that. It would also be nice to do some feature switches so that if the version of virt-install allows I could use network-config instead of bootcmd diff --git a/docs/notes.md b/docs/notes.md index 6149f3b..9b1921b 100644 --- a/docs/notes.md +++ b/docs/notes.md @@ -77,8 +77,8 @@ OSM_CMD $OSM_DIR/playbooks/export_networks.yml ``` ```bash - 60 less os-migrate-data-test-1/clouds.yaml - 61 less os-migrate-data-test-1/networks.yml - 67 $OSM_CMD $OSM_DIR/playbooks/export_subnets.yml - 68 less os-migrate-data-test-1/subnets.yml -``` \ No newline at end of file + 60 less os-migrate-data-test-1/clouds.yaml + 61 less os-migrate-data-test-1/networks.yml + 67 $OSM_CMD $OSM_DIR/playbooks/export_subnets.yml + 68 less os-migrate-data-test-1/subnets.yml +``` diff --git a/examples/create_aio.bash b/examples/create_aio.bash index 7a0c5b7..cfba82e 100644 --- a/examples/create_aio.bash +++ b/examples/create_aio.bash @@ -18,4 +18,3 @@ create_aio_vm \ --search-domain evatt.ingenious.com.au \ --rhn-user $AIO_RHN_USER \ --rhn-password $AIO_RHN_PASSWORD - diff --git a/examples/setup.bash b/examples/setup.bash index 9a3731f..a9b4162 100644 --- a/examples/setup.bash +++ b/examples/setup.bash @@ -13,4 +13,4 @@ os_migrate_setup \ --cirros-url https://isos.evatt.ingenious.com.au/cirros-0.6.2-x86_64-disk.img \ --rhel-url https://isos.evatt.ingenious.com.au/rhel-guest-image-8.4-1269.x86_64.qcow2 \ --ssh-key ~/.ssh/id_rsa.pub \ - --debug \ No newline at end of file + --debug diff --git a/pyproject.toml b/pyproject.toml index f37bfe5..d6a4a0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,4 +26,4 @@ create_aio_vm = "tripleo_aio_helpers.create_aio_vm:main" os_migrate_setup = "tripleo_aio_helpers.os_migrate_setup:main" os_migrate_teardown = "tripleo_aio_helpers.os_migrate_teardown:main" os_migrate_validate = "tripleo_aio_helpers.os_migrate_validate:main" -prepare_deployment = "tripleo_aio_helpers.prepare_deployment:main" \ No newline at end of file +prepare_deployment = "tripleo_aio_helpers.prepare_deployment:main" diff --git a/src/tripleo_aio_helpers/create_aio_vm.py b/src/tripleo_aio_helpers/create_aio_vm.py index 47bf8c7..1380b25 100644 --- a/src/tripleo_aio_helpers/create_aio_vm.py +++ b/src/tripleo_aio_helpers/create_aio_vm.py @@ -23,8 +23,12 @@ def parse_args(): parser.add_argument("--local-hostname", required=True) parser.add_argument("--user-data", default=template_path / "user-data.tpl") parser.add_argument("--meta-data", default=template_path / "meta-data.tpl") - parser.add_argument("--network-data", default=template_path / "network-config.tpl") - parser.add_argument("--instance-id", required=True, help="Hostname for the new VM") + parser.add_argument( + "--network-data", default=template_path / "network-config.tpl" + ) + parser.add_argument( + "--instance-id", required=True, help="Hostname for the new VM" + ) parser.add_argument("--output-image", required=True) parser.add_argument("--image-size", default="800G") parser.add_argument("--input-image", required=True) @@ -63,15 +67,6 @@ def parse_args(): print("You must specify a DNS server if you specify any addresses") sys.exit(1) - # if not (args.cidr_1 or args.cidr_2) and ( - # args.dns or args.gateway or args.search_domain - # ): - # print( - # "There's no point specifying DNS, gateway or search_domain if you" - # " don't specify addresses" - # ) - # sys.exit(1) - args.public_key = args.public_key.read() with open(args.user_data, encoding="utf-8") as user_data: @@ -153,7 +148,10 @@ def create_image(args): print(result) print("Resizing image") - cmd = f"virt-resize --expand /dev/sda3 {args.input_image} {args.output_image}" + cmd = ( + f"virt-resize --expand /dev/sda3 {args.input_image} " + f"{args.output_image}" + ) result = subprocess.check_output(cmd, shell=True, universal_newlines=True) @@ -205,14 +203,16 @@ def delete_user_data(): def install_packages(args): """Install the packages needed for virt-install to work""" cmd = ( - "sudo dnf install -y virt-install virt-viewer qemu-img " + "sudo dnf install -y virt-install virt-viewer qemu-img " "libguestfs.x86_64" - ) + ) print("installing needed packages...") if args.verbose: - print(subprocess.check_output(cmd, shell=True, universal_newlines=True)) + print( + subprocess.check_output(cmd, shell=True, universal_newlines=True) + ) def main(): diff --git a/src/tripleo_aio_helpers/os_migrate_setup.py b/src/tripleo_aio_helpers/os_migrate_setup.py index e085830..0b0e65f 100644 --- a/src/tripleo_aio_helpers/os_migrate_setup.py +++ b/src/tripleo_aio_helpers/os_migrate_setup.py @@ -35,7 +35,8 @@ def parse_args(): parser.add_argument("-g", "--gateway") parser.add_argument("-C", "--public-network-cidr") parser.add_argument( - "--ssh-key", help="File containing a public key to inject into the instances" + "--ssh-key", + help="File containing a public key to inject into the instances", ) parser.add_argument("--private-network-cidr", default="192.168.100.0/24") parser.add_argument("--public-net-start") @@ -76,7 +77,9 @@ def parse_args(): ) if not args.public_net_end: - args.public_net_end = get_from_env("--public-net-end", "AIO_PUBLIC_NET_END") + args.public_net_end = get_from_env( + "--public-net-end", "AIO_PUBLIC_NET_END" + ) if not args.dns_server: args.dns_server = get_from_env("--dns-server", "AIO_DNS_SERVER") @@ -130,7 +133,9 @@ def create_user(args): cmd = "user list -f json" user_exists = [ - x for x in json.loads(openstack_cmd(cmd, args)) if x["Name"] == args.username + x + for x in json.loads(openstack_cmd(cmd, args)) + if x["Name"] == args.username ] if user_exists: @@ -183,7 +188,8 @@ def create_public_network(args): args.public_network_id = network_exists[0]["ID"] else: cmd = ( - "network create -f json --external --provider-physical-network datacentre " + "network create -f json --external " + "--provider-physical-network datacentre " "--provider-network-type flat public" ) try: @@ -203,7 +209,8 @@ def create_public_network(args): "subnet create public-net -f json " f"--subnet-range {args.public_network_cidr} " f"--gateway {args.gateway} " - f"--allocation-pool start={args.public_net_start},end={args.public_net_end} " + f"--allocation-pool start={args.public_net_start}," + f"end={args.public_net_end} " "--network public " f"--host-route destination=0.0.0.0/0,gateway={args.gateway} " f"--dns-nameserver {args.dns_server}" @@ -217,7 +224,8 @@ def create_private_network(args): network_exists = json.loads( openstack_cmd( - f"network list -f json --project {args.project_id} --name private", args + f"network list -f json --project {args.project_id} --name private", + args, ) ) @@ -225,13 +233,18 @@ def create_private_network(args): print("Private network already exists - skipping") args.private_network_id = network_exists[0]["ID"] else: - cmd = f"network create -f json --internal private --project {args.project_id}" + cmd = ( + f"network create -f json --internal private " + f"--project {args.project_id}" + ) args.private_network_id = json.loads(openstack_cmd(cmd, args))["id"] print("Private network created.") subnet_exists = json.loads( openstack_cmd( - f"subnet list -f json --project {args.project_id} --name private-net", args + f"subnet list -f json --project {args.project_id} " + f"--name private-net", + args, ) ) if subnet_exists: @@ -240,7 +253,8 @@ def create_private_network(args): else: cmd = ( f"subnet create private-net -f json --project {args.project_id} " - f"--subnet-range {args.private_network_cidr} --network {args.private_network_id}" + f"--subnet-range {args.private_network_cidr} " + f"--network {args.private_network_id}" ) args.private_subnet_id = json.loads(openstack_cmd(cmd, args)) print("Private subnet created.") @@ -261,12 +275,12 @@ def create_flavor(args, flavor_name, memory, disk, cpus): print("creating cirros flavor") # Note can't add a description in RHOSP16, but perhaps should add for 17 cmd = f""" - flavor create -f json + flavor create -f json --ram {memory} --disk {disk} - --vcpus {cpus} - --private - --project {args.project_id} + --vcpus {cpus} + --private + --project {args.project_id} {flavor_name} """.replace( "\n", " " @@ -303,7 +317,6 @@ def create_image(image_name, image_url, disk_size, memory, args): return with tempfile.TemporaryDirectory() as tmp_dir: - # download image to tmpdir fname = f"{tmp_dir}/{image_name}.img" cmd = f"wget -O {fname} {image_url}" @@ -314,7 +327,8 @@ def create_image(image_name, image_url, disk_size, memory, args): cmd = f"scp {fname} {args.ssh}:{image_name}.img" execute_cmd(cmd) - # create image - note this will only work on a remote ssh host at the moment + # create image - note this will only work on a remote ssh host at + # the moment cmd = f""" image create -f json --container-format bare @@ -379,7 +393,9 @@ def create_instance(args, name, flavor, image, security_group, boot_size): "floating ip create -f json public", args, as_json=True ) _ = test_user_openstack_cmd( - f"server add floating ip {server['id']} {fip['floating_ip_address']}", args + f"server add floating ip {server['id']} " + f"{fip['floating_ip_address']}", + args, ) @@ -404,7 +420,7 @@ def create_rhel_instance(args): args.rhel_flavor, args.rhel_image, args.sg_id, - 120 + 120, ) @@ -416,7 +432,7 @@ def create_conversion_instance(args): args.rhel_flavor, args.rhel_image, args.sg_id, - 120 + 120, ) @@ -425,7 +441,9 @@ def create_keypair(args): key_exists = [ kp - for kp in json.loads(test_user_openstack_cmd("keypair list -f json ", args)) + for kp in json.loads( + test_user_openstack_cmd("keypair list -f json ", args) + ) if kp["Name"] == "test_keypair" ] @@ -440,7 +458,9 @@ def create_keypair(args): args.keypair_name = json.loads( test_user_openstack_cmd( - f"keypair create -f json --public-key {fname} test_keypair", args + f"keypair create -f json " + f"--public-key {fname} test_keypair", + args, ) )[ "name" @@ -458,7 +478,9 @@ def create_router(args): try: router_id = [ router - for router in openstack_cmd("router list -f json", args, as_json=True) + for router in openstack_cmd( + "router list -f json", args, as_json=True + ) if router["Name"] == "test-router" ][0]["ID"] except IndexError: @@ -470,13 +492,17 @@ def create_router(args): )["id"] print("router created") - router_info = openstack_cmd(f"router show -f json {router_id}", args, as_json=True) + router_info = openstack_cmd( + f"router show -f json {router_id}", args, as_json=True + ) if router_info["external_gateway_info"]: print("router gateway already set") else: openstack_cmd( - f"router set {router_id} --external-gateway {args.public_network_id}", args + f"router set {router_id} " + f"--external-gateway {args.public_network_id}", + args, ) print("router gateway added") @@ -511,9 +537,8 @@ def create_security_group(args): else: sec_grp = json.loads( test_user_openstack_cmd( - "security group create test-sg -f json", - args - ) + "security group create test-sg -f json", args + ) ) args.sg_id = sec_grp["id"] @@ -526,7 +551,8 @@ def create_security_group(args): ) _ = test_user_openstack_cmd( - ("security group rule create --protocol icmp " f"{args.sg_id}"), args + ("security group rule create --protocol icmp " f"{args.sg_id}"), + args, ) @@ -555,7 +581,7 @@ def main(): create_cirros_instance(args) create_rhel_instance(args) - ## create_os_migrate host + # create_os_migrate host if __name__ == "__main__": diff --git a/src/tripleo_aio_helpers/os_migrate_teardown.py b/src/tripleo_aio_helpers/os_migrate_teardown.py index f24cf3b..a743f1f 100644 --- a/src/tripleo_aio_helpers/os_migrate_teardown.py +++ b/src/tripleo_aio_helpers/os_migrate_teardown.py @@ -60,7 +60,9 @@ def destroy_user(args): """Delete the user if it exists""" cmd = "user list -f json" user_exists = [ - x for x in json.loads(openstack_cmd(cmd, args)) if x["Name"] == args.username + x + for x in json.loads(openstack_cmd(cmd, args)) + if x["Name"] == args.username ] if user_exists: @@ -78,24 +80,26 @@ def destroy_public_network(args): public_network_exists = [ network - for network in openstack_cmd("network list -f json", args, as_json=True) + for network in openstack_cmd( + "network list -f json", args, as_json=True + ) if network["Name"] == "public" ] if public_network_exists: routers = test_user_openstack_cmd( - "router list -f json", args, as_json=True - ) + "router list -f json", args, as_json=True + ) for router in routers: - router_details = test_user_openstack_cmd( f"router show -f json {router['ID']}", args, as_json=True ) for interface in router_details["interfaces_info"]: test_user_openstack_cmd( - f"router remove port {router['ID']} {interface['port_id']}", + f"router remove port {router['ID']} " + f"{interface['port_id']}", args, ) @@ -108,7 +112,9 @@ def destroy_private_network(args): """Delete the private network""" private_network_exists = [ network - for network in openstack_cmd("network list -f json", args, as_json=True) + for network in openstack_cmd( + "network list -f json", args, as_json=True + ) if network["Name"] == "private" ] @@ -176,7 +182,9 @@ def destroy_rhel_instance(args): def get_project_id(args): """Get the id of the test project""" result = openstack_cmd("project list -f json", args, as_json=True) - args.project_id = [r for r in result if r["Name"] == args.project_name][0]["ID"] + args.project_id = [r for r in result if r["Name"] == args.project_name][0][ + "ID" + ] def main(): diff --git a/src/tripleo_aio_helpers/os_migrate_validate.py b/src/tripleo_aio_helpers/os_migrate_validate.py index aee7ed3..49216a7 100644 --- a/src/tripleo_aio_helpers/os_migrate_validate.py +++ b/src/tripleo_aio_helpers/os_migrate_validate.py @@ -1,5 +1,5 @@ """ -Quick and dirty script to validate that the necessary project, user,roles, +Quick and dirty script to validate that the necessary project, user,roles, flavors, networks, images have been created """ import argparse @@ -16,7 +16,9 @@ def parse_args(): parser = argparse.ArgumentParser() # parser.add_argument("-d", "--project-domain", default="default") parser.add_argument("-n", "--project-name", default="test-project") - # parser.add_argument("-D", "--project-description", default="Test project") + # parser.add_argument( + # "-D", "--project-description", default="Test project" + # ) # parser.add_argument("-u", "--username", default="test-user") # parser.add_argument("-p", "--password", default="secrete123") # parser.add_argument("-c", "--cloud", default="standalone") @@ -61,7 +63,9 @@ def validate_user(args): """Validate that the user exists""" cmd = "user list -f json" user_exists = [ - x for x in json.loads(openstack_cmd(cmd, args)) if x["Name"] == args.username + x + for x in json.loads(openstack_cmd(cmd, args)) + if x["Name"] == args.username ] if user_exists: @@ -75,7 +79,10 @@ def validate_member_role(args): Validate that the member role has been assigned to the user. """ print(args) - # cmd = f"role add --user {args.username} --project {args.project_id} member" + # cmd = ( + # f"role add --user {args.username} " + # f"--project {args.project_id} member" + # ) # result = openstack_cmd(cmd, args) @@ -100,13 +107,14 @@ def validate_public_network(args): f"start={args.public_net_start},end={args.public_net_end} " "--network public" ) + print(cmd) def validate_private_network(args): """Coming soon - validate the private network""" # pylint: disable=unused-argument,unused-variable - cmd = "openstack network create --internal private" - cmd = ( + _ = "openstack network create --internal private" + _ = ( "openstack subnet create private-net " f"--subnet-range {args.private_network_cidr} --network private" ) diff --git a/src/tripleo_aio_helpers/prepare_deployment.py b/src/tripleo_aio_helpers/prepare_deployment.py index 4597e13..4ff8be0 100644 --- a/src/tripleo_aio_helpers/prepare_deployment.py +++ b/src/tripleo_aio_helpers/prepare_deployment.py @@ -4,6 +4,7 @@ Quick and dirty script to help setup config filers for a RHOSP AIO install import argparse import os import subprocess + import yaml @@ -18,7 +19,9 @@ def parse_args(): parser.add_argument("-a", "--address", required=True) parser.add_argument("-i", "--interface", required=True) parser.add_argument("-m", "--netmask", default=24) - parser.add_argument("-d", "--dns", nargs="+", action="append", required=True) + parser.add_argument( + "-d", "--dns", nargs="+", action="append", required=True + ) parser.add_argument("-D", "--domain", default="aio") parser.add_argument("--using-multiple-nics", action="store_true") parser.add_argument("-U", "--deployment-user") @@ -38,7 +41,8 @@ def parse_args(): parser.add_argument( "--cinder-pool-size", default="500280", - help="Size of the loopback device allocated to cinder. " "Defaults to 500280", + help="Size of the loopback device allocated to cinder. " + "Defaults to 500280", ) args = parser.parse_args() @@ -137,12 +141,16 @@ def main(): args = parse_args() containers_yaml = build_containers_yaml(args) - with open(args.containers_yaml_out, "w", encoding="utf-8") as containers_out: + with open( + args.containers_yaml_out, "w", encoding="utf-8" + ) as containers_out: containers_out.write(yaml.dump(containers_yaml)) print(f"containers yaml written to {args.containers_yaml_out}") standalone_parameters = get_standalone_parameters(args) - with open(args.standalone_yaml_out, "w", encoding="utf-8") as parameters_out: + with open( + args.standalone_yaml_out, "w", encoding="utf-8" + ) as parameters_out: parameters_out.write(yaml.dump(standalone_parameters)) print(f"standalone parameters yaml written to {args.standalone_yaml_out}") diff --git a/src/tripleo_aio_helpers/utils.py b/src/tripleo_aio_helpers/utils.py index dfd5eab..d37c5b2 100644 --- a/src/tripleo_aio_helpers/utils.py +++ b/src/tripleo_aio_helpers/utils.py @@ -20,7 +20,9 @@ def get_from_env(name, envvar, required=True): def openstack_cmd(cmd, args, as_json=False): - """Utility function to run an openstack command agains the standalone cloud""" + """ + Utility function to run an openstack command agains the standalone cloud + """ cmd = cmd.replace("\n", " ") cmd = "OS_CLOUD=standalone openstack " + cmd @@ -34,7 +36,9 @@ def openstack_cmd(cmd, args, as_json=False): return None try: - result = subprocess.check_output(cmd, shell=True, universal_newlines=True) + result = subprocess.check_output( + cmd, shell=True, universal_newlines=True + ) if as_json: result = json.loads(result) diff --git a/src/virt-install/network-config.tpl b/src/virt-install/network-config.tpl index 019c43f..59b79c8 100644 --- a/src/virt-install/network-config.tpl +++ b/src/virt-install/network-config.tpl @@ -17,4 +17,4 @@ network: address: - {data.dns} search: - - {data.search_domain} \ No newline at end of file + - {data.search_domain} diff --git a/src/virt-install/user-data.tpl b/src/virt-install/user-data.tpl index 457d0f3..4c9d9cd 100644 --- a/src/virt-install/user-data.tpl +++ b/src/virt-install/user-data.tpl @@ -25,4 +25,3 @@ runcmd: - "echo '*** ***'" - "echo '***************************************************************************'" - "reboot" - From 0f33e5612a09a6e0ee719aa3f9bdde6b519957ff Mon Sep 17 00:00:00 2001 From: Neill Cox Date: Sat, 21 Oct 2023 10:40:41 +1100 Subject: [PATCH 13/19] :sparkles: add functionality to os_migrate_teardown.py --- .../os_migrate_teardown.py | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/src/tripleo_aio_helpers/os_migrate_teardown.py b/src/tripleo_aio_helpers/os_migrate_teardown.py index a743f1f..479ff21 100644 --- a/src/tripleo_aio_helpers/os_migrate_teardown.py +++ b/src/tripleo_aio_helpers/os_migrate_teardown.py @@ -122,30 +122,47 @@ def destroy_private_network(args): print("deleting private network") test_user_openstack_cmd("network delete private", args) +def delete_flavor(args,name): + flavor_exists = [ + flavor + for flavor in openstack_cmd( + "flavor list -f json", args, as_json=True + ) + if flavor["Name"] == "cirros" + ] + + if flavor_exists: + print(f"deleting {name} flavor") + test_user_openstack_cmd("flavor delete f{name}", args) def destroy_cirros_flavor(args): - """Coming soon - create the cirros flavor""" - # pylint: disable=unused-argument - print("creating cirros flavor - NYI") - + """Delete the cirros flavor""" + delete_flavor(args, "cirros") def destroy_rhel_flavor(args): - """Coming soon - create the rhel flavor""" - # pylint: disable=unused-argument - print("creating rhel flavor - NYI") + """Delete the rhel flavor""" + delete_flavor(args, "rhel") +def delete_image(args, name): + image_exists = [ + image + for image in openstack_cmd( + "image list -f json", args, as_json=True + ) + if image["Name"] == "cirros" + ] + + if image_exists: + print(f"deleting {name} image") + test_user_openstack_cmd("image delete f{name}", args) def destroy_cirros_image(args): - """Coming soon - destroy the cirros image""" - # pylint: disable=unused-argument - print("destroying cirros image - NYI") - + """Delete the cirros image""" + delete_image(args, "cirros") def destroy_rhel_image(args): - """Coming soon - create the rhel image""" - # pylint: disable=unused-argument - print("creating rhel image - NYI") - + """Delete the rhel image""" + delete_image(args, "rhel") def destroy_cirros_instance(args): """Delete the cirros instance""" From 897052b2463e7d7115ab406b3b67d11d53645611 Mon Sep 17 00:00:00 2001 From: Neill Cox Date: Fri, 3 Nov 2023 19:09:40 +1100 Subject: [PATCH 14/19] :construction: Update teardown example --- examples/teardown.bash | 3 +-- .../{os_migrate_validate.py => os_migrate_validate_dst.py} | 0 2 files changed, 1 insertion(+), 2 deletions(-) rename src/tripleo_aio_helpers/{os_migrate_validate.py => os_migrate_validate_dst.py} (100%) diff --git a/examples/teardown.bash b/examples/teardown.bash index 2a424ad..7cc49e7 100644 --- a/examples/teardown.bash +++ b/examples/teardown.bash @@ -7,5 +7,4 @@ os_migrate_teardown \ --username test-user \ --cloud standalone \ --ssh stack@$AIO_HOST \ - --delete-instances \ - --delete-networks + --delete-all diff --git a/src/tripleo_aio_helpers/os_migrate_validate.py b/src/tripleo_aio_helpers/os_migrate_validate_dst.py similarity index 100% rename from src/tripleo_aio_helpers/os_migrate_validate.py rename to src/tripleo_aio_helpers/os_migrate_validate_dst.py From 77a5a9369cdf3eb843a7dc71ee1c4be7fbd97fbc Mon Sep 17 00:00:00 2001 From: Neill Cox Date: Fri, 3 Nov 2023 19:11:10 +1100 Subject: [PATCH 15/19] :construction: update open stack_command to accept a cloud parameter --- src/tripleo_aio_helpers/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tripleo_aio_helpers/utils.py b/src/tripleo_aio_helpers/utils.py index d37c5b2..eaa8f68 100644 --- a/src/tripleo_aio_helpers/utils.py +++ b/src/tripleo_aio_helpers/utils.py @@ -25,7 +25,7 @@ def openstack_cmd(cmd, args, as_json=False): """ cmd = cmd.replace("\n", " ") - cmd = "OS_CLOUD=standalone openstack " + cmd + cmd = f"OS_CLOUD={args.cloud} openstack " + cmd if args.ssh: cmd = f'ssh {args.ssh} "{cmd}"' From 5ae30a44cce78f829f876f1a2a61665ce76472f5 Mon Sep 17 00:00:00 2001 From: Neill Cox Date: Fri, 3 Nov 2023 19:21:35 +1100 Subject: [PATCH 16/19] :construction: update teardown script --- .../os_migrate_teardown.py | 48 +++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/src/tripleo_aio_helpers/os_migrate_teardown.py b/src/tripleo_aio_helpers/os_migrate_teardown.py index 479ff21..6d391c4 100644 --- a/src/tripleo_aio_helpers/os_migrate_teardown.py +++ b/src/tripleo_aio_helpers/os_migrate_teardown.py @@ -3,6 +3,7 @@ Quick and dirty script to help setup project, flavors, networks, images """ import argparse import json +import sys from .utils import openstack_cmd, test_user_openstack_cmd @@ -126,14 +127,16 @@ def delete_flavor(args,name): flavor_exists = [ flavor for flavor in openstack_cmd( - "flavor list -f json", args, as_json=True + "flavor list -f json --all", args, as_json=True ) - if flavor["Name"] == "cirros" + if flavor["Name"] == name ] - if flavor_exists: + flavor_id = flavor_exists[0]["ID"] print(f"deleting {name} flavor") - test_user_openstack_cmd("flavor delete f{name}", args) + openstack_cmd(f"flavor delete {flavor_id}", args) + else: + print(f"{name} flavour not found") def destroy_cirros_flavor(args): """Delete the cirros flavor""" @@ -149,20 +152,22 @@ def delete_image(args, name): for image in openstack_cmd( "image list -f json", args, as_json=True ) - if image["Name"] == "cirros" + if image["Name"] == name ] if image_exists: print(f"deleting {name} image") - test_user_openstack_cmd("image delete f{name}", args) + openstack_cmd(f"image delete {name}", args) + else: + print(f"{name} image not found") def destroy_cirros_image(args): """Delete the cirros image""" - delete_image(args, "cirros") + delete_image(args, "cirros_image") def destroy_rhel_image(args): """Delete the rhel image""" - delete_image(args, "rhel") + delete_image(args, "rhel_image") def destroy_cirros_instance(args): """Delete the cirros instance""" @@ -199,10 +204,13 @@ def destroy_rhel_instance(args): def get_project_id(args): """Get the id of the test project""" result = openstack_cmd("project list -f json", args, as_json=True) - args.project_id = [r for r in result if r["Name"] == args.project_name][0][ - "ID" - ] + try: + args.project_id = [r for r in result if r["Name"] == args.project_name][0][ + "ID" + ] + except IndexError: + args.project_id = None def main(): """main function""" @@ -211,8 +219,11 @@ def main(): get_project_id(args) if args.delete_instances or args.delete_all: - destroy_cirros_instance(args) - destroy_rhel_instance(args) + if args.project_id: + destroy_cirros_instance(args) + destroy_rhel_instance(args) + else: + print("Project not found, no instances to delete") if args.delete_images or args.delete_all: destroy_cirros_image(args) @@ -224,13 +235,20 @@ def main(): if args.delete_networks or args.delete_all: destroy_public_network(args) - destroy_private_network(args) + + if args.project_id: + destroy_private_network(args) + else: + print("Project not found, no private network to delete") if args.delete_user or args.delete_all: destroy_user(args) if args.delete_project or args.delete_all: - destroy_project(args) + if args.project_id: + destroy_project(args) + else: + print("Project not found. Can't delete.") if __name__ == "__main__": From dc510177bbdedf89e999cc1ddbbc48971ac4765c Mon Sep 17 00:00:00 2001 From: Neill Cox Date: Fri, 3 Nov 2023 19:41:01 +1100 Subject: [PATCH 17/19] Split validation into src and dst --- pyproject.toml | 3 +- .../os_migrate_validate_src.py | 188 ++++++++++++++++++ 2 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 src/tripleo_aio_helpers/os_migrate_validate_src.py diff --git a/pyproject.toml b/pyproject.toml index d6a4a0d..84d83fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,5 +25,6 @@ classifiers = [ create_aio_vm = "tripleo_aio_helpers.create_aio_vm:main" os_migrate_setup = "tripleo_aio_helpers.os_migrate_setup:main" os_migrate_teardown = "tripleo_aio_helpers.os_migrate_teardown:main" -os_migrate_validate = "tripleo_aio_helpers.os_migrate_validate:main" +os_migrate_validate_dst = "tripleo_aio_helpers.os_migrate_validate_dst:main" +os_migrate_validate_src = "tripleo_aio_helpers.os_migrate_validate_src:main" prepare_deployment = "tripleo_aio_helpers.prepare_deployment:main" diff --git a/src/tripleo_aio_helpers/os_migrate_validate_src.py b/src/tripleo_aio_helpers/os_migrate_validate_src.py new file mode 100644 index 0000000..3838215 --- /dev/null +++ b/src/tripleo_aio_helpers/os_migrate_validate_src.py @@ -0,0 +1,188 @@ +""" +Quick and dirty script to validate that the necessary project, user,roles, +flavors, networks, images have been created +""" +import argparse +import json + +from .utils import openstack_cmd + + +def parse_args(): + """Parse the command line arguments""" + + # home = os.environ.get('HOME') + + parser = argparse.ArgumentParser() + # parser.add_argument("-d", "--project-domain", default="default") + parser.add_argument("-n", "--project-name", default="test-project") + # parser.add_argument( + # "-D", "--project-description", default="Test project" + # ) + # parser.add_argument("-u", "--username", default="test-user") + # parser.add_argument("-p", "--password", default="secrete123") + # parser.add_argument("-c", "--cloud", default="standalone") + # parser.add_argument("-g", "--gateway", default="10.76.23.254") + # parser.add_argument( + # "-C", "--public-network-cider", default="10.76.23.0/24" + # ) + # parser.add_argument("--private-network-cidr", default="192.168.100.0/24") + # parser.add_argument("--publice-net-start", default="10.76.23.50") + # parser.add_argument("--publice-net-end", default="10.76.23.52") + # parser.add_argument("--dns-server", default="10.76.23.245") + + # parser.add_argument("--dry-run", action="store_true") + parser.add_argument( + "--ssh", help="Connection string to run commands on a remote host." + ) + + # export OS_CLOUD=standalone + # export STANDALONE_HOST=10.76.23.39 + + args = parser.parse_args() + + return args + + +def validate_project(args): + """Validate that the project exists""" + cmd = "project list -f json" + project_exists = [ + x + for x in json.loads(openstack_cmd(cmd, args)) + if x["Name"] == args.project_name + ] + + if project_exists: + print(project_exists) + else: + print("Project {args.project_name} not found.") + + +def validate_user(args): + """Validate that the user exists""" + cmd = "user list -f json" + user_exists = [ + x + for x in json.loads(openstack_cmd(cmd, args)) + if x["Name"] == args.username + ] + + if user_exists: + print(user_exists) + else: + print("User not found") + +def validate_group_exists(args) + print("NYI") + +def validate_admin_in_group(args): + print("NYI") + +def validate_group_has_member_role(args): + print("NYI") + + +def validate_member_role(args): + """ + Validate that the member role has been assigned to the user. + """ + print(args) + # cmd = ( + # f"role add --user {args.username} " + # f"--project {args.project_id} member" + # ) + + # result = openstack_cmd(cmd, args) + + # cmd = f"role assignment list --user {args.user_id} --role member -f json" + # result = json.loads(openstack_cmd(cmd, args)) + + # if result: + # print("User has member role") + + +def validate_public_network(args): + """Coming soon - validate the public network""" + # pylint: disable=unused-argument,unused-variable + print("Validate public network - NYI") + cmd = ( + "network create --external --provider-physical-network datacentre " + "--provider-network-type flat public" + ) + cmd = ( + f"subnet create public-net --subnet-range {args.publice_network_cidr} " + f"--no-dhcp --gateway {args.gateway} --allocation-pool " + f"start={args.public_net_start},end={args.public_net_end} " + "--network public" + ) + print(cmd) + + +def validate_private_network(args): + """Coming soon - validate the private network""" + # pylint: disable=unused-argument,unused-variable + _ = "openstack network create --internal private" + _ = ( + "openstack subnet create private-net " + f"--subnet-range {args.private_network_cidr} --network private" + ) + print("validate private network - NYI") + + +def validate_cirros_flavor(args): + """Coming soon - create the cirros flavor""" + # pylint: disable=unused-argument + print("validate cirros flavor - NYI") + + +def validate_rhel_flavor(args): + """Coming soon - validate the rhel flavor""" + # pylint: disable=unused-argument + print("validate rhel flavor - NYI") + + +def validate_cirros_image(args): + """Coming soon - validate the cirros image""" + # pylint: disable=unused-argument + print("validate cirros image - NYI") + + +def validate_rhel_image(args): + """Coming soon - validate the rhel image""" + # pylint: disable=unused-argument + print("validate rhel image - NYI") + + +def validate_cirros_instance(args): + """Coming soon - validate the cirros instance""" + # pylint: disable=unused-argument + print("validate cirros instance - NYI") + + +def validate_rhel_instance(args): + """Coming soon - create the rhel instance""" + # pylint: disable=unused-argument + print("validate rhel instance - NYI") + + +def main(): + """main function""" + args = parse_args() + + validate_user(args) + validate_group(args) + validate_admin_in_group(args) + validate_admin_has_member_role(args) + validate_public_network(args) + validate_private_network(args) + validate_cirros_flavor(args) + validate_rhel_flavor(args) + validate_cirros_image(args) + validate_rhel_image(args) + validate_cirros_instance(args) + validate_rhel_instance(args) + + +if __name__ == "__main__": + main() From 0681468b8337e631f3dd58e51cc350459d1f3c1a Mon Sep 17 00:00:00 2001 From: Neill Cox Date: Wed, 15 Nov 2023 10:04:47 +1100 Subject: [PATCH 18/19] :construction: Too much stuff for one commit. Do better Neill --- src/tripleo_aio_helpers/create_aio_vm.py | 22 +- src/tripleo_aio_helpers/os_migrate_setup.py | 39 +-- .../os_migrate_teardown.py | 51 ++-- .../os_migrate_validate_dst.py | 282 +++++++++++++----- .../os_migrate_validate_src.py | 75 ++--- src/tripleo_aio_helpers/prepare_deployment.py | 15 +- src/tripleo_aio_helpers/utils.py | 19 +- 7 files changed, 291 insertions(+), 212 deletions(-) diff --git a/src/tripleo_aio_helpers/create_aio_vm.py b/src/tripleo_aio_helpers/create_aio_vm.py index 1380b25..da45d8d 100644 --- a/src/tripleo_aio_helpers/create_aio_vm.py +++ b/src/tripleo_aio_helpers/create_aio_vm.py @@ -23,12 +23,8 @@ def parse_args(): parser.add_argument("--local-hostname", required=True) parser.add_argument("--user-data", default=template_path / "user-data.tpl") parser.add_argument("--meta-data", default=template_path / "meta-data.tpl") - parser.add_argument( - "--network-data", default=template_path / "network-config.tpl" - ) - parser.add_argument( - "--instance-id", required=True, help="Hostname for the new VM" - ) + parser.add_argument("--network-data", default=template_path / "network-config.tpl") + parser.add_argument("--instance-id", required=True, help="Hostname for the new VM") parser.add_argument("--output-image", required=True) parser.add_argument("--image-size", default="800G") parser.add_argument("--input-image", required=True) @@ -148,10 +144,7 @@ def create_image(args): print(result) print("Resizing image") - cmd = ( - f"virt-resize --expand /dev/sda3 {args.input_image} " - f"{args.output_image}" - ) + cmd = f"virt-resize --expand /dev/sda3 {args.input_image} " f"{args.output_image}" result = subprocess.check_output(cmd, shell=True, universal_newlines=True) @@ -202,17 +195,12 @@ def delete_user_data(): def install_packages(args): """Install the packages needed for virt-install to work""" - cmd = ( - "sudo dnf install -y virt-install virt-viewer qemu-img " - "libguestfs.x86_64" - ) + cmd = "sudo dnf install -y virt-install virt-viewer qemu-img " "libguestfs.x86_64" print("installing needed packages...") if args.verbose: - print( - subprocess.check_output(cmd, shell=True, universal_newlines=True) - ) + print(subprocess.check_output(cmd, shell=True, universal_newlines=True)) def main(): diff --git a/src/tripleo_aio_helpers/os_migrate_setup.py b/src/tripleo_aio_helpers/os_migrate_setup.py index 0b0e65f..0fa0534 100644 --- a/src/tripleo_aio_helpers/os_migrate_setup.py +++ b/src/tripleo_aio_helpers/os_migrate_setup.py @@ -77,9 +77,7 @@ def parse_args(): ) if not args.public_net_end: - args.public_net_end = get_from_env( - "--public-net-end", "AIO_PUBLIC_NET_END" - ) + args.public_net_end = get_from_env("--public-net-end", "AIO_PUBLIC_NET_END") if not args.dns_server: args.dns_server = get_from_env("--dns-server", "AIO_DNS_SERVER") @@ -133,9 +131,7 @@ def create_user(args): cmd = "user list -f json" user_exists = [ - x - for x in json.loads(openstack_cmd(cmd, args)) - if x["Name"] == args.username + x for x in json.loads(openstack_cmd(cmd, args)) if x["Name"] == args.username ] if user_exists: @@ -234,16 +230,14 @@ def create_private_network(args): args.private_network_id = network_exists[0]["ID"] else: cmd = ( - f"network create -f json --internal private " - f"--project {args.project_id}" + f"network create -f json --internal private " f"--project {args.project_id}" ) args.private_network_id = json.loads(openstack_cmd(cmd, args))["id"] print("Private network created.") subnet_exists = json.loads( openstack_cmd( - f"subnet list -f json --project {args.project_id} " - f"--name private-net", + f"subnet list -f json --project {args.project_id} " f"--name private-net", args, ) ) @@ -393,8 +387,7 @@ def create_instance(args, name, flavor, image, security_group, boot_size): "floating ip create -f json public", args, as_json=True ) _ = test_user_openstack_cmd( - f"server add floating ip {server['id']} " - f"{fip['floating_ip_address']}", + f"server add floating ip {server['id']} " f"{fip['floating_ip_address']}", args, ) @@ -441,9 +434,7 @@ def create_keypair(args): key_exists = [ kp - for kp in json.loads( - test_user_openstack_cmd("keypair list -f json ", args) - ) + for kp in json.loads(test_user_openstack_cmd("keypair list -f json ", args)) if kp["Name"] == "test_keypair" ] @@ -458,8 +449,7 @@ def create_keypair(args): args.keypair_name = json.loads( test_user_openstack_cmd( - f"keypair create -f json " - f"--public-key {fname} test_keypair", + f"keypair create -f json " f"--public-key {fname} test_keypair", args, ) )[ @@ -478,9 +468,7 @@ def create_router(args): try: router_id = [ router - for router in openstack_cmd( - "router list -f json", args, as_json=True - ) + for router in openstack_cmd("router list -f json", args, as_json=True) if router["Name"] == "test-router" ][0]["ID"] except IndexError: @@ -492,16 +480,13 @@ def create_router(args): )["id"] print("router created") - router_info = openstack_cmd( - f"router show -f json {router_id}", args, as_json=True - ) + router_info = openstack_cmd(f"router show -f json {router_id}", args, as_json=True) if router_info["external_gateway_info"]: print("router gateway already set") else: openstack_cmd( - f"router set {router_id} " - f"--external-gateway {args.public_network_id}", + f"router set {router_id} " f"--external-gateway {args.public_network_id}", args, ) print("router gateway added") @@ -536,9 +521,7 @@ def create_security_group(args): args.sg_id = sg_exists[0]["ID"] else: sec_grp = json.loads( - test_user_openstack_cmd( - "security group create test-sg -f json", args - ) + test_user_openstack_cmd("security group create test-sg -f json", args) ) args.sg_id = sec_grp["id"] diff --git a/src/tripleo_aio_helpers/os_migrate_teardown.py b/src/tripleo_aio_helpers/os_migrate_teardown.py index 6d391c4..f573770 100644 --- a/src/tripleo_aio_helpers/os_migrate_teardown.py +++ b/src/tripleo_aio_helpers/os_migrate_teardown.py @@ -17,11 +17,12 @@ def parse_args(): parser.add_argument("-n", "--project-name", default="test-project") parser.add_argument("-u", "--username", default="test-user") parser.add_argument("-p", "--password", default="secrete123") - parser.add_argument("-c", "--cloud", default="standalone") + parser.add_argument("-c", "--cloud", default="dst_admin") parser.add_argument("--delete-all", action="store_true") parser.add_argument("--delete-images", action="store_true") parser.add_argument("--delete-flavors", action="store_true") - parser.add_argument("--delete-networks", action="store_true") + parser.add_argument("--delete-private-network", action="store_true") + parser.add_argument("--delete-public-network", action="store_true") parser.add_argument("--delete-instances", action="store_true") parser.add_argument("--delete-user", action="store_true") parser.add_argument("--delete-project", action="store_true") @@ -61,9 +62,7 @@ def destroy_user(args): """Delete the user if it exists""" cmd = "user list -f json" user_exists = [ - x - for x in json.loads(openstack_cmd(cmd, args)) - if x["Name"] == args.username + x for x in json.loads(openstack_cmd(cmd, args)) if x["Name"] == args.username ] if user_exists: @@ -81,16 +80,12 @@ def destroy_public_network(args): public_network_exists = [ network - for network in openstack_cmd( - "network list -f json", args, as_json=True - ) + for network in openstack_cmd("network list -f json", args, as_json=True) if network["Name"] == "public" ] if public_network_exists: - routers = test_user_openstack_cmd( - "router list -f json", args, as_json=True - ) + routers = test_user_openstack_cmd("router list -f json", args, as_json=True) for router in routers: router_details = test_user_openstack_cmd( @@ -99,8 +94,7 @@ def destroy_public_network(args): for interface in router_details["interfaces_info"]: test_user_openstack_cmd( - f"router remove port {router['ID']} " - f"{interface['port_id']}", + f"router remove port {router['ID']} " f"{interface['port_id']}", args, ) @@ -113,22 +107,19 @@ def destroy_private_network(args): """Delete the private network""" private_network_exists = [ network - for network in openstack_cmd( - "network list -f json", args, as_json=True - ) + for network in test_user_openstack_cmd("network list -f json", args, as_json=True) if network["Name"] == "private" ] if private_network_exists: print("deleting private network") - test_user_openstack_cmd("network delete private", args) + test_user_openstack_cmd(f"network delete private", args) -def delete_flavor(args,name): + +def delete_flavor(args, name): flavor_exists = [ flavor - for flavor in openstack_cmd( - "flavor list -f json --all", args, as_json=True - ) + for flavor in openstack_cmd("flavor list -f json --all", args, as_json=True) if flavor["Name"] == name ] if flavor_exists: @@ -138,20 +129,21 @@ def delete_flavor(args,name): else: print(f"{name} flavour not found") + def destroy_cirros_flavor(args): """Delete the cirros flavor""" delete_flavor(args, "cirros") + def destroy_rhel_flavor(args): """Delete the rhel flavor""" delete_flavor(args, "rhel") + def delete_image(args, name): image_exists = [ image - for image in openstack_cmd( - "image list -f json", args, as_json=True - ) + for image in openstack_cmd("image list -f json", args, as_json=True) if image["Name"] == name ] @@ -161,14 +153,17 @@ def delete_image(args, name): else: print(f"{name} image not found") + def destroy_cirros_image(args): """Delete the cirros image""" delete_image(args, "cirros_image") + def destroy_rhel_image(args): """Delete the rhel image""" delete_image(args, "rhel_image") + def destroy_cirros_instance(args): """Delete the cirros instance""" try: @@ -206,12 +201,11 @@ def get_project_id(args): result = openstack_cmd("project list -f json", args, as_json=True) try: - args.project_id = [r for r in result if r["Name"] == args.project_name][0][ - "ID" - ] + args.project_id = [r for r in result if r["Name"] == args.project_name][0]["ID"] except IndexError: args.project_id = None + def main(): """main function""" args = parse_args() @@ -233,9 +227,10 @@ def main(): destroy_cirros_flavor(args) destroy_rhel_flavor(args) - if args.delete_networks or args.delete_all: + if args.delete_public_network: destroy_public_network(args) + if args.delete_private_network or args.delete_all: if args.project_id: destroy_private_network(args) else: diff --git a/src/tripleo_aio_helpers/os_migrate_validate_dst.py b/src/tripleo_aio_helpers/os_migrate_validate_dst.py index 49216a7..1b008c4 100644 --- a/src/tripleo_aio_helpers/os_migrate_validate_dst.py +++ b/src/tripleo_aio_helpers/os_migrate_validate_dst.py @@ -1,43 +1,42 @@ """ Quick and dirty script to validate that the necessary project, user,roles, -flavors, networks, images have been created +flavors, networks, images have not been created """ import argparse import json +from rich import print + from .utils import openstack_cmd +def failed(msg, args): + args.failed = True + print(f"[red]{msg} :x:[/red]") + + +def passed(msg): + print(f"[green]{msg} :white_heavy_check_mark:[/green]") + + def parse_args(): """Parse the command line arguments""" - # home = os.environ.get('HOME') - parser = argparse.ArgumentParser() - # parser.add_argument("-d", "--project-domain", default="default") parser.add_argument("-n", "--project-name", default="test-project") + parser.add_argument("-u", "--username", default="test-user") + parser.add_argument("-c", "--cloud", default="dst_admin") + parser.add_argument("--private-network-name", default="private") + parser.add_argument("--cirros-flavor-name", default="cirros") + parser.add_argument("--rhel-flavor-name", default="rhel") + parser.add_argument("--cirros-instance-name", default="cirros-test-instance") + parser.add_argument("--rhel-instance-name", default="rhel-test-instance") + parser.add_argument("--router-name", default="test-router") + parser.add_argument("--group-name", default="os-migrate") + # parser.add_argument( - # "-D", "--project-description", default="Test project" - # ) - # parser.add_argument("-u", "--username", default="test-user") - # parser.add_argument("-p", "--password", default="secrete123") - # parser.add_argument("-c", "--cloud", default="standalone") - # parser.add_argument("-g", "--gateway", default="10.76.23.254") - # parser.add_argument( - # "-C", "--public-network-cider", default="10.76.23.0/24" + # "--ssh", help="Connection string to run commands on a remote host." # ) - # parser.add_argument("--private-network-cidr", default="192.168.100.0/24") - # parser.add_argument("--publice-net-start", default="10.76.23.50") - # parser.add_argument("--publice-net-end", default="10.76.23.52") - # parser.add_argument("--dns-server", default="10.76.23.245") - - # parser.add_argument("--dry-run", action="store_true") - parser.add_argument( - "--ssh", help="Connection string to run commands on a remote host." - ) - - # export OS_CLOUD=standalone - # export STANDALONE_HOST=10.76.23.39 args = parser.parse_args() @@ -54,107 +53,234 @@ def validate_project(args): ] if project_exists: - print(project_exists) + passed(f"project {args.project_name} exists with id {project_exists[0]['ID']}") else: - print("Project {args.project_name} not found.") + failed("Project {args.project_name} not found.", args) def validate_user(args): """Validate that the user exists""" cmd = "user list -f json" user_exists = [ - x - for x in json.loads(openstack_cmd(cmd, args)) - if x["Name"] == args.username + x for x in json.loads(openstack_cmd(cmd, args)) if x["Name"] == args.username ] if user_exists: - print(user_exists) + passed(f"user {args.username} exists with ID: {user_exists[0]['ID']}") else: - print("User not found") + failed("User not found", args) def validate_member_role(args): """ Validate that the member role has been assigned to the user. """ - print(args) - # cmd = ( - # f"role add --user {args.username} " - # f"--project {args.project_id} member" - # ) + cmd = f"role assignment list --user {args.username} --names --project {args.project_name} --role member -f json" - # result = openstack_cmd(cmd, args) + if len(openstack_cmd(cmd, args, as_json=True)) > 0: + passed(f"User has member role in {args.project_name}") + else: + failed(f"User does not have member role in {args.project_name}") - # cmd = f"role assignment list --user {args.user_id} --role member -f json" - # result = json.loads(openstack_cmd(cmd, args)) - # if result: - # print("User has member role") +def validate_group(args): + check( + args, + "group list -f json", + "group", + f"group {args.group_name} exists", + "Name", + "os-migrate", + fail_if="not found" + ) +def validate_groups_has_member_role(args): + cmd = f"role assignment list --group {args.group_name} --names --project {args.project_name} --role member -f json" + + if len(openstack_cmd(cmd, args, as_json=True)) > 0: + passed(f"Group {args.group_name} has member role in {args.project_name}") + else: + failed(f"Group {args.group_name} does not have member role in {args.project_name}") + + +def validate_admin_in_group(args): + cmd = f"group contains user {args.group_name} admin" + result = openstack_cmd(cmd, args, as_json=False) + + if "admin in group" in result: + passed(result[:-1]) + else: + failed(result[:-1], args) def validate_public_network(args): """Coming soon - validate the public network""" # pylint: disable=unused-argument,unused-variable - print("Validate public network - NYI") - cmd = ( - "network create --external --provider-physical-network datacentre " - "--provider-network-type flat public" - ) - cmd = ( - f"subnet create public-net --subnet-range {args.publice_network_cidr} " - f"--no-dhcp --gateway {args.gateway} --allocation-pool " - f"start={args.public_net_start},end={args.public_net_end} " - "--network public" - ) - print(cmd) + + cmd = "network list -f json" + + try: + network_exists = [ + n for n in openstack_cmd(cmd, args, as_json=True) if n["Name"] == "public" + ][0] + passed(f"public network exists with id: {network_exists['ID']}") + except IndexError: + failed("public network not found", args) + + cmd = f"subnet list -f json" + try: + subnet = [ + s + for s in openstack_cmd(cmd, args, as_json=True) + if s["Name"] == "public-net" + ][0] + subnet_details = openstack_cmd( + f"subnet show -f json {subnet['ID']}", args, as_json=True + ) + passed( + f"public subnet found allocation pool: {subnet_details['allocation_pools'][0]}" + ) + except IndexError: + failed("public subnet not found", args) def validate_private_network(args): """Coming soon - validate the private network""" # pylint: disable=unused-argument,unused-variable - _ = "openstack network create --internal private" - _ = ( - "openstack subnet create private-net " - f"--subnet-range {args.private_network_cidr} --network private" - ) - print("validate private network - NYI") + cmd = "network list -f json" + try: + network_exists = [ + n + for n in openstack_cmd(cmd, args, as_json=True) + if n["Name"] == args.private_network_name + ][0] + failed( + f"private ({args.private_network_name}) network exists with id: {network_exists['ID']}", + args, + ) + except IndexError: + passed("private network not found") def validate_cirros_flavor(args): - """Coming soon - create the cirros flavor""" - # pylint: disable=unused-argument - print("validate cirros flavor - NYI") + """Validate that the cirros flavor doesn't exist""" + + cmd = "flavor list -f json --all" + + try: + flavor = [ + f + for f in openstack_cmd(cmd, args, as_json=True) + if f["Name"] == args.cirros_flavor_name + ][0] + failed(f"cirros flavor found (ID:{flavor['ID']})", args) + except IndexError: + passed("Cirros flavor not found.") def validate_rhel_flavor(args): - """Coming soon - validate the rhel flavor""" - # pylint: disable=unused-argument - print("validate rhel flavor - NYI") + """Validate that the rhel flavor doesn't exist""" + + cmd = "flavor list -f json --all" + + try: + flavor = [ + f + for f in openstack_cmd(cmd, args, as_json=True) + if f["Name"] == args.rhel_flavor_name + ][0] + failed(f"rhel flavor found (ID:{flavor['ID']})", args) + except IndexError: + passed("RHEL flavor not found.") def validate_cirros_image(args): - """Coming soon - validate the cirros image""" - # pylint: disable=unused-argument - print("validate cirros image - NYI") + """Validate that the cirros image does not exist""" + + cmd = "image list -f json --all" + + try: + image = [ + f + for f in openstack_cmd(cmd, args, as_json=True) + if f["Name"] == args.cirros_image_name + ][0] + failed(f"Cirros image found (ID:{image['ID']})", args) + except IndexError: + passed("Cirros image not found.") def validate_rhel_image(args): - """Coming soon - validate the rhel image""" + """Validate that the rhel image does not exist""" # pylint: disable=unused-argument - print("validate rhel image - NYI") + cmd = "image list -f json --all" + + try: + image = [ + f + for f in openstack_cmd(cmd, args, as_json=True) + if f["Name"] == args.rhel_image_name + ][0] + failed(f"RHEL image found (ID:{image['ID']})", args) + except IndexError: + passed("RHEL image not found.") def validate_cirros_instance(args): - """Coming soon - validate the cirros instance""" - # pylint: disable=unused-argument - print("validate cirros instance - NYI") + """Validate that the cirros instance does not exist""" + cmd = "server list -f json --all" + + try: + instance = [ + f + for f in openstack_cmd(cmd, args, as_json=True) + if f["Name"] == args.cirros_instance_name + ][0] + failed(f"Cirros instance found (ID:{instance['ID']})", args) + except IndexError: + passed("Cirros instance not found.") def validate_rhel_instance(args): - """Coming soon - create the rhel instance""" - # pylint: disable=unused-argument - print("validate rhel instance - NYI") + """Validate that the RHEL instance does not exist""" + cmd = "server list -f json --all" + + try: + instance = [ + f + for f in openstack_cmd(cmd, args, as_json=True) + if f["Name"] == args.rhel_instance_name + ][0] + failed(f"RHEL instance found (ID:{instance['ID']})", args) + except IndexError: + passed("RHEL instance not found.") + + +def check(args, cmd, thing, msg, key, value, id_key="ID", fail_if="found"): + if fail_if not in ["found", "not found"]: + raise ValueError("fail_if must be eitther 'found' or 'not found'") + + try: + found = [i for i in openstack_cmd(cmd, args, as_json=True) if i[key] == value][0] + if fail_if == "found": + failed(msg.format(key=id_key, value=found[id_key]), args) + else: + passed(f"{thing} not found") + except IndexError: + if fail_if == "found": + passed(f"{thing} not found") + else: + failed(msg.format(key=id_key, value=found[id_key]), args) + + +def validate_router(args): + check( + args, + f"router list --project {args.project_name} -f json", + "router", + "router found ", + "Name", + args.router_name, + ) def main(): @@ -164,6 +290,9 @@ def main(): validate_user(args) validate_project(args) validate_member_role(args) + validate_group(args) + validate_groups_has_member_role(args) + validate_admin_in_group(args) validate_public_network(args) validate_private_network(args) validate_cirros_flavor(args) @@ -172,6 +301,7 @@ def main(): validate_rhel_image(args) validate_cirros_instance(args) validate_rhel_instance(args) + validate_router(args) if __name__ == "__main__": diff --git a/src/tripleo_aio_helpers/os_migrate_validate_src.py b/src/tripleo_aio_helpers/os_migrate_validate_src.py index 3838215..2c9e714 100644 --- a/src/tripleo_aio_helpers/os_migrate_validate_src.py +++ b/src/tripleo_aio_helpers/os_migrate_validate_src.py @@ -5,39 +5,19 @@ flavors, networks, images have been created import argparse import json -from .utils import openstack_cmd +from .utils import openstack_cmd, passed, failed def parse_args(): """Parse the command line arguments""" - # home = os.environ.get('HOME') - parser = argparse.ArgumentParser() - # parser.add_argument("-d", "--project-domain", default="default") parser.add_argument("-n", "--project-name", default="test-project") - # parser.add_argument( - # "-D", "--project-description", default="Test project" - # ) - # parser.add_argument("-u", "--username", default="test-user") - # parser.add_argument("-p", "--password", default="secrete123") - # parser.add_argument("-c", "--cloud", default="standalone") - # parser.add_argument("-g", "--gateway", default="10.76.23.254") - # parser.add_argument( - # "-C", "--public-network-cider", default="10.76.23.0/24" - # ) - # parser.add_argument("--private-network-cidr", default="192.168.100.0/24") - # parser.add_argument("--publice-net-start", default="10.76.23.50") - # parser.add_argument("--publice-net-end", default="10.76.23.52") - # parser.add_argument("--dns-server", default="10.76.23.245") - - # parser.add_argument("--dry-run", action="store_true") - parser.add_argument( - "--ssh", help="Connection string to run commands on a remote host." - ) - - # export OS_CLOUD=standalone - # export STANDALONE_HOST=10.76.23.39 + parser.add_argument("-u", "--username", default="test-user") + parser.add_argument("-c", "--cloud", default="src_admin") + parser.add_argument("--private-network-name", default="private") + parser.add_argument("--cirros-flavor-name", default="cirros") + parser.add_argument("--rhel-flavor-name", default="rhel") args = parser.parse_args() @@ -54,33 +34,34 @@ def validate_project(args): ] if project_exists: - print(project_exists) + passed(project_exists) else: - print("Project {args.project_name} not found.") + failed("Project {args.project_name} not found.", args) def validate_user(args): """Validate that the user exists""" cmd = "user list -f json" user_exists = [ - x - for x in json.loads(openstack_cmd(cmd, args)) - if x["Name"] == args.username + x for x in json.loads(openstack_cmd(cmd, args)) if x["Name"] == args.username ] if user_exists: - print(user_exists) + passed(user_exists) else: - print("User not found") + failed("User not found", args) + + +def validate_group_exists(args): + failed("NYI", args) -def validate_group_exists(args) - print("NYI") def validate_admin_in_group(args): - print("NYI") + failed("NYI", args) + def validate_group_has_member_role(args): - print("NYI") + failed("NYI", args) def validate_member_role(args): @@ -105,7 +86,7 @@ def validate_member_role(args): def validate_public_network(args): """Coming soon - validate the public network""" # pylint: disable=unused-argument,unused-variable - print("Validate public network - NYI") + failed("Validate public network - NYI", args) cmd = ( "network create --external --provider-physical-network datacentre " "--provider-network-type flat public" @@ -127,43 +108,43 @@ def validate_private_network(args): "openstack subnet create private-net " f"--subnet-range {args.private_network_cidr} --network private" ) - print("validate private network - NYI") + failed("validate private network - NYI", args) def validate_cirros_flavor(args): """Coming soon - create the cirros flavor""" # pylint: disable=unused-argument - print("validate cirros flavor - NYI") + failed("validate cirros flavor - NYI", args) def validate_rhel_flavor(args): """Coming soon - validate the rhel flavor""" # pylint: disable=unused-argument - print("validate rhel flavor - NYI") + failed("validate rhel flavor - NYI", args) def validate_cirros_image(args): """Coming soon - validate the cirros image""" # pylint: disable=unused-argument - print("validate cirros image - NYI") + failed("validate cirros image - NYI", args) def validate_rhel_image(args): """Coming soon - validate the rhel image""" # pylint: disable=unused-argument - print("validate rhel image - NYI") + failed("validate rhel image - NYI", args) def validate_cirros_instance(args): """Coming soon - validate the cirros instance""" # pylint: disable=unused-argument - print("validate cirros instance - NYI") + failed("validate cirros instance - NYI", args) def validate_rhel_instance(args): """Coming soon - create the rhel instance""" # pylint: disable=unused-argument - print("validate rhel instance - NYI") + failed("validate rhel instance - NYI", args) def main(): @@ -171,9 +152,9 @@ def main(): args = parse_args() validate_user(args) - validate_group(args) + validate_group_exists(args) validate_admin_in_group(args) - validate_admin_has_member_role(args) + validate_group_has_member_role(args) validate_public_network(args) validate_private_network(args) validate_cirros_flavor(args) diff --git a/src/tripleo_aio_helpers/prepare_deployment.py b/src/tripleo_aio_helpers/prepare_deployment.py index 4ff8be0..539ef10 100644 --- a/src/tripleo_aio_helpers/prepare_deployment.py +++ b/src/tripleo_aio_helpers/prepare_deployment.py @@ -19,9 +19,7 @@ def parse_args(): parser.add_argument("-a", "--address", required=True) parser.add_argument("-i", "--interface", required=True) parser.add_argument("-m", "--netmask", default=24) - parser.add_argument( - "-d", "--dns", nargs="+", action="append", required=True - ) + parser.add_argument("-d", "--dns", nargs="+", action="append", required=True) parser.add_argument("-D", "--domain", default="aio") parser.add_argument("--using-multiple-nics", action="store_true") parser.add_argument("-U", "--deployment-user") @@ -41,8 +39,7 @@ def parse_args(): parser.add_argument( "--cinder-pool-size", default="500280", - help="Size of the loopback device allocated to cinder. " - "Defaults to 500280", + help="Size of the loopback device allocated to cinder. " "Defaults to 500280", ) args = parser.parse_args() @@ -141,16 +138,12 @@ def main(): args = parse_args() containers_yaml = build_containers_yaml(args) - with open( - args.containers_yaml_out, "w", encoding="utf-8" - ) as containers_out: + with open(args.containers_yaml_out, "w", encoding="utf-8") as containers_out: containers_out.write(yaml.dump(containers_yaml)) print(f"containers yaml written to {args.containers_yaml_out}") standalone_parameters = get_standalone_parameters(args) - with open( - args.standalone_yaml_out, "w", encoding="utf-8" - ) as parameters_out: + with open(args.standalone_yaml_out, "w", encoding="utf-8") as parameters_out: parameters_out.write(yaml.dump(standalone_parameters)) print(f"standalone parameters yaml written to {args.standalone_yaml_out}") diff --git a/src/tripleo_aio_helpers/utils.py b/src/tripleo_aio_helpers/utils.py index eaa8f68..9e884c8 100644 --- a/src/tripleo_aio_helpers/utils.py +++ b/src/tripleo_aio_helpers/utils.py @@ -4,6 +4,8 @@ import os import subprocess import sys +from rich import print + def get_from_env(name, envvar, required=True): """Get the value for a parameter from an environment variable""" @@ -27,18 +29,16 @@ def openstack_cmd(cmd, args, as_json=False): cmd = cmd.replace("\n", " ") cmd = f"OS_CLOUD={args.cloud} openstack " + cmd - if args.ssh: + if "ssh" in args and args.ssh: cmd = f'ssh {args.ssh} "{cmd}"' - if args.dry_run: + if "dry_run" in args and args.dry_run: print("dry-run specified. Not executing cmd. cmd was:") print(cmd) return None try: - result = subprocess.check_output( - cmd, shell=True, universal_newlines=True - ) + result = subprocess.check_output(cmd, shell=True, universal_newlines=True) if as_json: result = json.loads(result) @@ -64,3 +64,12 @@ def test_user_openstack_cmd(cmd, args, as_json=False): print(cmd) return openstack_cmd(cmd, args, as_json=as_json) + + +def failed(msg, args): + args.failed = True + print(f"[red]{msg} :x:[/red]") + + +def passed(msg): + print(f"[green]{msg} :white_heavy_check_mark:[/green]") From 846d858c2e05dca566fe2619a298cc6810f9034c Mon Sep 17 00:00:00 2001 From: Neill Cox Date: Wed, 15 Nov 2023 01:00:55 +0000 Subject: [PATCH 19/19] :construction: images, logging --- requirements.txt | 1 + .../os_migrate_validate_dst.py | 24 +++++++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/requirements.txt b/requirements.txt index cdb17ee..f862667 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ # pyflakes==2.5.0 # pylint==2.13.9 PyYAML==6.0.1 +rich diff --git a/src/tripleo_aio_helpers/os_migrate_validate_dst.py b/src/tripleo_aio_helpers/os_migrate_validate_dst.py index 1b008c4..1b54093 100644 --- a/src/tripleo_aio_helpers/os_migrate_validate_dst.py +++ b/src/tripleo_aio_helpers/os_migrate_validate_dst.py @@ -4,6 +4,7 @@ flavors, networks, images have not been created """ import argparse import json +import logging from rich import print @@ -29,10 +30,13 @@ def parse_args(): parser.add_argument("--private-network-name", default="private") parser.add_argument("--cirros-flavor-name", default="cirros") parser.add_argument("--rhel-flavor-name", default="rhel") + parser.add_argument("--cirros-image-name", default="cirros_image") + parser.add_argument("--rhel-image-name", default="rhel_image") parser.add_argument("--cirros-instance-name", default="cirros-test-instance") parser.add_argument("--rhel-instance-name", default="rhel-test-instance") parser.add_argument("--router-name", default="test-router") parser.add_argument("--group-name", default="os-migrate") + parser.add_argument("-l", "--log-level", default="WARN") # parser.add_argument( # "--ssh", help="Connection string to run commands on a remote host." @@ -76,6 +80,7 @@ def validate_member_role(args): Validate that the member role has been assigned to the user. """ cmd = f"role assignment list --user {args.username} --names --project {args.project_name} --role member -f json" + logging.debug(cmd) if len(openstack_cmd(cmd, args, as_json=True)) > 0: passed(f"User has member role in {args.project_name}") @@ -197,16 +202,19 @@ def validate_cirros_image(args): """Validate that the cirros image does not exist""" cmd = "image list -f json --all" + images = openstack_cmd(cmd, args, as_json=True) + + logging.debug(images) try: image = [ - f - for f in openstack_cmd(cmd, args, as_json=True) - if f["Name"] == args.cirros_image_name + i + for i in images + if i["Name"] == args.cirros_image_name ][0] - failed(f"Cirros image found (ID:{image['ID']})", args) + failed(f"Cirros image ({args.cirros_image_name}) found (ID:{image['ID']})", args) except IndexError: - passed("Cirros image not found.") + passed(f"Cirros image ({args.cirros_image_name}) not found.") def validate_rhel_image(args): @@ -220,9 +228,9 @@ def validate_rhel_image(args): for f in openstack_cmd(cmd, args, as_json=True) if f["Name"] == args.rhel_image_name ][0] - failed(f"RHEL image found (ID:{image['ID']})", args) + failed(f"RHEL image ({args.rhel_image_name}) found (ID:{image['ID']})", args) except IndexError: - passed("RHEL image not found.") + passed(f"RHEL image ({args.rhel_image_name}) not found.") def validate_cirros_instance(args): @@ -287,6 +295,8 @@ def main(): """main function""" args = parse_args() + logging.basicConfig(level=args.log_level) + validate_user(args) validate_project(args) validate_member_role(args)