Add nova-metadata service

Add a service for handling nova metadata api services. This was
previously handled by the neutron-gateway and still is for
deployemnts up to and including Pike, For the neutron metadata
service and the nova service to communicate they need a shared
secret. To achieve this, the change includes:

* A charmhelper sync to get support for multiple wsgi vhosts
* Rendering new wsgi vhost and corresponding haproxy config.
* Setting a shared-secret down the relation with the neutron
  gateway.
* Remove fragile keystone authtoken checks as they are failing
  after a ch sync and any issues will be caught by the instance
  launch functional test.

Change-Id: I5ad15ba782cb87b6fdb3c0941a6482d201670bff
This commit is contained in:
Liam Young 2018-08-08 12:31:55 +00:00
parent d3a843ea4c
commit e20db83c7d
18 changed files with 588 additions and 120 deletions

View File

@ -431,3 +431,21 @@ options:
description: |
A comma-separated list of nagios servicegroups. If left empty, the
nagios_context will be used as the servicegroup.
vendor-data:
type: string
default:
description: |
A JSON-formatted string that will serve as vendor metadata
(via "StaticJSON" provider) to all VM's within an OpenStack deployment,
regardless of project or domain. For deployments prior to Queens this
value should be set in the neutron-gateway charm.
vendor-data-url:
type: string
default:
description: |
A URL serving JSON-formatted data that will serve as vendor metadata
(via "DynamicJSON" provider) to all VM's within an OpenStack deployment,
regardless of project or domain.
.
Only supported in OpenStack Newton and higher. For deployments prior to
Queens this value should be set in the neutron-gateway charm.

View File

@ -1,12 +1,14 @@
{% if auth_host -%}
[keystone_authtoken]
auth_uri = {{ service_protocol }}://{{ service_host }}:{{ service_port }}
auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}
auth_type = password
{% if api_version == "3" -%}
auth_uri = {{ service_protocol }}://{{ service_host }}:{{ service_port }}/v3
auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}/v3
project_domain_name = {{ admin_domain_name }}
user_domain_name = {{ admin_domain_name }}
{% else -%}
auth_uri = {{ service_protocol }}://{{ service_host }}:{{ service_port }}
auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}
project_domain_name = default
user_domain_name = default
{% endif -%}

View File

@ -19,6 +19,7 @@ from charmhelpers.core.hookenv import (
config,
relation_ids,
relation_set,
leader_get,
log,
DEBUG,
related_units,
@ -44,6 +45,10 @@ from charmhelpers.contrib.openstack.ip import (
INTERNAL,
PUBLIC,
)
from charmhelpers.contrib.openstack.utils import (
os_release,
CompareOpenStackReleases,
)
def context_complete(ctxt):
@ -159,6 +164,8 @@ class HAProxyContext(context.HAProxyContext):
singlenode_mode=True)
placement_api = determine_api_port(api_port('nova-placement-api'),
singlenode_mode=True)
metadata_api = determine_api_port(api_port('nova-api-metadata'),
singlenode_mode=True)
# Apache ports
a_compute_api = determine_apache_port(api_port('nova-api-os-compute'),
singlenode_mode=True)
@ -168,12 +175,15 @@ class HAProxyContext(context.HAProxyContext):
singlenode_mode=True)
a_placement_api = determine_apache_port(api_port('nova-placement-api'),
singlenode_mode=True)
a_metadata_api = determine_apache_port(api_port('nova-api-metadata'),
singlenode_mode=True)
# to be set in nova.conf accordingly.
listen_ports = {
'osapi_compute_listen_port': compute_api,
'ec2_listen_port': ec2_api,
's3_listen_port': s3_api,
'placement_listen_port': placement_api,
'metadata_listen_port': metadata_api,
}
port_mapping = {
@ -185,6 +195,8 @@ class HAProxyContext(context.HAProxyContext):
api_port('nova-objectstore'), a_s3_api],
'nova-placement-api': [
api_port('nova-placement-api'), a_placement_api],
'nova-api-metadata': [
api_port('nova-api-metadata'), a_metadata_api],
}
# for haproxy.conf
@ -195,6 +207,15 @@ class HAProxyContext(context.HAProxyContext):
return ctxt
class MetaDataHAProxyContext(HAProxyContext):
"""Context for the nova metadata service."""
def __call__(self):
ctxt = super(MetaDataHAProxyContext, self).__call__()
ctxt['port'] = ctxt['listen_ports']['metadata_listen_port']
return ctxt
def canonical_url():
"""Returns the correct HTTP URL to this host given the state of HTTPS
configuration and hacluster.
@ -395,3 +416,31 @@ class NovaAPISharedDBContext(context.SharedDBContext):
prefix = 'nova_api_{}'
ctxt = {prefix.format(k): v for k, v in ctxt.items()}
return ctxt
class NovaMetadataContext(context.OSContextGenerator):
'''
Context used for configuring the nova metadata service.
'''
def __call__(self):
cmp_os_release = CompareOpenStackReleases(os_release('nova-common'))
ctxt = {}
if cmp_os_release >= 'rocky':
ctxt['vendordata_providers'] = []
vdata = config('vendor-data')
vdata_url = config('vendor-data-url')
if vdata:
ctxt['vendor_data'] = True
ctxt['vendordata_providers'].append('StaticJSON')
if vdata_url:
ctxt['vendor_data_url'] = vdata_url
ctxt['vendordata_providers'].append('DynamicJSON')
ctxt['metadata_proxy_shared_secret'] = leader_get(
'shared-metadata-secret')
ctxt['enable_metadata'] = True
else:
ctxt['enable_metadata'] = False
return ctxt

View File

@ -91,6 +91,8 @@ from nova_cc_utils import (
determine_ports,
disable_package_apache_site,
do_openstack_upgrade,
get_metadata_settings,
get_shared_metadatasecret,
is_api_ready,
is_cellv2_init_ready,
keystone_ca_cert_b64,
@ -98,6 +100,7 @@ from nova_cc_utils import (
placement_api_enabled,
save_script_rc,
services,
set_shared_metadatasecret,
ssh_compute_add,
ssh_compute_remove,
ssh_known_hosts_lines,
@ -116,6 +119,7 @@ from nova_cc_utils import (
serial_console_settings,
pause_unit_helper,
resume_unit_helper,
write_vendordata,
)
from charmhelpers.contrib.hahelpers.cluster import (
@ -349,6 +353,11 @@ def config_changed():
update_nova_consoleauth_config()
update_aws_compat_services()
if config('vendor-data'):
write_vendordata(config('vendor-data'))
if is_leader() and not get_shared_metadatasecret():
set_shared_metadatasecret()
@hooks.hook('amqp-relation-joined')
def amqp_joined(relation_id=None):
@ -744,6 +753,7 @@ def quantum_joined(rid=None, remote_restart=False):
if remote_restart:
rel_settings['restart_trigger'] = str(uuid.uuid4())
rel_settings.update(get_metadata_settings(CONFIGS))
relation_set(relation_id=rid, **rel_settings)
@ -790,6 +800,10 @@ def cluster_changed():
log('Database sync not ready. Would shut down services but '
'unit is in paused state, not issuing stop/pause to all '
'services')
# The shared metadata secret is stored in the leader-db and if its changed
# the gateway needs to know.
for rid in relation_ids('quantum-network-service'):
quantum_joined(rid=rid, remote_restart=False)
@hooks.hook('ha-relation-joined')

View File

@ -19,6 +19,9 @@ import ConfigParser
from base64 import b64encode
from collections import OrderedDict
from copy import deepcopy
from urlparse import urlparse
from uuid import uuid1
import json
from charmhelpers.contrib.openstack import context, templating
@ -63,12 +66,14 @@ from charmhelpers.core.hookenv import (
config,
is_leader,
log,
leader_get,
leader_set,
relation_get,
relation_ids,
remote_unit,
DEBUG,
INFO,
ERROR,
INFO,
status_set,
related_units,
local_unit,
@ -93,12 +98,19 @@ from charmhelpers.core.decorators import (
retry_on_exception,
)
from charmhelpers.contrib.openstack.ip import (
canonical_url,
INTERNAL,
)
import nova_cc_context
TEMPLATES = 'templates/'
CLUSTER_RES = 'grp_nova_vips'
SHARED_METADATA_SECRET_KEY = 'shared-metadata-secret'
# The interface is said to be satisfied if anyone of the interfaces in the
# list has a complete context.
REQUIRED_INTERFACES = {
@ -119,7 +131,6 @@ BASE_PACKAGES = [
'python-psycopg2',
'python-psutil',
'python-six',
'uuid',
'python-memcache',
]
@ -144,6 +155,7 @@ SERVICE_BLACKLIST = {
API_PORTS = {
'nova-api-ec2': 8773,
'nova-api-os-compute': 8774,
'nova-api-metadata': 8775,
'nova-api-os-volume': 8776,
'nova-placement-api': 8778,
'nova-objectstore': 3333,
@ -162,6 +174,9 @@ WSGI_NOVA_PLACEMENT_API_CONF = \
'/etc/apache2/sites-enabled/wsgi-openstack-api.conf'
PACKAGE_NOVA_PLACEMENT_API_CONF = \
'/etc/apache2/sites-enabled/nova-placement-api.conf'
WSGI_NOVA_METADATA_API_CONF = \
'/etc/apache2/sites-enabled/wsgi-openstack-metadata.conf'
VENDORDATA_FILE = '/etc/nova/vendor_data.json'
def resolve_services():
@ -208,7 +223,8 @@ BASE_RESOURCE_MAP = OrderedDict([
context.VolumeAPIContext('nova-common'),
nova_cc_context.NeutronAPIContext(),
nova_cc_context.SerialConsoleContext(),
context.MemcacheContext()],
context.MemcacheContext(),
nova_cc_context.NovaMetadataContext()],
}),
(NOVA_API_PASTE, {
'services': [s for s in resolve_services() if 'api' in s],
@ -337,7 +353,20 @@ def resource_map(actual_services=True):
svcs = resource_map[cfile]['services']
if 'nova-placement-api' in svcs:
svcs.remove('nova-placement-api')
if enable_metadata_api():
if actual_services:
svcs = ['apache2']
else:
svcs = ['nova-api-metadata']
resource_map[WSGI_NOVA_METADATA_API_CONF] = {
'contexts': [
context.WSGIWorkerConfigContext(
name="nova_meta",
user='nova',
group='nova',
script='/usr/bin/nova-metadata-wsgi'),
nova_cc_context.MetaDataHAProxyContext()],
'services': svcs}
return resource_map
@ -413,13 +442,20 @@ def console_attributes(attr, proto=None):
def determine_packages():
# currently all packages match service names
cmp_os_release = CompareOpenStackReleases(os_release('nova-common'))
packages = deepcopy(BASE_PACKAGES)
for v in resource_map(actual_services=False).values():
packages.extend(v['services'])
# The nova-api-metadata service is served via wsgi and the package is
# only needed for the standalone service so remove it to avoid port
# clashes.
try:
packages.remove("nova-api-metadata")
except ValueError:
pass
if console_attributes('packages'):
packages.extend(console_attributes('packages'))
if (config('enable-serial-console') and
CompareOpenStackReleases(os_release('nova-common')) >= 'juno'):
if (config('enable-serial-console') and cmp_os_release >= 'juno'):
packages.extend(SERIAL_CONSOLE['packages'])
packages.extend(token_cache_pkgs(source=config('openstack-origin')))
@ -1359,9 +1395,52 @@ def placement_api_enabled():
return CompareOpenStackReleases(os_release('nova-common')) >= 'ocata'
def enable_metadata_api(release=None):
"""Should nova-metadata-api be running on this unit for this release."""
if not release:
release = os_release('nova-common')
return CompareOpenStackReleases(os_release('nova-common')) >= 'rocky'
def disable_package_apache_site():
"""Ensure that the package-provided apache configuration is disabled to
prevent it from conflicting with the charm-provided version.
"""
if os.path.exists(PACKAGE_NOVA_PLACEMENT_API_CONF):
subprocess.check_call(['a2dissite', 'nova-placement-api'])
def get_shared_metadatasecret():
"""Return the shared metadata secret."""
return leader_get(SHARED_METADATA_SECRET_KEY)
def set_shared_metadatasecret():
"""Store the shared metadata secret."""
leader_set({SHARED_METADATA_SECRET_KEY: uuid1()})
def get_metadata_settings(configs):
"""Return the settings for accessing the metadata service."""
if enable_metadata_api():
url = urlparse(canonical_url(configs, INTERNAL))
settings = {
'nova-metadata-host': url.netloc,
'nova-metadata-protocol': url.scheme,
'nova-metadata-port': API_PORTS['nova-api-metadata'],
'shared-metadata-secret': get_shared_metadatasecret()}
else:
settings = {}
return settings
def write_vendordata(vdata):
"""Write supplied vendor data out to a file."""
try:
json_vdata = json.loads(vdata)
except (TypeError, json.decoder.JSONDecodeError) as e:
log('Error decoding vendor-data. {}'.format(e), level=ERROR)
return False
with open(VENDORDATA_FILE, 'w') as vdata_file:
vdata_file.write(json.dumps(json_vdata, sort_keys=True, indent=2))
return True

View File

@ -2,7 +2,6 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
pbr>=1.8.0,<1.9.0
PyYAML>=3.1.0
simplejson>=2.2.0
netifaces>=0.10.4
netaddr>=0.7.12,!=0.7.16

View File

@ -112,6 +112,9 @@ quantum_admin_password = {{ admin_password }}
quantum_admin_auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}/v2.0
{% endif -%}
{% elif network_manager and network_manager == 'neutron' -%}
neutron_metadata_proxy_shared_secret = {{ metadata_proxy_shared_secret }}
service_neutron_metadata_proxy = True
metadata_workers = {{ workers }}
network_api_class = nova.network.neutronv2.api.API
neutron_url = {{ neutron_url }}
{% if auth_host -%}

View File

@ -111,6 +111,11 @@ volume_api_class=nova.volume.cinder.API
{% endfor -%}
{% endif -%}
{% if vendor_data -%}
vendordata_driver = nova.api.metadata.vendordata_json.JsonFileVendorData
vendordata_jsonfile_path = /etc/nova/vendor_data.json
{% endif -%}
{% if listen_ports -%}
{% for key, value in listen_ports.iteritems() -%}
{{ key }} = {{ value }}
@ -134,13 +139,9 @@ volume_api_class=nova.volume.cinder.API
api_servers = {{ glance_api_servers }}
{% endif -%}
{% if network_manager and network_manager == 'neutron' -%}
[neutron]
url = {{ neutron_url }}
auth_strategy = keystone
auth_section = keystone_authtoken
auth_plugin = password
{% endif -%}
{% if network_manager and network_manager == 'neutron' %}
{% include "parts/section-neutron" %}
{% endif %}
{% include "section-keystone-authtoken-mitaka" %}

181
templates/newton/nova.conf Normal file
View File

@ -0,0 +1,181 @@
# newton
###############################################################################
# [ WARNING ]
# Configuration file maintained by Juju. Local changes may be overwritten.
###############################################################################
[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
iscsi_helper=tgtadm
libvirt_use_virtio_for_bridges=True
connection_type=libvirt
root_helper=sudo nova-rootwrap /etc/nova/rootwrap.conf
api_paste_config=/etc/nova/api-paste.ini
volumes_path=/var/lib/nova/volumes
enabled_apis=osapi_compute,metadata
auth_strategy=keystone
compute_driver=libvirt.LibvirtDriver
use_ipv6 = {{ use_ipv6 }}
osapi_compute_listen = {{ bind_host }}
metadata_host = {{ bind_host }}
s3_listen = {{ bind_host }}
osapi_compute_workers = {{ workers }}
{% if vendor_data -%}
vendordata_driver = nova.api.metadata.vendordata_json.JsonFileVendorData
vendordata_jsonfile_path = /etc/nova/vendor_data.json
{% endif -%}
{% if additional_neutron_filters is defined %}
scheduler_default_filters = {{ scheduler_default_filters }},{{ additional_neutron_filters }}
{% else %}
scheduler_default_filters = {{ scheduler_default_filters }}
{% endif %}
{% if pci_alias %}
pci_alias = {{ pci_alias }}
{% endif %}
cpu_allocation_ratio = {{ cpu_allocation_ratio }}
ram_allocation_ratio = {{ ram_allocation_ratio }}
disk_allocation_ratio = {{ disk_allocation_ratio }}
use_syslog={{ use_syslog }}
my_ip = {{ host_ip }}
{% include "parts/novnc" %}
{% if rbd_pool -%}
rbd_pool = {{ rbd_pool }}
rbd_user = {{ rbd_user }}
rbd_secret_uuid = {{ rbd_secret_uuid }}
{% endif -%}
{% if neutron_plugin and neutron_plugin in ('ovs', 'midonet') -%}
libvirt_vif_driver = nova.virt.libvirt.vif.LibvirtGenericVIFDriver
libvirt_user_virtio_for_bridges = True
{% if neutron_security_groups -%}
security_group_api = {{ network_manager }}
nova_firewall_driver = nova.virt.firewall.NoopFirewallDriver
{% endif -%}
{% if external_network -%}
default_floating_pool = {{ external_network }}
{% endif -%}
{% endif -%}
{% if neutron_plugin and neutron_plugin == 'vsp' -%}
neutron_ovs_bridge = alubr0
{% endif -%}
{% if neutron_plugin and neutron_plugin == 'nvp' -%}
security_group_api = neutron
nova_firewall_driver = nova.virt.firewall.NoopFirewallDriver
{% if external_network -%}
default_floating_pool = {{ external_network }}
{% endif -%}
{% endif -%}
{% if neutron_plugin and neutron_plugin == 'Calico' -%}
security_group_api = neutron
nova_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_config -%}
{% for key, value in network_manager_config.iteritems() -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif -%}
{% if network_manager and network_manager == 'neutron' -%}
network_api_class = nova.network.neutronv2.api.API
use_neutron = True
{% else -%}
network_manager = nova.network.manager.FlatDHCPManager
{% endif -%}
{% if default_floating_pool -%}
default_floating_pool = {{ default_floating_pool }}
{% endif -%}
{% if volume_service -%}
volume_api_class=nova.volume.cinder.API
{% endif -%}
{% if user_config_flags -%}
{% for key, value in user_config_flags.iteritems() -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif -%}
{% if listen_ports -%}
{% for key, value in listen_ports.iteritems() -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif -%}
{% if sections and 'DEFAULT' in sections -%}
{% for key, value in sections['DEFAULT'] -%}
{{ key }} = {{ value }}
{% endfor -%}
{% endif %}
{% include "section-zeromq" %}
{% include "parts/database-v2" %}
{% include "parts/database-api" %}
{% if glance_api_servers -%}
[glance]
api_servers = {{ glance_api_servers }}
{% endif -%}
{% if network_manager and network_manager == 'neutron' %}
{% include "parts/section-neutron" %}
{% endif %}
{% include "section-keystone-authtoken-mitaka" %}
{% include "parts/section-cinder" %}
[osapi_v3]
enabled=True
{% include "parts/cell" %}
[conductor]
workers = {{ workers }}
{% include "section-rabbitmq-oslo" %}
{% include "section-oslo-notifications" %}
[oslo_concurrency]
lock_path=/var/lock/nova
[spice]
{% include "parts/spice" %}
{% include "parts/section-serial-console" %}
{% if memcached_servers %}
[cache]
enabled = true
backend = oslo_cache.memcache_pool
memcache_servers = {{ memcached_servers }}
{% endif %}
{% include "section-oslo-middleware" %}
[wsgi]
api_paste_config=/etc/nova/api-paste.ini

View File

@ -126,13 +126,9 @@ volume_api_class=nova.volume.cinder.API
api_servers = {{ glance_api_servers }}
{% endif -%}
{% if network_manager and network_manager == 'neutron' -%}
[neutron]
url = {{ neutron_url }}
auth_strategy = keystone
auth_section = keystone_authtoken
auth_type = password
{% endif -%}
{% if network_manager and network_manager == 'neutron' %}
{% include "parts/section-neutron" %}
{% endif %}
{% include "section-keystone-authtoken-mitaka" %}
@ -183,6 +179,15 @@ enabled_filters = {{ scheduler_default_filters }}
[api]
auth_strategy=keystone
{% if vendor_data or vendor_data_url -%}
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 -%}
[wsgi]
api_paste_config=/etc/nova/api-paste.ini

View File

@ -0,0 +1,21 @@
[neutron]
url = {{ neutron_url }}
{% if auth_host -%}
auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}
auth_type = password
{% if api_version == "3" -%}
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 }}
{% endif -%}
# {{ enable_metadata }}
{% if enable_metadata -%}
service_metadata_proxy = True
metadata_proxy_shared_secret = {{ metadata_proxy_shared_secret }}
{% endif -%}

View File

@ -202,6 +202,15 @@ enabled_filters = {{ scheduler_default_filters }}
[api]
auth_strategy=keystone
{% if vendor_data or vendor_data_url -%}
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 -%}
[wsgi]
api_paste_config=/etc/nova/api-paste.ini

View File

@ -1,12 +1,12 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
charm-tools>=2.0.0
coverage>=3.6
mock>=1.2
flake8>=2.2.4,<=2.4.1
os-testr>=0.4.1
charm-tools>=2.0.0
requests==2.6.0
requests>=2.18.4
# BEGIN: Amulet OpenStack Charm Helper Requirements
# Liberty client lower constraints
amulet>=1.14.3,<2.0

View File

@ -660,16 +660,6 @@ class NovaCCBasicDeployment(OpenStackAmuletDeployment):
ks_ec2 = "{}/ec2tokens".format(ks_ep)
ks_ncc_rel = self.keystone_sentry.relation(
'identity-service', 'nova-cloud-controller:identity-service')
ks_uri = "http://{}:{}/".format(ks_ncc_rel['service_host'],
ks_ncc_rel['service_port'])
id_uri = "{}://{}:{}/".format(ks_ncc_rel['auth_protocol'],
ks_ncc_rel['service_host'],
ks_ncc_rel['auth_port'])
db_ncc_rel = self.pxc_sentry.relation(
'shared-db', 'nova-cloud-controller:shared-db')
@ -706,15 +696,6 @@ class NovaCCBasicDeployment(OpenStackAmuletDeployment):
expected['database'] = {
'connection': db_uri
}
expected['keystone_authtoken'] = {
'auth_uri': ks_uri,
'auth_host': ks_ncc_rel['service_host'],
'auth_port': ks_ncc_rel['auth_port'],
'auth_protocol': ks_ncc_rel['auth_protocol'],
'admin_tenant_name': ks_ncc_rel['service_tenant'],
'admin_user': ks_ncc_rel['service_username'],
'admin_password': ks_ncc_rel['service_password'],
}
expected['DEFAULT'].update({
'lock_path': '/var/lock/nova',
'libvirt_use_virtio_for_bridges': 'True',
@ -734,14 +715,6 @@ class NovaCCBasicDeployment(OpenStackAmuletDeployment):
expected['glance'] = {
'api_servers': gl_ncc_rel['glance-api-server'],
}
expected['keystone_authtoken'] = {
'identity_uri': id_uri.rstrip('/'),
'auth_uri': ks_uri,
'admin_tenant_name': ks_ncc_rel['service_tenant'],
'admin_user': ks_ncc_rel['service_username'],
'admin_password': ks_ncc_rel['service_password'],
'signing_dir': '/var/cache/nova',
}
expected['osapi_v3'] = {
'enabled': 'True',
}
@ -761,44 +734,6 @@ class NovaCCBasicDeployment(OpenStackAmuletDeployment):
'lock_path': '/var/lock/nova',
}
if self._get_openstack_release() >= self.xenial_queens:
expected['keystone_authtoken'] = {
'auth_uri': ks_uri.rstrip('/'),
'auth_url': id_uri.rstrip('/'),
'auth_type': 'password',
'project_domain_name': 'service_domain',
'user_domain_name': 'service_domain',
'project_name': 'services',
'username': ks_ncc_rel['service_username'],
'password': ks_ncc_rel['service_password'],
'signing_dir': '/var/cache/nova'
}
elif self._get_openstack_release() >= self.trusty_mitaka:
expected['keystone_authtoken'] = {
'auth_uri': ks_uri.rstrip('/'),
'auth_url': id_uri.rstrip('/'),
'auth_type': 'password',
'project_domain_name': 'default',
'user_domain_name': 'default',
'project_name': 'services',
'username': ks_ncc_rel['service_username'],
'password': ks_ncc_rel['service_password'],
'signing_dir': '/var/cache/nova'
}
elif self._get_openstack_release() >= self.trusty_liberty:
# Liberty
expected['keystone_authtoken'] = {
'auth_uri': ks_uri.rstrip('/'),
'auth_url': id_uri.rstrip('/'),
'auth_plugin': 'password',
'project_domain_id': 'default',
'user_domain_id': 'default',
'project_name': 'services',
'username': 'nova',
'password': ks_ncc_rel['service_password'],
'signing_dir': '/var/cache/nova',
}
if self._get_openstack_release() < self.trusty_mitaka:
expected['DEFAULT'].update({
'ec2_private_dns_show_ip': 'True',

View File

@ -14,7 +14,7 @@
import os
from mock import patch, MagicMock
from mock import patch
os.environ['JUJU_UNIT_NAME'] = 'nova-cloud-controller'
with patch('charmhelpers.core.hookenv.config') as config:
@ -22,20 +22,11 @@ with patch('charmhelpers.core.hookenv.config') as config:
config.return_value = 'nova'
import nova_cc_utils as utils # noqa
_reg = utils.register_configs
_map = utils.restart_map
utils.register_configs = MagicMock()
utils.restart_map = MagicMock()
with patch('nova_cc_utils.guard_map') as gmap:
with patch('charmhelpers.core.hookenv.config') as config:
config.return_value = False
gmap.return_value = {}
import openstack_upgrade
utils.register_configs = _reg
utils.restart_map = _map
with patch('charmhelpers.core.hookenv.config') as config:
with patch('nova_cc_utils.restart_map'):
config.return_value = 'ovs'
with patch('nova_cc_utils.register_configs') as register_configs:
import openstack_upgrade
from test_utils import CharmTestCase

View File

@ -26,13 +26,15 @@ from charmhelpers.contrib.openstack import utils
from test_utils import CharmTestCase
TO_PATCH = [
'relation_ids',
'relation_get',
'related_units',
'config',
'log',
'relations_for_id',
'https',
'leader_get',
'log',
'os_release',
'related_units',
'relation_get',
'relation_ids',
'relations_for_id',
]
@ -392,3 +394,47 @@ class NovaComputeContextTests(CharmTestCase):
expected['additional_neutron_filters'] = 'PciPassthroughFilter'
ctxt = context.NeutronAPIContext()()
self.assertEqual(ctxt, expected)
def test_vendordata_static(self):
_vdata = '{"good": "json"}'
self.os_release.return_value = 'rocky'
self.test_config.set('vendor-data', _vdata)
ctxt = context.NovaMetadataContext()()
self.assertTrue(ctxt['vendor_data'])
self.assertEqual(ctxt['vendordata_providers'], ['StaticJSON'])
def test_vendordata_dynamic(self):
_vdata_url = 'http://example.org/vdata'
self.os_release.return_value = 'rocky'
self.test_config.set('vendor-data-url', _vdata_url)
ctxt = context.NovaMetadataContext()()
self.assertEqual(ctxt['vendor_data_url'], _vdata_url)
self.assertEqual(ctxt['vendordata_providers'], ['DynamicJSON'])
def test_vendordata_static_and_dynamic(self):
self.os_release.return_value = 'rocky'
_vdata = '{"good": "json"}'
_vdata_url = 'http://example.org/vdata'
self.test_config.set('vendor-data', _vdata)
self.test_config.set('vendor-data-url', _vdata_url)
ctxt = context.NovaMetadataContext()()
self.assertTrue(ctxt['vendor_data'])
self.assertEqual(ctxt['vendor_data_url'], _vdata_url)
self.assertEqual(ctxt['vendordata_providers'], ['StaticJSON',
'DynamicJSON'])
def test_vendordata_mitaka(self):
self.os_release.return_value = 'mitaka'
self.leader_get.return_value = 'auuid'
_vdata_url = 'http://example.org/vdata'
self.test_config.set('vendor-data-url', _vdata_url)
ctxt = context.NovaMetadataContext()()
self.assertEqual(ctxt, {'enable_metadata': False})

View File

@ -137,6 +137,9 @@ class NovaCCHooksTests(CharmTestCase):
self.assertTrue(self.execd_preinstall.called)
self.assertTrue(self.service_pause.called)
@patch.object(hooks, 'set_shared_metadatasecret')
@patch.object(hooks, 'get_shared_metadatasecret')
@patch.object(hooks, 'is_leader')
@patch.object(hooks, 'update_aws_compat_services')
@patch.object(hooks, 'update_nova_consoleauth_config')
@patch.object(hooks, 'is_db_initialised')
@ -150,7 +153,11 @@ class NovaCCHooksTests(CharmTestCase):
mock_determine_packages,
mock_is_db_initialised,
mock_update_nova_consoleauth_config,
mock_update_aws_compat_services):
mock_update_aws_compat_services,
mock_is_leader,
mock_get_shared_metadatasecret,
mock_set_shared_metadatasecret):
self.get_shared_metadatasecret = None
mock_determine_packages.return_value = []
utils_config.side_effect = self.test_config.get
self.test_config.set('console-access-protocol', 'dummy')
@ -163,6 +170,41 @@ class NovaCCHooksTests(CharmTestCase):
self.assertTrue(mock_update_nova_consoleauth_config.called)
self.assertTrue(mock_update_aws_compat_services.called)
@patch.object(hooks, 'set_shared_metadatasecret')
@patch.object(hooks, 'get_shared_metadatasecret')
@patch.object(hooks, 'update_aws_compat_services')
@patch.object(hooks, 'update_nova_consoleauth_config')
@patch.object(hooks, 'is_db_initialised')
@patch.object(hooks, 'determine_packages')
@patch.object(utils, 'service_resume')
@patch.object(utils, 'config')
@patch.object(hooks, 'filter_installed_packages')
@patch.object(hooks, 'configure_https')
def test_config_changed_ocata(self, conf_https, mock_filter_packages,
utils_config, mock_service_resume,
mock_determine_packages,
mock_is_db_initialised,
mock_update_nova_consoleauth_config,
mock_update_aws_compat_services,
mock_get_shared_metadatasecret,
mock_set_shared_metadatasecret):
mock_get_shared_metadatasecret.return_value = None
self.is_leader.return_value = True
mock_determine_packages.return_value = []
utils_config.side_effect = self.test_config.get
self.test_config.set('console-access-protocol', 'dummy')
self.openstack_upgrade_available.return_value = False
mock_is_db_initialised.return_value = False
self.os_release.return_value = 'diablo'
hooks.config_changed()
self.assertTrue(self.save_script_rc.called)
mock_filter_packages.assert_called_with([])
self.assertTrue(mock_update_nova_consoleauth_config.called)
self.assertTrue(mock_update_aws_compat_services.called)
mock_set_shared_metadatasecret.assert_called_once_with()
@patch.object(hooks, 'set_shared_metadatasecret')
@patch.object(hooks, 'get_shared_metadatasecret')
@patch.object(hooks, 'update_aws_compat_services')
@patch.object(hooks, 'update_nova_consoleauth_config')
@patch.object(hooks, 'is_db_initialised')
@ -177,7 +219,9 @@ class NovaCCHooksTests(CharmTestCase):
mock_determine_packages,
mock_is_db_initialised,
mock_update_nova_consoleauth_cfg,
mock_update_aws_compat_services):
mock_update_aws_compat_services,
mock_get_shared_metadatasecret,
mock_set_shared_metadatasecret):
mock_determine_packages.return_value = []
utils_config.side_effect = self.test_config.get
self.test_config.set('console-access-protocol', 'dummy')
@ -191,6 +235,8 @@ class NovaCCHooksTests(CharmTestCase):
self.assertTrue(mock_update_aws_compat_services.called)
self.service_pause.assert_called_with('neutron-server')
@patch.object(hooks, 'set_shared_metadatasecret')
@patch.object(hooks, 'get_shared_metadatasecret')
@patch.object(hooks, 'update_aws_compat_services')
@patch.object(hooks, 'update_nova_consoleauth_config')
@patch.object(hooks, 'is_db_initialised')
@ -205,7 +251,9 @@ class NovaCCHooksTests(CharmTestCase):
mock_determine_packages,
mock_is_db_initialised,
mock_update_nova_consoleauth_cfg,
mock_update_aws_compat_services):
mock_update_aws_compat_services,
mock_get_shared_metadatasecret,
mock_set_shared_metadatasecret):
mock_determine_packages.return_value = []
utils_config.side_effect = self.test_config.get
self.test_config.set('console-access-protocol', 'dummy')
@ -220,6 +268,8 @@ class NovaCCHooksTests(CharmTestCase):
self.assertTrue(mock_update_aws_compat_services.called)
self.service_pause.assert_called_with('neutron-server')
@patch.object(hooks, 'set_shared_metadatasecret')
@patch.object(hooks, 'get_shared_metadatasecret')
@patch.object(hooks, 'update_aws_compat_services')
@patch.object(hooks, 'update_nova_consoleauth_config')
@patch.object(hooks, 'is_db_initialised')
@ -247,7 +297,9 @@ class NovaCCHooksTests(CharmTestCase):
mock_quantum_joined,
mock_is_db_initialised,
mock_update_nova_consoleauth_config,
mock_update_aws_compat_services):
mock_update_aws_compat_services,
mock_get_shared_metadatasecret,
mock_set_shared_metadatasecret):
mock_determine_packages.return_value = []
mock_is_db_initialised.return_value = False
self.openstack_upgrade_available.return_value = True
@ -269,6 +321,8 @@ class NovaCCHooksTests(CharmTestCase):
self.assertTrue(mock_update_nova_consoleauth_config.called)
self.assertTrue(mock_update_aws_compat_services.called)
@patch.object(hooks, 'set_shared_metadatasecret')
@patch.object(hooks, 'get_shared_metadatasecret')
@patch.object(hooks, 'update_aws_compat_services')
@patch.object(hooks, 'update_nova_consoleauth_config')
@patch.object(hooks, 'is_db_initialised')
@ -282,7 +336,9 @@ class NovaCCHooksTests(CharmTestCase):
mock_service_resume,
mock_is_db_initialised,
mock_update_nova_consoleauth_config,
mock_update_aws_compat_services):
mock_update_aws_compat_services,
mock_get_shared_metadatasecret,
mock_set_shared_metadatasecret):
self.openstack_upgrade_available.return_value = False
self.config_value_changed.return_value = True
self.related_units.return_value = ['unit/0']
@ -1007,6 +1063,8 @@ class NovaCCHooksTests(CharmTestCase):
call(**args),
])
@patch.object(hooks, 'set_shared_metadatasecret')
@patch.object(hooks, 'get_shared_metadatasecret')
@patch.object(hooks, 'update_aws_compat_services')
@patch.object(hooks, 'is_db_initialised')
@patch.object(hooks, 'determine_packages')
@ -1020,7 +1078,9 @@ class NovaCCHooksTests(CharmTestCase):
mock_service_pause,
mock_determine_packages,
mock_is_db_initialised,
mock_update_aws_compat_svcs):
mock_update_aws_compat_svcs,
mock_get_shared_metadatasecret,
mock_set_shared_metadatasecret):
mock_determine_packages.return_value = []
mock_is_db_initialised.return_value = False
self.config_value_changed.return_value = False

View File

@ -13,7 +13,7 @@
# limitations under the License.
from collections import OrderedDict
from mock import patch, MagicMock, call
from mock import patch, MagicMock, call, mock_open
from test_utils import (
CharmTestCase,
@ -36,10 +36,13 @@ TO_PATCH = [
'apt_install',
'config',
'configure_installation_source',
'canonical_url',
'disable_policy_rcd',
'is_leader',
'is_unit_paused_set',
'lsb_release',
'leader_get',
'leader_set',
'enable_policy_rcd',
'get_os_codename_install_source',
'log',
@ -62,6 +65,7 @@ TO_PATCH = [
'token_cache_pkgs',
'enable_memcache',
'status_set',
'uuid1',
]
SCRIPTRC_ENV_VARS = {
@ -138,7 +142,7 @@ RESTART_MAP_ICEHOUSE = OrderedDict([
RESTART_MAP_OCATA_ACTUAL = OrderedDict([
('/etc/nova/nova.conf', [
'nova-api-ec2', 'nova-api-os-compute', 'nova-objectstore',
'nova-cert', 'nova-scheduler', 'nova-conductor', 'apache2'
'nova-cert', 'nova-scheduler', 'nova-conductor', 'apache2',
]),
('/etc/nova/api-paste.ini', [
'nova-api-ec2', 'nova-api-os-compute', 'apache2'
@ -417,7 +421,7 @@ class NovaCCUtilsTests(CharmTestCase):
ex = list(set(utils.BASE_PACKAGES + utils.BASE_SERVICES))
# nova-placement-api is purposely dropped unless it's ocata
ex.remove('nova-placement-api')
self.assertEqual(ex, pkgs)
self.assertEqual(sorted(ex), sorted(pkgs))
@patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
def test_determine_packages_base_ocata(self, subcontext):
@ -427,7 +431,7 @@ class NovaCCUtilsTests(CharmTestCase):
self.enable_memcache.return_value = False
pkgs = utils.determine_packages()
ex = list(set(utils.BASE_PACKAGES + utils.BASE_SERVICES))
self.assertEqual(ex, pkgs)
self.assertEqual(sorted(ex), sorted(pkgs))
@patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
def test_determine_packages_serial_console(self,
@ -1234,3 +1238,54 @@ class NovaCCUtilsTests(CharmTestCase):
amqp.assert_called_once()
shared_db.assert_called_once()
self.log.assert_not_called()
def test_placement_api_enabled(self):
self.os_release.return_value = 'ocata'
self.assertTrue(utils.placement_api_enabled())
self.os_release.return_value = 'mitaka'
self.assertFalse(utils.placement_api_enabled())
def test_enable_metadata_api(self):
self.os_release.return_value = 'pike'
self.assertFalse(utils.enable_metadata_api())
self.os_release.return_value = 'rocky'
self.assertTrue(utils.enable_metadata_api())
def test_get_shared_metadatasecret(self):
self.leader_get.return_value = 'auuid'
self.assertEqual(utils.get_shared_metadatasecret(), 'auuid')
def test_set_shared_metadatasecret(self):
self.uuid1.return_value = 'auuid'
utils.set_shared_metadatasecret()
self.leader_set.assert_called_once_with({
'shared-metadata-secret': 'auuid'})
@patch.object(utils, 'get_shared_metadatasecret')
def test_get_metadata_settings(self, mock_get_shared_metadatasecret):
self.os_release.return_value = 'rocky'
self.canonical_url.return_value = 'http://someaddr'
mock_get_shared_metadatasecret.return_value = 'auuid'
self.assertEqual(
utils.get_metadata_settings('configs'),
{
'nova-metadata-host': 'someaddr',
'nova-metadata-port': 8775,
'nova-metadata-protocol': 'http',
'shared-metadata-secret': 'auuid'})
def test_get_metadata_settings_pike(self):
self.os_release.return_value = 'pike'
self.assertEqual(
utils.get_metadata_settings('configs'),
{})
def test_write_vendordata(self):
m = mock_open()
with patch.object(utils, 'open', m, create=True):
utils.write_vendordata('{"a": "b"}')
expected_calls = [
call('/etc/nova/vendor_data.json', 'w'),
call().write('{\n "a": "b"\n}')]
for c in expected_calls:
self.assertTrue(c in m.mock_calls)