diff --git a/hooks/charmhelpers/contrib/openstack/amulet/deployment.py b/hooks/charmhelpers/contrib/openstack/amulet/deployment.py index 8e57467b..e6d5de63 100644 --- a/hooks/charmhelpers/contrib/openstack/amulet/deployment.py +++ b/hooks/charmhelpers/contrib/openstack/amulet/deployment.py @@ -294,8 +294,10 @@ class OpenStackAmuletDeployment(AmuletDeployment): ('bionic', None): self.bionic_queens, ('bionic', 'cloud:bionic-rocky'): self.bionic_rocky, ('bionic', 'cloud:bionic-stein'): self.bionic_stein, + ('bionic', 'cloud:bionic-train'): self.bionic_train, ('cosmic', None): self.cosmic_rocky, ('disco', None): self.disco_stein, + ('eoan', None): self.eoan_train, } return releases[(self.series, self.openstack)] @@ -313,6 +315,7 @@ class OpenStackAmuletDeployment(AmuletDeployment): ('bionic', 'queens'), ('cosmic', 'rocky'), ('disco', 'stein'), + ('eoan', 'train'), ]) if self.openstack: os_origin = self.openstack.split(':')[1] diff --git a/hooks/charmhelpers/contrib/openstack/amulet/utils.py b/hooks/charmhelpers/contrib/openstack/amulet/utils.py index 53fa6506..0a5f81bd 100644 --- a/hooks/charmhelpers/contrib/openstack/amulet/utils.py +++ b/hooks/charmhelpers/contrib/openstack/amulet/utils.py @@ -54,11 +54,15 @@ NOVA_CLIENT_VERSION = "2" OPENSTACK_RELEASES_PAIRS = [ 'trusty_icehouse', 'trusty_kilo', 'trusty_liberty', - 'trusty_mitaka', 'xenial_mitaka', 'xenial_newton', - 'yakkety_newton', 'xenial_ocata', 'zesty_ocata', - 'xenial_pike', 'artful_pike', 'xenial_queens', - 'bionic_queens', 'bionic_rocky', 'cosmic_rocky', - 'bionic_stein', 'disco_stein'] + 'trusty_mitaka', 'xenial_mitaka', + 'xenial_newton', 'yakkety_newton', + 'xenial_ocata', 'zesty_ocata', + 'xenial_pike', 'artful_pike', + 'xenial_queens', 'bionic_queens', + 'bionic_rocky', 'cosmic_rocky', + 'bionic_stein', 'disco_stein', + 'bionic_train', 'eoan_train', +] class OpenStackAmuletUtils(AmuletUtils): diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index a37f61af..6ccaf14a 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -521,6 +521,86 @@ class IdentityCredentialsContext(IdentityServiceContext): return {} +class NovaVendorMetadataContext(OSContextGenerator): + """Context used for configuring nova vendor metadata on nova.conf file.""" + + def __init__(self, os_release_pkg, interfaces=None): + """Initialize the NovaVendorMetadataContext object. + + :param os_release_pkg: the package name to extract the OpenStack + release codename from. + :type os_release_pkg: str + :param interfaces: list of string values to be used as the Context's + relation interfaces. + :type interfaces: List[str] + """ + self.os_release_pkg = os_release_pkg + if interfaces is not None: + self.interfaces = interfaces + + def __call__(self): + cmp_os_release = CompareOpenStackReleases( + os_release(self.os_release_pkg)) + ctxt = {'vendor_data': False} + + vdata_providers = [] + vdata = config('vendor-data') + vdata_url = config('vendor-data-url') + + if vdata: + try: + # validate the JSON. If invalid, we do not set anything here + json.loads(vdata) + except (TypeError, ValueError) as e: + log('Error decoding vendor-data. {}'.format(e), level=ERROR) + else: + ctxt['vendor_data'] = True + # Mitaka does not support DynamicJSON + # so vendordata_providers is not needed + if cmp_os_release > 'mitaka': + vdata_providers.append('StaticJSON') + + if vdata_url: + if cmp_os_release > 'mitaka': + ctxt['vendor_data_url'] = vdata_url + vdata_providers.append('DynamicJSON') + else: + log('Dynamic vendor data unsupported' + ' for {}.'.format(cmp_os_release), level=ERROR) + if vdata_providers: + ctxt['vendordata_providers'] = ','.join(vdata_providers) + + return ctxt + + +class NovaVendorMetadataJSONContext(OSContextGenerator): + """Context used for writing nova vendor metadata json file.""" + + def __init__(self, os_release_pkg): + """Initialize the NovaVendorMetadataJSONContext object. + + :param os_release_pkg: the package name to extract the OpenStack + release codename from. + :type os_release_pkg: str + """ + self.os_release_pkg = os_release_pkg + + def __call__(self): + ctxt = {'vendor_data_json': '{}'} + + vdata = config('vendor-data') + if vdata: + try: + # validate the JSON. If invalid, we return empty. + json.loads(vdata) + except (TypeError, ValueError) as e: + log('Error decoding vendor-data. {}'.format(e), level=ERROR) + else: + ctxt['vendor_data_json'] = vdata + + return ctxt + + class AMQPContext(OSContextGenerator): def __init__(self, ssl_dir=None, rel_name='amqp', relation_prefix=None, @@ -702,6 +782,25 @@ class CephContext(OSContextGenerator): ensure_packages(['ceph-common']) return ctxt + def context_complete(self, ctxt): + """Overridden here to ensure the context is actually complete. + + We set `key` and `auth` to None here, by default, to ensure + that the context will always evaluate to incomplete until the + Ceph relation has actually sent these details; otherwise, + there is a potential race condition between the relation + appearing and the first unit actually setting this data on the + relation. + + :param ctxt: The current context members + :type ctxt: Dict[str, ANY] + :returns: True if the context is complete + :rtype: bool + """ + if 'auth' not in ctxt or 'key' not in ctxt: + return False + return super(CephContext, self).context_complete(ctxt) + class HAProxyContext(OSContextGenerator): """Provides half a context for the haproxy template, which describes diff --git a/hooks/charmhelpers/contrib/openstack/neutron.py b/hooks/charmhelpers/contrib/openstack/neutron.py index 0f847f56..fb5607f3 100644 --- a/hooks/charmhelpers/contrib/openstack/neutron.py +++ b/hooks/charmhelpers/contrib/openstack/neutron.py @@ -217,6 +217,11 @@ def neutron_plugins(): plugins['nsx']['config'] = '/etc/neutron/nsx.ini' plugins['vsp']['driver'] = ( 'nuage_neutron.plugins.nuage.plugin.NuagePlugin') + if CompareOpenStackReleases(release) >= 'newton': + plugins['vsp']['config'] = '/etc/neutron/plugins/ml2/ml2_conf.ini' + plugins['vsp']['driver'] = 'neutron.plugins.ml2.plugin.Ml2Plugin' + plugins['vsp']['server_packages'] = ['neutron-server', + 'neutron-plugin-ml2'] return plugins diff --git a/hooks/charmhelpers/contrib/openstack/templates/vendor_data.json b/hooks/charmhelpers/contrib/openstack/templates/vendor_data.json new file mode 100644 index 00000000..904f612a --- /dev/null +++ b/hooks/charmhelpers/contrib/openstack/templates/vendor_data.json @@ -0,0 +1 @@ +{{ vendor_data_json }} \ No newline at end of file diff --git a/hooks/charmhelpers/contrib/openstack/utils.py b/hooks/charmhelpers/contrib/openstack/utils.py index 1914ab84..d43a4d20 100644 --- a/hooks/charmhelpers/contrib/openstack/utils.py +++ b/hooks/charmhelpers/contrib/openstack/utils.py @@ -120,6 +120,7 @@ OPENSTACK_RELEASES = ( 'queens', 'rocky', 'stein', + 'train', ) UBUNTU_OPENSTACK_RELEASE = OrderedDict([ @@ -139,6 +140,7 @@ UBUNTU_OPENSTACK_RELEASE = OrderedDict([ ('bionic', 'queens'), ('cosmic', 'rocky'), ('disco', 'stein'), + ('eoan', 'train'), ]) @@ -159,6 +161,7 @@ OPENSTACK_CODENAMES = OrderedDict([ ('2018.1', 'queens'), ('2018.2', 'rocky'), ('2019.1', 'stein'), + ('2019.2', 'train'), ]) # The ugly duckling - must list releases oldest to newest @@ -195,6 +198,8 @@ SWIFT_CODENAMES = OrderedDict([ ['2.18.0', '2.19.0']), ('stein', ['2.20.0', '2.21.0']), + ('train', + ['2.22.0']), ]) # >= Liberty version->codename mapping @@ -208,6 +213,7 @@ PACKAGE_CODENAMES = { ('17', 'queens'), ('18', 'rocky'), ('19', 'stein'), + ('20', 'train'), ]), 'neutron-common': OrderedDict([ ('7', 'liberty'), @@ -218,6 +224,7 @@ PACKAGE_CODENAMES = { ('12', 'queens'), ('13', 'rocky'), ('14', 'stein'), + ('15', 'train'), ]), 'cinder-common': OrderedDict([ ('7', 'liberty'), @@ -228,6 +235,7 @@ PACKAGE_CODENAMES = { ('12', 'queens'), ('13', 'rocky'), ('14', 'stein'), + ('15', 'train'), ]), 'keystone': OrderedDict([ ('8', 'liberty'), @@ -238,6 +246,7 @@ PACKAGE_CODENAMES = { ('13', 'queens'), ('14', 'rocky'), ('15', 'stein'), + ('16', 'train'), ]), 'horizon-common': OrderedDict([ ('8', 'liberty'), @@ -248,6 +257,7 @@ PACKAGE_CODENAMES = { ('13', 'queens'), ('14', 'rocky'), ('15', 'stein'), + ('16', 'train'), ]), 'ceilometer-common': OrderedDict([ ('5', 'liberty'), @@ -258,6 +268,7 @@ PACKAGE_CODENAMES = { ('10', 'queens'), ('11', 'rocky'), ('12', 'stein'), + ('13', 'train'), ]), 'heat-common': OrderedDict([ ('5', 'liberty'), @@ -268,6 +279,7 @@ PACKAGE_CODENAMES = { ('10', 'queens'), ('11', 'rocky'), ('12', 'stein'), + ('13', 'train'), ]), 'glance-common': OrderedDict([ ('11', 'liberty'), @@ -278,6 +290,7 @@ PACKAGE_CODENAMES = { ('16', 'queens'), ('17', 'rocky'), ('18', 'stein'), + ('19', 'train'), ]), 'openstack-dashboard': OrderedDict([ ('8', 'liberty'), @@ -288,6 +301,7 @@ PACKAGE_CODENAMES = { ('13', 'queens'), ('14', 'rocky'), ('15', 'stein'), + ('16', 'train'), ]), } diff --git a/hooks/charmhelpers/contrib/storage/linux/ceph.py b/hooks/charmhelpers/contrib/storage/linux/ceph.py index 2c62092c..bbfe1933 100644 --- a/hooks/charmhelpers/contrib/storage/linux/ceph.py +++ b/hooks/charmhelpers/contrib/storage/linux/ceph.py @@ -1488,7 +1488,7 @@ def is_broker_action_done(action, rid=None, unit=None): @param action: name of action to be performed @returns True if action complete otherwise False """ - rdata = relation_get(rid, unit) or {} + rdata = relation_get(rid=rid, unit=unit) or {} broker_rsp = rdata.get(get_broker_rsp_key()) if not broker_rsp: return False @@ -1510,7 +1510,7 @@ def mark_broker_action_done(action, rid=None, unit=None): @param action: name of action to be performed @returns None """ - rdata = relation_get(rid, unit) or {} + rdata = relation_get(rid=rid, unit=unit) or {} broker_rsp = rdata.get(get_broker_rsp_key()) if not broker_rsp: return diff --git a/hooks/charmhelpers/fetch/ubuntu.py b/hooks/charmhelpers/fetch/ubuntu.py index c6d9341e..24c76e34 100644 --- a/hooks/charmhelpers/fetch/ubuntu.py +++ b/hooks/charmhelpers/fetch/ubuntu.py @@ -173,6 +173,14 @@ CLOUD_ARCHIVE_POCKETS = { 'stein/proposed': 'bionic-proposed/stein', 'bionic-stein/proposed': 'bionic-proposed/stein', 'bionic-proposed/stein': 'bionic-proposed/stein', + # Train + 'train': 'bionic-updates/train', + 'bionic-train': 'bionic-updates/train', + 'bionic-train/updates': 'bionic-updates/train', + 'bionic-updates/train': 'bionic-updates/train', + 'train/proposed': 'bionic-proposed/train', + 'bionic-train/proposed': 'bionic-proposed/train', + 'bionic-proposed/train': 'bionic-proposed/train', } @@ -522,14 +530,16 @@ def add_source(source, key=None, fail_invalid=False): for r, fn in six.iteritems(_mapping): m = re.match(r, source) if m: - # call the assoicated function with the captured groups - # raises SourceConfigError on error. - fn(*m.groups()) if key: + # Import key before adding the source which depends on it, + # as refreshing packages could fail otherwise. try: import_key(key) except GPGKeyError as e: raise SourceConfigError(str(e)) + # call the associated function with the captured groups + # raises SourceConfigError on error. + fn(*m.groups()) break else: # nothing matched. log an error and maybe sys.exit diff --git a/hooks/nova_compute_context.py b/hooks/nova_compute_context.py index f11f7241..e069812e 100644 --- a/hooks/nova_compute_context.py +++ b/hooks/nova_compute_context.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json import uuid import os import platform @@ -398,8 +399,38 @@ class SerialConsoleContext(context.OSContextGenerator): } -class CloudComputeContext(context.OSContextGenerator): +class CloudComputeVendorJSONContext(context.OSContextGenerator): + """Receives vendor_data.json from nova cloud controller node.""" + interfaces = ['cloud-compute'] + + @property + def vendor_json(self): + """ + Returns the json string to be written in vendor_data.json file, + received from nova-cloud-controller charm through relation attribute + vendor_json. + """ + for rid in relation_ids('cloud-compute'): + for unit in related_units(rid): + vendor_data_string = relation_get( + 'vendor_json', rid=rid, unit=unit) + if vendor_data_string: + return vendor_data_string + + def __call__(self): + """ + Returns a dict in which the value of vendor_data_json is the json + string to be written in vendor_data.json file. + """ + ctxt = {'vendor_data_json': '{}'} + vendor_data = self.vendor_json + if vendor_data: + ctxt['vendor_data_json'] = vendor_data + return ctxt + + +class CloudComputeContext(context.OSContextGenerator): ''' Generates main context for writing nova.conf and quantum.conf templates from a cloud-compute relation changed hook. Mainly used for determinig @@ -437,6 +468,38 @@ class CloudComputeContext(context.OSContextGenerator): region = relation_get('region', rid=rid, unit=unit) return region + @property + def vendor_data(self): + """ + Returns vendor metadata related parameters to be written in + nova.conf, received from nova-cloud-controller charm through relation + attribute vendor_data. + """ + vendor_data_json = {} + for rid in relation_ids('cloud-compute'): + for unit in related_units(rid): + vendor_data_string = relation_get( + 'vendor_data', rid=rid, unit=unit) + if vendor_data_string: + vendor_data_json = json.loads(vendor_data_string) + return vendor_data_json + + def vendor_data_context(self): + vdata_ctxt = {} + vendor_data_json = self.vendor_data + if vendor_data_json: + # NOTE(ganso): avoid returning any extra keys to context + if vendor_data_json.get('vendor_data'): + vdata_ctxt['vendor_data'] = vendor_data_json['vendor_data'] + if vendor_data_json.get('vendor_data_url'): + vdata_ctxt['vendor_data_url'] = vendor_data_json[ + 'vendor_data_url'] + if vendor_data_json.get('vendordata_providers'): + vdata_ctxt['vendordata_providers'] = vendor_data_json[ + 'vendordata_providers'] + + return vdata_ctxt + def flat_dhcp_context(self): ec2_host = None for rid in relation_ids('cloud-compute'): @@ -617,6 +680,8 @@ class CloudComputeContext(context.OSContextGenerator): if region: ctxt['region'] = region + ctxt.update(self.vendor_data_context()) + if self.context_complete(ctxt): return ctxt diff --git a/hooks/nova_compute_utils.py b/hooks/nova_compute_utils.py index f7c9d7c2..f524f0f9 100644 --- a/hooks/nova_compute_utils.py +++ b/hooks/nova_compute_utils.py @@ -86,6 +86,7 @@ from charmhelpers.core.hugepage import hugepage_support from nova_compute_context import ( nova_metadata_requirement, CloudComputeContext, + CloudComputeVendorJSONContext, LxdContext, MetadataServiceContext, NovaComputeLibvirtContext, @@ -167,6 +168,7 @@ LIBVIRTD_CONF = '/etc/libvirt/libvirtd.conf' LIBVIRT_BIN = '/etc/default/libvirt-bin' LIBVIRT_BIN_OVERRIDES = '/etc/init/libvirt-bin.override' NOVA_CONF = '%s/nova.conf' % NOVA_CONF_DIR +VENDORDATA_FILE = '%s/vendor_data.json' % NOVA_CONF_DIR QEMU_KVM = '/etc/default/qemu-kvm' NOVA_API_AA_PROFILE_PATH = ('/etc/apparmor.d/{}'.format(NOVA_API_AA_PROFILE)) NOVA_COMPUTE_AA_PROFILE_PATH = ('/etc/apparmor.d/{}' @@ -211,6 +213,10 @@ BASE_RESOURCE_MAP = { context.IdentityCredentialsContext( rel_name='cloud-credentials')], }, + VENDORDATA_FILE: { + 'services': [], + 'contexts': [CloudComputeVendorJSONContext()], + }, NOVA_API_AA_PROFILE_PATH: { 'services': ['nova-api'], 'contexts': [NovaAPIAppArmorContext()], diff --git a/templates/queens/nova.conf b/templates/queens/nova.conf new file mode 100644 index 00000000..61254056 --- /dev/null +++ b/templates/queens/nova.conf @@ -0,0 +1,294 @@ +# queens +############################################################################### +# [ WARNING ] +# Configuration file maintained by Juju. Local changes may be overwritten. +{% if restart_trigger -%} +# restart trigger: {{ restart_trigger }} +{% endif -%} +############################################################################### +[DEFAULT] +verbose={{ verbose }} +debug={{ debug }} +dhcpbridge_flagfile=/etc/nova/nova.conf +dhcpbridge=/usr/bin/nova-dhcpbridge +logdir=/var/log/nova +state_path=/var/lib/nova +force_dhcp_release=True +use_syslog = {{ use_syslog }} +ec2_private_dns_show_ip=True +api_paste_config=/etc/nova/api-paste.ini +enabled_apis=osapi_compute,metadata +auth_strategy=keystone +my_ip = {{ host_ip }} +force_raw_images = {{ force_raw_images }} + +{% if debug -%} +default_log_levels = "amqp=WARN, amqplib=WARN, boto=WARN, qpid=WARN, sqlalchemy=WARN, suds=INFO, oslo.messaging=INFO, oslo_messaging=DEBUG, iso8601=WARN, requests.packages.urllib3.connectionpool=WARN, urllib3.connectionpool=WARN, websocket=WARN, requests.packages.urllib3.util.retry=WARN, urllib3.util.retry=WARN, keystonemiddleware=WARN, routes.middleware=WARN, stevedore=WARN, taskflow=WARN, keystoneauth=WARN, oslo.cache=INFO, dogpile.core.dogpile=INFO" +{% endif -%} + +{% if transport_url %} +transport_url = {{ transport_url }} +{% endif %} + +{% if arch == 'aarch64' -%} +libvirt_use_virtio_for_bridges=False +libvirt_disk_prefix=vd +{% endif -%} + +{% if console_vnc_type -%} +vnc_enabled = True +novnc_enabled = True +vnc_keymap = {{ console_keymap }} +vncserver_listen = 0.0.0.0 +vncserver_proxyclient_address = {{ console_listen_addr }} +{% if console_access_protocol == 'novnc' or console_access_protocol == 'vnc' -%} +novncproxy_base_url = {{ novnc_proxy_address }} +{% endif -%} +{% if console_access_protocol == 'xvpvnc' or console_access_protocol == 'vnc' -%} +xvpvncproxy_port = {{ xvpvnc_proxy_port }} +xvpvncproxy_host = {{ xvpvnc_proxy_host }} +xvpvncproxy_base_url = {{ xvpvnc_proxy_address }} +{% endif -%} +{% else -%} +vnc_enabled = False +novnc_enabled = False +{% endif -%} + +{% if neutron_plugin and neutron_plugin in ('ovs', 'midonet') -%} +libvirt_vif_driver = nova.virt.libvirt.vif.LibvirtGenericVIFDriver +{% if neutron_security_groups -%} +security_group_api = neutron +firewall_driver = nova.virt.firewall.NoopFirewallDriver +{% endif -%} +{% endif -%} + +{% if neutron_plugin and neutron_plugin == 'vsp' -%} +network_api_class=nova.network.neutronv2.api.API +libvirt_vif_driver=nova.virt.libvirt.vif.LibvirtGenericVIFDriver +neutron_ovs_bridge=alubr0 +security_group_api=neutron +firewall_driver = nova.virt.firewall.NoopFirewallDriver +{% endif -%} + +{% if neutron_plugin and (neutron_plugin == 'nvp' or neutron_plugin == 'nsx') -%} +libvirt_vif_driver = nova.virt.libvirt.vif.LibvirtOpenVswitchVirtualPortDriver +security_group_api = neutron +firewall_driver = nova.virt.firewall.NoopFirewallDriver +{% endif -%} + +{% if neutron_plugin and neutron_plugin == 'Calico' -%} +security_group_api = neutron +firewall_driver = nova.virt.firewall.NoopFirewallDriver +{% endif -%} + +{% if neutron_plugin and neutron_plugin == 'plumgrid' -%} +security_group_api=neutron +firewall_driver = nova.virt.firewall.NoopFirewallDriver +{% endif -%} + +{% if network_manager != 'neutron' and network_manager_config -%} +{% for key, value in network_manager_config.items() -%} +{{ key }} = {{ value }} +{% endfor -%} +{% endif -%} + +{% if network_manager == 'neutron' -%} +network_api_class = nova.network.neutronv2.api.API +use_neutron = True +{% else -%} +network_manager = nova.network.manager.FlatDHCPManager +{% endif -%} + +{% if network_device_mtu -%} +network_device_mtu = {{ network_device_mtu }} +{% endif -%} + +{% if volume_service -%} +volume_api_class = nova.volume.cinder.API +{% endif -%} + +{% if user_config_flags -%} +{% for key, value in user_config_flags.items() -%} +{{ key }} = {{ value }} +{% endfor -%} +{% endif -%} + +{% if instances_path -%} +instances_path = {{ instances_path }} +{% endif -%} + +{% if sections and 'DEFAULT' in sections -%} +{% for key, value in sections['DEFAULT'] -%} +{{ key }} = {{ value }} +{% endfor -%} +{% endif -%} + +{% if vcpu_pin_set -%} +vcpu_pin_set = {{ vcpu_pin_set }} +{% endif -%} +reserved_host_memory_mb = {{ reserved_host_memory }} + +{% if reserved_huge_pages -%} +{% for value in reserved_huge_pages -%} +reserved_huge_pages = {{ value }} +{% endfor -%} +{% endif -%} + +{% include "section-zeromq" %} + +{% if default_availability_zone -%} +default_availability_zone = {{ default_availability_zone }} +{% endif -%} + +{% if resume_guests_state_on_host_boot -%} +resume_guests_state_on_host_boot = {{ resume_guests_state_on_host_boot }} +{% endif -%} + +metadata_workers = {{ workers }} + +[pci] +{% if pci_passthrough_whitelist -%} +passthrough_whitelist = {{ pci_passthrough_whitelist }} +{% endif -%} +{% if pci_alias %} +alias = {{ pci_alias }} +{% endif %} + +{% if network_manager == 'neutron' and network_manager_config -%} +[neutron] +url = {{ network_manager_config.neutron_url }} +{% if network_manager_config.keystone_host or auth_host -%} +{% if neutron_plugin and neutron_plugin == 'vsp' -%} +ovs_bridge = alubr0 +{% endif -%} +{% if auth_host -%} +auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }} +auth_type = password +{% if admin_domain_name -%} +project_domain_name = {{ admin_domain_name }} +user_domain_name = {{ admin_domain_name }} +{% else -%} +project_domain_name = default +user_domain_name = default +{% endif -%} +project_name = {{ admin_tenant_name }} +username = {{ admin_user }} +password = {{ admin_password }} +signing_dir = {{ signing_dir }} +{% endif -%} +{% if metadata_shared_secret -%} +metadata_proxy_shared_secret = {{ metadata_shared_secret }} +service_metadata_proxy=True +{% endif -%} +{% endif -%} +{% endif -%} + +{% include "section-keystone-authtoken-mitaka" %} + +{% if glance_api_servers -%} +[glance] +api_servers = {{ glance_api_servers }} +{% endif -%} + +{% if vendor_data or vendor_data_url -%} +[api] +vendordata_providers = {{ vendordata_providers }} +{% if vendor_data -%} +vendordata_jsonfile_path = /etc/nova/vendor_data.json +{% endif -%} +{% if vendor_data_url -%} +vendordata_dynamic_targets = {{ vendor_data_url }} +{% endif -%} +{% endif -%} + +{% if console_access_protocol == 'spice' -%} +[spice] +agent_enabled = True +enabled = True +html5proxy_base_url = {{ spice_proxy_address }} +keymap = {{ console_keymap }} +server_listen = 0.0.0.0 +server_proxyclient_address = {{ console_listen_addr }} +{% endif -%} + +[libvirt] +{% if cpu_mode -%} +cpu_mode = {{ cpu_mode }} +{% endif -%} +{% if cpu_model -%} +cpu_model = {{ cpu_model }} +{% endif -%} +{% if cpu_model_extra_flags %} +cpu_model_extra_flags = {{ cpu_model_extra_flags }} +{% endif %} +{% if libvirt_images_type -%} +images_type = {{ libvirt_images_type }} +{% endif -%} +{% if libvirt_images_type and rbd_pool -%} +images_rbd_pool = {{ rbd_pool }} +images_rbd_ceph_conf = {{ libvirt_rbd_images_ceph_conf }} +inject_password = false +inject_key = false +inject_partition = -2 +{% endif -%} +rbd_user = {{ rbd_user }} +rbd_secret_uuid = {{ rbd_secret_uuid }} +{% if live_migration_uri -%} +live_migration_uri = {{ live_migration_uri }} +{% endif -%} +{% if live_migration_permit_post_copy -%} +live_migration_permit_post_copy = {{ live_migration_permit_post_copy }} +{% endif -%} +{% if live_migration_permit_auto_converge -%} +live_migration_permit_auto_converge = {{ live_migration_permit_auto_converge }} +{% endif -%} +{% if disk_cachemodes -%} +disk_cachemodes = {{ disk_cachemodes }} +{% endif %} +# Disable tunnelled migration so that selective +# live block migration can be supported. +live_migration_tunnelled = False +{% if use_multipath -%} +volume_use_multipath = {{ use_multipath }} +{% endif %} +{% if default_ephemeral_format -%} +default_ephemeral_format = {{ default_ephemeral_format }} +{% endif %} +hw_disk_discard = unmap + +{% if virt_type == 'lxd' -%} +[lxd] +{% if enable_live_migration -%} +allow_live_migration = True +{% endif -%} +{% if storage_pool -%} +pool = {{ storage_pool }} +{% endif -%} +{% endif -%} + +{% include "parts/section-database" %} + +{% include "section-oslo-messaging-rabbit" %} + +[notifications] +# Starting in the Pike release, the notification_format includes both the +# versioned and unversioned message notifications. Ceilometer does not yet +# consume the versioned message notifications, so intentionally make the +# notification format unversioned until this is implemented. +notification_format = unversioned + +{% include "section-oslo-notifications" %} + +{% include "parts/section-cinder" %} + +[oslo_concurrency] +lock_path=/var/lock/nova + +[workarounds] +disable_libvirt_livesnapshot = False + +{% include "parts/section-ephemeral" %} + +{% include "parts/section-serial-console" %} + +{% include "parts/section-placement" %} diff --git a/templates/rocky/nova.conf b/templates/rocky/nova.conf index 06cb837b..8518d180 100644 --- a/templates/rocky/nova.conf +++ b/templates/rocky/nova.conf @@ -190,6 +190,17 @@ service_metadata_proxy=True api_servers = {{ glance_api_servers }} {% endif -%} +{% if vendor_data or vendor_data_url -%} +[api] +vendordata_providers = {{ vendordata_providers }} +{% if vendor_data -%} +vendordata_jsonfile_path = /etc/nova/vendor_data.json +{% endif -%} +{% if vendor_data_url -%} +vendordata_dynamic_targets = {{ vendor_data_url }} +{% endif -%} +{% endif -%} + {% if console_access_protocol == 'spice' -%} [spice] agent_enabled = True diff --git a/unit_tests/test_nova_compute_contexts.py b/unit_tests/test_nova_compute_contexts.py index d430dcbd..a6fd3098 100644 --- a/unit_tests/test_nova_compute_contexts.py +++ b/unit_tests/test_nova_compute_contexts.py @@ -147,6 +147,46 @@ class NovaComputeContextTests(CharmTestCase): } self.assertEqual(ex_ctxt, cloud_compute()) + @patch.object(context, '_network_manager') + def test_cloud_compute_vendordata_context(self, netman): + self.relation_ids.return_value = 'cloud-compute:0' + self.related_units.return_value = 'nova-cloud-controller/0' + data = ('{"vendor_data": true, "vendor_data_url": "fake_url",' + ' "foo": "bar",' + ' "vendordata_providers": "StaticJSON,DynamicJSON"}') + self.test_relation.set({ + 'vendor_data': data + }) + cloud_compute = context.CloudComputeContext() + ex_ctxt = { + 'vendor_data': True, + 'vendor_data_url': 'fake_url', + 'vendordata_providers': 'StaticJSON,DynamicJSON', + } + self.assertEqual(ex_ctxt, cloud_compute()) + + def test_cloud_compute_vendorJSON_context(self): + self.relation_ids.return_value = 'cloud-compute:0' + self.related_units.return_value = 'nova-cloud-controller/0' + data = '{"good": json"}' + self.test_relation.set({ + 'vendor_json': data + }) + cloud_compute = context.CloudComputeVendorJSONContext() + ex_ctxt = {'vendor_data_json': data} + self.assertEqual(ex_ctxt, cloud_compute()) + + def test_cloud_compute_vendorJSON_context_empty(self): + self.relation_ids.return_value = 'cloud-compute:0' + self.related_units.return_value = 'nova-cloud-controller/0' + data = '' + self.test_relation.set({ + 'vendor_json': data + }) + cloud_compute = context.CloudComputeVendorJSONContext() + ex_ctxt = {'vendor_data_json': '{}'} + self.assertEqual(ex_ctxt, cloud_compute()) + @patch.object(context, '_neutron_plugin') @patch.object(context, '_neutron_url') @patch.object(context, '_network_manager') diff --git a/unit_tests/test_nova_compute_utils.py b/unit_tests/test_nova_compute_utils.py index d89779c2..ff6822a1 100644 --- a/unit_tests/test_nova_compute_utils.py +++ b/unit_tests/test_nova_compute_utils.py @@ -276,6 +276,10 @@ class NovaComputeUtilsTests(CharmTestCase): 'contexts': [], 'services': ['nova-compute'] }, + '/etc/nova/vendor_data.json': { + 'contexts': [], + 'services': [] + }, '/etc/ceph/secret.xml': { 'contexts': [], 'services': [] @@ -332,6 +336,10 @@ class NovaComputeUtilsTests(CharmTestCase): 'contexts': [], 'services': ['nova-compute'] }, + '/etc/nova/vendor_data.json': { + 'contexts': [], + 'services': [] + }, '/etc/ceph/secret.xml': { 'contexts': [], 'services': [] @@ -386,6 +394,10 @@ class NovaComputeUtilsTests(CharmTestCase): 'contexts': [], 'services': ['nova-compute', 'nova-api', 'nova-network'] }, + '/etc/nova/vendor_data.json': { + 'contexts': [], + 'services': [] + }, '/etc/ceph/secret.xml': { 'contexts': [], 'services': [] @@ -448,6 +460,10 @@ class NovaComputeUtilsTests(CharmTestCase): 'contexts': [], 'services': ['nova-compute'] }, + '/etc/nova/vendor_data.json': { + 'contexts': [], + 'services': [] + }, '/etc/ceph/secret.xml': { 'contexts': [], 'services': []