diff --git a/.gitreview b/.gitreview new file mode 100644 index 0000000..83e20af --- /dev/null +++ b/.gitreview @@ -0,0 +1,4 @@ +[gerrit] +host=review.opendev.org +port=29418 +project=openstack/charm-ironic-conductor.git diff --git a/.zuul.yaml b/.zuul.yaml new file mode 100644 index 0000000..fd20909 --- /dev/null +++ b/.zuul.yaml @@ -0,0 +1,4 @@ +- project: + templates: + - openstack-python3-charm-jobs + - openstack-cover-jobs diff --git a/src/config.yaml b/src/config.yaml index 4a32be3..3c355f8 100644 --- a/src/config.yaml +++ b/src/config.yaml @@ -159,3 +159,12 @@ options: Note: Automated cleaning can be toggled on a per node basis, via node properties. Note: node cleaning may take a long time, especially if secure erase is enabled. + enabled-hw-types: + default: "ipmi" + type: string + description: | + Comma separated list of hardware types to enable. + Valid options are: + * ipmi + * redfish + * idrac diff --git a/src/layer.yaml b/src/layer.yaml index d4551c9..c5aa605 100644 --- a/src/layer.yaml +++ b/src/layer.yaml @@ -5,10 +5,10 @@ includes: - interface:rabbitmq - interface:keystone-credentials - interface:ironic-api -repo: https://github.com/gabriel-samfira/charm-ironic-conductor +repo: https://opendev.org/openstack/charm-ironic-conductor.git options: basic: use_venv: true include_system_packages: False - packages: [ 'libffi-dev', 'libssl-dev', 'libpython3.6-dev' ] + packages: [ 'libffi-dev', 'libssl-dev', 'libpython3-dev' ] diff --git a/src/lib/charm/openstack/ironic/api_utils.py b/src/lib/charm/openstack/ironic/api_utils.py index abaeec6..4df6229 100644 --- a/src/lib/charm/openstack/ironic/api_utils.py +++ b/src/lib/charm/openstack/ironic/api_utils.py @@ -6,8 +6,10 @@ import glanceclient import swiftclient import keystoneclient +SYSTEM_CA_BUNDLE = '/etc/ssl/certs/ca-certificates.crt' -def create_keystone_session(keystone, verify=True): + +def create_keystone_session(keystone): plugin_name = "password" username = keystone.credentials_username() password = keystone.credentials_password() @@ -41,17 +43,17 @@ def create_keystone_session(keystone, verify=True): loader = loading.get_plugin_loader(plugin_name) auth = loader.load_from_options(**plugin_args) - return ks_session.Session(auth=auth, verify=verify) + return ks_session.Session(auth=auth, verify=SYSTEM_CA_BUNDLE) class OSClients(object): - def __init__(self, session, cacert=None): + def __init__(self, session): self._session = session self._img_cli = glanceclient.Client( session=self._session, version=2) self._obj_cli = swiftclient.Connection( - session=self._session, cacert=cacert) + session=self._session, cacert=SYSTEM_CA_BUNDLE) self._ks = keystoneclient.v3.Client( session=session) self._stores = None diff --git a/src/lib/charm/openstack/ironic/controller_utils.py b/src/lib/charm/openstack/ironic/controller_utils.py index 98afa7d..ad48d4e 100644 --- a/src/lib/charm/openstack/ironic/controller_utils.py +++ b/src/lib/charm/openstack/ironic/controller_utils.py @@ -25,7 +25,7 @@ class PXEBootBase(object): "/usr/lib/syslinux/modules/bios/chain.c32": "chain.c32", "/usr/lib/syslinux/modules/bios/ldlinux.c32": "ldlinux.c32", "/usr/lib/grub/x86_64-efi-signed/grubnetx64.efi.signed": "grubx64.efi", - "/usr/lib/shim/shim.efi.signed": "bootx64.efi", + "/usr/lib/shim/shimx64.efi.signed": "bootx64.efi", "/usr/lib/ipxe/undionly.kpxe": "undionly.kpxe", "/usr/lib/ipxe/ipxe.efi": "ipxe.efi", } @@ -65,7 +65,7 @@ class PXEBootBase(object): for f in self.FILE_MAP: if os.path.isfile(f) is False: raise ValueError( - "Missing required file %s. Package not installes?" % f) + "Missing required file %s. Package not installed?" % f) shutil.copy( f, os.path.join(self.TFTP_ROOT, self.FILE_MAP[f]), follow_symlinks=True) @@ -88,27 +88,9 @@ class PXEBootBase(object): self.HTTP_ROOT, _IRONIC_USER, _IRONIC_GROUP, chowntopdir=True) -class PXEBootBionic(PXEBootBase): - - # This is a file map of source to destination. The destination is - # relative to self.TFTP_ROOT - FILE_MAP = { - "/usr/lib/PXELINUX/pxelinux.0": "pxelinux.0", - "/usr/lib/syslinux/modules/bios/chain.c32": "chain.c32", - "/usr/lib/syslinux/modules/bios/ldlinux.c32": "ldlinux.c32", - "/usr/lib/grub/x86_64-efi-signed/grubnetx64.efi.signed": "grubx64.efi", - "/usr/lib/shim/shimx64.efi.signed": "bootx64.efi", - "/usr/lib/ipxe/undionly.kpxe": "undionly.kpxe", - "/usr/lib/ipxe/ipxe.efi": "ipxe.efi", - } - - def get_pxe_config_class(charm_config): # We may need to make slight adjustments to package names and/or # configuration files, based on the version of Ubuntu we are installing # on. This function serves as a factory which will return an instance # of the proper class to the charm. For now we only have one class. - series = ch_host.get_distrib_codename() - if series == "bionic": - return PXEBootBionic(charm_config) return PXEBootBase(charm_config) diff --git a/src/lib/charm/openstack/ironic/ironic.py b/src/lib/charm/openstack/ironic/ironic.py index 90bad53..412c937 100644 --- a/src/lib/charm/openstack/ironic/ironic.py +++ b/src/lib/charm/openstack/ironic/ironic.py @@ -10,6 +10,7 @@ from charms_openstack.adapters import ( DatabaseRelationAdapter, OpenStackRelationAdapters, ) +from charmhelpers.contrib.openstack.utils import os_release import charm.openstack.ironic.controller_utils as controller_utils import charms_openstack.adapters as adapters @@ -19,15 +20,14 @@ import charms.reactive as reactive PACKAGES = [ 'ironic-conductor', + 'python3-dracclient', 'python3-keystoneauth1', 'python3-keystoneclient', 'python3-glanceclient', - 'python3-swiftclient', - 'python-mysqldb', - 'python3-dracclient', 'python3-sushy', + 'python3-swiftclient', + 'python3-mysqldb', 'python3-ironicclient', - 'python3-scciclient', 'shellinabox', 'openssl', 'socat', @@ -50,6 +50,100 @@ VALID_DEPLOY_INTERFACES = ["direct", "iscsi"] DEFAULT_DEPLOY_IFACE = "flat" DEFAULT_NET_IFACE = "direct" +# The IPMI HW type requires only ipmitool to function. This HW type +# remains pretty much unchanged across OpenStack releases and *should* +# work +_NOOP_INTERFACES = { + 'enabled_bios_interfaces': 'no-bios', + 'enabled_management_interfaces': 'noop', + 'enabled_inspect_interfaces': 'no-inspect', + 'enabled_console_interfaces': 'no-console', + 'enabled_raid_interfaces': 'no-raid', + 'enabled_vendor_interfaces': 'no-vendor', +} +_IPMI_HARDWARE_TYPE = { + 'needed_packages': ['ipmitool', 'shellinabox', 'socat'], + 'config_options': { + 'enabled_hardware_types': ['ipmi', 'intel-ipmi'], + 'enabled_management_interfaces': [ + 'ipmitool', 'intel-ipmitool'], + 'enabled_inspect_interfaces': [], + 'enabled_power_interfaces': ['ipmitool'], + 'enabled_console_interfaces': [ + 'ipmitool-socat', + 'ipmitool-shellinabox'], + 'enabled_raid_interfaces': [], + 'enabled_vendor_interfaces': ['ipmitool'], + 'enabled_boot_interfaces': ['pxe'], + 'enabled_bios_interfaces': [] + } +} + +_HW_TYPES_MAP = collections.OrderedDict([ + ('train', { + 'ipmi': _IPMI_HARDWARE_TYPE, + 'redfish': { + 'needed_packages': ['python3-sushy'], + 'config_options': { + 'enabled_hardware_types': ['redfish'], + 'enabled_management_interfaces': ['redfish'], + 'enabled_inspect_interfaces': ['redfish'], + 'enabled_power_interfaces': ['redfish'], + 'enabled_console_interfaces': [], + 'enabled_raid_interfaces': [], + 'enabled_vendor_interfaces': [], + 'enabled_boot_interfaces': ['pxe'], + 'enabled_bios_interfaces': [] + } + }, + 'idrac': { + 'needed_packages': ['python-dracclient', 'python3-sushy'], + 'config_options': { + 'enabled_hardware_types': ['idrac'], + 'enabled_management_interfaces': ['idrac-redfish'], + 'enabled_inspect_interfaces': ['idrac-redfish'], + 'enabled_power_interfaces': ['idrac-redfish'], + 'enabled_console_interfaces': [], + 'enabled_raid_interfaces': ['idrac-wsman'], + 'enabled_vendor_interfaces': ['idrac-wsman'], + 'enabled_boot_interfaces': ['pxe'], + 'enabled_bios_interfaces': [] + } + } + }), + ('ussuri', { + 'ipmi': _IPMI_HARDWARE_TYPE, + 'redfish': { + 'needed_packages': ['python3-sushy'], + 'config_options': { + 'enabled_hardware_types': ['redfish'], + 'enabled_management_interfaces': ['redfish'], + 'enabled_inspect_interfaces': ['redfish'], + 'enabled_power_interfaces': ['redfish'], + 'enabled_console_interfaces': [], + 'enabled_raid_interfaces': [], + 'enabled_vendor_interfaces': [], + 'enabled_boot_interfaces': ['pxe', 'redfish-virtual-media'], + 'enabled_bios_interfaces': [], + } + }, + 'idrac': { + 'needed_packages': ['python-dracclient', 'python3-sushy'], + 'config_options': { + 'enabled_hardware_types': ['idrac'], + 'enabled_management_interfaces': ['idrac-redfish'], + 'enabled_inspect_interfaces': ['idrac-redfish'], + 'enabled_power_interfaces': ['idrac-redfish'], + 'enabled_console_interfaces': [], + 'enabled_raid_interfaces': ['idrac-wsman'], + 'enabled_vendor_interfaces': ['idrac-wsman'], + 'enabled_boot_interfaces': ['pxe'], + 'enabled_bios_interfaces': ['idrac-wsman'] + } + } + }) +]) + OPENSTACK_RELEASE_KEY = 'ironic-charm.openstack-release-version' @@ -89,6 +183,7 @@ class IronicConductorCharm(charms_openstack.charm.OpenStackCharm): release = 'train' name = 'ironic' packages = PACKAGES + python_version = 3 service_type = 'ironic' default_service = 'ironic-conductor' @@ -127,6 +222,7 @@ class IronicConductorCharm(charms_openstack.charm.OpenStackCharm): self.pxe_config = controller_utils.get_pxe_config_class( self.config) self._setup_pxe_config(self.pxe_config) + self._setup_power_adapter_config() self._configure_defaults() if "neutron" in self.enabled_network_interfaces: self.mandatory_config.extend([ @@ -141,6 +237,65 @@ class IronicConductorCharm(charms_openstack.charm.OpenStackCharm): if not iface: self.config["default-deploy-interface"] = DEFAULT_DEPLOY_IFACE + def _get_hw_type_map(self): + release = os_release(self.release_pkg) + supported = list(_HW_TYPES_MAP.keys()) + latest = supported[-1] + hw_type_map = _HW_TYPES_MAP.get( + release, _HW_TYPES_MAP[latest]) + return hw_type_map + + def _get_power_adapter_packages(self): + pkgs = [] + hw_type_map = self._get_hw_type_map() + for hw_type in self.enabled_hw_types: + needed_pkgs = hw_type_map.get( + hw_type, {}).get("needed_packages", []) + pkgs.extend(needed_pkgs) + return list(set(pkgs)) + + def _get_hardware_types_config(self): + hw_type_map = self._get_hw_type_map() + configs = {} + for hw_type in self.enabled_hw_types: + details = hw_type_map.get(hw_type, None) + if details is None: + # Not a valid hardware type. No need to raise here, + # we will let the operator know when we validate the + # config in custom_assess_status_check() + continue + driver_cfg = details['config_options'] + for cfg_opt in driver_cfg.items(): + if not configs.get(cfg_opt[0], None): + configs[cfg_opt[0]] = cfg_opt[1] + else: + configs[cfg_opt[0]].extend(cfg_opt[1]) + opt_list = list(set(configs[cfg_opt[0]])) + opt_list.sort() + configs[cfg_opt[0]] = opt_list + + if self.config.get('use-ipxe', None): + configs["enabled_boot_interfaces"].append('ipxe') + + # append the noop interfaces at the end + for noop in _NOOP_INTERFACES: + if configs.get(noop, None) is not None: + configs[noop].append(_NOOP_INTERFACES[noop]) + + for opt in configs: + if len(configs[opt]) > 0: + configs[opt] = ", ".join(configs[opt]) + else: + configs[opt] = "" + return configs + + def _setup_power_adapter_config(self): + pkgs = self._get_power_adapter_packages() + config = self._get_hardware_types_config() + self.packages.extend(pkgs) + self.packages = list(set(self.packages)) + self.config["hardware_type_cfg"] = config + def _setup_pxe_config(self, cfg): self.packages.extend(cfg.determine_packages()) self.packages = list(set(self.packages)) @@ -178,7 +333,7 @@ class IronicConductorCharm(charms_openstack.charm.OpenStackCharm): for interface in interfaces: if interface not in valid_interfaces: raise ValueError( - 'Network interface "%s" is not valid. Valid ' + 'Network interface %s is not valid. Valid ' 'interfaces are: %s' % ( interface, ", ".join(valid_interfaces))) @@ -197,14 +352,14 @@ class IronicConductorCharm(charms_openstack.charm.OpenStackCharm): for interface in interfaces: if interface not in valid_interfaces: raise ValueError( - 'Deploy interface "%s" is not valid. Valid ' + 'Deploy interface %s is not valid. Valid ' 'interfaces are: %s' % ( interface, ", ".join(valid_interfaces))) if reactive.is_flag_set("config.complete"): if "direct" in interfaces and has_secret is False: raise ValueError( - 'run "set-temp-url-secret" action on leader to ' - 'enable "direct" deploy method') + 'run set-temp-url-secret action on leader to ' + 'enable direct deploy method') def _validate_default_deploy_interface(self): iface = self.config["default-deploy-interface"] @@ -215,12 +370,29 @@ class IronicConductorCharm(charms_openstack.charm.OpenStackCharm): iface, ", ".join( self.enabled_deploy_interfaces))) + def _validate_enabled_hw_type(self): + hw_types = self._get_hw_type_map() + unsupported = [] + for hw_type in self.enabled_hw_types: + if hw_types.get(hw_type, None) is None: + unsupported.append(hw_type) + if len(unsupported) > 0: + raise ValueError( + 'hardware type(s) %s not supported at ' + 'this time' % ", ".join(unsupported)) + @property def enabled_network_interfaces(self): network_interfaces = self.config.get( 'enabled-network-interfaces', "").replace(" ", "") return network_interfaces.split(",") + @property + def enabled_hw_types(self): + hw_types = self.config.get( + 'enabled-hw-types', "ipmi").replace(" ", "") + return hw_types.split(",") + @property def enabled_deploy_interfaces(self): network_interfaces = self.config.get( @@ -231,25 +403,32 @@ class IronicConductorCharm(charms_openstack.charm.OpenStackCharm): try: self._validate_network_interfaces(self.enabled_network_interfaces) except Exception as err: - msg = ("invalid enabled-network-interfaces config: %s" % err) + msg = ("invalid enabled-network-interfaces config, %s" % err) return ('blocked', msg) try: self._validate_default_net_interface() except Exception as err: - msg = ("invalid default-network-interface config: %s" % err) + msg = ("invalid default-network-interface config, %s" % err) return ('blocked', msg) try: self._validate_deploy_interfaces( self.enabled_deploy_interfaces) except Exception as err: - msg = ("invalid enabled-deploy-interfaces config: %s" % err) + msg = ("invalid enabled-deploy-interfaces config, %s" % err) return ('blocked', msg) try: self._validate_default_deploy_interface() except Exception as err: - msg = ("invalid default-deploy-interface config: %s" % err) + msg = ("invalid default-deploy-interface config, %s" % err) return ('blocked', msg) + + try: + self._validate_enabled_hw_type() + except Exception as err: + msg = ("invalid enabled-hw-types config, %s" % err) + return ('blocked', msg) + return (None, None) diff --git a/src/templates/train/ironic.conf b/src/templates/train/ironic.conf index ef694cd..4c5a59f 100644 --- a/src/templates/train/ironic.conf +++ b/src/templates/train/ironic.conf @@ -5,20 +5,21 @@ auth_strategy=keystone my_ip = {{ options.internal_interface_ip }} enabled_deploy_interfaces = {{ options.enabled_deploy_interfaces }} -enabled_hardware_types = ipmi,ilo,idrac,redfish,irmc -{% if options.use_ipxe -%} -enabled_boot_interfaces = pxe,ipxe,ilo-pxe,ilo-ipxe,irmc-pxe -{% else -%} -enabled_boot_interfaces = pxe,ilo-pxe,irmc-pxe -{% endif -%} -enabled_management_interfaces = ipmitool,redfish,ilo,irmc,idrac,noop -enabled_inspect_interfaces = idrac,ilo,irmc,redfish,no-inspect +enabled_hardware_types = {{ options.hardware_type_cfg.enabled_hardware_types }} +enabled_boot_interfaces = {{ options.hardware_type_cfg.enabled_boot_interfaces }} + +enabled_management_interfaces = {{ options.hardware_type_cfg.enabled_management_interfaces }} +enabled_inspect_interfaces = {{ options.hardware_type_cfg.enabled_inspect_interfaces }} + enabled_network_interfaces = {{ options.enabled_network_interfaces }} -enabled_power_interfaces = ipmitool,redfish,ilo,irmc,idrac + +enabled_power_interfaces = {{ options.hardware_type_cfg.enabled_power_interfaces }} enabled_storage_interfaces = cinder,noop -enabled_console_interfaces = ipmitool-socat,ipmitool-shellinabox,no-console -enabled_raid_interfaces = agent,idrac,irmc,no-raid -enabled_vendor_interfaces = ipmitool,idrac,ilo,no-vendor + +enabled_console_interfaces = {{ options.hardware_type_cfg.enabled_console_interfaces }} +enabled_raid_interfaces = {{ options.hardware_type_cfg.enabled_raid_interfaces }} +enabled_vendor_interfaces = {{ options.hardware_type_cfg.enabled_vendor_interfaces }} +enabled_bios_interfaces = {{ options.hardware_type_cfg.enabled_bios_interfaces }} default_deploy_interface = {{ options.default_deploy_interface }} default_network_interface = {{ options.default_network_interface }} diff --git a/src/tests/00-setup b/src/tests/00-setup deleted file mode 100755 index f0616a5..0000000 --- a/src/tests/00-setup +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -sudo add-apt-repository ppa:juju/stable -y -sudo apt-get update -sudo apt-get install amulet python-requests -y diff --git a/src/tests/10-deploy b/src/tests/10-deploy deleted file mode 100755 index c7cc7b5..0000000 --- a/src/tests/10-deploy +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/python3 - -import amulet -import requests -import unittest - - -class TestCharm(unittest.TestCase): - def setUp(self): - self.d = amulet.Deployment() - - self.d.add('charm-ironic') - self.d.expose('charm-ironic') - - self.d.setup(timeout=900) - self.d.sentry.wait() - - self.unit = self.d.sentry['charm-ironic'][0] - - def test_service(self): - # test we can access over http - page = requests.get('http://{}'.format(self.unit.info['public-address'])) - self.assertEqual(page.status_code, 200) - # Now you can use self.d.sentry[SERVICE][UNIT] to address each of the units and perform - # more in-depth steps. Each self.d.sentry[SERVICE][UNIT] has the following methods: - # - .info - An array of the information of that unit from Juju - # - .file(PATH) - Get the details of a file on that unit - # - .file_contents(PATH) - Get plain text output of PATH file from that unit - # - .directory(PATH) - Get details of directory - # - .directory_contents(PATH) - List files and folders in PATH on that unit - # - .relation(relation, service:rel) - Get relation data from return service - - -if __name__ == '__main__': - unittest.main() diff --git a/src/tests/bundles/bionic-train.yaml b/src/tests/bundles/bionic-train.yaml new file mode 100644 index 0000000..912c8c5 --- /dev/null +++ b/src/tests/bundles/bionic-train.yaml @@ -0,0 +1,250 @@ +options: + source: &source cloud:bionic-train/proposed +series: bionic +local_overlay_enabled: false +relations: +- - nova-ironic + - ironic-api +- - ironic-conductor + - ironic-api +- - neutron-ironic-agent:identity-credentials + - keystone +- - neutron-ironic-agent + - neutron-api +- - neutron-openvswitch + - neutron-api +- - ironic-api:amqp + - rabbitmq-server:amqp +- - ironic-api + - keystone +- - ironic-api:shared-db + - mysql:shared-db +- - ironic-conductor:amqp + - rabbitmq-server:amqp +- - ironic-conductor + - keystone +- - ironic-conductor:shared-db + - mysql:shared-db +- - nova-ironic:amqp + - rabbitmq-server:amqp +- - nova-ironic + - glance +- - nova-ironic + - keystone +- - nova-ironic + - nova-cloud-controller +- - neutron-gateway:amqp + - rabbitmq-server:amqp +- - keystone:shared-db + - mysql:shared-db +- - nova-cloud-controller:identity-service + - keystone:identity-service +- - glance:identity-service + - keystone:identity-service +- - neutron-api:identity-service + - keystone:identity-service +- - neutron-api:shared-db + - mysql:shared-db +- - neutron-api:amqp + - rabbitmq-server:amqp +- - neutron-gateway:neutron-plugin-api + - neutron-api:neutron-plugin-api +- - glance:shared-db + - mysql:shared-db +- - glance:amqp + - rabbitmq-server:amqp +- - nova-cloud-controller:image-service + - glance:image-service +- - nova-cloud-controller:amqp + - rabbitmq-server:amqp +- - nova-cloud-controller:quantum-network-service + - neutron-gateway:quantum-network-service +- - nova-cloud-controller:shared-db + - mysql:shared-db +- - nova-cloud-controller:neutron-api + - neutron-api:neutron-api +- - cinder:image-service + - glance:image-service +- - cinder:amqp + - rabbitmq-server:amqp +- - cinder:identity-service + - keystone:identity-service +- - cinder:cinder-volume-service + - nova-cloud-controller:cinder-volume-service +- - cinder:shared-db + - mysql:shared-db +- - placement + - mysql +- - placement + - keystone +- - placement + - nova-cloud-controller +- - ceph-mon:client + - nova-ironic:ceph +- - ceph-mon:client + - glance:ceph +- - ceph-radosgw:mon + - ceph-mon:radosgw +- - ceph-radosgw:identity-service + - keystone:identity-service +- - ceph-osd:mon + - ceph-mon:osd +- - ceph-radosgw:object-store + - glance +- - vault:shared-db + - mysql:shared-db +- - vault:certificates + - ceph-radosgw +- - vault:certificates + - cinder +- - vault:certificates + - glance:certificates +- - vault:certificates + - keystone:certificates +- - vault:certificates + - neutron-api:certificates +- - vault:certificates + - nova-cloud-controller:certificates +- - vault:certificates + - placement:certificates +- - vault + - ironic-conductor +- - vault:certificates + - ironic-api:certificates +- - ironic-api + - hacluster-ironic +services: + cinder: + charm: cs:~openstack-charmers-next/cinder + num_units: 1 + constraints: mem=2G + options: + block-device: vdb + glance-api-version: 2 + openstack-origin: *source + worker-multiplier: 0.25 + storage: + block-devices: cinder,50G + ceph-radosgw: + charm: cs:~openstack-charmers-next/ceph-radosgw + num_units: 1 + constraints: mem=2G + options: + source: *source + namespace-tenants: True + ceph-mon: + charm: cs:ceph-mon + num_units: 3 + constraints: mem=2G + options: + expected-osd-count: 3 + source: *source + ceph-osd: + charm: cs:ceph-osd + num_units: 3 + constraints: mem=2G + options: + source: *source + storage: + osd-devices: 'cinder,30G' + glance: + charm: cs:~openstack-charmers-next/glance + num_units: 1 + constraints: mem=2G + options: + openstack-origin: *source + worker-multiplier: 0.25 + keystone: + charm: cs:~openstack-charmers-next/keystone + num_units: 1 + constraints: mem=2G + options: + openstack-origin: *source + worker-multiplier: 0.25 + mysql: + charm: cs:percona-cluster + num_units: 1 + constraints: mem=4G + options: + innodb-buffer-pool-size: 256M + max-connections: 1000 + performance-schema: true + neutron-api: + charm: cs:~openstack-charmers-next/neutron-api + num_units: 1 + constraints: mem=2G + options: + flat-network-providers: "physnet1" + neutron-security-groups: true + openstack-origin: *source + manage-neutron-plugin-legacy-mode: false + worker-multiplier: 0.25 + neutron-gateway: + charm: cs:~openstack-charmers-next/neutron-gateway + num_units: 1 + constraints: mem=2G + options: + openstack-origin: *source + enable-isolated-metadata: true + enable-metadata-network: true + bridge-mappings: physnet1:br-ex + nova-cloud-controller: + charm: cs:~openstack-charmers-next/nova-cloud-controller + num_units: 1 + constraints: mem=2G + options: + network-manager: Neutron + openstack-origin: *source + worker-multiplier: 0.25 + nova-ironic: + charm: cs:~openstack-charmers-next/nova-compute + num_units: 1 + constraints: mem=2G + options: + enable-live-migration: false + enable-resize: false + openstack-origin: *source + virt-type: ironic + placement: + charm: cs:placement + num_units: 1 + constraints: mem=2G + options: + openstack-origin: *source + worker-multiplier: 0.25 + rabbitmq-server: + charm: cs:rabbitmq-server + num_units: 1 + constraints: mem=2G + hacluster-ironic: + charm: cs:~openstack-charmers-next/hacluster + num_units: 0 + ironic-api: + charm: cs:~openstack-charmers-next/ironic-api + num_units: 3 + constraints: mem=2G + options: + openstack-origin: *source + ironic-conductor: + charm: ../../../ironic-conductor + num_units: 1 + constraints: mem=2G + options: + openstack-origin: *source + max-tftp-block-size: 1418 + disable-secure-erase: true + use-ipxe: true + enabled-network-interfaces: "flat, noop" + neutron-openvswitch: + charm: cs:~openstack-charmers-next/neutron-openvswitch + num_units: 0 + options: + bridge-mappings: physnet1:br-ex + neutron-ironic-agent: + charm: cs:~openstack-charmers-next/neutron-api-plugin-ironic + num_units: 0 + options: + openstack-origin: *source + vault: + charm: cs:~openstack-charmers-next/vault + num_units: 1 diff --git a/src/tests/bundles/bionic-ussuri.yaml b/src/tests/bundles/bionic-ussuri.yaml new file mode 100644 index 0000000..33e62cd --- /dev/null +++ b/src/tests/bundles/bionic-ussuri.yaml @@ -0,0 +1,250 @@ +options: + source: &source cloud:bionic-ussuri/proposed +series: bionic +local_overlay_enabled: false +relations: +- - nova-ironic + - ironic-api +- - ironic-conductor + - ironic-api +- - neutron-ironic-agent:identity-credentials + - keystone +- - neutron-ironic-agent + - neutron-api +- - neutron-openvswitch + - neutron-api +- - ironic-api:amqp + - rabbitmq-server:amqp +- - ironic-api + - keystone +- - ironic-api:shared-db + - mysql:shared-db +- - ironic-conductor:amqp + - rabbitmq-server:amqp +- - ironic-conductor + - keystone +- - ironic-conductor:shared-db + - mysql:shared-db +- - nova-ironic:amqp + - rabbitmq-server:amqp +- - nova-ironic + - glance +- - nova-ironic + - keystone +- - nova-ironic + - nova-cloud-controller +- - neutron-gateway:amqp + - rabbitmq-server:amqp +- - keystone:shared-db + - mysql:shared-db +- - nova-cloud-controller:identity-service + - keystone:identity-service +- - glance:identity-service + - keystone:identity-service +- - neutron-api:identity-service + - keystone:identity-service +- - neutron-api:shared-db + - mysql:shared-db +- - neutron-api:amqp + - rabbitmq-server:amqp +- - neutron-gateway:neutron-plugin-api + - neutron-api:neutron-plugin-api +- - glance:shared-db + - mysql:shared-db +- - glance:amqp + - rabbitmq-server:amqp +- - nova-cloud-controller:image-service + - glance:image-service +- - nova-cloud-controller:amqp + - rabbitmq-server:amqp +- - nova-cloud-controller:quantum-network-service + - neutron-gateway:quantum-network-service +- - nova-cloud-controller:shared-db + - mysql:shared-db +- - nova-cloud-controller:neutron-api + - neutron-api:neutron-api +- - cinder:image-service + - glance:image-service +- - cinder:amqp + - rabbitmq-server:amqp +- - cinder:identity-service + - keystone:identity-service +- - cinder:cinder-volume-service + - nova-cloud-controller:cinder-volume-service +- - cinder:shared-db + - mysql:shared-db +- - placement + - mysql +- - placement + - keystone +- - placement + - nova-cloud-controller +- - ceph-mon:client + - nova-ironic:ceph +- - ceph-mon:client + - glance:ceph +- - ceph-radosgw:mon + - ceph-mon:radosgw +- - ceph-radosgw:identity-service + - keystone:identity-service +- - ceph-osd:mon + - ceph-mon:osd +- - ceph-radosgw:object-store + - glance +- - vault:shared-db + - mysql:shared-db +- - vault:certificates + - ceph-radosgw +- - vault:certificates + - cinder +- - vault:certificates + - glance:certificates +- - vault:certificates + - keystone:certificates +- - vault:certificates + - neutron-api:certificates +- - vault:certificates + - nova-cloud-controller:certificates +- - vault:certificates + - placement:certificates +- - vault + - ironic-conductor +- - vault:certificates + - ironic-api:certificates +- - ironic-api + - hacluster-ironic +services: + cinder: + charm: cs:~openstack-charmers-next/cinder + num_units: 1 + constraints: mem=2G + options: + block-device: vdb + glance-api-version: 2 + openstack-origin: *source + worker-multiplier: 0.25 + storage: + block-devices: cinder,50G + ceph-radosgw: + charm: cs:~openstack-charmers-next/ceph-radosgw + num_units: 1 + constraints: mem=2G + options: + source: *source + namespace-tenants: True + ceph-mon: + charm: cs:ceph-mon + num_units: 3 + constraints: mem=2G + options: + expected-osd-count: 3 + source: *source + ceph-osd: + charm: cs:ceph-osd + num_units: 3 + constraints: mem=2G + options: + source: *source + storage: + osd-devices: 'cinder,30G' + glance: + charm: cs:~openstack-charmers-next/glance + num_units: 1 + constraints: mem=2G + options: + openstack-origin: *source + worker-multiplier: 0.25 + keystone: + charm: cs:~openstack-charmers-next/keystone + num_units: 1 + constraints: mem=2G + options: + openstack-origin: *source + worker-multiplier: 0.25 + mysql: + charm: cs:percona-cluster + num_units: 1 + constraints: mem=4G + options: + innodb-buffer-pool-size: 256M + max-connections: 1000 + performance-schema: true + hacluster-ironic: + charm: cs:~openstack-charmers-next/hacluster + num_units: 0 + neutron-api: + charm: cs:~openstack-charmers-next/neutron-api + num_units: 3 + constraints: mem=2G + options: + flat-network-providers: "physnet1" + neutron-security-groups: true + openstack-origin: *source + manage-neutron-plugin-legacy-mode: false + worker-multiplier: 0.25 + neutron-gateway: + charm: cs:~openstack-charmers-next/neutron-gateway + num_units: 1 + constraints: mem=2G + options: + openstack-origin: *source + enable-isolated-metadata: true + enable-metadata-network: true + bridge-mappings: physnet1:br-ex + nova-cloud-controller: + charm: cs:~openstack-charmers-next/nova-cloud-controller + num_units: 1 + constraints: mem=2G + options: + network-manager: Neutron + openstack-origin: *source + worker-multiplier: 0.25 + nova-ironic: + charm: cs:~openstack-charmers-next/nova-compute + num_units: 1 + constraints: mem=2G + options: + enable-live-migration: false + enable-resize: false + openstack-origin: *source + virt-type: ironic + placement: + charm: cs:placement + num_units: 1 + constraints: mem=2G + options: + openstack-origin: *source + worker-multiplier: 0.25 + rabbitmq-server: + charm: cs:rabbitmq-server + num_units: 1 + constraints: mem=2G + ironic-api: + charm: cs:~openstack-charmers-next/ironic-api + num_units: 3 + constraints: mem=2G + options: + openstack-origin: *source + ironic-conductor: + charm: ../../../ironic-conductor + num_units: 1 + constraints: mem=2G + options: + openstack-origin: *source + max-tftp-block-size: 1418 + disable-secure-erase: true + use-ipxe: true + enabled-network-interfaces: "flat, noop" + neutron-openvswitch: + charm: cs:~openstack-charmers-next/neutron-openvswitch + num_units: 0 + options: + bridge-mappings: physnet1:br-ex + neutron-ironic-agent: + charm: cs:~openstack-charmers-next/neutron-api-plugin-ironic + num_units: 0 + options: + openstack-origin: *source + vault: + charm: cs:~openstack-charmers-next/vault + num_units: 1 diff --git a/src/tests/bundles/focal-ussuri.yaml b/src/tests/bundles/focal-ussuri.yaml new file mode 100644 index 0000000..67b525b --- /dev/null +++ b/src/tests/bundles/focal-ussuri.yaml @@ -0,0 +1,284 @@ +options: + source: &source distro +series: focal +local_overlay_enabled: false +relations: +- - nova-ironic + - ironic-api +- - ironic-conductor + - ironic-api +- - neutron-ironic-agent:identity-credentials + - keystone +- - neutron-ironic-agent + - neutron-api +- - neutron-openvswitch + - neutron-api +- - ironic-api:amqp + - rabbitmq-server:amqp +- - ironic-api + - keystone +- - ironic-api:shared-db + - ironic-api-mysql-router:shared-db +- - ironic-conductor:amqp + - rabbitmq-server:amqp +- - ironic-conductor + - keystone +- - ironic-conductor:shared-db + - ironic-conductor-mysql-router:shared-db +- - nova-ironic:amqp + - rabbitmq-server:amqp +- - nova-ironic + - glance +- - nova-ironic + - keystone +- - nova-ironic + - nova-cloud-controller +- - neutron-gateway:amqp + - rabbitmq-server:amqp +- - keystone:shared-db + - keystone-mysql-router:shared-db +- - nova-cloud-controller:identity-service + - keystone:identity-service +- - glance:identity-service + - keystone:identity-service +- - neutron-api:identity-service + - keystone:identity-service +- - neutron-api:shared-db + - neutron-api-mysql-router:shared-db +- - neutron-api:amqp + - rabbitmq-server:amqp +- - neutron-gateway:neutron-plugin-api + - neutron-api:neutron-plugin-api +- - glance:shared-db + - glance-mysql-router:shared-db +- - glance:amqp + - rabbitmq-server:amqp +- - nova-cloud-controller:image-service + - glance:image-service +- - nova-cloud-controller:amqp + - rabbitmq-server:amqp +- - nova-cloud-controller:quantum-network-service + - neutron-gateway:quantum-network-service +- - nova-cloud-controller:shared-db + - nova-cloud-controller-mysql-router:shared-db +- - nova-cloud-controller:neutron-api + - neutron-api:neutron-api +- - cinder:image-service + - glance:image-service +- - cinder:amqp + - rabbitmq-server:amqp +- - cinder:identity-service + - keystone:identity-service +- - cinder:cinder-volume-service + - nova-cloud-controller:cinder-volume-service +- - cinder:shared-db + - cinder-mysql-router:shared-db +- - placement:shared-db + - placement-mysql-router:shared-db +- - placement + - keystone +- - placement + - nova-cloud-controller +- - ceph-mon:client + - nova-ironic:ceph +- - ceph-mon:client + - glance:ceph +- - ceph-radosgw:mon + - ceph-mon:radosgw +- - ceph-radosgw:identity-service + - keystone:identity-service +- - ceph-osd:mon + - ceph-mon:osd +- - ceph-radosgw:object-store + - glance +- - mysql-innodb-cluster:db-router + - nova-cloud-controller-mysql-router:db-router +- - mysql-innodb-cluster:db-router + - keystone-mysql-router:db-router +- - mysql-innodb-cluster:db-router + - glance-mysql-router:db-router +- - mysql-innodb-cluster:db-router + - neutron-api-mysql-router:db-router +- - mysql-innodb-cluster:db-router + - placement-mysql-router:db-router +- - mysql-innodb-cluster:db-router + - cinder-mysql-router:db-router +- - mysql-innodb-cluster:db-router + - ironic-api-mysql-router:db-router +- - mysql-innodb-cluster:db-router + - ironic-conductor-mysql-router:db-router +- - vault-mysql-router:db-router + - mysql-innodb-cluster:db-router +- - vault:shared-db + - vault-mysql-router:shared-db +- - vault:certificates + - ceph-radosgw +- - vault:certificates + - cinder +- - vault:certificates + - glance:certificates +- - vault:certificates + - keystone:certificates +- - vault:certificates + - neutron-api:certificates +- - vault:certificates + - nova-cloud-controller:certificates +- - vault:certificates + - placement:certificates +- - vault + - ironic-conductor +- - vault:certificates + - ironic-api:certificates +- - ironic-api + - hacluster-ironic +services: + nova-cloud-controller-mysql-router: + charm: cs:~openstack-charmers-next/mysql-router + keystone-mysql-router: + charm: cs:~openstack-charmers-next/mysql-router + glance-mysql-router: + charm: cs:~openstack-charmers-next/mysql-router + neutron-api-mysql-router: + charm: cs:~openstack-charmers-next/mysql-router + placement-mysql-router: + charm: cs:~openstack-charmers-next/mysql-router + vault-mysql-router: + charm: cs:~openstack-charmers-next/mysql-router + cinder-mysql-router: + charm: cs:~openstack-charmers-next/mysql-router + ironic-api-mysql-router: + charm: cs:~openstack-charmers-next/mysql-router + ironic-conductor-mysql-router: + charm: cs:~openstack-charmers-next/mysql-router + mysql-innodb-cluster: + charm: cs:~openstack-charmers-next/mysql-innodb-cluster + num_units: 3 + constraints: mem=4G + options: + source: *source + cinder: + charm: cs:~openstack-charmers-next/cinder + num_units: 1 + constraints: mem=2G + options: + block-device: vdb + glance-api-version: 2 + openstack-origin: *source + worker-multiplier: 0.25 + storage: + block-devices: cinder,50G + ceph-radosgw: + charm: cs:~openstack-charmers-next/ceph-radosgw + num_units: 1 + constraints: mem=2G + options: + source: *source + namespace-tenants: True + ceph-mon: + charm: cs:ceph-mon + num_units: 3 + constraints: mem=2G + options: + expected-osd-count: 3 + source: *source + ceph-osd: + charm: cs:ceph-osd + num_units: 3 + constraints: mem=2G + options: + source: *source + storage: + osd-devices: 'cinder,30G' + glance: + charm: cs:~openstack-charmers-next/glance + num_units: 1 + constraints: mem=2G + options: + openstack-origin: *source + worker-multiplier: 0.25 + keystone: + charm: cs:~openstack-charmers-next/keystone + num_units: 1 + constraints: mem=2G + options: + openstack-origin: *source + worker-multiplier: 0.25 + neutron-api: + charm: cs:~openstack-charmers-next/neutron-api + num_units: 1 + constraints: mem=2G + options: + flat-network-providers: "physnet1" + neutron-security-groups: true + openstack-origin: *source + manage-neutron-plugin-legacy-mode: false + worker-multiplier: 0.25 + neutron-gateway: + charm: cs:~openstack-charmers-next/neutron-gateway + num_units: 1 + constraints: mem=2G + options: + openstack-origin: *source + enable-isolated-metadata: true + enable-metadata-network: true + bridge-mappings: physnet1:br-ex + nova-cloud-controller: + charm: cs:~openstack-charmers-next/nova-cloud-controller + num_units: 1 + constraints: mem=2G + options: + network-manager: Neutron + openstack-origin: *source + worker-multiplier: 0.25 + nova-ironic: + charm: cs:~openstack-charmers-next/nova-compute + num_units: 1 + constraints: mem=2G + options: + enable-live-migration: false + enable-resize: false + openstack-origin: *source + virt-type: ironic + placement: + charm: cs:placement + num_units: 1 + constraints: mem=2G + options: + openstack-origin: *source + worker-multiplier: 0.25 + rabbitmq-server: + charm: cs:rabbitmq-server + num_units: 1 + constraints: mem=2G + hacluster-ironic: + charm: cs:~openstack-charmers-next/hacluster + num_units: 0 + ironic-api: + charm: cs:~openstack-charmers-next/ironic-api + num_units: 3 + constraints: mem=2G + options: + openstack-origin: *source + ironic-conductor: + charm: ../../../ironic-conductor + num_units: 1 + constraints: mem=2G + options: + openstack-origin: *source + max-tftp-block-size: 1418 + disable-secure-erase: true + use-ipxe: true + enabled-network-interfaces: "flat, noop" + neutron-openvswitch: + charm: cs:~openstack-charmers-next/neutron-openvswitch + num_units: 0 + options: + bridge-mappings: physnet1:br-ex + neutron-ironic-agent: + charm: cs:~openstack-charmers-next/neutron-api-plugin-ironic + num_units: 0 + options: + openstack-origin: *source + vault: + charm: cs:~openstack-charmers-next/vault + num_units: 1 diff --git a/src/tests/bundles/focal-victoria.yaml b/src/tests/bundles/focal-victoria.yaml new file mode 100644 index 0000000..2cce02d --- /dev/null +++ b/src/tests/bundles/focal-victoria.yaml @@ -0,0 +1,284 @@ +options: + source: &source cloud:focal-victoria/proposed +series: focal +local_overlay_enabled: false +relations: +- - nova-ironic + - ironic-api +- - ironic-conductor + - ironic-api +- - neutron-ironic-agent:identity-credentials + - keystone +- - neutron-ironic-agent + - neutron-api +- - neutron-openvswitch + - neutron-api +- - ironic-api:amqp + - rabbitmq-server:amqp +- - ironic-api + - keystone +- - ironic-api:shared-db + - ironic-api-mysql-router:shared-db +- - ironic-conductor:amqp + - rabbitmq-server:amqp +- - ironic-conductor + - keystone +- - ironic-conductor:shared-db + - ironic-conductor-mysql-router:shared-db +- - nova-ironic:amqp + - rabbitmq-server:amqp +- - nova-ironic + - glance +- - nova-ironic + - keystone +- - nova-ironic + - nova-cloud-controller +- - neutron-gateway:amqp + - rabbitmq-server:amqp +- - keystone:shared-db + - keystone-mysql-router:shared-db +- - nova-cloud-controller:identity-service + - keystone:identity-service +- - glance:identity-service + - keystone:identity-service +- - neutron-api:identity-service + - keystone:identity-service +- - neutron-api:shared-db + - neutron-api-mysql-router:shared-db +- - neutron-api:amqp + - rabbitmq-server:amqp +- - neutron-gateway:neutron-plugin-api + - neutron-api:neutron-plugin-api +- - glance:shared-db + - glance-mysql-router:shared-db +- - glance:amqp + - rabbitmq-server:amqp +- - nova-cloud-controller:image-service + - glance:image-service +- - nova-cloud-controller:amqp + - rabbitmq-server:amqp +- - nova-cloud-controller:quantum-network-service + - neutron-gateway:quantum-network-service +- - nova-cloud-controller:shared-db + - nova-cloud-controller-mysql-router:shared-db +- - nova-cloud-controller:neutron-api + - neutron-api:neutron-api +- - cinder:image-service + - glance:image-service +- - cinder:amqp + - rabbitmq-server:amqp +- - cinder:identity-service + - keystone:identity-service +- - cinder:cinder-volume-service + - nova-cloud-controller:cinder-volume-service +- - cinder:shared-db + - cinder-mysql-router:shared-db +- - placement:shared-db + - placement-mysql-router:shared-db +- - placement + - keystone +- - placement + - nova-cloud-controller +- - ceph-mon:client + - nova-ironic:ceph +- - ceph-mon:client + - glance:ceph +- - ceph-radosgw:mon + - ceph-mon:radosgw +- - ceph-radosgw:identity-service + - keystone:identity-service +- - ceph-osd:mon + - ceph-mon:osd +- - ceph-radosgw:object-store + - glance +- - mysql-innodb-cluster:db-router + - nova-cloud-controller-mysql-router:db-router +- - mysql-innodb-cluster:db-router + - keystone-mysql-router:db-router +- - mysql-innodb-cluster:db-router + - glance-mysql-router:db-router +- - mysql-innodb-cluster:db-router + - neutron-api-mysql-router:db-router +- - mysql-innodb-cluster:db-router + - placement-mysql-router:db-router +- - mysql-innodb-cluster:db-router + - cinder-mysql-router:db-router +- - mysql-innodb-cluster:db-router + - ironic-api-mysql-router:db-router +- - mysql-innodb-cluster:db-router + - ironic-conductor-mysql-router:db-router +- - vault-mysql-router:db-router + - mysql-innodb-cluster:db-router +- - vault:shared-db + - vault-mysql-router:shared-db +- - vault:certificates + - ceph-radosgw +- - vault:certificates + - cinder +- - vault:certificates + - glance:certificates +- - vault:certificates + - keystone:certificates +- - vault:certificates + - neutron-api:certificates +- - vault:certificates + - nova-cloud-controller:certificates +- - vault:certificates + - placement:certificates +- - vault + - ironic-conductor +- - vault:certificates + - ironic-api:certificates +- - ironic-api + - hacluster-ironic +services: + nova-cloud-controller-mysql-router: + charm: cs:~openstack-charmers-next/mysql-router + keystone-mysql-router: + charm: cs:~openstack-charmers-next/mysql-router + glance-mysql-router: + charm: cs:~openstack-charmers-next/mysql-router + neutron-api-mysql-router: + charm: cs:~openstack-charmers-next/mysql-router + placement-mysql-router: + charm: cs:~openstack-charmers-next/mysql-router + vault-mysql-router: + charm: cs:~openstack-charmers-next/mysql-router + cinder-mysql-router: + charm: cs:~openstack-charmers-next/mysql-router + ironic-api-mysql-router: + charm: cs:~openstack-charmers-next/mysql-router + ironic-conductor-mysql-router: + charm: cs:~openstack-charmers-next/mysql-router + mysql-innodb-cluster: + charm: cs:~openstack-charmers-next/mysql-innodb-cluster + num_units: 3 + constraints: mem=4G + options: + source: *source + cinder: + charm: cs:~openstack-charmers-next/cinder + num_units: 1 + constraints: mem=2G + options: + block-device: vdb + glance-api-version: 2 + openstack-origin: *source + worker-multiplier: 0.25 + storage: + block-devices: cinder,50G + ceph-radosgw: + charm: cs:~openstack-charmers-next/ceph-radosgw + num_units: 1 + constraints: mem=2G + options: + source: *source + namespace-tenants: True + ceph-mon: + charm: cs:ceph-mon + num_units: 3 + constraints: mem=2G + options: + expected-osd-count: 3 + source: *source + ceph-osd: + charm: cs:ceph-osd + num_units: 3 + constraints: mem=2G + options: + source: *source + storage: + osd-devices: 'cinder,30G' + glance: + charm: cs:~openstack-charmers-next/glance + num_units: 1 + constraints: mem=2G + options: + openstack-origin: *source + worker-multiplier: 0.25 + keystone: + charm: cs:~openstack-charmers-next/keystone + num_units: 1 + constraints: mem=2G + options: + openstack-origin: *source + worker-multiplier: 0.25 + neutron-api: + charm: cs:~openstack-charmers-next/neutron-api + num_units: 1 + constraints: mem=2G + options: + flat-network-providers: "physnet1" + neutron-security-groups: true + openstack-origin: *source + manage-neutron-plugin-legacy-mode: false + worker-multiplier: 0.25 + neutron-gateway: + charm: cs:~openstack-charmers-next/neutron-gateway + num_units: 1 + constraints: mem=2G + options: + openstack-origin: *source + enable-isolated-metadata: true + enable-metadata-network: true + bridge-mappings: physnet1:br-ex + nova-cloud-controller: + charm: cs:~openstack-charmers-next/nova-cloud-controller + num_units: 1 + constraints: mem=2G + options: + network-manager: Neutron + openstack-origin: *source + worker-multiplier: 0.25 + nova-ironic: + charm: cs:~openstack-charmers-next/nova-compute + num_units: 1 + constraints: mem=2G + options: + enable-live-migration: false + enable-resize: false + openstack-origin: *source + virt-type: ironic + placement: + charm: cs:placement + num_units: 1 + constraints: mem=2G + options: + openstack-origin: *source + worker-multiplier: 0.25 + rabbitmq-server: + charm: cs:rabbitmq-server + num_units: 1 + constraints: mem=2G + hacluster-ironic: + charm: cs:~openstack-charmers-next/hacluster + num_units: 0 + ironic-api: + charm: cs:~openstack-charmers-next/ironic-api + num_units: 3 + constraints: mem=2G + options: + openstack-origin: *source + ironic-conductor: + charm: ../../../ironic-conductor + num_units: 1 + constraints: mem=2G + options: + openstack-origin: *source + max-tftp-block-size: 1418 + disable-secure-erase: true + use-ipxe: true + enabled-network-interfaces: "flat, noop" + neutron-openvswitch: + charm: cs:~openstack-charmers-next/neutron-openvswitch + num_units: 0 + options: + bridge-mappings: physnet1:br-ex + neutron-ironic-agent: + charm: cs:~openstack-charmers-next/neutron-api-plugin-ironic + num_units: 0 + options: + openstack-origin: *source + vault: + charm: cs:~openstack-charmers-next/vault + num_units: 1 diff --git a/src/tests/bundles/overlays/bionic-train.yaml.j2 b/src/tests/bundles/overlays/bionic-train.yaml.j2 new file mode 120000 index 0000000..f07f22d --- /dev/null +++ b/src/tests/bundles/overlays/bionic-train.yaml.j2 @@ -0,0 +1 @@ +ironic.j2 \ No newline at end of file diff --git a/src/tests/bundles/overlays/bionic-ussuri.yaml.j2 b/src/tests/bundles/overlays/bionic-ussuri.yaml.j2 new file mode 120000 index 0000000..f07f22d --- /dev/null +++ b/src/tests/bundles/overlays/bionic-ussuri.yaml.j2 @@ -0,0 +1 @@ +ironic.j2 \ No newline at end of file diff --git a/src/tests/bundles/overlays/focal-ussuri.yaml.j2 b/src/tests/bundles/overlays/focal-ussuri.yaml.j2 new file mode 120000 index 0000000..f07f22d --- /dev/null +++ b/src/tests/bundles/overlays/focal-ussuri.yaml.j2 @@ -0,0 +1 @@ +ironic.j2 \ No newline at end of file diff --git a/src/tests/bundles/overlays/focal-victoria.yaml.j2 b/src/tests/bundles/overlays/focal-victoria.yaml.j2 new file mode 120000 index 0000000..f07f22d --- /dev/null +++ b/src/tests/bundles/overlays/focal-victoria.yaml.j2 @@ -0,0 +1 @@ +ironic.j2 \ No newline at end of file diff --git a/src/tests/bundles/overlays/ironic.j2 b/src/tests/bundles/overlays/ironic.j2 new file mode 100644 index 0000000..6e5f5c6 --- /dev/null +++ b/src/tests/bundles/overlays/ironic.j2 @@ -0,0 +1,4 @@ +applications: + ironic-api: + options: + vip: '{{ OS_VIP00 }}' diff --git a/src/tests/tests.yaml b/src/tests/tests.yaml new file mode 100644 index 0000000..25a863f --- /dev/null +++ b/src/tests/tests.yaml @@ -0,0 +1,29 @@ +charm_name: ironic-api +gate_bundles: +- focal-ussuri +- bionic-train +- bionic-ussuri +smoke_bundles: +- bionic-ussuri +dev_bundles: +- focal-victoria +target_deploy_status: + vault: + workload-status: blocked + workload-status-message: Vault needs to be initialized + ironic-conductor: + workload-status: blocked + workload-status-message: invalid enabled-deploy-interfaces config +configure: +- zaza.openstack.charm_tests.vault.setup.auto_initialize +- zaza.openstack.charm_tests.ironic.setup.set_temp_url_secret +- zaza.openstack.charm_tests.ironic.setup.add_ironic_deployment_image +- zaza.openstack.charm_tests.ironic.setup.add_ironic_os_image +- zaza.openstack.charm_tests.ironic.setup.create_bm_flavors +# Ironic will require a flat network to test the flat network type. Once a proper +# testing environment will be available for Ironic, we will need to add the setup +# call to create that flat network +#- zaza.openstack.charm_tests.neutron.setup.basic_overcloud_network +- zaza.openstack.charm_tests.nova.setup.manage_ssh_key +tests: +- zaza.openstack.charm_tests.ironic.tests.IronicTest diff --git a/src/wheelhouse.txt b/src/wheelhouse.txt index a0cbdfb..0709844 100644 --- a/src/wheelhouse.txt +++ b/src/wheelhouse.txt @@ -7,3 +7,4 @@ python-glanceclient python-swiftclient python-keystoneclient zipp < 2.0.0 +importlib-metadata>=2.0 diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py index 3af8c51..4057fa1 100644 --- a/unit_tests/__init__.py +++ b/unit_tests/__init__.py @@ -45,3 +45,15 @@ sys.modules['charms.reactive.bus'] = charms.reactive.bus sys.modules['charms.reactive.bus'] = charms.reactive.decorators sys.modules['charms.reactive.flags'] = charms.reactive.flags sys.modules['charms.reactive.relations'] = charms.reactive.relations + +keystoneauth1 = mock.MagicMock() +sys.modules['keystoneauth1'] = keystoneauth1 + +glanceclient = mock.MagicMock() +sys.modules['glanceclient'] = glanceclient + +swiftclient = mock.MagicMock() +sys.modules['swiftclient'] = swiftclient + +keystoneclient = mock.MagicMock() +sys.modules['keystoneclient'] = keystoneclient diff --git a/unit_tests/test_api_utils.py b/unit_tests/test_api_utils.py index 5404f6f..9715106 100644 --- a/unit_tests/test_api_utils.py +++ b/unit_tests/test_api_utils.py @@ -20,6 +20,10 @@ from keystoneauth1 import exceptions as ks_exc import charm.openstack.ironic.api_utils as api_utils +class _NotFoundException(Exception): + pass + + class TestGetKeystoneSession(test_utils.PatchHelper): def setUp(self): @@ -54,7 +58,8 @@ class TestGetKeystoneSession(test_utils.PatchHelper): self.loading.get_plugin_loader.assert_called_with("v3password") loader.load_from_options.assert_called_with(**self.ks_expect) - self.ks_session.Session.assert_called_with(auth=auth, verify=True) + self.ks_session.Session.assert_called_with( + auth=auth, verify=api_utils.SYSTEM_CA_BUNDLE) def test_create_keystone_session_v2(self): self.patch_object(api_utils, 'loading') @@ -70,7 +75,8 @@ class TestGetKeystoneSession(test_utils.PatchHelper): self.loading.get_plugin_loader.assert_called_with("password") loader.load_from_options.assert_called_with(**self.ks_expect) - self.ks_session.Session.assert_called_with(auth=auth, verify=True) + self.ks_session.Session.assert_called_with( + auth=auth, verify=api_utils.SYSTEM_CA_BUNDLE) class TestOSClients(test_utils.PatchHelper): @@ -108,7 +114,8 @@ class TestOSClients(test_utils.PatchHelper): self.target = api_utils.OSClients(self.session) self.glance_client.assert_called_with(session=self.session, version=2) - self.swift_con.assert_called_with(session=self.session, cacert=None) + self.swift_con.assert_called_with( + session=self.session, cacert=api_utils.SYSTEM_CA_BUNDLE) self.ks_client.assert_called_with(session=self.session) def test_stores_info(self): @@ -173,6 +180,7 @@ class TestOSClients(test_utils.PatchHelper): self.assertTrue(result) def test_does_not_have_service_type(self): + ks_exc.http.NotFound = _NotFoundException self.ks_client.services.find.side_effect = ks_exc.http.NotFound() self.target._ks = self.ks_client diff --git a/unit_tests/test_controller_utils.py b/unit_tests/test_controller_utils.py index 25c947a..4c5b937 100644 --- a/unit_tests/test_controller_utils.py +++ b/unit_tests/test_controller_utils.py @@ -24,16 +24,6 @@ import charm.openstack.ironic.controller_utils as controller_utils class TestGetPXEBootClass(test_utils.PatchHelper): - def test_get_pxe_config_class_bionic(self): - self.patch_object( - ch_host, 'get_distrib_codename') - self.get_distrib_codename.return_value = "bionic" - charm_config = {} - pxe_class = controller_utils.get_pxe_config_class(charm_config) - self.assertTrue( - isinstance( - pxe_class, controller_utils.PXEBootBionic)) - def test_get_pxe_config_class(self): self.patch_object( ch_host, 'get_distrib_codename') diff --git a/unit_tests/test_lib_charm_openstack_ironic.py b/unit_tests/test_lib_charm_openstack_ironic.py index 7d65d01..87da886 100644 --- a/unit_tests/test_lib_charm_openstack_ironic.py +++ b/unit_tests/test_lib_charm_openstack_ironic.py @@ -19,6 +19,8 @@ import charms.leadership as leadership import charmhelpers.core.hookenv as hookenv import charms.reactive as reactive +from charmhelpers.contrib.openstack.utils import os_release + from charm.openstack.ironic import ironic from charm.openstack.ironic import controller_utils as ctrl_util @@ -70,6 +72,118 @@ class TestIronicCharm(test_utils.PatchHelper): self.get_pxe_config_class.return_value = self.mocked_pxe_cfg + def test_setup_power_adapter_config_train(self): + os_release.return_value = "train" + cfg_data = { + "enabled-hw-types": "ipmi, redfish, idrac", + } + + hookenv.config.return_value = cfg_data + + target = ironic.IronicConductorCharm() + target._setup_power_adapter_config() + expected = { + "enabled_hardware_types": "idrac, intel-ipmi, ipmi, redfish", + "enabled_management_interfaces": ("idrac-redfish, intel-ipmitool, " + "ipmitool, redfish, noop"), + "enabled_inspect_interfaces": "idrac-redfish, redfish, no-inspect", + "enabled_power_interfaces": "idrac-redfish, ipmitool, redfish", + "enabled_console_interfaces": ("ipmitool-shellinabox, " + "ipmitool-socat, no-console"), + "enabled_raid_interfaces": "idrac-wsman, no-raid", + "enabled_vendor_interfaces": "idrac-wsman, ipmitool, no-vendor", + "enabled_boot_interfaces": "pxe", + "enabled_bios_interfaces": "no-bios" + } + self.assertEqual( + target.config["hardware_type_cfg"], + expected) + + def test_setup_power_adapter_config_train_ipxe(self): + os_release.return_value = "train" + cfg_data = { + "enabled-hw-types": "ipmi, redfish, idrac", + "use-ipxe": True, + } + + hookenv.config.return_value = cfg_data + + target = ironic.IronicConductorCharm() + target._setup_power_adapter_config() + expected = { + "enabled_hardware_types": "idrac, intel-ipmi, ipmi, redfish", + "enabled_management_interfaces": ("idrac-redfish, intel-ipmitool, " + "ipmitool, redfish, noop"), + "enabled_inspect_interfaces": "idrac-redfish, redfish, no-inspect", + "enabled_power_interfaces": "idrac-redfish, ipmitool, redfish", + "enabled_console_interfaces": ("ipmitool-shellinabox, " + "ipmitool-socat, no-console"), + "enabled_raid_interfaces": "idrac-wsman, no-raid", + "enabled_vendor_interfaces": "idrac-wsman, ipmitool, no-vendor", + "enabled_boot_interfaces": "pxe, ipxe", + "enabled_bios_interfaces": "no-bios" + } + + self.assertEqual( + target.config["hardware_type_cfg"], + expected) + + def test_setup_power_adapter_config_unknown(self): + # test that it defaults to latest, in this case ussuri + os_release.return_value = "unknown" + cfg_data = { + "enabled-hw-types": "ipmi, redfish, idrac", + } + + hookenv.config.return_value = cfg_data + + target = ironic.IronicConductorCharm() + target._setup_power_adapter_config() + expected = { + "enabled_hardware_types": "idrac, intel-ipmi, ipmi, redfish", + "enabled_management_interfaces": ("idrac-redfish, intel-ipmitool, " + "ipmitool, redfish, noop"), + "enabled_inspect_interfaces": "idrac-redfish, redfish, no-inspect", + "enabled_power_interfaces": "idrac-redfish, ipmitool, redfish", + "enabled_console_interfaces": ("ipmitool-shellinabox, " + "ipmitool-socat, no-console"), + "enabled_raid_interfaces": "idrac-wsman, no-raid", + "enabled_vendor_interfaces": "idrac-wsman, ipmitool, no-vendor", + "enabled_boot_interfaces": "pxe, redfish-virtual-media", + "enabled_bios_interfaces": "idrac-wsman, no-bios" + } + self.assertEqual( + target.config["hardware_type_cfg"], + expected) + + def test_setup_power_adapter_config_ussuri(self): + os_release.return_value = "ussuri" + cfg_data = { + "enabled-hw-types": "ipmi, redfish, idrac", + } + + hookenv.config.return_value = cfg_data + + target = ironic.IronicConductorCharm() + target._setup_power_adapter_config() + self.maxDiff = None + expected = { + "enabled_hardware_types": "idrac, intel-ipmi, ipmi, redfish", + "enabled_management_interfaces": ("idrac-redfish, intel-ipmitool, " + "ipmitool, redfish, noop"), + "enabled_inspect_interfaces": "idrac-redfish, redfish, no-inspect", + "enabled_power_interfaces": "idrac-redfish, ipmitool, redfish", + "enabled_console_interfaces": ("ipmitool-shellinabox, " + "ipmitool-socat, no-console"), + "enabled_raid_interfaces": "idrac-wsman, no-raid", + "enabled_vendor_interfaces": "idrac-wsman, ipmitool, no-vendor", + "enabled_boot_interfaces": "pxe, redfish-virtual-media", + "enabled_bios_interfaces": "idrac-wsman, no-bios" + } + self.assertEqual( + target.config["hardware_type_cfg"], + expected) + def test_get_amqp_credentials(self): cfg_data = { "rabbit-user": "ironic", @@ -159,11 +273,24 @@ class TestIronicCharm(test_utils.PatchHelper): target = ironic.IronicConductorCharm() target._setup_pxe_config(self.mocked_pxe_cfg) expected_pkgs = ironic.PACKAGES + ["fakepkg1", "fakepkg2"] + expected_cfg = { 'tftpboot': ctrl_util.PXEBootBase.TFTP_ROOT, 'httpboot': ctrl_util.PXEBootBase.HTTP_ROOT, 'ironic_user': ctrl_util.PXEBootBase.IRONIC_USER, 'ironic_group': ctrl_util.PXEBootBase.IRONIC_GROUP, + 'hardware_type_cfg': { + 'enabled_hardware_types': 'intel-ipmi, ipmi', + 'enabled_management_interfaces': ('intel-ipmitool, ipmitool,' + ' noop'), + 'enabled_inspect_interfaces': 'no-inspect', + 'enabled_power_interfaces': 'ipmitool', + 'enabled_console_interfaces': ('ipmitool-shellinabox, ' + 'ipmitool-socat, no-console'), + 'enabled_raid_interfaces': 'no-raid', + 'enabled_vendor_interfaces': 'ipmitool, no-vendor', + 'enabled_boot_interfaces': 'pxe', + 'enabled_bios_interfaces': 'no-bios'}, 'default-network-interface': 'fake_net', 'default-deploy-interface': 'fake_deploy'} @@ -188,7 +315,7 @@ class TestIronicCharm(test_utils.PatchHelper): target._validate_deploy_interfaces(["bogus"]) expected_msg = ( - 'Deploy interface "bogus" is not valid.' + 'Deploy interface bogus is not valid.' ' Valid interfaces are: direct, iscsi') self.assertIsNone( @@ -204,8 +331,8 @@ class TestIronicCharm(test_utils.PatchHelper): with self.assertRaises(ValueError) as err: target._validate_deploy_interfaces(["direct"]) expected_msg = ( - 'run "set-temp-url-secret" action on ' - 'leader to enable "direct" deploy method') + 'run set-temp-url-secret action on ' + 'leader to enable direct deploy method') self.assertEqual(str(err.exception), expected_msg) def test_validate_default_net_interface(self): @@ -266,8 +393,8 @@ class TestIronicCharm(test_utils.PatchHelper): target = ironic.IronicConductorCharm() expected_status = ( 'blocked', - 'invalid enabled-network-interfaces config: Network interface ' - '"bogus" is not valid. Valid interfaces are: neutron, flat, noop' + 'invalid enabled-network-interfaces config, Network interface ' + 'bogus is not valid. Valid interfaces are: neutron, flat, noop' ) self.assertEqual(target.custom_assess_status_check(), expected_status) @@ -280,8 +407,8 @@ class TestIronicCharm(test_utils.PatchHelper): target = ironic.IronicConductorCharm() expected_status = ( 'blocked', - 'invalid enabled-deploy-interfaces config: Deploy interface ' - '"bogus" is not valid. Valid interfaces are: direct, iscsi' + 'invalid enabled-deploy-interfaces config, Deploy interface ' + 'bogus is not valid. Valid interfaces are: direct, iscsi' ) self.assertEqual(target.custom_assess_status_check(), expected_status) @@ -294,7 +421,7 @@ class TestIronicCharm(test_utils.PatchHelper): target = ironic.IronicConductorCharm() expected_status = ( 'blocked', - 'invalid default-network-interface config: ' + 'invalid default-network-interface config, ' 'default-network-interface (bogus) is not enabled ' 'in enabled-network-interfaces: neutron, flat, noop' ) @@ -309,7 +436,7 @@ class TestIronicCharm(test_utils.PatchHelper): target = ironic.IronicConductorCharm() expected_status = ( 'blocked', - 'invalid default-deploy-interface config: default-deploy-interface' + 'invalid default-deploy-interface config, default-deploy-interface' ' (bogus) is not enabled in enabled-deploy-interfaces: direct, ' 'iscsi') self.assertEqual(target.custom_assess_status_check(), expected_status)