Skip to content

Commit

Permalink
Add nova-metadata service
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Liam Young committed Oct 3, 2018
1 parent d3a843e commit e20db83
Show file tree
Hide file tree
Showing 18 changed files with 587 additions and 119 deletions.
18 changes: 18 additions & 0 deletions config.yaml
Expand Up @@ -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.
@@ -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 -%}
Expand Down
49 changes: 49 additions & 0 deletions hooks/nova_cc_context.py
Expand Up @@ -19,6 +19,7 @@
config,
relation_ids,
relation_set,
leader_get,
log,
DEBUG,
related_units,
Expand All @@ -44,6 +45,10 @@
INTERNAL,
PUBLIC,
)
from charmhelpers.contrib.openstack.utils import (
os_release,
CompareOpenStackReleases,
)


def context_complete(ctxt):
Expand Down Expand Up @@ -159,6 +164,8 @@ def __call__(self):
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)
Expand All @@ -168,12 +175,15 @@ def __call__(self):
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 = {
Expand All @@ -185,6 +195,8 @@ def __call__(self):
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
Expand All @@ -195,6 +207,15 @@ def __call__(self):
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.
Expand Down Expand Up @@ -395,3 +416,31 @@ def __call__(self):
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
14 changes: 14 additions & 0 deletions hooks/nova_cc_hooks.py
Expand Up @@ -91,13 +91,16 @@
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,
migrate_nova_databases,
placement_api_enabled,
save_script_rc,
services,
set_shared_metadatasecret,
ssh_compute_add,
ssh_compute_remove,
ssh_known_hosts_lines,
Expand All @@ -116,6 +119,7 @@
serial_console_settings,
pause_unit_helper,
resume_unit_helper,
write_vendordata,
)

from charmhelpers.contrib.hahelpers.cluster import (
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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)


Expand Down Expand Up @@ -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')
Expand Down
91 changes: 85 additions & 6 deletions hooks/nova_cc_utils.py
Expand Up @@ -19,6 +19,9 @@
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

Expand Down Expand Up @@ -63,12 +66,14 @@
config,
is_leader,
log,
leader_get,
leader_set,
relation_get,
relation_ids,
remote_unit,
DEBUG,
INFO,
ERROR,
INFO,
status_set,
related_units,
local_unit,
Expand All @@ -93,12 +98,19 @@
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 = {
Expand All @@ -119,7 +131,6 @@
'python-psycopg2',
'python-psutil',
'python-six',
'uuid',
'python-memcache',
]

Expand All @@ -144,6 +155,7 @@
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,
Expand All @@ -162,6 +174,9 @@
'/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():
Expand Down Expand Up @@ -208,7 +223,8 @@ def resolve_services():
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],
Expand Down Expand Up @@ -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


Expand Down Expand Up @@ -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')))
Expand Down Expand Up @@ -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
1 change: 0 additions & 1 deletion requirements.txt
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions templates/icehouse/nova.conf
Expand Up @@ -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 -%}
Expand Down

0 comments on commit e20db83

Please sign in to comment.