Charmhelpers sync to get vaultlocker fixes
Also gates checking vaultlocker status until it is installed Change-Id: I07f92132b0340b538ee472887c7fd0e0cc911453 Closes-Bug: #1849323
This commit is contained in:
parent
83b2201285
commit
3e67cc5387
@ -35,8 +35,9 @@ Examples:
|
|||||||
>>> ufw.enable()
|
>>> ufw.enable()
|
||||||
>>> ufw.service('4949', 'close') # munin
|
>>> ufw.service('4949', 'close') # munin
|
||||||
"""
|
"""
|
||||||
import re
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
import six
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from charmhelpers.core import hookenv
|
from charmhelpers.core import hookenv
|
||||||
@ -236,29 +237,45 @@ def default_policy(policy='deny', direction='incoming'):
|
|||||||
|
|
||||||
|
|
||||||
def modify_access(src, dst='any', port=None, proto=None, action='allow',
|
def modify_access(src, dst='any', port=None, proto=None, action='allow',
|
||||||
index=None):
|
index=None, prepend=False, comment=None):
|
||||||
"""
|
"""
|
||||||
Grant access to an address or subnet
|
Grant access to an address or subnet
|
||||||
|
|
||||||
:param src: address (e.g. 192.168.1.234) or subnet
|
:param src: address (e.g. 192.168.1.234) or subnet
|
||||||
(e.g. 192.168.1.0/24).
|
(e.g. 192.168.1.0/24).
|
||||||
|
:type src: Optional[str]
|
||||||
:param dst: destiny of the connection, if the machine has multiple IPs and
|
:param dst: destiny of the connection, if the machine has multiple IPs and
|
||||||
connections to only one of those have to accepted this is the
|
connections to only one of those have to accepted this is the
|
||||||
field has to be set.
|
field has to be set.
|
||||||
|
:type dst: Optional[str]
|
||||||
:param port: destiny port
|
:param port: destiny port
|
||||||
|
:type port: Optional[int]
|
||||||
:param proto: protocol (tcp or udp)
|
:param proto: protocol (tcp or udp)
|
||||||
|
:type proto: Optional[str]
|
||||||
:param action: `allow` or `delete`
|
:param action: `allow` or `delete`
|
||||||
|
:type action: str
|
||||||
:param index: if different from None the rule is inserted at the given
|
:param index: if different from None the rule is inserted at the given
|
||||||
`index`.
|
`index`.
|
||||||
|
:type index: Optional[int]
|
||||||
|
:param prepend: Whether to insert the rule before all other rules matching
|
||||||
|
the rule's IP type.
|
||||||
|
:type prepend: bool
|
||||||
|
:param comment: Create the rule with a comment
|
||||||
|
:type comment: Optional[str]
|
||||||
"""
|
"""
|
||||||
if not is_enabled():
|
if not is_enabled():
|
||||||
hookenv.log('ufw is disabled, skipping modify_access()', level='WARN')
|
hookenv.log('ufw is disabled, skipping modify_access()', level='WARN')
|
||||||
return
|
return
|
||||||
|
|
||||||
if action == 'delete':
|
if action == 'delete':
|
||||||
|
if index is not None:
|
||||||
|
cmd = ['ufw', '--force', 'delete', str(index)]
|
||||||
|
else:
|
||||||
cmd = ['ufw', 'delete', 'allow']
|
cmd = ['ufw', 'delete', 'allow']
|
||||||
elif index is not None:
|
elif index is not None:
|
||||||
cmd = ['ufw', 'insert', str(index), action]
|
cmd = ['ufw', 'insert', str(index), action]
|
||||||
|
elif prepend:
|
||||||
|
cmd = ['ufw', 'prepend', action]
|
||||||
else:
|
else:
|
||||||
cmd = ['ufw', action]
|
cmd = ['ufw', action]
|
||||||
|
|
||||||
@ -274,6 +291,9 @@ def modify_access(src, dst='any', port=None, proto=None, action='allow',
|
|||||||
if proto is not None:
|
if proto is not None:
|
||||||
cmd += ['proto', proto]
|
cmd += ['proto', proto]
|
||||||
|
|
||||||
|
if comment:
|
||||||
|
cmd.extend(['comment', comment])
|
||||||
|
|
||||||
hookenv.log('ufw {}: {}'.format(action, ' '.join(cmd)), level='DEBUG')
|
hookenv.log('ufw {}: {}'.format(action, ' '.join(cmd)), level='DEBUG')
|
||||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
||||||
(stdout, stderr) = p.communicate()
|
(stdout, stderr) = p.communicate()
|
||||||
@ -337,3 +357,33 @@ def service(name, action):
|
|||||||
else:
|
else:
|
||||||
raise UFWError(("'{}' not supported, use 'allow' "
|
raise UFWError(("'{}' not supported, use 'allow' "
|
||||||
"or 'delete'").format(action))
|
"or 'delete'").format(action))
|
||||||
|
|
||||||
|
|
||||||
|
def status():
|
||||||
|
"""Retrieve firewall rules as represented by UFW.
|
||||||
|
|
||||||
|
:returns: Tuples with rule number and data
|
||||||
|
(1, {'to': '', 'action':, 'from':, '', ipv6: True, 'comment': ''})
|
||||||
|
:rtype: Iterator[Tuple[int, Dict[str, Union[bool, str]]]]
|
||||||
|
"""
|
||||||
|
if six.PY2:
|
||||||
|
raise RuntimeError('Call to function not supported on Python2')
|
||||||
|
cp = subprocess.run(('ufw', 'status', 'numbered',),
|
||||||
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||||
|
check=True, universal_newlines=True)
|
||||||
|
for line in cp.stdout.splitlines():
|
||||||
|
if not line.startswith('['):
|
||||||
|
continue
|
||||||
|
ipv6 = True if '(v6)' in line else False
|
||||||
|
line = line.replace('(v6)', '')
|
||||||
|
line = line.replace('[', '')
|
||||||
|
line = line.replace(']', '')
|
||||||
|
line = line.replace('Anywhere', 'any')
|
||||||
|
row = line.split()
|
||||||
|
yield (int(row[0]), {
|
||||||
|
'to': row[1],
|
||||||
|
'action': ' '.join(row[2:4]).lower(),
|
||||||
|
'from': row[4],
|
||||||
|
'ipv6': ipv6,
|
||||||
|
'comment': row[6] if len(row) > 5 and row[5] == '#' else '',
|
||||||
|
})
|
||||||
|
@ -1940,7 +1940,7 @@ class VolumeAPIContext(InternalEndpointContext):
|
|||||||
as well as the catalog_info string that would be supplied. Returns
|
as well as the catalog_info string that would be supplied. Returns
|
||||||
a dict containing the volume_api_version and the volume_catalog_info.
|
a dict containing the volume_api_version and the volume_catalog_info.
|
||||||
"""
|
"""
|
||||||
rel = os_release(self.pkg, base='icehouse')
|
rel = os_release(self.pkg)
|
||||||
version = '2'
|
version = '2'
|
||||||
if CompareOpenStackReleases(rel) >= 'pike':
|
if CompareOpenStackReleases(rel) >= 'pike':
|
||||||
version = '3'
|
version = '3'
|
||||||
@ -2140,7 +2140,7 @@ class VersionsContext(OSContextGenerator):
|
|||||||
self.pkg = pkg
|
self.pkg = pkg
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
ostack = os_release(self.pkg, base='icehouse')
|
ostack = os_release(self.pkg)
|
||||||
osystem = lsb_release()['DISTRIB_CODENAME'].lower()
|
osystem = lsb_release()['DISTRIB_CODENAME'].lower()
|
||||||
return {
|
return {
|
||||||
'openstack_release': ostack,
|
'openstack_release': ostack,
|
||||||
|
@ -157,10 +157,11 @@ def generate_ha_relation_data(service,
|
|||||||
_relation_data = {'resources': {}, 'resource_params': {}}
|
_relation_data = {'resources': {}, 'resource_params': {}}
|
||||||
|
|
||||||
if haproxy_enabled:
|
if haproxy_enabled:
|
||||||
|
_meta = 'meta migration-threshold="INFINITY" failure-timeout="5s"'
|
||||||
_haproxy_res = 'res_{}_haproxy'.format(service)
|
_haproxy_res = 'res_{}_haproxy'.format(service)
|
||||||
_relation_data['resources'] = {_haproxy_res: 'lsb:haproxy'}
|
_relation_data['resources'] = {_haproxy_res: 'lsb:haproxy'}
|
||||||
_relation_data['resource_params'] = {
|
_relation_data['resource_params'] = {
|
||||||
_haproxy_res: 'op monitor interval="5s"'
|
_haproxy_res: '{} op monitor interval="5s"'.format(_meta)
|
||||||
}
|
}
|
||||||
_relation_data['init_services'] = {_haproxy_res: 'haproxy'}
|
_relation_data['init_services'] = {_haproxy_res: 'haproxy'}
|
||||||
_relation_data['clones'] = {
|
_relation_data['clones'] = {
|
||||||
|
@ -17,9 +17,11 @@ import contextlib
|
|||||||
import os
|
import os
|
||||||
import six
|
import six
|
||||||
import shutil
|
import shutil
|
||||||
|
import sys
|
||||||
import yaml
|
import yaml
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
|
import charmhelpers
|
||||||
import charmhelpers.core.hookenv as hookenv
|
import charmhelpers.core.hookenv as hookenv
|
||||||
import charmhelpers.core.host as ch_host
|
import charmhelpers.core.host as ch_host
|
||||||
|
|
||||||
@ -115,8 +117,8 @@ library for further details).
|
|||||||
default: False
|
default: False
|
||||||
description: |
|
description: |
|
||||||
If True then use the resource file named 'policyd-override' to install
|
If True then use the resource file named 'policyd-override' to install
|
||||||
override yaml files in the service's policy.d directory. The resource
|
override YAML files in the service's policy.d directory. The resource
|
||||||
file should be a zip file containing at least one yaml file with a .yaml
|
file should be a ZIP file containing at least one yaml file with a .yaml
|
||||||
or .yml extension. If False then remove the overrides.
|
or .yml extension. If False then remove the overrides.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -134,14 +136,14 @@ resources:
|
|||||||
Policy Overrides
|
Policy Overrides
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
This service allows for policy overrides using the `policy.d` directory. This
|
This feature allows for policy overrides using the `policy.d` directory. This
|
||||||
is an **advanced** feature and the policies that the service supports should be
|
is an **advanced** feature and the policies that the OpenStack service supports
|
||||||
clearly and unambiguously understood before trying to override, or add to, the
|
should be clearly and unambiguously understood before trying to override, or
|
||||||
default policies that the service uses.
|
add to, the default policies that the service uses. The charm also has some
|
||||||
|
policy defaults. They should also be understood before being overridden.
|
||||||
|
|
||||||
The charm also has some policy defaults. They should also be understood before
|
> **Caution**: It is possible to break the system (for tenants and other
|
||||||
being overridden. It is possible to break the system (for tenants and other
|
services) if policies are incorrectly applied to the service.
|
||||||
services) if policies are incorrectly applied to the service.
|
|
||||||
|
|
||||||
Policy overrides are YAML files that contain rules that will add to, or
|
Policy overrides are YAML files that contain rules that will add to, or
|
||||||
override, existing policy rules in the service. The `policy.d` directory is
|
override, existing policy rules in the service. The `policy.d` directory is
|
||||||
@ -149,30 +151,16 @@ a place to put the YAML override files. This charm owns the
|
|||||||
`/etc/keystone/policy.d` directory, and as such, any manual changes to it will
|
`/etc/keystone/policy.d` directory, and as such, any manual changes to it will
|
||||||
be overwritten on charm upgrades.
|
be overwritten on charm upgrades.
|
||||||
|
|
||||||
Policy overrides are provided to the charm using a resource file called
|
Overrides are provided to the charm using a Juju resource called
|
||||||
`policyd-override`. This is attached to the charm using (for example):
|
`policyd-override`. The resource is a ZIP file. This file, say
|
||||||
|
`overrides.zip`, is attached to the charm by:
|
||||||
|
|
||||||
juju attach-resource <charm-name> policyd-override=<some-file>
|
|
||||||
|
|
||||||
The `<charm-name>` is the name that this charm is deployed as, with
|
juju attach-resource <charm-name> policyd-override=overrides.zip
|
||||||
`<some-file>` being the resource file containing the policy overrides.
|
|
||||||
|
|
||||||
The format of the resource file is a ZIP file (.zip extension) containing at
|
The policy override is enabled in the charm using:
|
||||||
least one YAML file with an extension of `.yaml` or `.yml`. Note that any
|
|
||||||
directories in the ZIP file are ignored; all of the files are flattened into a
|
|
||||||
single directory. There must not be any duplicated filenames; this will cause
|
|
||||||
an error and nothing in the resource file will be applied.
|
|
||||||
|
|
||||||
(ed. next part is optional is the charm supports some form of
|
juju config <charm-name> use-policyd-override=true
|
||||||
template/substitution on a read file)
|
|
||||||
|
|
||||||
If a (ed. "one or more of") [`.j2`, `.tmpl`, `.tpl`] file is found in the
|
|
||||||
resource file then the charm will perform a substitution with charm variables
|
|
||||||
taken from the config or relations. (ed. edit as appropriate to include the
|
|
||||||
variable).
|
|
||||||
|
|
||||||
To enable the policy overrides the config option `use-policyd-override` must be
|
|
||||||
set to `True`.
|
|
||||||
|
|
||||||
When `use-policyd-override` is `True` the status line of the charm will be
|
When `use-policyd-override` is `True` the status line of the charm will be
|
||||||
prefixed with `PO:` indicating that policies have been overridden. If the
|
prefixed with `PO:` indicating that policies have been overridden. If the
|
||||||
@ -180,12 +168,8 @@ installation of the policy override YAML files failed for any reason then the
|
|||||||
status line will be prefixed with `PO (broken):`. The log file for the charm
|
status line will be prefixed with `PO (broken):`. The log file for the charm
|
||||||
will indicate the reason. No policy override files are installed if the `PO
|
will indicate the reason. No policy override files are installed if the `PO
|
||||||
(broken):` is shown. The status line indicates that the overrides are broken,
|
(broken):` is shown. The status line indicates that the overrides are broken,
|
||||||
not that the policy for the service has failed - they will be the defaults for
|
not that the policy for the service has failed. The policy will be the defaults
|
||||||
the charm and service.
|
for the charm and service.
|
||||||
|
|
||||||
If the policy overrides did not install then *either* attach a new, corrected,
|
|
||||||
resource file *or* disable the policy overrides by setting
|
|
||||||
`use-policyd-override` to False.
|
|
||||||
|
|
||||||
Policy overrides on one service may affect the functionality of another
|
Policy overrides on one service may affect the functionality of another
|
||||||
service. Therefore, it may be necessary to provide policy overrides for
|
service. Therefore, it may be necessary to provide policy overrides for
|
||||||
@ -251,7 +235,10 @@ def maybe_do_policyd_overrides(openstack_release,
|
|||||||
blacklist_paths=None,
|
blacklist_paths=None,
|
||||||
blacklist_keys=None,
|
blacklist_keys=None,
|
||||||
template_function=None,
|
template_function=None,
|
||||||
restart_handler=None):
|
restart_handler=None,
|
||||||
|
user=None,
|
||||||
|
group=None,
|
||||||
|
config_changed=False):
|
||||||
"""If the config option is set, get the resource file and process it to
|
"""If the config option is set, get the resource file and process it to
|
||||||
enable the policy.d overrides for the service passed.
|
enable the policy.d overrides for the service passed.
|
||||||
|
|
||||||
@ -280,6 +267,11 @@ def maybe_do_policyd_overrides(openstack_release,
|
|||||||
directory. However, for any services where this is buggy then a
|
directory. However, for any services where this is buggy then a
|
||||||
restart_handler can be used to force the policy.d files to be read.
|
restart_handler can be used to force the policy.d files to be read.
|
||||||
|
|
||||||
|
If the config_changed param is True, then the handling is slightly
|
||||||
|
different: It will only perform the policyd overrides if the config is True
|
||||||
|
and the success file doesn't exist. Otherwise, it does nothing as the
|
||||||
|
resource file has already been processed.
|
||||||
|
|
||||||
:param openstack_release: The openstack release that is installed.
|
:param openstack_release: The openstack release that is installed.
|
||||||
:type openstack_release: str
|
:type openstack_release: str
|
||||||
:param service: the service name to construct the policy.d directory for.
|
:param service: the service name to construct the policy.d directory for.
|
||||||
@ -295,16 +287,43 @@ def maybe_do_policyd_overrides(openstack_release,
|
|||||||
:param restart_handler: The function to call if the service should be
|
:param restart_handler: The function to call if the service should be
|
||||||
restarted.
|
restarted.
|
||||||
:type restart_handler: Union[None, Callable[]]
|
:type restart_handler: Union[None, Callable[]]
|
||||||
|
:param user: The user to create/write files/directories as
|
||||||
|
:type user: Union[None, str]
|
||||||
|
:param group: the group to create/write files/directories as
|
||||||
|
:type group: Union[None, str]
|
||||||
|
:param config_changed: Set to True for config_changed hook.
|
||||||
|
:type config_changed: bool
|
||||||
"""
|
"""
|
||||||
|
_user = service if user is None else user
|
||||||
|
_group = service if group is None else group
|
||||||
|
if not is_policyd_override_valid_on_this_release(openstack_release):
|
||||||
|
return
|
||||||
|
hookenv.log("Running maybe_do_policyd_overrides",
|
||||||
|
level=POLICYD_LOG_LEVEL_DEFAULT)
|
||||||
config = hookenv.config()
|
config = hookenv.config()
|
||||||
try:
|
try:
|
||||||
if not config.get(POLICYD_CONFIG_NAME, False):
|
if not config.get(POLICYD_CONFIG_NAME, False):
|
||||||
|
clean_policyd_dir_for(service,
|
||||||
|
blacklist_paths,
|
||||||
|
user=_user,
|
||||||
|
group=_group)
|
||||||
|
if (os.path.isfile(_policy_success_file())
|
||||||
|
and restart_handler is not None
|
||||||
|
and callable(restart_handler)):
|
||||||
|
restart_handler()
|
||||||
remove_policy_success_file()
|
remove_policy_success_file()
|
||||||
clean_policyd_dir_for(service, blacklist_paths)
|
|
||||||
return
|
return
|
||||||
except Exception:
|
except Exception as e:
|
||||||
|
hookenv.log("... ERROR: Exception is: {}".format(str(e)),
|
||||||
|
level=POLICYD_CONFIG_NAME)
|
||||||
|
import traceback
|
||||||
|
hookenv.log(traceback.format_exc(), level=POLICYD_LOG_LEVEL_DEFAULT)
|
||||||
return
|
return
|
||||||
if not is_policyd_override_valid_on_this_release(openstack_release):
|
# if the policyd overrides have been performed when doing config_changed
|
||||||
|
# just return
|
||||||
|
if config_changed and is_policy_success_file_set():
|
||||||
|
hookenv.log("... already setup, so skipping.",
|
||||||
|
level=POLICYD_LOG_LEVEL_DEFAULT)
|
||||||
return
|
return
|
||||||
# from now on it should succeed; if it doesn't then status line will show
|
# from now on it should succeed; if it doesn't then status line will show
|
||||||
# broken.
|
# broken.
|
||||||
@ -316,49 +335,18 @@ def maybe_do_policyd_overrides(openstack_release,
|
|||||||
restart_handler()
|
restart_handler()
|
||||||
|
|
||||||
|
|
||||||
def maybe_do_policyd_overrides_on_config_changed(openstack_release,
|
@charmhelpers.deprecate("Use maybe_do_poliyd_overrrides instead")
|
||||||
service,
|
def maybe_do_policyd_overrides_on_config_changed(*args, **kwargs):
|
||||||
blacklist_paths=None,
|
"""This function is designed to be called from the config changed hook.
|
||||||
blacklist_keys=None,
|
|
||||||
template_function=None,
|
DEPRECATED: please use maybe_do_policyd_overrides() with the param
|
||||||
restart_handler=None):
|
`config_changed` as `True`.
|
||||||
"""This function is designed to be called from the config changed hook
|
|
||||||
handler. It will only perform the policyd overrides if the config is True
|
|
||||||
and the success file doesn't exist. Otherwise, it does nothing as the
|
|
||||||
resource file has already been processed.
|
|
||||||
|
|
||||||
See maybe_do_policyd_overrides() for more details on the params.
|
See maybe_do_policyd_overrides() for more details on the params.
|
||||||
|
|
||||||
:param openstack_release: The openstack release that is installed.
|
|
||||||
:type openstack_release: str
|
|
||||||
:param service: the service name to construct the policy.d directory for.
|
|
||||||
:type service: str
|
|
||||||
:param blacklist_paths: optional list of paths to leave alone
|
|
||||||
:type blacklist_paths: Union[None, List[str]]
|
|
||||||
:param blacklist_keys: optional list of keys that mustn't appear in the
|
|
||||||
yaml file's
|
|
||||||
:type blacklist_keys: Union[None, List[str]]
|
|
||||||
:param template_function: Optional function that can modify the string
|
|
||||||
prior to being processed as a Yaml document.
|
|
||||||
:type template_function: Union[None, Callable[[str], str]]
|
|
||||||
:param restart_handler: The function to call if the service should be
|
|
||||||
restarted.
|
|
||||||
:type restart_handler: Union[None, Callable[]]
|
|
||||||
"""
|
"""
|
||||||
config = hookenv.config()
|
if 'config_changed' not in kwargs.keys():
|
||||||
try:
|
kwargs['config_changed'] = True
|
||||||
if not config.get(POLICYD_CONFIG_NAME, False):
|
return maybe_do_policyd_overrides(*args, **kwargs)
|
||||||
remove_policy_success_file()
|
|
||||||
clean_policyd_dir_for(service, blacklist_paths)
|
|
||||||
return
|
|
||||||
except Exception:
|
|
||||||
return
|
|
||||||
# if the policyd overrides have been performed just return
|
|
||||||
if os.path.isfile(_policy_success_file()):
|
|
||||||
return
|
|
||||||
maybe_do_policyd_overrides(
|
|
||||||
openstack_release, service, blacklist_paths, blacklist_keys,
|
|
||||||
template_function, restart_handler)
|
|
||||||
|
|
||||||
|
|
||||||
def get_policy_resource_filename():
|
def get_policy_resource_filename():
|
||||||
@ -375,13 +363,16 @@ def get_policy_resource_filename():
|
|||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def open_and_filter_yaml_files(filepath):
|
def open_and_filter_yaml_files(filepath, has_subdirs=False):
|
||||||
"""Validate that the filepath provided is a zip file and contains at least
|
"""Validate that the filepath provided is a zip file and contains at least
|
||||||
one (.yaml|.yml) file, and that the files are not duplicated when the zip
|
one (.yaml|.yml) file, and that the files are not duplicated when the zip
|
||||||
file is flattened. Note that the yaml files are not checked. This is the
|
file is flattened. Note that the yaml files are not checked. This is the
|
||||||
first stage in validating the policy zipfile; individual yaml files are not
|
first stage in validating the policy zipfile; individual yaml files are not
|
||||||
checked for validity or black listed keys.
|
checked for validity or black listed keys.
|
||||||
|
|
||||||
|
If the has_subdirs param is True, then the files are flattened to the first
|
||||||
|
directory, and the files in the root are ignored.
|
||||||
|
|
||||||
An example of use is:
|
An example of use is:
|
||||||
|
|
||||||
with open_and_filter_yaml_files(some_path) as zfp, g:
|
with open_and_filter_yaml_files(some_path) as zfp, g:
|
||||||
@ -390,6 +381,8 @@ def open_and_filter_yaml_files(filepath):
|
|||||||
|
|
||||||
:param filepath: a filepath object that can be opened by zipfile
|
:param filepath: a filepath object that can be opened by zipfile
|
||||||
:type filepath: Union[AnyStr, os.PathLike[AntStr]]
|
:type filepath: Union[AnyStr, os.PathLike[AntStr]]
|
||||||
|
:param has_subdirs: Keep first level of subdirectories in yaml file.
|
||||||
|
:type has_subdirs: bool
|
||||||
:returns: (zfp handle,
|
:returns: (zfp handle,
|
||||||
a generator of the (name, filename, ZipInfo object) tuples) as a
|
a generator of the (name, filename, ZipInfo object) tuples) as a
|
||||||
tuple.
|
tuple.
|
||||||
@ -402,7 +395,7 @@ def open_and_filter_yaml_files(filepath):
|
|||||||
with zipfile.ZipFile(filepath, 'r') as zfp:
|
with zipfile.ZipFile(filepath, 'r') as zfp:
|
||||||
# first pass through; check for duplicates and at least one yaml file.
|
# first pass through; check for duplicates and at least one yaml file.
|
||||||
names = collections.defaultdict(int)
|
names = collections.defaultdict(int)
|
||||||
yamlfiles = _yamlfiles(zfp)
|
yamlfiles = _yamlfiles(zfp, has_subdirs)
|
||||||
for name, _, _, _ in yamlfiles:
|
for name, _, _, _ in yamlfiles:
|
||||||
names[name] += 1
|
names[name] += 1
|
||||||
# There must be at least 1 yaml file.
|
# There must be at least 1 yaml file.
|
||||||
@ -418,26 +411,49 @@ def open_and_filter_yaml_files(filepath):
|
|||||||
yield (zfp, yamlfiles)
|
yield (zfp, yamlfiles)
|
||||||
|
|
||||||
|
|
||||||
def _yamlfiles(zipfile):
|
def _yamlfiles(zipfile, has_subdirs=False):
|
||||||
"""Helper to get a yaml file (according to POLICYD_VALID_EXTS extensions)
|
"""Helper to get a yaml file (according to POLICYD_VALID_EXTS extensions)
|
||||||
and the infolist item from a zipfile.
|
and the infolist item from a zipfile.
|
||||||
|
|
||||||
|
If the `has_subdirs` param is True, the the only yaml files that have a
|
||||||
|
directory component are read, and then first part of the directory
|
||||||
|
component is kept, along with the filename in the name. e.g. an entry with
|
||||||
|
a filename of:
|
||||||
|
|
||||||
|
compute/someotherdir/override.yaml
|
||||||
|
|
||||||
|
is returned as:
|
||||||
|
|
||||||
|
compute/override, yaml, override.yaml, <ZipInfo object>
|
||||||
|
|
||||||
|
This is to help with the special, additional, processing that the dashboard
|
||||||
|
charm requires.
|
||||||
|
|
||||||
:param zipfile: the zipfile to read zipinfo items from
|
:param zipfile: the zipfile to read zipinfo items from
|
||||||
:type zipfile: zipfile.ZipFile
|
:type zipfile: zipfile.ZipFile
|
||||||
:returns: generator of (name, ext, filename, info item) for each self-identified
|
:param has_subdirs: Keep first level of subdirectories in yaml file.
|
||||||
yaml file.
|
:type has_subdirs: bool
|
||||||
|
:returns: generator of (name, ext, filename, info item) for each
|
||||||
|
self-identified yaml file.
|
||||||
:rtype: List[(str, str, str, zipfile.ZipInfo)]
|
:rtype: List[(str, str, str, zipfile.ZipInfo)]
|
||||||
"""
|
"""
|
||||||
l = []
|
files = []
|
||||||
for infolist_item in zipfile.infolist():
|
for infolist_item in zipfile.infolist():
|
||||||
|
try:
|
||||||
if infolist_item.is_dir():
|
if infolist_item.is_dir():
|
||||||
continue
|
continue
|
||||||
_, name_ext = os.path.split(infolist_item.filename)
|
except AttributeError:
|
||||||
|
# fallback to "old" way to determine dir entry for pre-py36
|
||||||
|
if infolist_item.filename.endswith('/'):
|
||||||
|
continue
|
||||||
|
_dir, name_ext = os.path.split(infolist_item.filename)
|
||||||
name, ext = os.path.splitext(name_ext)
|
name, ext = os.path.splitext(name_ext)
|
||||||
|
if has_subdirs and _dir != "":
|
||||||
|
name = os.path.join(_dir.split(os.path.sep)[0], name)
|
||||||
ext = ext.lower()
|
ext = ext.lower()
|
||||||
if ext and ext in POLICYD_VALID_EXTS:
|
if ext and ext in POLICYD_VALID_EXTS:
|
||||||
l.append((name, ext, name_ext, infolist_item))
|
files.append((name, ext, name_ext, infolist_item))
|
||||||
return l
|
return files
|
||||||
|
|
||||||
|
|
||||||
def read_and_validate_yaml(stream_or_doc, blacklist_keys=None):
|
def read_and_validate_yaml(stream_or_doc, blacklist_keys=None):
|
||||||
@ -483,9 +499,6 @@ def read_and_validate_yaml(stream_or_doc, blacklist_keys=None):
|
|||||||
def policyd_dir_for(service):
|
def policyd_dir_for(service):
|
||||||
"""Return the policy directory for the named service.
|
"""Return the policy directory for the named service.
|
||||||
|
|
||||||
This assumes the default name of "policy.d" which is kept across all
|
|
||||||
charms.
|
|
||||||
|
|
||||||
:param service: str
|
:param service: str
|
||||||
:returns: the policy.d override directory.
|
:returns: the policy.d override directory.
|
||||||
:rtype: os.PathLike[str]
|
:rtype: os.PathLike[str]
|
||||||
@ -493,7 +506,7 @@ def policyd_dir_for(service):
|
|||||||
return os.path.join("/", "etc", service, "policy.d")
|
return os.path.join("/", "etc", service, "policy.d")
|
||||||
|
|
||||||
|
|
||||||
def clean_policyd_dir_for(service, keep_paths=None):
|
def clean_policyd_dir_for(service, keep_paths=None, user=None, group=None):
|
||||||
"""Clean out the policyd directory except for items that should be kept.
|
"""Clean out the policyd directory except for items that should be kept.
|
||||||
|
|
||||||
The keep_paths, if used, should be set to the full path of the files that
|
The keep_paths, if used, should be set to the full path of the files that
|
||||||
@ -506,12 +519,19 @@ def clean_policyd_dir_for(service, keep_paths=None):
|
|||||||
:type service: str
|
:type service: str
|
||||||
:param keep_paths: optional list of paths to not delete.
|
:param keep_paths: optional list of paths to not delete.
|
||||||
:type keep_paths: Union[None, List[str]]
|
:type keep_paths: Union[None, List[str]]
|
||||||
|
:param user: The user to create/write files/directories as
|
||||||
|
:type user: Union[None, str]
|
||||||
|
:param group: the group to create/write files/directories as
|
||||||
|
:type group: Union[None, str]
|
||||||
"""
|
"""
|
||||||
|
_user = service if user is None else user
|
||||||
|
_group = service if group is None else group
|
||||||
keep_paths = keep_paths or []
|
keep_paths = keep_paths or []
|
||||||
path = policyd_dir_for(service)
|
path = policyd_dir_for(service)
|
||||||
|
hookenv.log("Cleaning path: {}".format(path), level=hookenv.DEBUG)
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
ch_host.mkdir(path, owner=service, group=service, perms=0o775)
|
ch_host.mkdir(path, owner=_user, group=_group, perms=0o775)
|
||||||
_scanner = os.scandir if six.PY3 else _py2_scandir
|
_scanner = os.scandir if sys.version_info > (3, 4) else _py2_scandir
|
||||||
for direntry in _scanner(path):
|
for direntry in _scanner(path):
|
||||||
# see if the path should be kept.
|
# see if the path should be kept.
|
||||||
if direntry.path in keep_paths:
|
if direntry.path in keep_paths:
|
||||||
@ -523,6 +543,22 @@ def clean_policyd_dir_for(service, keep_paths=None):
|
|||||||
os.remove(direntry.path)
|
os.remove(direntry.path)
|
||||||
|
|
||||||
|
|
||||||
|
def maybe_create_directory_for(path, user, group):
|
||||||
|
"""For the filename 'path', ensure that the directory for that path exists.
|
||||||
|
|
||||||
|
Note that if the directory already exists then the permissions are NOT
|
||||||
|
changed.
|
||||||
|
|
||||||
|
:param path: the filename including the path to it.
|
||||||
|
:type path: str
|
||||||
|
:param user: the user to create the directory as
|
||||||
|
:param group: the group to create the directory as
|
||||||
|
"""
|
||||||
|
_dir, _ = os.path.split(path)
|
||||||
|
if not os.path.exists(_dir):
|
||||||
|
ch_host.mkdir(_dir, owner=user, group=group, perms=0o775)
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def _py2_scandir(path):
|
def _py2_scandir(path):
|
||||||
"""provide a py2 implementation of os.scandir if this module ever gets used
|
"""provide a py2 implementation of os.scandir if this module ever gets used
|
||||||
@ -558,6 +594,11 @@ def path_for_policy_file(service, name):
|
|||||||
It is constructed using policyd_dir_for(), the name and the ".yaml"
|
It is constructed using policyd_dir_for(), the name and the ".yaml"
|
||||||
extension.
|
extension.
|
||||||
|
|
||||||
|
For horizon, for example, it's a bit more complicated. The name param is
|
||||||
|
actually "override_service_dir/a_name", where target_service needs to be
|
||||||
|
one the allowed horizon override services. This translation and check is
|
||||||
|
done in the _yamlfiles() function.
|
||||||
|
|
||||||
:param service: the service name
|
:param service: the service name
|
||||||
:type service: str
|
:type service: str
|
||||||
:param name: the name for the policy override
|
:param name: the name for the policy override
|
||||||
@ -585,6 +626,22 @@ def remove_policy_success_file():
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def set_policy_success_file():
|
||||||
|
"""Set the file that indicates successful policyd override."""
|
||||||
|
open(_policy_success_file(), "w").close()
|
||||||
|
|
||||||
|
|
||||||
|
def is_policy_success_file_set():
|
||||||
|
"""Returns True if the policy success file has been set.
|
||||||
|
|
||||||
|
This indicates that policies are overridden and working properly.
|
||||||
|
|
||||||
|
:returns: True if the policy file is set
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
|
return os.path.isfile(_policy_success_file())
|
||||||
|
|
||||||
|
|
||||||
def policyd_status_message_prefix():
|
def policyd_status_message_prefix():
|
||||||
"""Return the prefix str for the status line.
|
"""Return the prefix str for the status line.
|
||||||
|
|
||||||
@ -594,7 +651,7 @@ def policyd_status_message_prefix():
|
|||||||
:returns: the prefix
|
:returns: the prefix
|
||||||
:rtype: str
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
if os.path.isfile(_policy_success_file()):
|
if is_policy_success_file_set():
|
||||||
return "PO:"
|
return "PO:"
|
||||||
return "PO (broken):"
|
return "PO (broken):"
|
||||||
|
|
||||||
@ -603,7 +660,11 @@ def process_policy_resource_file(resource_file,
|
|||||||
service,
|
service,
|
||||||
blacklist_paths=None,
|
blacklist_paths=None,
|
||||||
blacklist_keys=None,
|
blacklist_keys=None,
|
||||||
template_function=None):
|
template_function=None,
|
||||||
|
preserve_topdir=False,
|
||||||
|
preprocess_filename=None,
|
||||||
|
user=None,
|
||||||
|
group=None):
|
||||||
"""Process the resource file (which should contain at least one yaml file)
|
"""Process the resource file (which should contain at least one yaml file)
|
||||||
and write those files to the service's policy.d directory.
|
and write those files to the service's policy.d directory.
|
||||||
|
|
||||||
@ -623,6 +684,16 @@ def process_policy_resource_file(resource_file,
|
|||||||
its file path reconstructed. This, also, must not match any path in the
|
its file path reconstructed. This, also, must not match any path in the
|
||||||
black list.
|
black list.
|
||||||
|
|
||||||
|
The yaml filename can be modified in two ways. If the `preserve_topdir`
|
||||||
|
param is True, then files will be flattened to the top dir. This allows
|
||||||
|
for creating sets of files that can be grouped into a single level tree
|
||||||
|
structure.
|
||||||
|
|
||||||
|
Secondly, if the `preprocess_filename` param is not None and callable()
|
||||||
|
then the name is passed to that function for preprocessing before being
|
||||||
|
converted to the end location. This is to allow munging of the filename
|
||||||
|
prior to being tested for a blacklist path.
|
||||||
|
|
||||||
If any error occurs, then the policy.d directory is cleared, the error is
|
If any error occurs, then the policy.d directory is cleared, the error is
|
||||||
written to the log, and the status line will eventually show as failed.
|
written to the log, and the status line will eventually show as failed.
|
||||||
|
|
||||||
@ -638,17 +709,39 @@ def process_policy_resource_file(resource_file,
|
|||||||
:param template_function: Optional function that can modify the yaml
|
:param template_function: Optional function that can modify the yaml
|
||||||
document.
|
document.
|
||||||
:type template_function: Union[None, Callable[[AnyStr], AnyStr]]
|
:type template_function: Union[None, Callable[[AnyStr], AnyStr]]
|
||||||
|
:param preserve_topdir: Keep the toplevel subdir
|
||||||
|
:type preserve_topdir: bool
|
||||||
|
:param preprocess_filename: Optional function to use to process filenames
|
||||||
|
extracted from the resource file.
|
||||||
|
:type preprocess_filename: Union[None, Callable[[AnyStr]. AnyStr]]
|
||||||
|
:param user: The user to create/write files/directories as
|
||||||
|
:type user: Union[None, str]
|
||||||
|
:param group: the group to create/write files/directories as
|
||||||
|
:type group: Union[None, str]
|
||||||
:returns: True if the processing was successful, False if not.
|
:returns: True if the processing was successful, False if not.
|
||||||
:rtype: boolean
|
:rtype: boolean
|
||||||
"""
|
"""
|
||||||
|
hookenv.log("Running process_policy_resource_file", level=hookenv.DEBUG)
|
||||||
blacklist_paths = blacklist_paths or []
|
blacklist_paths = blacklist_paths or []
|
||||||
completed = False
|
completed = False
|
||||||
|
_preprocess = None
|
||||||
|
if preprocess_filename is not None and callable(preprocess_filename):
|
||||||
|
_preprocess = preprocess_filename
|
||||||
|
_user = service if user is None else user
|
||||||
|
_group = service if group is None else group
|
||||||
try:
|
try:
|
||||||
with open_and_filter_yaml_files(resource_file) as (zfp, gen):
|
with open_and_filter_yaml_files(
|
||||||
|
resource_file, preserve_topdir) as (zfp, gen):
|
||||||
# first clear out the policy.d directory and clear success
|
# first clear out the policy.d directory and clear success
|
||||||
remove_policy_success_file()
|
remove_policy_success_file()
|
||||||
clean_policyd_dir_for(service, blacklist_paths)
|
clean_policyd_dir_for(service,
|
||||||
|
blacklist_paths,
|
||||||
|
user=_user,
|
||||||
|
group=_group)
|
||||||
for name, ext, filename, zipinfo in gen:
|
for name, ext, filename, zipinfo in gen:
|
||||||
|
# See if the name should be preprocessed.
|
||||||
|
if _preprocess is not None:
|
||||||
|
name = _preprocess(name)
|
||||||
# construct a name for the output file.
|
# construct a name for the output file.
|
||||||
yaml_filename = path_for_policy_file(service, name)
|
yaml_filename = path_for_policy_file(service, name)
|
||||||
if yaml_filename in blacklist_paths:
|
if yaml_filename in blacklist_paths:
|
||||||
@ -666,8 +759,12 @@ def process_policy_resource_file(resource_file,
|
|||||||
"available".format(filename))
|
"available".format(filename))
|
||||||
doc = template_function(doc)
|
doc = template_function(doc)
|
||||||
yaml_doc = read_and_validate_yaml(doc, blacklist_keys)
|
yaml_doc = read_and_validate_yaml(doc, blacklist_keys)
|
||||||
with open(yaml_filename, "wt") as f:
|
# we may have to create the directory
|
||||||
yaml.dump(yaml_doc, f)
|
maybe_create_directory_for(yaml_filename, _user, _group)
|
||||||
|
ch_host.write_file(yaml_filename,
|
||||||
|
yaml.dump(yaml_doc).encode('utf-8'),
|
||||||
|
_user,
|
||||||
|
_group)
|
||||||
# Every thing worked, so we mark up a success.
|
# Every thing worked, so we mark up a success.
|
||||||
completed = True
|
completed = True
|
||||||
except (BadZipFile, BadPolicyZipFile, BadPolicyYamlFile) as e:
|
except (BadZipFile, BadPolicyZipFile, BadPolicyYamlFile) as e:
|
||||||
@ -691,10 +788,13 @@ def process_policy_resource_file(resource_file,
|
|||||||
hookenv.log("Processing {} failed: cleaning policy.d directory"
|
hookenv.log("Processing {} failed: cleaning policy.d directory"
|
||||||
.format(resource_file),
|
.format(resource_file),
|
||||||
level=POLICYD_LOG_LEVEL_DEFAULT)
|
level=POLICYD_LOG_LEVEL_DEFAULT)
|
||||||
clean_policyd_dir_for(service, blacklist_paths)
|
clean_policyd_dir_for(service,
|
||||||
|
blacklist_paths,
|
||||||
|
user=_user,
|
||||||
|
group=_group)
|
||||||
else:
|
else:
|
||||||
# touch the success filename
|
# touch the success filename
|
||||||
hookenv.log("policy.d overrides installed.",
|
hookenv.log("policy.d overrides installed.",
|
||||||
level=POLICYD_LOG_LEVEL_DEFAULT)
|
level=POLICYD_LOG_LEVEL_DEFAULT)
|
||||||
open(_policy_success_file(), "w").close()
|
set_policy_success_file()
|
||||||
return completed
|
return completed
|
||||||
|
@ -204,7 +204,7 @@ SWIFT_CODENAMES = OrderedDict([
|
|||||||
('stein',
|
('stein',
|
||||||
['2.20.0', '2.21.0']),
|
['2.20.0', '2.21.0']),
|
||||||
('train',
|
('train',
|
||||||
['2.22.0']),
|
['2.22.0', '2.23.0']),
|
||||||
])
|
])
|
||||||
|
|
||||||
# >= Liberty version->codename mapping
|
# >= Liberty version->codename mapping
|
||||||
@ -531,7 +531,7 @@ def reset_os_release():
|
|||||||
_os_rel = None
|
_os_rel = None
|
||||||
|
|
||||||
|
|
||||||
def os_release(package, base='essex', reset_cache=False):
|
def os_release(package, base=None, reset_cache=False):
|
||||||
'''
|
'''
|
||||||
Returns OpenStack release codename from a cached global.
|
Returns OpenStack release codename from a cached global.
|
||||||
|
|
||||||
@ -542,6 +542,8 @@ def os_release(package, base='essex', reset_cache=False):
|
|||||||
the installation source, the earliest release supported by the charm should
|
the installation source, the earliest release supported by the charm should
|
||||||
be returned.
|
be returned.
|
||||||
'''
|
'''
|
||||||
|
if not base:
|
||||||
|
base = UBUNTU_OPENSTACK_RELEASE[lsb_release()['DISTRIB_CODENAME']]
|
||||||
global _os_rel
|
global _os_rel
|
||||||
if reset_cache:
|
if reset_cache:
|
||||||
reset_os_release()
|
reset_os_release()
|
||||||
@ -670,7 +672,10 @@ def openstack_upgrade_available(package):
|
|||||||
codename = get_os_codename_install_source(src)
|
codename = get_os_codename_install_source(src)
|
||||||
avail_vers = get_os_version_codename_swift(codename)
|
avail_vers = get_os_version_codename_swift(codename)
|
||||||
else:
|
else:
|
||||||
|
try:
|
||||||
avail_vers = get_os_version_install_source(src)
|
avail_vers = get_os_version_install_source(src)
|
||||||
|
except:
|
||||||
|
avail_vers = cur_vers
|
||||||
apt.init()
|
apt.init()
|
||||||
return apt.version_compare(avail_vers, cur_vers) >= 1
|
return apt.version_compare(avail_vers, cur_vers) >= 1
|
||||||
|
|
||||||
@ -1693,7 +1698,7 @@ def enable_memcache(source=None, release=None, package=None):
|
|||||||
if release:
|
if release:
|
||||||
_release = release
|
_release = release
|
||||||
else:
|
else:
|
||||||
_release = os_release(package, base='icehouse')
|
_release = os_release(package)
|
||||||
if not _release:
|
if not _release:
|
||||||
_release = get_os_codename_install_source(source)
|
_release = get_os_codename_install_source(source)
|
||||||
|
|
||||||
|
@ -37,9 +37,13 @@ class VaultKVContext(context.OSContextGenerator):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
|
import hvac
|
||||||
|
ctxt = {}
|
||||||
|
# NOTE(hopem): see https://bugs.launchpad.net/charm-helpers/+bug/1849323
|
||||||
db = unitdata.kv()
|
db = unitdata.kv()
|
||||||
last_token = db.get('last-token')
|
# currently known-good secret-id
|
||||||
secret_id = db.get('secret-id')
|
secret_id = db.get('secret-id')
|
||||||
|
|
||||||
for relation_id in hookenv.relation_ids(self.interfaces[0]):
|
for relation_id in hookenv.relation_ids(self.interfaces[0]):
|
||||||
for unit in hookenv.related_units(relation_id):
|
for unit in hookenv.related_units(relation_id):
|
||||||
data = hookenv.relation_get(unit=unit,
|
data = hookenv.relation_get(unit=unit,
|
||||||
@ -54,27 +58,48 @@ class VaultKVContext(context.OSContextGenerator):
|
|||||||
|
|
||||||
# Tokens may change when secret_id's are being
|
# Tokens may change when secret_id's are being
|
||||||
# reissued - if so use token to get new secret_id
|
# reissued - if so use token to get new secret_id
|
||||||
if token != last_token:
|
token_success = False
|
||||||
|
try:
|
||||||
secret_id = retrieve_secret_id(
|
secret_id = retrieve_secret_id(
|
||||||
url=vault_url,
|
url=vault_url,
|
||||||
token=token
|
token=token
|
||||||
)
|
)
|
||||||
|
token_success = True
|
||||||
|
except hvac.exceptions.InvalidRequest:
|
||||||
|
# Try next
|
||||||
|
pass
|
||||||
|
|
||||||
|
if token_success:
|
||||||
db.set('secret-id', secret_id)
|
db.set('secret-id', secret_id)
|
||||||
db.set('last-token', token)
|
|
||||||
db.flush()
|
db.flush()
|
||||||
|
|
||||||
ctxt = {
|
ctxt['vault_url'] = vault_url
|
||||||
'vault_url': vault_url,
|
ctxt['role_id'] = json.loads(role_id)
|
||||||
'role_id': json.loads(role_id),
|
ctxt['secret_id'] = secret_id
|
||||||
'secret_id': secret_id,
|
ctxt['secret_backend'] = self.secret_backend
|
||||||
'secret_backend': self.secret_backend,
|
|
||||||
}
|
|
||||||
vault_ca = data.get('vault_ca')
|
vault_ca = data.get('vault_ca')
|
||||||
if vault_ca:
|
if vault_ca:
|
||||||
ctxt['vault_ca'] = json.loads(vault_ca)
|
ctxt['vault_ca'] = json.loads(vault_ca)
|
||||||
|
|
||||||
self.complete = True
|
self.complete = True
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if secret_id:
|
||||||
|
ctxt['vault_url'] = vault_url
|
||||||
|
ctxt['role_id'] = json.loads(role_id)
|
||||||
|
ctxt['secret_id'] = secret_id
|
||||||
|
ctxt['secret_backend'] = self.secret_backend
|
||||||
|
vault_ca = data.get('vault_ca')
|
||||||
|
if vault_ca:
|
||||||
|
ctxt['vault_ca'] = json.loads(vault_ca)
|
||||||
|
|
||||||
|
if self.complete:
|
||||||
|
break
|
||||||
|
|
||||||
|
if ctxt:
|
||||||
|
self.complete = True
|
||||||
|
|
||||||
return ctxt
|
return ctxt
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
def write_vaultlocker_conf(context, priority=100):
|
def write_vaultlocker_conf(context, priority=100):
|
||||||
|
@ -422,6 +422,8 @@ def enabled_manager_modules():
|
|||||||
cmd = ['ceph', 'mgr', 'module', 'ls']
|
cmd = ['ceph', 'mgr', 'module', 'ls']
|
||||||
try:
|
try:
|
||||||
modules = check_output(cmd)
|
modules = check_output(cmd)
|
||||||
|
if six.PY3:
|
||||||
|
modules = modules.decode('UTF-8')
|
||||||
except CalledProcessError as e:
|
except CalledProcessError as e:
|
||||||
log("Failed to list ceph modules: {}".format(e), WARNING)
|
log("Failed to list ceph modules: {}".format(e), WARNING)
|
||||||
return []
|
return []
|
||||||
@ -1185,6 +1187,15 @@ class CephBrokerRq(object):
|
|||||||
self.request_id = str(uuid.uuid1())
|
self.request_id = str(uuid.uuid1())
|
||||||
self.ops = []
|
self.ops = []
|
||||||
|
|
||||||
|
def add_op(self, op):
|
||||||
|
"""Add an op if it is not already in the list.
|
||||||
|
|
||||||
|
:param op: Operation to add.
|
||||||
|
:type op: dict
|
||||||
|
"""
|
||||||
|
if op not in self.ops:
|
||||||
|
self.ops.append(op)
|
||||||
|
|
||||||
def add_op_request_access_to_group(self, name, namespace=None,
|
def add_op_request_access_to_group(self, name, namespace=None,
|
||||||
permission=None, key_name=None,
|
permission=None, key_name=None,
|
||||||
object_prefix_permissions=None):
|
object_prefix_permissions=None):
|
||||||
@ -1198,7 +1209,7 @@ class CephBrokerRq(object):
|
|||||||
'rwx': ['prefix1', 'prefix2'],
|
'rwx': ['prefix1', 'prefix2'],
|
||||||
'class-read': ['prefix3']}
|
'class-read': ['prefix3']}
|
||||||
"""
|
"""
|
||||||
self.ops.append({
|
self.add_op({
|
||||||
'op': 'add-permissions-to-key', 'group': name,
|
'op': 'add-permissions-to-key', 'group': name,
|
||||||
'namespace': namespace,
|
'namespace': namespace,
|
||||||
'name': key_name or service_name(),
|
'name': key_name or service_name(),
|
||||||
@ -1251,7 +1262,7 @@ class CephBrokerRq(object):
|
|||||||
if pg_num and weight:
|
if pg_num and weight:
|
||||||
raise ValueError('pg_num and weight are mutually exclusive')
|
raise ValueError('pg_num and weight are mutually exclusive')
|
||||||
|
|
||||||
self.ops.append({'op': 'create-pool', 'name': name,
|
self.add_op({'op': 'create-pool', 'name': name,
|
||||||
'replicas': replica_count, 'pg_num': pg_num,
|
'replicas': replica_count, 'pg_num': pg_num,
|
||||||
'weight': weight, 'group': group,
|
'weight': weight, 'group': group,
|
||||||
'group-namespace': namespace, 'app-name': app_name,
|
'group-namespace': namespace, 'app-name': app_name,
|
||||||
@ -1283,7 +1294,7 @@ class CephBrokerRq(object):
|
|||||||
:param max_objects: Maximum objects quota to apply
|
:param max_objects: Maximum objects quota to apply
|
||||||
:type max_objects: int
|
:type max_objects: int
|
||||||
"""
|
"""
|
||||||
self.ops.append({'op': 'create-pool', 'name': name,
|
self.add_op({'op': 'create-pool', 'name': name,
|
||||||
'pool-type': 'erasure',
|
'pool-type': 'erasure',
|
||||||
'erasure-profile': erasure_profile,
|
'erasure-profile': erasure_profile,
|
||||||
'weight': weight,
|
'weight': weight,
|
||||||
|
@ -34,6 +34,8 @@ import errno
|
|||||||
import tempfile
|
import tempfile
|
||||||
from subprocess import CalledProcessError
|
from subprocess import CalledProcessError
|
||||||
|
|
||||||
|
from charmhelpers import deprecate
|
||||||
|
|
||||||
import six
|
import six
|
||||||
if not six.PY3:
|
if not six.PY3:
|
||||||
from UserDict import UserDict
|
from UserDict import UserDict
|
||||||
@ -119,6 +121,24 @@ def log(message, level=None):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def function_log(message):
|
||||||
|
"""Write a function progress message"""
|
||||||
|
command = ['function-log']
|
||||||
|
if not isinstance(message, six.string_types):
|
||||||
|
message = repr(message)
|
||||||
|
command += [message[:SH_MAX_ARG]]
|
||||||
|
# Missing function-log should not cause failures in unit tests
|
||||||
|
# Send function_log output to stderr
|
||||||
|
try:
|
||||||
|
subprocess.call(command)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == errno.ENOENT:
|
||||||
|
message = "function-log: {}".format(message)
|
||||||
|
print(message, file=sys.stderr)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
class Serializable(UserDict):
|
class Serializable(UserDict):
|
||||||
"""Wrapper, an object that can be serialized to yaml or json"""
|
"""Wrapper, an object that can be serialized to yaml or json"""
|
||||||
|
|
||||||
@ -946,9 +966,23 @@ def charm_dir():
|
|||||||
return os.environ.get('CHARM_DIR')
|
return os.environ.get('CHARM_DIR')
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_exists(cmd):
|
||||||
|
"""Return True if the specified cmd exists in the path"""
|
||||||
|
return any(
|
||||||
|
os.access(os.path.join(path, cmd), os.X_OK)
|
||||||
|
for path in os.environ["PATH"].split(os.pathsep)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
|
@deprecate("moved to function_get()", log=log)
|
||||||
def action_get(key=None):
|
def action_get(key=None):
|
||||||
"""Gets the value of an action parameter, or all key/value param pairs"""
|
"""
|
||||||
|
.. deprecated:: 0.20.7
|
||||||
|
Alias for :func:`function_get`.
|
||||||
|
|
||||||
|
Gets the value of an action parameter, or all key/value param pairs.
|
||||||
|
"""
|
||||||
cmd = ['action-get']
|
cmd = ['action-get']
|
||||||
if key is not None:
|
if key is not None:
|
||||||
cmd.append(key)
|
cmd.append(key)
|
||||||
@ -957,36 +991,103 @@ def action_get(key=None):
|
|||||||
return action_data
|
return action_data
|
||||||
|
|
||||||
|
|
||||||
|
@cached
|
||||||
|
def function_get(key=None):
|
||||||
|
"""Gets the value of an action parameter, or all key/value param pairs"""
|
||||||
|
cmd = ['function-get']
|
||||||
|
# Fallback for older charms.
|
||||||
|
if not cmd_exists('function-get'):
|
||||||
|
cmd = ['action-get']
|
||||||
|
|
||||||
|
if key is not None:
|
||||||
|
cmd.append(key)
|
||||||
|
cmd.append('--format=json')
|
||||||
|
function_data = json.loads(subprocess.check_output(cmd).decode('UTF-8'))
|
||||||
|
return function_data
|
||||||
|
|
||||||
|
|
||||||
|
@deprecate("moved to function_set()", log=log)
|
||||||
def action_set(values):
|
def action_set(values):
|
||||||
"""Sets the values to be returned after the action finishes"""
|
"""
|
||||||
|
.. deprecated:: 0.20.7
|
||||||
|
Alias for :func:`function_set`.
|
||||||
|
|
||||||
|
Sets the values to be returned after the action finishes.
|
||||||
|
"""
|
||||||
cmd = ['action-set']
|
cmd = ['action-set']
|
||||||
for k, v in list(values.items()):
|
for k, v in list(values.items()):
|
||||||
cmd.append('{}={}'.format(k, v))
|
cmd.append('{}={}'.format(k, v))
|
||||||
subprocess.check_call(cmd)
|
subprocess.check_call(cmd)
|
||||||
|
|
||||||
|
|
||||||
def action_fail(message):
|
def function_set(values):
|
||||||
"""Sets the action status to failed and sets the error message.
|
"""Sets the values to be returned after the function finishes"""
|
||||||
|
cmd = ['function-set']
|
||||||
|
# Fallback for older charms.
|
||||||
|
if not cmd_exists('function-get'):
|
||||||
|
cmd = ['action-set']
|
||||||
|
|
||||||
The results set by action_set are preserved."""
|
for k, v in list(values.items()):
|
||||||
|
cmd.append('{}={}'.format(k, v))
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
|
||||||
|
|
||||||
|
@deprecate("moved to function_fail()", log=log)
|
||||||
|
def action_fail(message):
|
||||||
|
"""
|
||||||
|
.. deprecated:: 0.20.7
|
||||||
|
Alias for :func:`function_fail`.
|
||||||
|
|
||||||
|
Sets the action status to failed and sets the error message.
|
||||||
|
|
||||||
|
The results set by action_set are preserved.
|
||||||
|
"""
|
||||||
subprocess.check_call(['action-fail', message])
|
subprocess.check_call(['action-fail', message])
|
||||||
|
|
||||||
|
|
||||||
|
def function_fail(message):
|
||||||
|
"""Sets the function status to failed and sets the error message.
|
||||||
|
|
||||||
|
The results set by function_set are preserved."""
|
||||||
|
cmd = ['function-fail']
|
||||||
|
# Fallback for older charms.
|
||||||
|
if not cmd_exists('function-fail'):
|
||||||
|
cmd = ['action-fail']
|
||||||
|
cmd.append(message)
|
||||||
|
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
|
||||||
|
|
||||||
def action_name():
|
def action_name():
|
||||||
"""Get the name of the currently executing action."""
|
"""Get the name of the currently executing action."""
|
||||||
return os.environ.get('JUJU_ACTION_NAME')
|
return os.environ.get('JUJU_ACTION_NAME')
|
||||||
|
|
||||||
|
|
||||||
|
def function_name():
|
||||||
|
"""Get the name of the currently executing function."""
|
||||||
|
return os.environ.get('JUJU_FUNCTION_NAME') or action_name()
|
||||||
|
|
||||||
|
|
||||||
def action_uuid():
|
def action_uuid():
|
||||||
"""Get the UUID of the currently executing action."""
|
"""Get the UUID of the currently executing action."""
|
||||||
return os.environ.get('JUJU_ACTION_UUID')
|
return os.environ.get('JUJU_ACTION_UUID')
|
||||||
|
|
||||||
|
|
||||||
|
def function_id():
|
||||||
|
"""Get the ID of the currently executing function."""
|
||||||
|
return os.environ.get('JUJU_FUNCTION_ID') or action_uuid()
|
||||||
|
|
||||||
|
|
||||||
def action_tag():
|
def action_tag():
|
||||||
"""Get the tag for the currently executing action."""
|
"""Get the tag for the currently executing action."""
|
||||||
return os.environ.get('JUJU_ACTION_TAG')
|
return os.environ.get('JUJU_ACTION_TAG')
|
||||||
|
|
||||||
|
|
||||||
|
def function_tag():
|
||||||
|
"""Get the tag for the currently executing function."""
|
||||||
|
return os.environ.get('JUJU_FUNCTION_TAG') or action_tag()
|
||||||
|
|
||||||
|
|
||||||
def status_set(workload_state, message):
|
def status_set(workload_state, message):
|
||||||
"""Set the workload state with a message
|
"""Set the workload state with a message
|
||||||
|
|
||||||
|
@ -166,6 +166,8 @@ def install():
|
|||||||
db.set('nova-compute-charm-use-fqdn', True)
|
db.set('nova-compute-charm-use-fqdn', True)
|
||||||
db.flush()
|
db.flush()
|
||||||
|
|
||||||
|
install_vaultlocker()
|
||||||
|
|
||||||
|
|
||||||
@hooks.hook('config-changed')
|
@hooks.hook('config-changed')
|
||||||
@restart_on_change(restart_map())
|
@restart_on_change(restart_map())
|
||||||
|
@ -36,6 +36,7 @@ from charmhelpers.fetch import (
|
|||||||
apt_autoremove,
|
apt_autoremove,
|
||||||
apt_mark,
|
apt_mark,
|
||||||
filter_missing_packages,
|
filter_missing_packages,
|
||||||
|
filter_installed_packages,
|
||||||
)
|
)
|
||||||
|
|
||||||
from charmhelpers.core.fstab import Fstab
|
from charmhelpers.core.fstab import Fstab
|
||||||
@ -306,6 +307,10 @@ def libvirt_daemon():
|
|||||||
return LIBVIRT_BIN_DAEMON
|
return LIBVIRT_BIN_DAEMON
|
||||||
|
|
||||||
|
|
||||||
|
def vaultlocker_installed():
|
||||||
|
return len(filter_installed_packages(['vaultlocker'])) == 0
|
||||||
|
|
||||||
|
|
||||||
def resource_map():
|
def resource_map():
|
||||||
'''
|
'''
|
||||||
Dynamically generate a map of resources that will be managed for a single
|
Dynamically generate a map of resources that will be managed for a single
|
||||||
@ -316,6 +321,18 @@ def resource_map():
|
|||||||
resource_map = deepcopy(BASE_RESOURCE_MAP)
|
resource_map = deepcopy(BASE_RESOURCE_MAP)
|
||||||
else:
|
else:
|
||||||
resource_map = deepcopy(LIBVIRT_RESOURCE_MAP)
|
resource_map = deepcopy(LIBVIRT_RESOURCE_MAP)
|
||||||
|
|
||||||
|
# if vault deps are not installed it is not yet possible to check the vault
|
||||||
|
# context status since it requires the hvac dependency.
|
||||||
|
if not vaultlocker_installed():
|
||||||
|
to_delete = []
|
||||||
|
for item in resource_map[NOVA_CONF]['contexts']:
|
||||||
|
if isinstance(item, type(vaultlocker.VaultKVContext())):
|
||||||
|
to_delete.append(item)
|
||||||
|
|
||||||
|
for item in to_delete:
|
||||||
|
resource_map[NOVA_CONF]['contexts'].remove(item)
|
||||||
|
|
||||||
net_manager = network_manager()
|
net_manager = network_manager()
|
||||||
|
|
||||||
# Network manager gets set late by the cloud-compute interface.
|
# Network manager gets set late by the cloud-compute interface.
|
||||||
@ -882,7 +899,17 @@ def assess_status_func(configs, services_=None):
|
|||||||
@return f() -> None : a function that assesses the unit's workload status
|
@return f() -> None : a function that assesses the unit's workload status
|
||||||
"""
|
"""
|
||||||
required_interfaces = REQUIRED_INTERFACES.copy()
|
required_interfaces = REQUIRED_INTERFACES.copy()
|
||||||
required_interfaces.update(get_optional_relations())
|
|
||||||
|
optional_relations = get_optional_relations()
|
||||||
|
if 'vault' in optional_relations:
|
||||||
|
# skip check if hvac dependency not installed yet
|
||||||
|
if not vaultlocker_installed():
|
||||||
|
log("Vault dependencies not yet met so removing from status check")
|
||||||
|
del optional_relations['vault']
|
||||||
|
else:
|
||||||
|
log("Vault dependencies met so including in status check")
|
||||||
|
|
||||||
|
required_interfaces.update(optional_relations)
|
||||||
return make_assess_status_func(
|
return make_assess_status_func(
|
||||||
configs, required_interfaces,
|
configs, required_interfaces,
|
||||||
services=services_ or services(), ports=None)
|
services=services_ or services(), ports=None)
|
||||||
@ -952,21 +979,28 @@ def determine_block_device():
|
|||||||
def configure_local_ephemeral_storage():
|
def configure_local_ephemeral_storage():
|
||||||
"""Configure local block device for use as ephemeral instance storage"""
|
"""Configure local block device for use as ephemeral instance storage"""
|
||||||
# Preflight check vault relation if encryption is enabled
|
# Preflight check vault relation if encryption is enabled
|
||||||
|
|
||||||
|
encrypt = config('encrypt')
|
||||||
|
if encrypt:
|
||||||
|
if not vaultlocker_installed():
|
||||||
|
log("Encryption requested but vaultlocker not yet installed",
|
||||||
|
level=DEBUG)
|
||||||
|
return
|
||||||
|
|
||||||
vault_kv = vaultlocker.VaultKVContext(
|
vault_kv = vaultlocker.VaultKVContext(
|
||||||
secret_backend=vaultlocker.VAULTLOCKER_BACKEND
|
secret_backend=vaultlocker.VAULTLOCKER_BACKEND
|
||||||
)
|
)
|
||||||
context = vault_kv()
|
context = vault_kv()
|
||||||
encrypt = config('encrypt')
|
if vault_kv.complete:
|
||||||
if encrypt and not vault_kv.complete:
|
# NOTE: only write vaultlocker configuration once relation is
|
||||||
|
# complete otherwise we run the chance of an empty
|
||||||
|
# configuration file being installed on a machine with other
|
||||||
|
# vaultlocker based services
|
||||||
|
vaultlocker.write_vaultlocker_conf(context, priority=80)
|
||||||
|
else:
|
||||||
log("Encryption requested but vault relation not complete",
|
log("Encryption requested but vault relation not complete",
|
||||||
level=DEBUG)
|
level=DEBUG)
|
||||||
return
|
return
|
||||||
elif encrypt and vault_kv.complete:
|
|
||||||
# NOTE: only write vaultlocker configuration once relation is complete
|
|
||||||
# otherwise we run the chance of an empty configuration file
|
|
||||||
# being installed on a machine with other vaultlocker based
|
|
||||||
# services
|
|
||||||
vaultlocker.write_vaultlocker_conf(context, priority=80)
|
|
||||||
|
|
||||||
db = kv()
|
db = kv()
|
||||||
storage_configured = db.get('storage-configured', False)
|
storage_configured = db.get('storage-configured', False)
|
||||||
|
@ -947,12 +947,15 @@ class NovaComputeUtilsTests(CharmTestCase):
|
|||||||
self.config.assert_called_with('ephemeral-device')
|
self.config.assert_called_with('ephemeral-device')
|
||||||
self.storage_list.assert_called_with('ephemeral-device')
|
self.storage_list.assert_called_with('ephemeral-device')
|
||||||
|
|
||||||
|
@patch.object(utils, 'filter_installed_packages')
|
||||||
@patch.object(utils, 'uuid')
|
@patch.object(utils, 'uuid')
|
||||||
@patch.object(utils, 'determine_block_device')
|
@patch.object(utils, 'determine_block_device')
|
||||||
def test_configure_local_ephemeral_storage_encrypted(
|
def test_configure_local_ephemeral_storage_encrypted(
|
||||||
self,
|
self,
|
||||||
determine_block_device,
|
determine_block_device,
|
||||||
uuid):
|
uuid,
|
||||||
|
filter_installed_packages):
|
||||||
|
filter_installed_packages.return_value = []
|
||||||
determine_block_device.return_value = '/dev/sdb'
|
determine_block_device.return_value = '/dev/sdb'
|
||||||
uuid.uuid4.return_value = 'test'
|
uuid.uuid4.return_value = 'test'
|
||||||
|
|
||||||
@ -1039,7 +1042,10 @@ class NovaComputeUtilsTests(CharmTestCase):
|
|||||||
self.assertTrue(self.test_kv.get('storage-configured'))
|
self.assertTrue(self.test_kv.get('storage-configured'))
|
||||||
self.vaultlocker.write_vaultlocker_conf.assert_not_called()
|
self.vaultlocker.write_vaultlocker_conf.assert_not_called()
|
||||||
|
|
||||||
def test_configure_local_ephemeral_storage_done(self):
|
@patch.object(utils, 'filter_installed_packages')
|
||||||
|
def test_configure_local_ephemeral_storage_done(self,
|
||||||
|
filter_installed_packages):
|
||||||
|
filter_installed_packages.return_value = []
|
||||||
self.test_kv.set('storage-configured', True)
|
self.test_kv.set('storage-configured', True)
|
||||||
|
|
||||||
mock_context = MagicMock()
|
mock_context = MagicMock()
|
||||||
|
Loading…
Reference in New Issue
Block a user