Watch & react to placement and neutron ep changes.

Request to be informed of changes to placement and neutron changes.
If a placement change occurs restart nova-scheduler as it will
cache the old endpoint url and tell nova-compute to restart its
services as they will have done the same.

Change-Id: I7537723e40a5a25672fbbdc2d5c3144724f6240a
Closes-Bug: #1862974
This commit is contained in:
Liam Young 2020-02-17 11:03:49 +00:00
parent 9913156282
commit 335b67c66a
6 changed files with 138 additions and 19 deletions

View File

@ -17,7 +17,6 @@ import contextlib
import os
import six
import shutil
import sys
import yaml
import zipfile
@ -531,7 +530,7 @@ def clean_policyd_dir_for(service, keep_paths=None, user=None, group=None):
hookenv.log("Cleaning path: {}".format(path), level=hookenv.DEBUG)
if not os.path.exists(path):
ch_host.mkdir(path, owner=_user, group=_group, perms=0o775)
_scanner = os.scandir if sys.version_info > (3, 4) else _py2_scandir
_scanner = os.scandir if hasattr(os, 'scandir') else _fallback_scandir
for direntry in _scanner(path):
# see if the path should be kept.
if direntry.path in keep_paths:
@ -560,23 +559,25 @@ def maybe_create_directory_for(path, user, group):
@contextlib.contextmanager
def _py2_scandir(path):
"""provide a py2 implementation of os.scandir if this module ever gets used
in a py2 charm (unlikely). uses os.listdir() to get the names in the path,
and then mocks the is_dir() function using os.path.isdir() to check for a
def _fallback_scandir(path):
"""Fallback os.scandir implementation.
provide a fallback implementation of os.scandir if this module ever gets
used in a py2 or py34 charm. Uses os.listdir() to get the names in the path,
and then mocks the is_dir() function using os.path.isdir() to check for
directory.
:param path: the path to list the directories for
:type path: str
:returns: Generator that provides _P27Direntry objects
:rtype: ContextManager[_P27Direntry]
:returns: Generator that provides _FBDirectory objects
:rtype: ContextManager[_FBDirectory]
"""
for f in os.listdir(path):
yield _P27Direntry(f)
yield _FBDirectory(f)
class _P27Direntry(object):
"""Mock a scandir Direntry object with enough to use in
class _FBDirectory(object):
"""Mock a scandir Directory object with enough to use in
clean_policyd_dir_for
"""

View File

@ -658,6 +658,93 @@ def config_value_changed(option):
return current != saved
def get_endpoint_key(service_name, relation_id, unit_name):
"""Return the key used to refer to an ep changed notification from a unit.
:param service_name: Service name eg nova, neutron, placement etc
:type service_name: str
:param relation_id: The id of the relation the unit is on.
:type relation_id: str
:param unit_name: The name of the unit publishing the notification.
:type unit_name: str
:returns: The key used to refer to an ep changed notification from a unit
:rtype: str
"""
return '{}-{}-{}'.format(
service_name,
relation_id.replace(':', '_'),
unit_name.replace('/', '_'))
def get_endpoint_notifications(service_names, rel_name='identity-service'):
"""Return all notifications for the given services.
:param service_names: List of service name.
:type service_name: List
:param rel_name: Name of the relation to query
:type rel_name: str
:returns: A dict containing the source of the notification and its nonce.
:rtype: Dict[str, str]
"""
notifications = {}
for rid in relation_ids(rel_name):
for unit in related_units(relid=rid):
ep_changed_json = relation_get(
rid=rid,
unit=unit,
attribute='ep_changed')
if ep_changed_json:
ep_changed = json.loads(ep_changed_json)
for service in service_names:
if ep_changed.get(service):
key = get_endpoint_key(service, rid, unit)
notifications[key] = ep_changed[service]
return notifications
def endpoint_changed(service_name, rel_name='identity-service'):
"""Whether a new notification has been recieved for an endpoint.
:param service_name: Service name eg nova, neutron, placement etc
:type service_name: str
:param rel_name: Name of the relation to query
:type rel_name: str
:returns: Whether endpoint has changed
:rtype: bool
"""
changed = False
with unitdata.HookData()() as t:
db = t[0]
notifications = get_endpoint_notifications(
[service_name],
rel_name=rel_name)
for key, nonce in notifications.items():
if db.get(key) != nonce:
juju_log(('New endpoint change notification found: '
'{}={}').format(key, nonce),
'INFO')
changed = True
break
return changed
def save_endpoint_changed_triggers(service_names, rel_name='identity-service'):
"""Save the enpoint triggers in db so it can be tracked if they changed.
:param service_names: List of service name.
:type service_name: List
:param rel_name: Name of the relation to query
:type rel_name: str
"""
with unitdata.HookData()() as t:
db = t[0]
notifications = get_endpoint_notifications(
service_names,
rel_name=rel_name)
for key, nonce in notifications.items():
db.set(key, nonce)
def save_script_rc(script_path="scripts/scriptrc", **env_vars):
"""
Write an rc file in the charm-delivered directory containing

View File

@ -1042,7 +1042,7 @@ def filesystem_mounted(fs):
def make_filesystem(blk_device, fstype='ext4', timeout=10):
"""Make a new filesystem on the specified block device."""
count = 0
e_noent = os.errno.ENOENT
e_noent = errno.ENOENT
while not os.path.exists(blk_device):
if count >= timeout:
log('Gave up waiting on block device %s' % blk_device,

View File

@ -25,6 +25,7 @@ UBUNTU_RELEASES = (
'cosmic',
'disco',
'eoan',
'focal'
)

View File

@ -1,4 +1,5 @@
import platform
import os
def get_platform():
@ -9,9 +10,13 @@ def get_platform():
This string is used to decide which platform module should be imported.
"""
# linux_distribution is deprecated and will be removed in Python 3.7
# Warings *not* disabled, as we certainly need to fix this.
tuple_platform = platform.linux_distribution()
current_platform = tuple_platform[0]
# Warnings *not* disabled, as we certainly need to fix this.
if hasattr(platform, 'linux_distribution'):
tuple_platform = platform.linux_distribution()
current_platform = tuple_platform[0]
else:
current_platform = _get_platform_from_fs()
if "Ubuntu" in current_platform:
return "ubuntu"
elif "CentOS" in current_platform:
@ -26,3 +31,16 @@ def get_platform():
else:
raise RuntimeError("This module is not supported on {}."
.format(current_platform))
def _get_platform_from_fs():
"""Get Platform from /etc/os-release."""
with open(os.path.join(os.sep, 'etc', 'os-release')) as fin:
content = dict(
line.split('=', 1)
for line in fin.read().splitlines()
if '=' in line
)
for k, v in content.items():
content[k] = v.strip('"')
return content["NAME"]

View File

@ -434,10 +434,12 @@ def identity_joined(rid=None):
public_url = ch_ip.canonical_url(CONFIGS, ch_ip.PUBLIC)
internal_url = ch_ip.canonical_url(CONFIGS, ch_ip.INTERNAL)
admin_url = ch_ip.canonical_url(CONFIGS, ch_ip.ADMIN)
hookenv.relation_set(relation_id=rid,
**ncc_utils.determine_endpoints(public_url,
internal_url,
admin_url))
settings = ncc_utils.determine_endpoints(
public_url,
internal_url,
admin_url)
settings['subscribe_ep_change'] = 'neutron placement'
hookenv.relation_set(relation_id=rid, relation_settings=settings)
@hooks.hook('identity-service-relation-changed')
@ -456,6 +458,16 @@ def identity_changed():
for rid in hookenv.relation_ids('neutron-api'):
neutron_api_relation_joined(rid)
configure_https()
if ch_utils.endpoint_changed('placement'):
hookenv.log("Placement endpoint has changed, restarting services",
"INFO")
ch_host.service_restart('nova-scheduler')
hookenv.log(("Placement endpoint has changed, requesting nova-compute "
"restart"),
"INFO")
for rid in hookenv.relation_ids('cloud-compute'):
compute_joined(rid=rid, remote_restart=True)
ch_utils.save_endpoint_changed_triggers(['placement'])
@hooks.hook('nova-volume-service-relation-joined',