Enable focal and ussuri as part of the gate tests
Add bionic-ussuri and focal-ussuri (with mysql8 support) bundles. Change-Id: I7505e685511b7230d671669d7b5cc84243177155
This commit is contained in:
parent
8c01b71451
commit
d7767bcac2
|
@ -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
|
||||
"""
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
# limitations under the License.
|
||||
|
||||
# Common python helper functions used for OpenStack charms.
|
||||
from collections import OrderedDict
|
||||
from collections import OrderedDict, namedtuple
|
||||
from functools import wraps
|
||||
|
||||
import subprocess
|
||||
|
@ -36,15 +36,20 @@ from charmhelpers.contrib.network import ip
|
|||
from charmhelpers.core import unitdata
|
||||
|
||||
from charmhelpers.core.hookenv import (
|
||||
WORKLOAD_STATES,
|
||||
action_fail,
|
||||
action_set,
|
||||
config,
|
||||
expected_peer_units,
|
||||
expected_related_units,
|
||||
log as juju_log,
|
||||
charm_dir,
|
||||
INFO,
|
||||
ERROR,
|
||||
metadata,
|
||||
related_units,
|
||||
relation_get,
|
||||
relation_id,
|
||||
relation_ids,
|
||||
relation_set,
|
||||
status_set,
|
||||
|
@ -53,6 +58,7 @@ from charmhelpers.core.hookenv import (
|
|||
cached,
|
||||
leader_set,
|
||||
leader_get,
|
||||
local_unit,
|
||||
)
|
||||
|
||||
from charmhelpers.core.strutils import (
|
||||
|
@ -108,6 +114,10 @@ from charmhelpers.contrib.openstack.policyd import (
|
|||
POLICYD_CONFIG_NAME,
|
||||
)
|
||||
|
||||
from charmhelpers.contrib.openstack.ha.utils import (
|
||||
expect_ha,
|
||||
)
|
||||
|
||||
CLOUD_ARCHIVE_URL = "http://ubuntu-cloud.archive.canonical.com/ubuntu"
|
||||
CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA'
|
||||
|
||||
|
@ -278,7 +288,7 @@ PACKAGE_CODENAMES = {
|
|||
('14', 'rocky'),
|
||||
('15', 'stein'),
|
||||
('16', 'train'),
|
||||
('17', 'ussuri'),
|
||||
('18', 'ussuri'),
|
||||
]),
|
||||
'ceilometer-common': OrderedDict([
|
||||
('5', 'liberty'),
|
||||
|
@ -326,7 +336,7 @@ PACKAGE_CODENAMES = {
|
|||
('14', 'rocky'),
|
||||
('15', 'stein'),
|
||||
('16', 'train'),
|
||||
('17', 'ussuri'),
|
||||
('18', 'ussuri'),
|
||||
]),
|
||||
}
|
||||
|
||||
|
@ -555,9 +565,8 @@ def reset_os_release():
|
|||
_os_rel = None
|
||||
|
||||
|
||||
def os_release(package, base=None, reset_cache=False):
|
||||
'''
|
||||
Returns OpenStack release codename from a cached global.
|
||||
def os_release(package, base=None, reset_cache=False, source_key=None):
|
||||
"""Returns OpenStack release codename from a cached global.
|
||||
|
||||
If reset_cache then unset the cached os_release version and return the
|
||||
freshly determined version.
|
||||
|
@ -565,7 +574,20 @@ def os_release(package, base=None, reset_cache=False):
|
|||
If the codename can not be determined from either an installed package or
|
||||
the installation source, the earliest release supported by the charm should
|
||||
be returned.
|
||||
'''
|
||||
|
||||
:param package: Name of package to determine release from
|
||||
:type package: str
|
||||
:param base: Fallback codename if endavours to determine from package fail
|
||||
:type base: Optional[str]
|
||||
:param reset_cache: Reset any cached codename value
|
||||
:type reset_cache: bool
|
||||
:param source_key: Name of source configuration option
|
||||
(default: 'openstack-origin')
|
||||
:type source_key: Optional[str]
|
||||
:returns: OpenStack release codename
|
||||
:rtype: str
|
||||
"""
|
||||
source_key = source_key or 'openstack-origin'
|
||||
if not base:
|
||||
base = UBUNTU_OPENSTACK_RELEASE[lsb_release()['DISTRIB_CODENAME']]
|
||||
global _os_rel
|
||||
|
@ -575,7 +597,7 @@ def os_release(package, base=None, reset_cache=False):
|
|||
return _os_rel
|
||||
_os_rel = (
|
||||
get_os_codename_package(package, fatal=False) or
|
||||
get_os_codename_install_source(config('openstack-origin')) or
|
||||
get_os_codename_install_source(config(source_key)) or
|
||||
base)
|
||||
return _os_rel
|
||||
|
||||
|
@ -658,6 +680,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
|
||||
|
@ -1711,6 +1820,16 @@ def os_application_version_set(package):
|
|||
application_version_set(application_version)
|
||||
|
||||
|
||||
def os_application_status_set(check_function):
|
||||
"""Run the supplied function and set the application status accordingly.
|
||||
|
||||
:param check_function: Function to run to get app states and messages.
|
||||
:type check_function: function
|
||||
"""
|
||||
state, message = check_function()
|
||||
status_set(state, message, application=True)
|
||||
|
||||
|
||||
def enable_memcache(source=None, release=None, package=None):
|
||||
"""Determine if memcache should be enabled on the local unit
|
||||
|
||||
|
@ -1947,3 +2066,287 @@ def is_db_maintenance_mode(relid=None):
|
|||
'WARN')
|
||||
pass
|
||||
return True in notifications
|
||||
|
||||
|
||||
@cached
|
||||
def container_scoped_relations():
|
||||
"""Get all the container scoped relations
|
||||
|
||||
:returns: List of relation names
|
||||
:rtype: List
|
||||
"""
|
||||
md = metadata()
|
||||
relations = []
|
||||
for relation_type in ('provides', 'requires', 'peers'):
|
||||
for relation in md.get(relation_type, []):
|
||||
if md[relation_type][relation].get('scope') == 'container':
|
||||
relations.append(relation)
|
||||
return relations
|
||||
|
||||
|
||||
def is_db_ready(use_current_context=False, rel_name=None):
|
||||
"""Check remote database is ready to be used.
|
||||
|
||||
Database relations are expected to provide a list of 'allowed' units to
|
||||
confirm that the database is ready for use by those units.
|
||||
|
||||
If db relation has provided this information and local unit is a member,
|
||||
returns True otherwise False.
|
||||
|
||||
:param use_current_context: Whether to limit checks to current hook
|
||||
context.
|
||||
:type use_current_context: bool
|
||||
:param rel_name: Name of relation to check
|
||||
:type rel_name: string
|
||||
:returns: Whether remote db is ready.
|
||||
:rtype: bool
|
||||
:raises: Exception
|
||||
"""
|
||||
key = 'allowed_units'
|
||||
|
||||
rel_name = rel_name or 'shared-db'
|
||||
this_unit = local_unit()
|
||||
|
||||
if use_current_context:
|
||||
if relation_id() in relation_ids(rel_name):
|
||||
rids_units = [(None, None)]
|
||||
else:
|
||||
raise Exception("use_current_context=True but not in {} "
|
||||
"rel hook contexts (currently in {})."
|
||||
.format(rel_name, relation_id()))
|
||||
else:
|
||||
rids_units = [(r_id, u)
|
||||
for r_id in relation_ids(rel_name)
|
||||
for u in related_units(r_id)]
|
||||
|
||||
for rid, unit in rids_units:
|
||||
allowed_units = relation_get(rid=rid, unit=unit, attribute=key)
|
||||
if allowed_units and this_unit in allowed_units.split():
|
||||
juju_log("This unit ({}) is in allowed unit list from {}".format(
|
||||
this_unit,
|
||||
unit), 'DEBUG')
|
||||
return True
|
||||
|
||||
juju_log("This unit was not found in any allowed unit list")
|
||||
return False
|
||||
|
||||
|
||||
def is_expected_scale(peer_relation_name='cluster'):
|
||||
"""Query juju goal-state to determine whether our peer- and dependency-
|
||||
relations are at the expected scale.
|
||||
|
||||
Useful for deferring per unit per relation housekeeping work until we are
|
||||
ready to complete it successfully and without unnecessary repetiton.
|
||||
|
||||
Always returns True if version of juju used does not support goal-state.
|
||||
|
||||
:param peer_relation_name: Name of peer relation
|
||||
:type rel_name: string
|
||||
:returns: True or False
|
||||
:rtype: bool
|
||||
"""
|
||||
def _get_relation_id(rel_type):
|
||||
return next((rid for rid in relation_ids(reltype=rel_type)), None)
|
||||
|
||||
Relation = namedtuple('Relation', 'rel_type rel_id')
|
||||
peer_rid = _get_relation_id(peer_relation_name)
|
||||
# Units with no peers should still have a peer relation.
|
||||
if not peer_rid:
|
||||
juju_log('Not at expected scale, no peer relation found', 'DEBUG')
|
||||
return False
|
||||
expected_relations = [
|
||||
Relation(rel_type='shared-db', rel_id=_get_relation_id('shared-db'))]
|
||||
if expect_ha():
|
||||
expected_relations.append(
|
||||
Relation(
|
||||
rel_type='ha',
|
||||
rel_id=_get_relation_id('ha')))
|
||||
juju_log(
|
||||
'Checking scale of {} relations'.format(
|
||||
','.join([r.rel_type for r in expected_relations])),
|
||||
'DEBUG')
|
||||
try:
|
||||
if (len(related_units(relid=peer_rid)) <
|
||||
len(list(expected_peer_units()))):
|
||||
return False
|
||||
for rel in expected_relations:
|
||||
if not rel.rel_id:
|
||||
juju_log(
|
||||
'Expected to find {} relation, but it is missing'.format(
|
||||
rel.rel_type),
|
||||
'DEBUG')
|
||||
return False
|
||||
# Goal state returns every unit even for container scoped
|
||||
# relations but the charm only ever has a relation with
|
||||
# the local unit.
|
||||
if rel.rel_type in container_scoped_relations():
|
||||
expected_count = 1
|
||||
else:
|
||||
expected_count = len(
|
||||
list(expected_related_units(reltype=rel.rel_type)))
|
||||
if len(related_units(relid=rel.rel_id)) < expected_count:
|
||||
juju_log(
|
||||
('Not at expected scale, not enough units on {} '
|
||||
'relation'.format(rel.rel_type)),
|
||||
'DEBUG')
|
||||
return False
|
||||
except NotImplementedError:
|
||||
return True
|
||||
juju_log('All checks have passed, unit is at expected scale', 'DEBUG')
|
||||
return True
|
||||
|
||||
|
||||
def get_peer_key(unit_name):
|
||||
"""Get the peer key for this unit.
|
||||
|
||||
The peer key is the key a unit uses to publish its status down the peer
|
||||
relation
|
||||
|
||||
:param unit_name: Name of unit
|
||||
:type unit_name: string
|
||||
:returns: Peer key for given unit
|
||||
:rtype: string
|
||||
"""
|
||||
return 'unit-state-{}'.format(unit_name.replace('/', '-'))
|
||||
|
||||
|
||||
UNIT_READY = 'READY'
|
||||
UNIT_NOTREADY = 'NOTREADY'
|
||||
UNIT_UNKNOWN = 'UNKNOWN'
|
||||
UNIT_STATES = [UNIT_READY, UNIT_NOTREADY, UNIT_UNKNOWN]
|
||||
|
||||
|
||||
def inform_peers_unit_state(state, relation_name='cluster'):
|
||||
"""Inform peers of the state of this unit.
|
||||
|
||||
:param state: State of unit to publish
|
||||
:type state: string
|
||||
:param relation_name: Name of relation to publish state on
|
||||
:type relation_name: string
|
||||
"""
|
||||
if state not in UNIT_STATES:
|
||||
raise ValueError(
|
||||
"Setting invalid state {} for unit".format(state))
|
||||
for r_id in relation_ids(relation_name):
|
||||
relation_set(relation_id=r_id,
|
||||
relation_settings={
|
||||
get_peer_key(local_unit()): state})
|
||||
|
||||
|
||||
def get_peers_unit_state(relation_name='cluster'):
|
||||
"""Get the state of all peers.
|
||||
|
||||
:param relation_name: Name of relation to check peers on.
|
||||
:type relation_name: string
|
||||
:returns: Unit states keyed on unit name.
|
||||
:rtype: dict
|
||||
:raises: ValueError
|
||||
"""
|
||||
r_ids = relation_ids(relation_name)
|
||||
rids_units = [(r, u) for r in r_ids for u in related_units(r)]
|
||||
unit_states = {}
|
||||
for r_id, unit in rids_units:
|
||||
settings = relation_get(unit=unit, rid=r_id)
|
||||
unit_states[unit] = settings.get(get_peer_key(unit), UNIT_UNKNOWN)
|
||||
if unit_states[unit] not in UNIT_STATES:
|
||||
raise ValueError(
|
||||
"Unit in unknown state {}".format(unit_states[unit]))
|
||||
return unit_states
|
||||
|
||||
|
||||
def are_peers_ready(relation_name='cluster'):
|
||||
"""Check if all peers are ready.
|
||||
|
||||
:param relation_name: Name of relation to check peers on.
|
||||
:type relation_name: string
|
||||
:returns: Whether all units are ready.
|
||||
:rtype: bool
|
||||
"""
|
||||
unit_states = get_peers_unit_state(relation_name)
|
||||
return all(v == UNIT_READY for v in unit_states.values())
|
||||
|
||||
|
||||
def inform_peers_if_ready(check_unit_ready_func, relation_name='cluster'):
|
||||
"""Inform peers if this unit is ready.
|
||||
|
||||
The check function should return a tuple (state, message). A state
|
||||
of 'READY' indicates the unit is READY.
|
||||
|
||||
:param check_unit_ready_func: Function to run to check readiness
|
||||
:type check_unit_ready_func: function
|
||||
:param relation_name: Name of relation to check peers on.
|
||||
:type relation_name: string
|
||||
"""
|
||||
unit_ready, msg = check_unit_ready_func()
|
||||
if unit_ready:
|
||||
state = UNIT_READY
|
||||
else:
|
||||
state = UNIT_NOTREADY
|
||||
juju_log('Telling peers this unit is: {}'.format(state), 'DEBUG')
|
||||
inform_peers_unit_state(state, relation_name)
|
||||
|
||||
|
||||
def check_api_unit_ready(check_db_ready=True):
|
||||
"""Check if this unit is ready.
|
||||
|
||||
:param check_db_ready: Include checks of database readiness.
|
||||
:type check_db_ready: bool
|
||||
:returns: Whether unit state is ready and status message
|
||||
:rtype: (bool, str)
|
||||
"""
|
||||
unit_state, msg = get_api_unit_status(check_db_ready=check_db_ready)
|
||||
return unit_state == WORKLOAD_STATES.ACTIVE, msg
|
||||
|
||||
|
||||
def get_api_unit_status(check_db_ready=True):
|
||||
"""Return a workload status and message for this unit.
|
||||
|
||||
:param check_db_ready: Include checks of database readiness.
|
||||
:type check_db_ready: bool
|
||||
:returns: Workload state and message
|
||||
:rtype: (bool, str)
|
||||
"""
|
||||
unit_state = WORKLOAD_STATES.ACTIVE
|
||||
msg = 'Unit is ready'
|
||||
if is_db_maintenance_mode():
|
||||
unit_state = WORKLOAD_STATES.MAINTENANCE
|
||||
msg = 'Database in maintenance mode.'
|
||||
elif is_unit_paused_set():
|
||||
unit_state = WORKLOAD_STATES.BLOCKED
|
||||
msg = 'Unit paused.'
|
||||
elif check_db_ready and not is_db_ready():
|
||||
unit_state = WORKLOAD_STATES.WAITING
|
||||
msg = 'Allowed_units list provided but this unit not present'
|
||||
elif not is_db_initialised():
|
||||
unit_state = WORKLOAD_STATES.WAITING
|
||||
msg = 'Database not initialised'
|
||||
elif not is_expected_scale():
|
||||
unit_state = WORKLOAD_STATES.WAITING
|
||||
msg = 'Charm and its dependencies not yet at expected scale'
|
||||
juju_log(msg, 'DEBUG')
|
||||
return unit_state, msg
|
||||
|
||||
|
||||
def check_api_application_ready():
|
||||
"""Check if this application is ready.
|
||||
|
||||
:returns: Whether application state is ready and status message
|
||||
:rtype: (bool, str)
|
||||
"""
|
||||
app_state, msg = get_api_application_status()
|
||||
return app_state == WORKLOAD_STATES.ACTIVE, msg
|
||||
|
||||
|
||||
def get_api_application_status():
|
||||
"""Return a workload status and message for this application.
|
||||
|
||||
:returns: Workload state and message
|
||||
:rtype: (bool, str)
|
||||
"""
|
||||
app_state, msg = get_api_unit_status()
|
||||
if app_state == WORKLOAD_STATES.ACTIVE:
|
||||
if are_peers_ready():
|
||||
return WORKLOAD_STATES.ACTIVE, 'Application Ready'
|
||||
else:
|
||||
return WORKLOAD_STATES.WAITING, 'Some units are not ready'
|
||||
return app_state, msg
|
||||
|
|
|
@ -37,7 +37,19 @@ class VaultKVContext(context.OSContextGenerator):
|
|||
)
|
||||
|
||||
def __call__(self):
|
||||
import hvac
|
||||
try:
|
||||
import hvac
|
||||
except ImportError:
|
||||
# BUG: #1862085 - if the relation is made to vault, but the
|
||||
# 'encrypt' option is not made, then the charm errors with an
|
||||
# import warning. This catches that, logs a warning, and returns
|
||||
# with an empty context.
|
||||
hookenv.log("VaultKVContext: trying to use hvac pythong module "
|
||||
"but it's not available. Is secrets-stroage relation "
|
||||
"made, but encrypt option not set?",
|
||||
level=hookenv.WARNING)
|
||||
# return an emptry context on hvac import error
|
||||
return {}
|
||||
ctxt = {}
|
||||
# NOTE(hopem): see https://bugs.launchpad.net/charm-helpers/+bug/1849323
|
||||
db = unitdata.kv()
|
||||
|
@ -128,9 +140,16 @@ def vault_relation_complete(backend=None):
|
|||
:ptype backend: string
|
||||
:returns: whether the relation to vault is complete
|
||||
:rtype: bool"""
|
||||
vault_kv = VaultKVContext(secret_backend=backend or VAULTLOCKER_BACKEND)
|
||||
vault_kv()
|
||||
return vault_kv.complete
|
||||
try:
|
||||
import hvac
|
||||
except ImportError:
|
||||
return False
|
||||
try:
|
||||
vault_kv = VaultKVContext(secret_backend=backend or VAULTLOCKER_BACKEND)
|
||||
vault_kv()
|
||||
return vault_kv.complete
|
||||
except hvac.exceptions.InvalidRequest:
|
||||
return False
|
||||
|
||||
|
||||
# TODO: contrib a high level unwrap method to hvac that works
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
# Adam Gandelman <adamg@ubuntu.com>
|
||||
#
|
||||
|
||||
import collections
|
||||
import errno
|
||||
import hashlib
|
||||
import math
|
||||
|
@ -93,6 +94,88 @@ LEGACY_PG_COUNT = 200
|
|||
DEFAULT_MINIMUM_PGS = 2
|
||||
|
||||
|
||||
class OsdPostUpgradeError(Exception):
|
||||
"""Error class for OSD post-upgrade operations."""
|
||||
pass
|
||||
|
||||
|
||||
class OSDSettingConflict(Exception):
|
||||
"""Error class for conflicting osd setting requests."""
|
||||
pass
|
||||
|
||||
|
||||
class OSDSettingNotAllowed(Exception):
|
||||
"""Error class for a disallowed setting."""
|
||||
pass
|
||||
|
||||
|
||||
OSD_SETTING_EXCEPTIONS = (OSDSettingConflict, OSDSettingNotAllowed)
|
||||
|
||||
OSD_SETTING_WHITELIST = [
|
||||
'osd heartbeat grace',
|
||||
'osd heartbeat interval',
|
||||
]
|
||||
|
||||
|
||||
def _order_dict_by_key(rdict):
|
||||
"""Convert a dictionary into an OrderedDict sorted by key.
|
||||
|
||||
:param rdict: Dictionary to be ordered.
|
||||
:type rdict: dict
|
||||
:returns: Ordered Dictionary.
|
||||
:rtype: collections.OrderedDict
|
||||
"""
|
||||
return collections.OrderedDict(sorted(rdict.items(), key=lambda k: k[0]))
|
||||
|
||||
|
||||
def get_osd_settings(relation_name):
|
||||
"""Consolidate requested osd settings from all clients.
|
||||
|
||||
Consolidate requested osd settings from all clients. Check that the
|
||||
requested setting is on the whitelist and it does not conflict with
|
||||
any other requested settings.
|
||||
|
||||
:returns: Dictionary of settings
|
||||
:rtype: dict
|
||||
|
||||
:raises: OSDSettingNotAllowed
|
||||
:raises: OSDSettingConflict
|
||||
"""
|
||||
rel_ids = relation_ids(relation_name)
|
||||
osd_settings = {}
|
||||
for relid in rel_ids:
|
||||
for unit in related_units(relid):
|
||||
unit_settings = relation_get('osd-settings', unit, relid) or '{}'
|
||||
unit_settings = json.loads(unit_settings)
|
||||
for key, value in unit_settings.items():
|
||||
if key not in OSD_SETTING_WHITELIST:
|
||||
msg = 'Illegal settings "{}"'.format(key)
|
||||
raise OSDSettingNotAllowed(msg)
|
||||
if key in osd_settings:
|
||||
if osd_settings[key] != unit_settings[key]:
|
||||
msg = 'Conflicting settings for "{}"'.format(key)
|
||||
raise OSDSettingConflict(msg)
|
||||
else:
|
||||
osd_settings[key] = value
|
||||
return _order_dict_by_key(osd_settings)
|
||||
|
||||
|
||||
def send_osd_settings():
|
||||
"""Pass on requested OSD settings to osd units."""
|
||||
try:
|
||||
settings = get_osd_settings('client')
|
||||
except OSD_SETTING_EXCEPTIONS as e:
|
||||
# There is a problem with the settings, not passing them on. Update
|
||||
# status will notify the user.
|
||||
log(e, level=ERROR)
|
||||
return
|
||||
data = {
|
||||
'osd-settings': json.dumps(settings, sort_keys=True)}
|
||||
for relid in relation_ids('osd'):
|
||||
relation_set(relation_id=relid,
|
||||
relation_settings=data)
|
||||
|
||||
|
||||
def validator(value, valid_type, valid_range=None):
|
||||
"""
|
||||
Used to validate these: http://docs.ceph.com/docs/master/rados/operations/pools/#set-pool-values
|
||||
|
@ -1042,7 +1125,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,
|
||||
|
@ -1635,5 +1718,67 @@ class CephConfContext(object):
|
|||
continue
|
||||
|
||||
ceph_conf[key] = conf[key]
|
||||
|
||||
return ceph_conf
|
||||
|
||||
|
||||
class CephOSDConfContext(CephConfContext):
|
||||
"""Ceph config (ceph.conf) context.
|
||||
|
||||
Consolidates settings from config-flags via CephConfContext with
|
||||
settings provided by the mons. The config-flag values are preserved in
|
||||
conf['osd'], settings from the mons which do not clash with config-flag
|
||||
settings are in conf['osd_from_client'] and finally settings which do
|
||||
clash are in conf['osd_from_client_conflict']. Rather than silently drop
|
||||
the conflicting settings they are provided in the context so they can be
|
||||
rendered commented out to give some visability to the admin.
|
||||
"""
|
||||
|
||||
def __init__(self, permitted_sections=None):
|
||||
super(CephOSDConfContext, self).__init__(
|
||||
permitted_sections=permitted_sections)
|
||||
try:
|
||||
self.settings_from_mons = get_osd_settings('mon')
|
||||
except OSDSettingConflict:
|
||||
log(
|
||||
"OSD settings from mons are inconsistent, ignoring them",
|
||||
level=WARNING)
|
||||
self.settings_from_mons = {}
|
||||
|
||||
def filter_osd_from_mon_settings(self):
|
||||
"""Filter settings from client relation against config-flags.
|
||||
|
||||
:returns: A tuple (
|
||||
,config-flag values,
|
||||
,client settings which do not conflict with config-flag values,
|
||||
,client settings which confilct with config-flag values)
|
||||
:rtype: (OrderedDict, OrderedDict, OrderedDict)
|
||||
"""
|
||||
ceph_conf = super(CephOSDConfContext, self).__call__()
|
||||
conflicting_entries = {}
|
||||
clear_entries = {}
|
||||
for key, value in self.settings_from_mons.items():
|
||||
if key in ceph_conf.get('osd', {}):
|
||||
if ceph_conf['osd'][key] != value:
|
||||
conflicting_entries[key] = value
|
||||
else:
|
||||
clear_entries[key] = value
|
||||
clear_entries = _order_dict_by_key(clear_entries)
|
||||
conflicting_entries = _order_dict_by_key(conflicting_entries)
|
||||
return ceph_conf, clear_entries, conflicting_entries
|
||||
|
||||
def __call__(self):
|
||||
"""Construct OSD config context.
|
||||
|
||||
Standard context with two additional special keys.
|
||||
osd_from_client_conflict: client settings which confilct with
|
||||
config-flag values
|
||||
osd_from_client: settings which do not conflict with config-flag
|
||||
values
|
||||
|
||||
:returns: OSD config context dict.
|
||||
:rtype: dict
|
||||
"""
|
||||
conf, osd_clear, osd_conflict = self.filter_osd_from_mon_settings()
|
||||
conf['osd_from_client_conflict'] = osd_conflict
|
||||
conf['osd_from_client'] = osd_clear
|
||||
return conf
|
||||
|
|
|
@ -32,6 +32,10 @@ def loopback_devices():
|
|||
|
||||
/dev/loop0: [0807]:961814 (/tmp/my.img)
|
||||
|
||||
or:
|
||||
|
||||
/dev/loop0: [0807]:961814 (/tmp/my.img (deleted))
|
||||
|
||||
:returns: dict: a dict mapping {loopback_dev: backing_file}
|
||||
'''
|
||||
loopbacks = {}
|
||||
|
@ -39,9 +43,9 @@ def loopback_devices():
|
|||
output = check_output(cmd)
|
||||
if six.PY3:
|
||||
output = output.decode('utf-8')
|
||||
devs = [d.strip().split(' ') for d in output.splitlines() if d != '']
|
||||
devs = [d.strip().split(' ', 2) for d in output.splitlines() if d != '']
|
||||
for dev, _, f in devs:
|
||||
loopbacks[dev.replace(':', '')] = re.search(r'\((\S+)\)', f).groups()[0]
|
||||
loopbacks[dev.replace(':', '')] = re.search(r'\((.+)\)', f).groups()[0]
|
||||
return loopbacks
|
||||
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
from __future__ import print_function
|
||||
import copy
|
||||
from distutils.version import LooseVersion
|
||||
from enum import Enum
|
||||
from functools import wraps
|
||||
from collections import namedtuple
|
||||
import glob
|
||||
|
@ -57,6 +58,14 @@ RANGE_WARNING = ('Passing NO_PROXY string that includes a cidr. '
|
|||
'This may not be compatible with software you are '
|
||||
'running in your shell.')
|
||||
|
||||
|
||||
class WORKLOAD_STATES(Enum):
|
||||
ACTIVE = 'active'
|
||||
BLOCKED = 'blocked'
|
||||
MAINTENANCE = 'maintenance'
|
||||
WAITING = 'waiting'
|
||||
|
||||
|
||||
cache = {}
|
||||
|
||||
|
||||
|
@ -1088,22 +1097,33 @@ def function_tag():
|
|||
return os.environ.get('JUJU_FUNCTION_TAG') or action_tag()
|
||||
|
||||
|
||||
def status_set(workload_state, message):
|
||||
def status_set(workload_state, message, application=False):
|
||||
"""Set the workload state with a message
|
||||
|
||||
Use status-set to set the workload state with a message which is visible
|
||||
to the user via juju status. If the status-set command is not found then
|
||||
assume this is juju < 1.23 and juju-log the message unstead.
|
||||
assume this is juju < 1.23 and juju-log the message instead.
|
||||
|
||||
workload_state -- valid juju workload state.
|
||||
message -- status update message
|
||||
workload_state -- valid juju workload state. str or WORKLOAD_STATES
|
||||
message -- status update message
|
||||
application -- Whether this is an application state set
|
||||
"""
|
||||
valid_states = ['maintenance', 'blocked', 'waiting', 'active']
|
||||
if workload_state not in valid_states:
|
||||
raise ValueError(
|
||||
'{!r} is not a valid workload state'.format(workload_state)
|
||||
)
|
||||
cmd = ['status-set', workload_state, message]
|
||||
bad_state_msg = '{!r} is not a valid workload state'
|
||||
|
||||
if isinstance(workload_state, str):
|
||||
try:
|
||||
# Convert string to enum.
|
||||
workload_state = WORKLOAD_STATES[workload_state.upper()]
|
||||
except KeyError:
|
||||
raise ValueError(bad_state_msg.format(workload_state))
|
||||
|
||||
if workload_state not in WORKLOAD_STATES:
|
||||
raise ValueError(bad_state_msg.format(workload_state))
|
||||
|
||||
cmd = ['status-set']
|
||||
if application:
|
||||
cmd.append('--application')
|
||||
cmd.extend([workload_state.value, message])
|
||||
try:
|
||||
ret = subprocess.call(cmd)
|
||||
if ret == 0:
|
||||
|
@ -1111,7 +1131,7 @@ def status_set(workload_state, message):
|
|||
except OSError as e:
|
||||
if e.errno != errno.ENOENT:
|
||||
raise
|
||||
log_message = 'status-set failed: {} {}'.format(workload_state,
|
||||
log_message = 'status-set failed: {} {}'.format(workload_state.value,
|
||||
message)
|
||||
log(log_message, level='INFO')
|
||||
|
||||
|
@ -1526,13 +1546,13 @@ def env_proxy_settings(selected_settings=None):
|
|||
"""Get proxy settings from process environment variables.
|
||||
|
||||
Get charm proxy settings from environment variables that correspond to
|
||||
juju-http-proxy, juju-https-proxy and juju-no-proxy (available as of 2.4.2,
|
||||
see lp:1782236) in a format suitable for passing to an application that
|
||||
reacts to proxy settings passed as environment variables. Some applications
|
||||
support lowercase or uppercase notation (e.g. curl), some support only
|
||||
lowercase (e.g. wget), there are also subjectively rare cases of only
|
||||
uppercase notation support. no_proxy CIDR and wildcard support also varies
|
||||
between runtimes and applications as there is no enforced standard.
|
||||
juju-http-proxy, juju-https-proxy juju-no-proxy (available as of 2.4.2, see
|
||||
lp:1782236) and juju-ftp-proxy in a format suitable for passing to an
|
||||
application that reacts to proxy settings passed as environment variables.
|
||||
Some applications support lowercase or uppercase notation (e.g. curl), some
|
||||
support only lowercase (e.g. wget), there are also subjectively rare cases
|
||||
of only uppercase notation support. no_proxy CIDR and wildcard support also
|
||||
varies between runtimes and applications as there is no enforced standard.
|
||||
|
||||
Some applications may connect to multiple destinations and expose config
|
||||
options that would affect only proxy settings for a specific destination
|
||||
|
|
|
@ -25,6 +25,7 @@ UBUNTU_RELEASES = (
|
|||
'cosmic',
|
||||
'disco',
|
||||
'eoan',
|
||||
'focal'
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -17,14 +17,17 @@
|
|||
|
||||
import yaml
|
||||
|
||||
from subprocess import check_call
|
||||
from subprocess import check_call, CalledProcessError
|
||||
|
||||
from charmhelpers.core.hookenv import (
|
||||
log,
|
||||
DEBUG,
|
||||
ERROR,
|
||||
WARNING,
|
||||
)
|
||||
|
||||
from charmhelpers.core.host import is_container
|
||||
|
||||
__author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
|
||||
|
||||
|
||||
|
@ -62,4 +65,11 @@ def create(sysctl_dict, sysctl_file, ignore=False):
|
|||
if ignore:
|
||||
call.append("-e")
|
||||
|
||||
check_call(call)
|
||||
try:
|
||||
check_call(call)
|
||||
except CalledProcessError as e:
|
||||
if is_container():
|
||||
log("Error setting some sysctl keys in this container: {}".format(e.output),
|
||||
level=WARNING)
|
||||
else:
|
||||
raise e
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -11,6 +11,7 @@ series:
|
|||
- xenial
|
||||
- bionic
|
||||
- eoan
|
||||
- focal
|
||||
- trusty
|
||||
extra-bindings:
|
||||
public:
|
||||
|
|
|
@ -16,6 +16,7 @@ machines:
|
|||
7: {}
|
||||
8: {}
|
||||
9: {}
|
||||
10: {}
|
||||
|
||||
# We specify machine placements for these to improve iteration
|
||||
# time, given that machine "0" comes up way before machine "7"
|
||||
|
@ -83,13 +84,15 @@ applications:
|
|||
num_units: 1
|
||||
options:
|
||||
openstack-origin: *openstack-origin
|
||||
to:
|
||||
- '7'
|
||||
neutron-api:
|
||||
charm: cs:~openstack-charmers-next/neutron-api
|
||||
num_units: 1
|
||||
options:
|
||||
openstack-origin: *openstack-origin
|
||||
to:
|
||||
- '7'
|
||||
- '8'
|
||||
neutron-openvswitch:
|
||||
charm: cs:~openstack-charmers-next/neutron-openvswitch
|
||||
heat:
|
||||
|
@ -102,8 +105,8 @@ applications:
|
|||
debug: "True"
|
||||
verbose: "True"
|
||||
to:
|
||||
- '8'
|
||||
- '9'
|
||||
- '10'
|
||||
relations:
|
||||
- - heat:amqp
|
||||
- rabbitmq-server:amqp
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
variables:
|
||||
openstack-origin: &openstack-origin cloud:bionic-ussuri
|
||||
|
||||
series: &series bionic
|
||||
|
||||
machines:
|
||||
0:
|
||||
constraints: "mem=3072M"
|
||||
1: {}
|
||||
2: {}
|
||||
3: {}
|
||||
4: {}
|
||||
5:
|
||||
constraints: "root-disk=20G mem=4G cores=4"
|
||||
6: {}
|
||||
7: {}
|
||||
8: {}
|
||||
9:
|
||||
constaints: "mem=2048M"
|
||||
10:
|
||||
constaints: "mem=2048M"
|
||||
|
||||
# We specify machine placements for these to improve iteration
|
||||
# time, given that machine "0" comes up way before machine "7"
|
||||
applications:
|
||||
percona-cluster:
|
||||
charm: cs:~openstack-charmers-next/percona-cluster
|
||||
num_units: 1
|
||||
options:
|
||||
innodb-buffer-pool-size: 256M
|
||||
dataset-size: 25%
|
||||
max-connections: 1000
|
||||
source: *openstack-origin
|
||||
to:
|
||||
- '0'
|
||||
keystone:
|
||||
charm: cs:~openstack-charmers-next/keystone
|
||||
num_units: 1
|
||||
options:
|
||||
openstack-origin: *openstack-origin
|
||||
to:
|
||||
- '1'
|
||||
rabbitmq-server:
|
||||
charm: cs:~openstack-charmers-next/rabbitmq-server
|
||||
num_units: 1
|
||||
options:
|
||||
source: *openstack-origin
|
||||
to:
|
||||
- '2'
|
||||
glance:
|
||||
charm: cs:~openstack-charmers-next/glance
|
||||
num_units: 1
|
||||
options:
|
||||
openstack-origin: *openstack-origin
|
||||
to:
|
||||
- '3'
|
||||
nova-cloud-controller:
|
||||
charm: cs:~openstack-charmers-next/nova-cloud-controller
|
||||
num_units: 1
|
||||
options:
|
||||
openstack-origin: *openstack-origin
|
||||
api-rate-limit-rules: "( POST, '*', .*, 9999, MINUTE );"
|
||||
network-manager: Neutron
|
||||
to:
|
||||
- '4'
|
||||
nova-compute:
|
||||
charm: cs:~openstack-charmers-next/nova-compute
|
||||
num_units: 1
|
||||
options:
|
||||
openstack-origin: *openstack-origin
|
||||
config-flags: 'auto_assign_floating_ip=False'
|
||||
enable-live-migration: "False"
|
||||
to:
|
||||
- '5'
|
||||
placement:
|
||||
charm: cs:~openstack-charmers-next/placement
|
||||
num_units: 1
|
||||
options:
|
||||
openstack-origin: *openstack-origin
|
||||
debug: "True"
|
||||
to:
|
||||
- '6'
|
||||
neutron-gateway:
|
||||
charm: cs:~openstack-charmers-next/neutron-gateway
|
||||
num_units: 1
|
||||
options:
|
||||
openstack-origin: *openstack-origin
|
||||
to:
|
||||
- '7'
|
||||
neutron-api:
|
||||
charm: cs:~openstack-charmers-next/neutron-api
|
||||
num_units: 1
|
||||
options:
|
||||
manage-neutron-plugin-legacy-mode: true
|
||||
neutron-plugin: ovs
|
||||
openstack-origin: *openstack-origin
|
||||
to:
|
||||
- '8'
|
||||
neutron-openvswitch:
|
||||
charm: cs:~openstack-charmers-next/neutron-openvswitch
|
||||
heat:
|
||||
charm: ../../../heat
|
||||
num_units: 2
|
||||
options:
|
||||
openstack-origin: *openstack-origin
|
||||
debug: "True"
|
||||
verbose: "True"
|
||||
to:
|
||||
- '9'
|
||||
- '10'
|
||||
relations:
|
||||
- - heat:amqp
|
||||
- rabbitmq-server:amqp
|
||||
- - heat:identity-service
|
||||
- keystone:identity-service
|
||||
- - heat:shared-db
|
||||
- percona-cluster:shared-db
|
||||
- - nova-compute:image-service
|
||||
- glance:image-service
|
||||
- - nova-compute:amqp
|
||||
- rabbitmq-server:amqp
|
||||
- - nova-cloud-controller:shared-db
|
||||
- percona-cluster:shared-db
|
||||
- - nova-cloud-controller:identity-service
|
||||
- keystone:identity-service
|
||||
- - nova-cloud-controller:amqp
|
||||
- rabbitmq-server:amqp
|
||||
- - nova-cloud-controller:cloud-compute
|
||||
- nova-compute:cloud-compute
|
||||
- - nova-cloud-controller:image-service
|
||||
- glance:image-service
|
||||
- - placement:shared-db
|
||||
- percona-cluster:shared-db
|
||||
- - placement:identity-service
|
||||
- keystone:identity-service
|
||||
- - placement:placement
|
||||
- nova-cloud-controller:placement
|
||||
- - keystone:shared-db
|
||||
- percona-cluster:shared-db
|
||||
- - glance:identity-service
|
||||
- keystone:identity-service
|
||||
- - glance:shared-db
|
||||
- percona-cluster:shared-db
|
||||
- - glance:amqp
|
||||
- rabbitmq-server:amqp
|
||||
- - neutron-gateway:amqp
|
||||
- rabbitmq-server:amqp
|
||||
- - nova-cloud-controller:quantum-network-service
|
||||
- neutron-gateway:quantum-network-service
|
||||
- - neutron-api:shared-db
|
||||
- percona-cluster:shared-db
|
||||
- - neutron-api:amqp
|
||||
- rabbitmq-server:amqp
|
||||
- - neutron-api:neutron-api
|
||||
- nova-cloud-controller:neutron-api
|
||||
- - neutron-api:identity-service
|
||||
- keystone:identity-service
|
||||
- - nova-compute:neutron-plugin
|
||||
- neutron-openvswitch:neutron-plugin
|
||||
- - rabbitmq-server:amqp
|
||||
- neutron-openvswitch:amqp
|
|
@ -0,0 +1,226 @@
|
|||
variables:
|
||||
openstack-origin: &openstack-origin distro
|
||||
|
||||
series: &series focal
|
||||
|
||||
machines:
|
||||
0:
|
||||
constraints: "mem=3072M"
|
||||
1:
|
||||
constraints: "mem=3072M"
|
||||
2:
|
||||
constraints: "mem=3072M"
|
||||
3:
|
||||
4:
|
||||
5:
|
||||
6:
|
||||
7:
|
||||
constraints: "root-disk=20G mem=4G cores=4"
|
||||
8:
|
||||
9:
|
||||
10:
|
||||
11:
|
||||
constaints: "mem=2048M"
|
||||
12:
|
||||
constaints: "mem=2048M"
|
||||
|
||||
# We specify machine placements for these to improve iteration
|
||||
# time, given that machine "0" comes up way before machine "7"
|
||||
applications:
|
||||
|
||||
heat-mysql-router:
|
||||
charm: cs:~openstack-charmers-next/mysql-router
|
||||
nova-cloud-controller-mysql-router:
|
||||
charm: cs:~openstack-charmers-next/mysql-router
|
||||
placement-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
|
||||
|
||||
mysql-innodb-cluster:
|
||||
charm: cs:~openstack-charmers-next/mysql-innodb-cluster
|
||||
num_units: 3
|
||||
options:
|
||||
source: *openstack-origin
|
||||
to:
|
||||
- '0'
|
||||
- '1'
|
||||
- '2'
|
||||
|
||||
keystone:
|
||||
charm: cs:~openstack-charmers-next/keystone
|
||||
num_units: 1
|
||||
options:
|
||||
openstack-origin: *openstack-origin
|
||||
to:
|
||||
- '3'
|
||||
|
||||
rabbitmq-server:
|
||||
charm: cs:~openstack-charmers-next/rabbitmq-server
|
||||
num_units: 1
|
||||
options:
|
||||
source: *openstack-origin
|
||||
to:
|
||||
- '4'
|
||||
|
||||
glance:
|
||||
charm: cs:~openstack-charmers-next/glance
|
||||
num_units: 1
|
||||
options:
|
||||
openstack-origin: *openstack-origin
|
||||
to:
|
||||
- '5'
|
||||
|
||||
nova-cloud-controller:
|
||||
charm: cs:~openstack-charmers-next/nova-cloud-controller
|
||||
num_units: 1
|
||||
options:
|
||||
openstack-origin: *openstack-origin
|
||||
api-rate-limit-rules: "( POST, '*', .*, 9999, MINUTE );"
|
||||
network-manager: Neutron
|
||||
to:
|
||||
- '6'
|
||||
|
||||
nova-compute:
|
||||
charm: cs:~openstack-charmers-next/nova-compute
|
||||
num_units: 1
|
||||
options:
|
||||
openstack-origin: *openstack-origin
|
||||
config-flags: 'auto_assign_floating_ip=False'
|
||||
enable-live-migration: "False"
|
||||
to:
|
||||
- '7'
|
||||
|
||||
placement:
|
||||
charm: cs:~openstack-charmers-next/placement
|
||||
num_units: 1
|
||||
options:
|
||||
openstack-origin: *openstack-origin
|
||||
debug: "True"
|
||||
to:
|
||||
- '8'
|
||||
|
||||
neutron-gateway:
|
||||
charm: cs:~openstack-charmers-next/neutron-gateway
|
||||
num_units: 1
|
||||
options:
|
||||
openstack-origin: *openstack-origin
|
||||
to:
|
||||
- '9'
|
||||
|
||||
neutron-api:
|
||||
charm: cs:~openstack-charmers-next/neutron-api
|
||||
num_units: 1
|
||||
options:
|
||||
manage-neutron-plugin-legacy-mode: true
|
||||
neutron-plugin: ovs
|
||||
openstack-origin: *openstack-origin
|
||||
to:
|
||||
- '10'
|
||||
|
||||
neutron-openvswitch:
|
||||
charm: cs:~openstack-charmers-next/neutron-openvswitch
|
||||
|
||||
heat:
|
||||
charm: ../../../heat
|
||||
num_units: 2
|
||||
constraints: mem=2048
|
||||
options:
|
||||
openstack-origin: *openstack-origin
|
||||
debug: "True"
|
||||
verbose: "True"
|
||||
to:
|
||||
- '11'
|
||||
- '12'
|
||||
|
||||
relations:
|
||||
- - 'heat:amqp'
|
||||
- 'rabbitmq-server:amqp'
|
||||
|
||||
- - 'heat:identity-service'
|
||||
- 'keystone:identity-service'
|
||||
|
||||
- - "heat:shared-db"
|
||||
- "heat-mysql-router:shared-db"
|
||||
- - "heat-mysql-router:db-router"
|
||||
- "mysql-innodb-cluster:db-router"
|
||||
|
||||
- - 'nova-compute:image-service'
|
||||
- 'glance:image-service'
|
||||
|
||||
- - 'nova-compute:amqp'
|
||||
- 'rabbitmq-server:amqp'
|
||||
|
||||
- - "nova-cloud-controller:shared-db"
|
||||
- "nova-cloud-controller-mysql-router:shared-db"
|
||||
- - "nova-cloud-controller-mysql-router:db-router"
|
||||
- "mysql-innodb-cluster:db-router"
|
||||
|
||||
- - 'nova-cloud-controller:identity-service'
|
||||
- 'keystone:identity-service'
|
||||
|
||||
- - 'nova-cloud-controller:amqp'
|
||||
- 'rabbitmq-server:amqp'
|
||||
|
||||
- - 'nova-cloud-controller:cloud-compute'
|
||||
- 'nova-compute:cloud-compute'
|
||||
|
||||
- - 'nova-cloud-controller:image-service'
|
||||
- 'glance:image-service'
|
||||
|
||||
- - "placement:shared-db"
|
||||
- "placement-mysql-router:shared-db"
|
||||
- - "placement-mysql-router:db-router"
|
||||
- "mysql-innodb-cluster:db-router"
|
||||
|
||||
- - 'placement:identity-service'
|
||||
- 'keystone:identity-service'
|
||||
|
||||
- - 'placement:placement'
|
||||
- 'nova-cloud-controller:placement'
|
||||
|
||||
- - "keystone:shared-db"
|
||||
- "keystone-mysql-router:shared-db"
|
||||
- - "keystone-mysql-router:db-router"
|
||||
- "mysql-innodb-cluster:db-router"
|
||||
|
||||
- - 'glance:identity-service'
|
||||
- 'keystone:identity-service'
|
||||
|
||||
- - "glance:shared-db"
|
||||
- "glance-mysql-router:shared-db"
|
||||
- - "glance-mysql-router:db-router"
|
||||
- "mysql-innodb-cluster:db-router"
|
||||
|
||||
- - 'glance:amqp'
|
||||
- 'rabbitmq-server:amqp'
|
||||
|
||||
- - 'neutron-gateway:amqp'
|
||||
- 'rabbitmq-server:amqp'
|
||||
|
||||
- - 'nova-cloud-controller:quantum-network-service'
|
||||
- 'neutron-gateway:quantum-network-service'
|
||||
|
||||
- - "neutron-api:shared-db"
|
||||
- "neutron-api-mysql-router:shared-db"
|
||||
- - "neutron-api-mysql-router:db-router"
|
||||
- "mysql-innodb-cluster:db-router"
|
||||
|
||||
- - 'neutron-api:amqp'
|
||||
- 'rabbitmq-server:amqp'
|
||||
|
||||
- - 'neutron-api:neutron-api'
|
||||
- 'nova-cloud-controller:neutron-api'
|
||||
|
||||
- - 'neutron-api:identity-service'
|
||||
- 'keystone:identity-service'
|
||||
|
||||
- - 'nova-compute:neutron-plugin'
|
||||
- 'neutron-openvswitch:neutron-plugin'
|
||||
|
||||
- - 'rabbitmq-server:amqp'
|
||||
- 'neutron-openvswitch:amqp'
|
|
@ -11,6 +11,8 @@ gate_bundles:
|
|||
- bionic-rocky
|
||||
- bionic-stein
|
||||
- bionic-train
|
||||
- bionic-ussuri
|
||||
- focal-ussuri
|
||||
dev_bundles:
|
||||
- bionic-train
|
||||
configure:
|
||||
|
@ -23,3 +25,5 @@ tests:
|
|||
tests_options:
|
||||
policyd:
|
||||
service: heat
|
||||
force_deploy:
|
||||
- focal-ussuri
|
||||
|
|
Loading…
Reference in New Issue