Enable Bionic as a gate test
Change bionic test from dev to gate for 18.05. Change-Id: I21fdefe0aa0b019d5b211bb54d0a2fa9e38c2864
This commit is contained in:
3
.stestr.conf
Normal file
3
.stestr.conf
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[DEFAULT]
|
||||||
|
test_path=./unit_tests
|
||||||
|
top_dir=./
|
@@ -1,13 +0,0 @@
|
|||||||
# Copyright 2016 Canonical Ltd
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
@@ -17,9 +17,23 @@
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
_path = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
_parent = os.path.abspath(os.path.join(_path, ".."))
|
||||||
|
_hooks = os.path.abspath(os.path.join(_parent, "hooks"))
|
||||||
|
|
||||||
|
|
||||||
|
def _add_path(path):
|
||||||
|
if path not in sys.path:
|
||||||
|
sys.path.insert(1, path)
|
||||||
|
|
||||||
|
|
||||||
|
_add_path(_parent)
|
||||||
|
_add_path(_hooks)
|
||||||
|
|
||||||
|
|
||||||
from charmhelpers.core.hookenv import action_fail
|
from charmhelpers.core.hookenv import action_fail
|
||||||
|
|
||||||
from hooks.glance_utils import (
|
from glance_utils import (
|
||||||
pause_unit_helper,
|
pause_unit_helper,
|
||||||
resume_unit_helper,
|
resume_unit_helper,
|
||||||
register_configs,
|
register_configs,
|
||||||
|
@@ -1 +0,0 @@
|
|||||||
../charmhelpers
|
|
@@ -1 +0,0 @@
|
|||||||
../hooks
|
|
@@ -14,16 +14,33 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
_path = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
_parent = os.path.abspath(os.path.join(_path, ".."))
|
||||||
|
_hooks = os.path.abspath(os.path.join(_parent, "hooks"))
|
||||||
|
|
||||||
|
|
||||||
|
def _add_path(path):
|
||||||
|
if path not in sys.path:
|
||||||
|
sys.path.insert(1, path)
|
||||||
|
|
||||||
|
|
||||||
|
_add_path(_parent)
|
||||||
|
_add_path(_hooks)
|
||||||
|
|
||||||
|
|
||||||
from charmhelpers.contrib.openstack.utils import (
|
from charmhelpers.contrib.openstack.utils import (
|
||||||
do_action_openstack_upgrade,
|
do_action_openstack_upgrade,
|
||||||
)
|
)
|
||||||
|
|
||||||
from hooks.glance_relations import (
|
from glance_relations import (
|
||||||
config_changed,
|
config_changed,
|
||||||
CONFIGS
|
CONFIGS
|
||||||
)
|
)
|
||||||
|
|
||||||
from hooks.glance_utils import do_openstack_upgrade
|
from glance_utils import do_openstack_upgrade
|
||||||
|
|
||||||
|
|
||||||
def openstack_upgrade():
|
def openstack_upgrade():
|
||||||
|
@@ -797,9 +797,9 @@ class ApacheSSLContext(OSContextGenerator):
|
|||||||
key_filename = 'key'
|
key_filename = 'key'
|
||||||
|
|
||||||
write_file(path=os.path.join(ssl_dir, cert_filename),
|
write_file(path=os.path.join(ssl_dir, cert_filename),
|
||||||
content=b64decode(cert))
|
content=b64decode(cert), perms=0o640)
|
||||||
write_file(path=os.path.join(ssl_dir, key_filename),
|
write_file(path=os.path.join(ssl_dir, key_filename),
|
||||||
content=b64decode(key))
|
content=b64decode(key), perms=0o640)
|
||||||
|
|
||||||
def configure_ca(self):
|
def configure_ca(self):
|
||||||
ca_cert = get_ca_cert()
|
ca_cert = get_ca_cert()
|
||||||
@@ -1873,10 +1873,11 @@ class EnsureDirContext(OSContextGenerator):
|
|||||||
context is needed to do that before rendering a template.
|
context is needed to do that before rendering a template.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, dirname):
|
def __init__(self, dirname, **kwargs):
|
||||||
'''Used merely to ensure that a given directory exists.'''
|
'''Used merely to ensure that a given directory exists.'''
|
||||||
self.dirname = dirname
|
self.dirname = dirname
|
||||||
|
self.kwargs = kwargs
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
mkdir(self.dirname)
|
mkdir(self.dirname, **self.kwargs)
|
||||||
return {}
|
return {}
|
||||||
|
@@ -0,0 +1,5 @@
|
|||||||
|
[oslo_middleware]
|
||||||
|
|
||||||
|
# Bug #1758675
|
||||||
|
enable_proxy_headers_parsing = true
|
||||||
|
|
@@ -5,4 +5,7 @@ transport_url = {{ transport_url }}
|
|||||||
{% if notification_topics -%}
|
{% if notification_topics -%}
|
||||||
topics = {{ notification_topics }}
|
topics = {{ notification_topics }}
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
|
{% if notification_format -%}
|
||||||
|
notification_format = {{ notification_format }}
|
||||||
|
{% endif -%}
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
|
@@ -306,7 +306,7 @@ def get_os_codename_install_source(src):
|
|||||||
|
|
||||||
if src.startswith('cloud:'):
|
if src.startswith('cloud:'):
|
||||||
ca_rel = src.split(':')[1]
|
ca_rel = src.split(':')[1]
|
||||||
ca_rel = ca_rel.split('%s-' % ubuntu_rel)[1].split('/')[0]
|
ca_rel = ca_rel.split('-')[1].split('/')[0]
|
||||||
return ca_rel
|
return ca_rel
|
||||||
|
|
||||||
# Best guess match based on deb string provided
|
# Best guess match based on deb string provided
|
||||||
|
126
charmhelpers/contrib/openstack/vaultlocker.py
Normal file
126
charmhelpers/contrib/openstack/vaultlocker.py
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# Copyright 2018 Canonical Limited.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
import charmhelpers.contrib.openstack.alternatives as alternatives
|
||||||
|
import charmhelpers.contrib.openstack.context as context
|
||||||
|
|
||||||
|
import charmhelpers.core.hookenv as hookenv
|
||||||
|
import charmhelpers.core.host as host
|
||||||
|
import charmhelpers.core.templating as templating
|
||||||
|
import charmhelpers.core.unitdata as unitdata
|
||||||
|
|
||||||
|
VAULTLOCKER_BACKEND = 'charm-vaultlocker'
|
||||||
|
|
||||||
|
|
||||||
|
class VaultKVContext(context.OSContextGenerator):
|
||||||
|
"""Vault KV context for interaction with vault-kv interfaces"""
|
||||||
|
interfaces = ['secrets-storage']
|
||||||
|
|
||||||
|
def __init__(self, secret_backend=None):
|
||||||
|
super(context.OSContextGenerator, self).__init__()
|
||||||
|
self.secret_backend = (
|
||||||
|
secret_backend or 'charm-{}'.format(hookenv.service_name())
|
||||||
|
)
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
db = unitdata.kv()
|
||||||
|
last_token = db.get('last-token')
|
||||||
|
secret_id = db.get('secret-id')
|
||||||
|
for relation_id in hookenv.relation_ids(self.interfaces[0]):
|
||||||
|
for unit in hookenv.related_units(relation_id):
|
||||||
|
data = hookenv.relation_get(unit=unit,
|
||||||
|
rid=relation_id)
|
||||||
|
vault_url = data.get('vault_url')
|
||||||
|
role_id = data.get('{}_role_id'.format(hookenv.local_unit()))
|
||||||
|
token = data.get('{}_token'.format(hookenv.local_unit()))
|
||||||
|
|
||||||
|
if all([vault_url, role_id, token]):
|
||||||
|
token = json.loads(token)
|
||||||
|
vault_url = json.loads(vault_url)
|
||||||
|
|
||||||
|
# Tokens may change when secret_id's are being
|
||||||
|
# reissued - if so use token to get new secret_id
|
||||||
|
if token != last_token:
|
||||||
|
secret_id = retrieve_secret_id(
|
||||||
|
url=vault_url,
|
||||||
|
token=token
|
||||||
|
)
|
||||||
|
db.set('secret-id', secret_id)
|
||||||
|
db.set('last-token', token)
|
||||||
|
db.flush()
|
||||||
|
|
||||||
|
ctxt = {
|
||||||
|
'vault_url': vault_url,
|
||||||
|
'role_id': json.loads(role_id),
|
||||||
|
'secret_id': secret_id,
|
||||||
|
'secret_backend': self.secret_backend,
|
||||||
|
}
|
||||||
|
vault_ca = data.get('vault_ca')
|
||||||
|
if vault_ca:
|
||||||
|
ctxt['vault_ca'] = json.loads(vault_ca)
|
||||||
|
self.complete = True
|
||||||
|
return ctxt
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def write_vaultlocker_conf(context, priority=100):
|
||||||
|
"""Write vaultlocker configuration to disk and install alternative
|
||||||
|
|
||||||
|
:param context: Dict of data from vault-kv relation
|
||||||
|
:ptype: context: dict
|
||||||
|
:param priority: Priority of alternative configuration
|
||||||
|
:ptype: priority: int"""
|
||||||
|
charm_vl_path = "/var/lib/charm/{}/vaultlocker.conf".format(
|
||||||
|
hookenv.service_name()
|
||||||
|
)
|
||||||
|
host.mkdir(os.path.dirname(charm_vl_path), perms=0o700)
|
||||||
|
templating.render(source='vaultlocker.conf.j2',
|
||||||
|
target=charm_vl_path,
|
||||||
|
context=context, perms=0o600),
|
||||||
|
alternatives.install_alternative('vaultlocker.conf',
|
||||||
|
'/etc/vaultlocker/vaultlocker.conf',
|
||||||
|
charm_vl_path, priority)
|
||||||
|
|
||||||
|
|
||||||
|
def vault_relation_complete(backend=None):
|
||||||
|
"""Determine whether vault relation is complete
|
||||||
|
|
||||||
|
:param backend: Name of secrets backend requested
|
||||||
|
: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
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: contrib a high level unwrap method to hvac that works
|
||||||
|
def retrieve_secret_id(url, token):
|
||||||
|
"""Retrieve a response-wrapped secret_id from Vault
|
||||||
|
|
||||||
|
:param url: URL to Vault Server
|
||||||
|
:ptype url: str
|
||||||
|
:param token: One shot Token to use
|
||||||
|
:ptype token: str
|
||||||
|
:returns: secret_id to use for Vault Access
|
||||||
|
:rtype: str"""
|
||||||
|
import hvac
|
||||||
|
client = hvac.Client(url=url, token=token)
|
||||||
|
response = client._post('/v1/sys/wrapping/unwrap')
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
return data['data']['secret_id']
|
@@ -291,7 +291,7 @@ class Pool(object):
|
|||||||
|
|
||||||
class ReplicatedPool(Pool):
|
class ReplicatedPool(Pool):
|
||||||
def __init__(self, service, name, pg_num=None, replicas=2,
|
def __init__(self, service, name, pg_num=None, replicas=2,
|
||||||
percent_data=10.0):
|
percent_data=10.0, app_name=None):
|
||||||
super(ReplicatedPool, self).__init__(service=service, name=name)
|
super(ReplicatedPool, self).__init__(service=service, name=name)
|
||||||
self.replicas = replicas
|
self.replicas = replicas
|
||||||
if pg_num:
|
if pg_num:
|
||||||
@@ -301,6 +301,10 @@ class ReplicatedPool(Pool):
|
|||||||
self.pg_num = min(pg_num, max_pgs)
|
self.pg_num = min(pg_num, max_pgs)
|
||||||
else:
|
else:
|
||||||
self.pg_num = self.get_pgs(self.replicas, percent_data)
|
self.pg_num = self.get_pgs(self.replicas, percent_data)
|
||||||
|
if app_name:
|
||||||
|
self.app_name = app_name
|
||||||
|
else:
|
||||||
|
self.app_name = 'unknown'
|
||||||
|
|
||||||
def create(self):
|
def create(self):
|
||||||
if not pool_exists(self.service, self.name):
|
if not pool_exists(self.service, self.name):
|
||||||
@@ -313,6 +317,12 @@ class ReplicatedPool(Pool):
|
|||||||
update_pool(client=self.service,
|
update_pool(client=self.service,
|
||||||
pool=self.name,
|
pool=self.name,
|
||||||
settings={'size': str(self.replicas)})
|
settings={'size': str(self.replicas)})
|
||||||
|
try:
|
||||||
|
set_app_name_for_pool(client=self.service,
|
||||||
|
pool=self.name,
|
||||||
|
name=self.app_name)
|
||||||
|
except CalledProcessError:
|
||||||
|
log('Could not set app name for pool {}'.format(self.name, level=WARNING))
|
||||||
except CalledProcessError:
|
except CalledProcessError:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@@ -320,10 +330,14 @@ class ReplicatedPool(Pool):
|
|||||||
# Default jerasure erasure coded pool
|
# Default jerasure erasure coded pool
|
||||||
class ErasurePool(Pool):
|
class ErasurePool(Pool):
|
||||||
def __init__(self, service, name, erasure_code_profile="default",
|
def __init__(self, service, name, erasure_code_profile="default",
|
||||||
percent_data=10.0):
|
percent_data=10.0, app_name=None):
|
||||||
super(ErasurePool, self).__init__(service=service, name=name)
|
super(ErasurePool, self).__init__(service=service, name=name)
|
||||||
self.erasure_code_profile = erasure_code_profile
|
self.erasure_code_profile = erasure_code_profile
|
||||||
self.percent_data = percent_data
|
self.percent_data = percent_data
|
||||||
|
if app_name:
|
||||||
|
self.app_name = app_name
|
||||||
|
else:
|
||||||
|
self.app_name = 'unknown'
|
||||||
|
|
||||||
def create(self):
|
def create(self):
|
||||||
if not pool_exists(self.service, self.name):
|
if not pool_exists(self.service, self.name):
|
||||||
@@ -355,6 +369,12 @@ class ErasurePool(Pool):
|
|||||||
'erasure', self.erasure_code_profile]
|
'erasure', self.erasure_code_profile]
|
||||||
try:
|
try:
|
||||||
check_call(cmd)
|
check_call(cmd)
|
||||||
|
try:
|
||||||
|
set_app_name_for_pool(client=self.service,
|
||||||
|
pool=self.name,
|
||||||
|
name=self.app_name)
|
||||||
|
except CalledProcessError:
|
||||||
|
log('Could not set app name for pool {}'.format(self.name, level=WARNING))
|
||||||
except CalledProcessError:
|
except CalledProcessError:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@@ -778,6 +798,25 @@ def update_pool(client, pool, settings):
|
|||||||
check_call(cmd)
|
check_call(cmd)
|
||||||
|
|
||||||
|
|
||||||
|
def set_app_name_for_pool(client, pool, name):
|
||||||
|
"""
|
||||||
|
Calls `osd pool application enable` for the specified pool name
|
||||||
|
|
||||||
|
:param client: Name of the ceph client to use
|
||||||
|
:type client: str
|
||||||
|
:param pool: Pool to set app name for
|
||||||
|
:type pool: str
|
||||||
|
:param name: app name for the specified pool
|
||||||
|
:type name: str
|
||||||
|
|
||||||
|
:raises: CalledProcessError if ceph call fails
|
||||||
|
"""
|
||||||
|
if ceph_version() >= '12.0.0':
|
||||||
|
cmd = ['ceph', '--id', client, 'osd', 'pool',
|
||||||
|
'application', 'enable', pool, name]
|
||||||
|
check_call(cmd)
|
||||||
|
|
||||||
|
|
||||||
def create_pool(service, name, replicas=3, pg_num=None):
|
def create_pool(service, name, replicas=3, pg_num=None):
|
||||||
"""Create a new RADOS pool."""
|
"""Create a new RADOS pool."""
|
||||||
if pool_exists(service, name):
|
if pool_exists(service, name):
|
||||||
|
@@ -290,7 +290,7 @@ class Config(dict):
|
|||||||
self.implicit_save = True
|
self.implicit_save = True
|
||||||
self._prev_dict = None
|
self._prev_dict = None
|
||||||
self.path = os.path.join(charm_dir(), Config.CONFIG_FILE_NAME)
|
self.path = os.path.join(charm_dir(), Config.CONFIG_FILE_NAME)
|
||||||
if os.path.exists(self.path):
|
if os.path.exists(self.path) and os.stat(self.path).st_size:
|
||||||
self.load_previous()
|
self.load_previous()
|
||||||
atexit(self._implicit_save)
|
atexit(self._implicit_save)
|
||||||
|
|
||||||
@@ -310,7 +310,11 @@ class Config(dict):
|
|||||||
"""
|
"""
|
||||||
self.path = path or self.path
|
self.path = path or self.path
|
||||||
with open(self.path) as f:
|
with open(self.path) as f:
|
||||||
self._prev_dict = json.load(f)
|
try:
|
||||||
|
self._prev_dict = json.load(f)
|
||||||
|
except ValueError as e:
|
||||||
|
log('Unable to parse previous config data - {}'.format(str(e)),
|
||||||
|
level=ERROR)
|
||||||
for k, v in copy.deepcopy(self._prev_dict).items():
|
for k, v in copy.deepcopy(self._prev_dict).items():
|
||||||
if k not in self:
|
if k not in self:
|
||||||
self[k] = v
|
self[k] = v
|
||||||
@@ -354,22 +358,40 @@ class Config(dict):
|
|||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
@cached
|
_cache_config = None
|
||||||
|
|
||||||
|
|
||||||
def config(scope=None):
|
def config(scope=None):
|
||||||
"""Juju charm configuration"""
|
"""
|
||||||
config_cmd_line = ['config-get']
|
Get the juju charm configuration (scope==None) or individual key,
|
||||||
if scope is not None:
|
(scope=str). The returned value is a Python data structure loaded as
|
||||||
config_cmd_line.append(scope)
|
JSON from the Juju config command.
|
||||||
else:
|
|
||||||
config_cmd_line.append('--all')
|
:param scope: If set, return the value for the specified key.
|
||||||
config_cmd_line.append('--format=json')
|
:type scope: Optional[str]
|
||||||
|
:returns: Either the whole config as a Config, or a key from it.
|
||||||
|
:rtype: Any
|
||||||
|
"""
|
||||||
|
global _cache_config
|
||||||
|
config_cmd_line = ['config-get', '--all', '--format=json']
|
||||||
try:
|
try:
|
||||||
config_data = json.loads(
|
# JSON Decode Exception for Python3.5+
|
||||||
subprocess.check_output(config_cmd_line).decode('UTF-8'))
|
exc_json = json.decoder.JSONDecodeError
|
||||||
|
except AttributeError:
|
||||||
|
# JSON Decode Exception for Python2.7 through Python3.4
|
||||||
|
exc_json = ValueError
|
||||||
|
try:
|
||||||
|
if _cache_config is None:
|
||||||
|
config_data = json.loads(
|
||||||
|
subprocess.check_output(config_cmd_line).decode('UTF-8'))
|
||||||
|
_cache_config = Config(config_data)
|
||||||
if scope is not None:
|
if scope is not None:
|
||||||
return config_data
|
return _cache_config.get(scope)
|
||||||
return Config(config_data)
|
return _cache_config
|
||||||
except ValueError:
|
except (exc_json, UnicodeDecodeError) as e:
|
||||||
|
log('Unable to parse output from config-get: config_cmd_line="{}" '
|
||||||
|
'message="{}"'
|
||||||
|
.format(config_cmd_line, str(e)), level=ERROR)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@@ -307,7 +307,9 @@ class PortManagerCallback(ManagerCallback):
|
|||||||
"""
|
"""
|
||||||
def __call__(self, manager, service_name, event_name):
|
def __call__(self, manager, service_name, event_name):
|
||||||
service = manager.get_service(service_name)
|
service = manager.get_service(service_name)
|
||||||
new_ports = service.get('ports', [])
|
# turn this generator into a list,
|
||||||
|
# as we'll be going over it multiple times
|
||||||
|
new_ports = list(service.get('ports', []))
|
||||||
port_file = os.path.join(hookenv.charm_dir(), '.{}.ports'.format(service_name))
|
port_file = os.path.join(hookenv.charm_dir(), '.{}.ports'.format(service_name))
|
||||||
if os.path.exists(port_file):
|
if os.path.exists(port_file):
|
||||||
with open(port_file) as fp:
|
with open(port_file) as fp:
|
||||||
|
@@ -31,18 +31,22 @@ __author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
|
|||||||
def create(sysctl_dict, sysctl_file):
|
def create(sysctl_dict, sysctl_file):
|
||||||
"""Creates a sysctl.conf file from a YAML associative array
|
"""Creates a sysctl.conf file from a YAML associative array
|
||||||
|
|
||||||
:param sysctl_dict: a YAML-formatted string of sysctl options eg "{ 'kernel.max_pid': 1337 }"
|
:param sysctl_dict: a dict or YAML-formatted string of sysctl
|
||||||
|
options eg "{ 'kernel.max_pid': 1337 }"
|
||||||
:type sysctl_dict: str
|
:type sysctl_dict: str
|
||||||
:param sysctl_file: path to the sysctl file to be saved
|
:param sysctl_file: path to the sysctl file to be saved
|
||||||
:type sysctl_file: str or unicode
|
:type sysctl_file: str or unicode
|
||||||
:returns: None
|
:returns: None
|
||||||
"""
|
"""
|
||||||
try:
|
if type(sysctl_dict) is not dict:
|
||||||
sysctl_dict_parsed = yaml.safe_load(sysctl_dict)
|
try:
|
||||||
except yaml.YAMLError:
|
sysctl_dict_parsed = yaml.safe_load(sysctl_dict)
|
||||||
log("Error parsing YAML sysctl_dict: {}".format(sysctl_dict),
|
except yaml.YAMLError:
|
||||||
level=ERROR)
|
log("Error parsing YAML sysctl_dict: {}".format(sysctl_dict),
|
||||||
return
|
level=ERROR)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
sysctl_dict_parsed = sysctl_dict
|
||||||
|
|
||||||
with open(sysctl_file, "w") as fd:
|
with open(sysctl_file, "w") as fd:
|
||||||
for key, value in sysctl_dict_parsed.items():
|
for key, value in sysctl_dict_parsed.items():
|
||||||
|
@@ -166,6 +166,10 @@ class Storage(object):
|
|||||||
|
|
||||||
To support dicts, lists, integer, floats, and booleans values
|
To support dicts, lists, integer, floats, and booleans values
|
||||||
are automatically json encoded/decoded.
|
are automatically json encoded/decoded.
|
||||||
|
|
||||||
|
Note: to facilitate unit testing, ':memory:' can be passed as the
|
||||||
|
path parameter which causes sqlite3 to only build the db in memory.
|
||||||
|
This should only be used for testing purposes.
|
||||||
"""
|
"""
|
||||||
def __init__(self, path=None):
|
def __init__(self, path=None):
|
||||||
self.db_path = path
|
self.db_path = path
|
||||||
@@ -175,8 +179,9 @@ class Storage(object):
|
|||||||
else:
|
else:
|
||||||
self.db_path = os.path.join(
|
self.db_path = os.path.join(
|
||||||
os.environ.get('CHARM_DIR', ''), '.unit-state.db')
|
os.environ.get('CHARM_DIR', ''), '.unit-state.db')
|
||||||
with open(self.db_path, 'a') as f:
|
if self.db_path != ':memory:':
|
||||||
os.fchmod(f.fileno(), 0o600)
|
with open(self.db_path, 'a') as f:
|
||||||
|
os.fchmod(f.fileno(), 0o600)
|
||||||
self.conn = sqlite3.connect('%s' % self.db_path)
|
self.conn = sqlite3.connect('%s' % self.db_path)
|
||||||
self.cursor = self.conn.cursor()
|
self.cursor = self.conn.cursor()
|
||||||
self.revision = None
|
self.revision = None
|
||||||
|
@@ -1 +0,0 @@
|
|||||||
../charmhelpers
|
|
@@ -14,8 +14,21 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
_path = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
_parent = os.path.abspath(os.path.join(_path, ".."))
|
||||||
|
|
||||||
|
|
||||||
|
def _add_path(path):
|
||||||
|
if path not in sys.path:
|
||||||
|
sys.path.insert(1, path)
|
||||||
|
|
||||||
|
|
||||||
|
_add_path(_parent)
|
||||||
|
|
||||||
|
|
||||||
from subprocess import (
|
from subprocess import (
|
||||||
call,
|
call,
|
||||||
check_call,
|
check_call,
|
||||||
|
@@ -290,7 +290,7 @@ class Config(dict):
|
|||||||
self.implicit_save = True
|
self.implicit_save = True
|
||||||
self._prev_dict = None
|
self._prev_dict = None
|
||||||
self.path = os.path.join(charm_dir(), Config.CONFIG_FILE_NAME)
|
self.path = os.path.join(charm_dir(), Config.CONFIG_FILE_NAME)
|
||||||
if os.path.exists(self.path):
|
if os.path.exists(self.path) and os.stat(self.path).st_size:
|
||||||
self.load_previous()
|
self.load_previous()
|
||||||
atexit(self._implicit_save)
|
atexit(self._implicit_save)
|
||||||
|
|
||||||
@@ -310,7 +310,11 @@ class Config(dict):
|
|||||||
"""
|
"""
|
||||||
self.path = path or self.path
|
self.path = path or self.path
|
||||||
with open(self.path) as f:
|
with open(self.path) as f:
|
||||||
self._prev_dict = json.load(f)
|
try:
|
||||||
|
self._prev_dict = json.load(f)
|
||||||
|
except ValueError as e:
|
||||||
|
log('Unable to parse previous config data - {}'.format(str(e)),
|
||||||
|
level=ERROR)
|
||||||
for k, v in copy.deepcopy(self._prev_dict).items():
|
for k, v in copy.deepcopy(self._prev_dict).items():
|
||||||
if k not in self:
|
if k not in self:
|
||||||
self[k] = v
|
self[k] = v
|
||||||
@@ -354,22 +358,40 @@ class Config(dict):
|
|||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
@cached
|
_cache_config = None
|
||||||
|
|
||||||
|
|
||||||
def config(scope=None):
|
def config(scope=None):
|
||||||
"""Juju charm configuration"""
|
"""
|
||||||
config_cmd_line = ['config-get']
|
Get the juju charm configuration (scope==None) or individual key,
|
||||||
if scope is not None:
|
(scope=str). The returned value is a Python data structure loaded as
|
||||||
config_cmd_line.append(scope)
|
JSON from the Juju config command.
|
||||||
else:
|
|
||||||
config_cmd_line.append('--all')
|
:param scope: If set, return the value for the specified key.
|
||||||
config_cmd_line.append('--format=json')
|
:type scope: Optional[str]
|
||||||
|
:returns: Either the whole config as a Config, or a key from it.
|
||||||
|
:rtype: Any
|
||||||
|
"""
|
||||||
|
global _cache_config
|
||||||
|
config_cmd_line = ['config-get', '--all', '--format=json']
|
||||||
try:
|
try:
|
||||||
config_data = json.loads(
|
# JSON Decode Exception for Python3.5+
|
||||||
subprocess.check_output(config_cmd_line).decode('UTF-8'))
|
exc_json = json.decoder.JSONDecodeError
|
||||||
|
except AttributeError:
|
||||||
|
# JSON Decode Exception for Python2.7 through Python3.4
|
||||||
|
exc_json = ValueError
|
||||||
|
try:
|
||||||
|
if _cache_config is None:
|
||||||
|
config_data = json.loads(
|
||||||
|
subprocess.check_output(config_cmd_line).decode('UTF-8'))
|
||||||
|
_cache_config = Config(config_data)
|
||||||
if scope is not None:
|
if scope is not None:
|
||||||
return config_data
|
return _cache_config.get(scope)
|
||||||
return Config(config_data)
|
return _cache_config
|
||||||
except ValueError:
|
except (exc_json, UnicodeDecodeError) as e:
|
||||||
|
log('Unable to parse output from config-get: config_cmd_line="{}" '
|
||||||
|
'message="{}"'
|
||||||
|
.format(config_cmd_line, str(e)), level=ERROR)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@@ -307,7 +307,9 @@ class PortManagerCallback(ManagerCallback):
|
|||||||
"""
|
"""
|
||||||
def __call__(self, manager, service_name, event_name):
|
def __call__(self, manager, service_name, event_name):
|
||||||
service = manager.get_service(service_name)
|
service = manager.get_service(service_name)
|
||||||
new_ports = service.get('ports', [])
|
# turn this generator into a list,
|
||||||
|
# as we'll be going over it multiple times
|
||||||
|
new_ports = list(service.get('ports', []))
|
||||||
port_file = os.path.join(hookenv.charm_dir(), '.{}.ports'.format(service_name))
|
port_file = os.path.join(hookenv.charm_dir(), '.{}.ports'.format(service_name))
|
||||||
if os.path.exists(port_file):
|
if os.path.exists(port_file):
|
||||||
with open(port_file) as fp:
|
with open(port_file) as fp:
|
||||||
|
@@ -31,18 +31,22 @@ __author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
|
|||||||
def create(sysctl_dict, sysctl_file):
|
def create(sysctl_dict, sysctl_file):
|
||||||
"""Creates a sysctl.conf file from a YAML associative array
|
"""Creates a sysctl.conf file from a YAML associative array
|
||||||
|
|
||||||
:param sysctl_dict: a YAML-formatted string of sysctl options eg "{ 'kernel.max_pid': 1337 }"
|
:param sysctl_dict: a dict or YAML-formatted string of sysctl
|
||||||
|
options eg "{ 'kernel.max_pid': 1337 }"
|
||||||
:type sysctl_dict: str
|
:type sysctl_dict: str
|
||||||
:param sysctl_file: path to the sysctl file to be saved
|
:param sysctl_file: path to the sysctl file to be saved
|
||||||
:type sysctl_file: str or unicode
|
:type sysctl_file: str or unicode
|
||||||
:returns: None
|
:returns: None
|
||||||
"""
|
"""
|
||||||
try:
|
if type(sysctl_dict) is not dict:
|
||||||
sysctl_dict_parsed = yaml.safe_load(sysctl_dict)
|
try:
|
||||||
except yaml.YAMLError:
|
sysctl_dict_parsed = yaml.safe_load(sysctl_dict)
|
||||||
log("Error parsing YAML sysctl_dict: {}".format(sysctl_dict),
|
except yaml.YAMLError:
|
||||||
level=ERROR)
|
log("Error parsing YAML sysctl_dict: {}".format(sysctl_dict),
|
||||||
return
|
level=ERROR)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
sysctl_dict_parsed = sysctl_dict
|
||||||
|
|
||||||
with open(sysctl_file, "w") as fd:
|
with open(sysctl_file, "w") as fd:
|
||||||
for key, value in sysctl_dict_parsed.items():
|
for key, value in sysctl_dict_parsed.items():
|
||||||
|
@@ -166,6 +166,10 @@ class Storage(object):
|
|||||||
|
|
||||||
To support dicts, lists, integer, floats, and booleans values
|
To support dicts, lists, integer, floats, and booleans values
|
||||||
are automatically json encoded/decoded.
|
are automatically json encoded/decoded.
|
||||||
|
|
||||||
|
Note: to facilitate unit testing, ':memory:' can be passed as the
|
||||||
|
path parameter which causes sqlite3 to only build the db in memory.
|
||||||
|
This should only be used for testing purposes.
|
||||||
"""
|
"""
|
||||||
def __init__(self, path=None):
|
def __init__(self, path=None):
|
||||||
self.db_path = path
|
self.db_path = path
|
||||||
@@ -175,8 +179,9 @@ class Storage(object):
|
|||||||
else:
|
else:
|
||||||
self.db_path = os.path.join(
|
self.db_path = os.path.join(
|
||||||
os.environ.get('CHARM_DIR', ''), '.unit-state.db')
|
os.environ.get('CHARM_DIR', ''), '.unit-state.db')
|
||||||
with open(self.db_path, 'a') as f:
|
if self.db_path != ':memory:':
|
||||||
os.fchmod(f.fileno(), 0o600)
|
with open(self.db_path, 'a') as f:
|
||||||
|
os.fchmod(f.fileno(), 0o600)
|
||||||
self.conn = sqlite3.connect('%s' % self.db_path)
|
self.conn = sqlite3.connect('%s' % self.db_path)
|
||||||
self.cursor = self.conn.cursor()
|
self.cursor = self.conn.cursor()
|
||||||
self.revision = None
|
self.revision = None
|
||||||
|
2
tox.ini
2
tox.ini
@@ -60,7 +60,7 @@ basepython = python2.7
|
|||||||
deps = -r{toxinidir}/requirements.txt
|
deps = -r{toxinidir}/requirements.txt
|
||||||
-r{toxinidir}/test-requirements.txt
|
-r{toxinidir}/test-requirements.txt
|
||||||
commands =
|
commands =
|
||||||
bundletester -vl DEBUG -r json -o func-results.json gate-basic-xenial-pike --no-destroy
|
bundletester -vl DEBUG -r json -o func-results.json gate-basic-bionic-queens --no-destroy
|
||||||
|
|
||||||
[testenv:func27-dfs]
|
[testenv:func27-dfs]
|
||||||
# Charm Functional Test
|
# Charm Functional Test
|
||||||
|
@@ -11,3 +11,20 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
_path = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
_parent = os.path.abspath(os.path.join(_path, ".."))
|
||||||
|
_hooks = os.path.abspath(os.path.join(_parent, "hooks"))
|
||||||
|
_actions = os.path.abspath(os.path.join(_parent, "actions"))
|
||||||
|
|
||||||
|
|
||||||
|
def _add_path(path):
|
||||||
|
if path not in sys.path:
|
||||||
|
sys.path.insert(1, path)
|
||||||
|
|
||||||
|
|
||||||
|
_add_path(_parent)
|
||||||
|
_add_path(_hooks)
|
||||||
|
_add_path(_actions)
|
||||||
|
@@ -19,19 +19,19 @@ import mock
|
|||||||
from test_utils import CharmTestCase
|
from test_utils import CharmTestCase
|
||||||
|
|
||||||
os.environ['JUJU_UNIT_NAME'] = 'glance'
|
os.environ['JUJU_UNIT_NAME'] = 'glance'
|
||||||
with mock.patch('actions.hooks.glance_utils.register_configs') as configs:
|
with mock.patch('glance_utils.register_configs') as configs:
|
||||||
configs.return_value = 'test-config'
|
configs.return_value = 'test-config'
|
||||||
import actions.actions
|
import actions
|
||||||
|
|
||||||
|
|
||||||
class PauseTestCase(CharmTestCase):
|
class PauseTestCase(CharmTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(PauseTestCase, self).setUp(
|
super(PauseTestCase, self).setUp(
|
||||||
actions.actions, ["pause_unit_helper"])
|
actions, ["pause_unit_helper"])
|
||||||
|
|
||||||
def test_pauses_services(self):
|
def test_pauses_services(self):
|
||||||
actions.actions.pause([])
|
actions.pause([])
|
||||||
self.pause_unit_helper.assert_called_once_with('test-config')
|
self.pause_unit_helper.assert_called_once_with('test-config')
|
||||||
|
|
||||||
|
|
||||||
@@ -39,17 +39,17 @@ class ResumeTestCase(CharmTestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(ResumeTestCase, self).setUp(
|
super(ResumeTestCase, self).setUp(
|
||||||
actions.actions, ["resume_unit_helper"])
|
actions, ["resume_unit_helper"])
|
||||||
|
|
||||||
def test_pauses_services(self):
|
def test_pauses_services(self):
|
||||||
actions.actions.resume([])
|
actions.resume([])
|
||||||
self.resume_unit_helper.assert_called_once_with('test-config')
|
self.resume_unit_helper.assert_called_once_with('test-config')
|
||||||
|
|
||||||
|
|
||||||
class MainTestCase(CharmTestCase):
|
class MainTestCase(CharmTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(MainTestCase, self).setUp(actions.actions, ["action_fail"])
|
super(MainTestCase, self).setUp(actions, ["action_fail"])
|
||||||
|
|
||||||
def test_invokes_action(self):
|
def test_invokes_action(self):
|
||||||
dummy_calls = []
|
dummy_calls = []
|
||||||
@@ -57,13 +57,13 @@ class MainTestCase(CharmTestCase):
|
|||||||
def dummy_action(args):
|
def dummy_action(args):
|
||||||
dummy_calls.append(True)
|
dummy_calls.append(True)
|
||||||
|
|
||||||
with mock.patch.dict(actions.actions.ACTIONS, {"foo": dummy_action}):
|
with mock.patch.dict(actions.ACTIONS, {"foo": dummy_action}):
|
||||||
actions.actions.main(["foo"])
|
actions.main(["foo"])
|
||||||
self.assertEqual(dummy_calls, [True])
|
self.assertEqual(dummy_calls, [True])
|
||||||
|
|
||||||
def test_unknown_action(self):
|
def test_unknown_action(self):
|
||||||
"""Unknown actions aren't a traceback."""
|
"""Unknown actions aren't a traceback."""
|
||||||
exit_string = actions.actions.main(["foo"])
|
exit_string = actions.main(["foo"])
|
||||||
self.assertEqual("Action foo undefined", exit_string)
|
self.assertEqual("Action foo undefined", exit_string)
|
||||||
|
|
||||||
def test_failing_action(self):
|
def test_failing_action(self):
|
||||||
@@ -75,6 +75,6 @@ class MainTestCase(CharmTestCase):
|
|||||||
def dummy_action(args):
|
def dummy_action(args):
|
||||||
raise ValueError("uh oh")
|
raise ValueError("uh oh")
|
||||||
|
|
||||||
with mock.patch.dict(actions.actions.ACTIONS, {"foo": dummy_action}):
|
with mock.patch.dict(actions.ACTIONS, {"foo": dummy_action}):
|
||||||
actions.actions.main(["foo"])
|
actions.main(["foo"])
|
||||||
self.assertEqual(dummy_calls, ["uh oh"])
|
self.assertEqual(dummy_calls, ["uh oh"])
|
||||||
|
@@ -25,10 +25,17 @@ mock_apt = MagicMock()
|
|||||||
sys.modules['apt'] = mock_apt
|
sys.modules['apt'] = mock_apt
|
||||||
mock_apt.apt_pkg = MagicMock()
|
mock_apt.apt_pkg = MagicMock()
|
||||||
|
|
||||||
with patch('actions.hooks.glance_utils.register_configs'):
|
with patch('charmhelpers.contrib.hardening.harden.harden') as mock_dec, \
|
||||||
with patch('hooks.glance_utils.register_configs'):
|
patch('charmhelpers.contrib.openstack.utils.'
|
||||||
with patch('actions.hooks.glance_utils.restart_map'):
|
'os_requires_version') as mock_os, \
|
||||||
from actions import openstack_upgrade
|
patch('glance_utils.register_configs'), \
|
||||||
|
patch('glance_utils.restart_map'):
|
||||||
|
mock_dec.side_effect = (lambda *dargs, **dkwargs: lambda f:
|
||||||
|
lambda *args, **kwargs: f(*args, **kwargs))
|
||||||
|
|
||||||
|
mock_os.side_effect = (lambda *dargs, **dkwargs: lambda f:
|
||||||
|
lambda *args, **kwargs: f(*args, **kwargs))
|
||||||
|
import openstack_upgrade
|
||||||
|
|
||||||
from test_utils import CharmTestCase
|
from test_utils import CharmTestCase
|
||||||
|
|
||||||
@@ -44,10 +51,10 @@ class TestGlanceUpgradeActions(CharmTestCase):
|
|||||||
super(TestGlanceUpgradeActions, self).setUp(openstack_upgrade,
|
super(TestGlanceUpgradeActions, self).setUp(openstack_upgrade,
|
||||||
TO_PATCH)
|
TO_PATCH)
|
||||||
|
|
||||||
@patch('actions.charmhelpers.contrib.openstack.utils.config')
|
@patch('charmhelpers.contrib.openstack.utils.config')
|
||||||
@patch('actions.charmhelpers.contrib.openstack.utils.action_set')
|
@patch('charmhelpers.contrib.openstack.utils.action_set')
|
||||||
@patch('actions.charmhelpers.contrib.openstack.utils.openstack_upgrade_available') # noqa
|
@patch('charmhelpers.contrib.openstack.utils.openstack_upgrade_available') # noqa
|
||||||
@patch('actions.charmhelpers.contrib.openstack.utils.juju_log')
|
@patch('charmhelpers.contrib.openstack.utils.juju_log')
|
||||||
@patch('subprocess.check_output')
|
@patch('subprocess.check_output')
|
||||||
def test_openstack_upgrade_true(self, _check_output, log, upgrade_avail,
|
def test_openstack_upgrade_true(self, _check_output, log, upgrade_avail,
|
||||||
action_set, config):
|
action_set, config):
|
||||||
@@ -60,10 +67,10 @@ class TestGlanceUpgradeActions(CharmTestCase):
|
|||||||
self.assertTrue(self.do_openstack_upgrade.called)
|
self.assertTrue(self.do_openstack_upgrade.called)
|
||||||
self.assertTrue(self.config_changed.called)
|
self.assertTrue(self.config_changed.called)
|
||||||
|
|
||||||
@patch('actions.charmhelpers.contrib.openstack.utils.config')
|
@patch('charmhelpers.contrib.openstack.utils.config')
|
||||||
@patch('actions.charmhelpers.contrib.openstack.utils.action_set')
|
@patch('charmhelpers.contrib.openstack.utils.action_set')
|
||||||
@patch('actions.charmhelpers.contrib.openstack.utils.openstack_upgrade_available') # noqa
|
@patch('charmhelpers.contrib.openstack.utils.openstack_upgrade_available') # noqa
|
||||||
@patch('actions.charmhelpers.contrib.openstack.utils.juju_log')
|
@patch('charmhelpers.contrib.openstack.utils.juju_log')
|
||||||
@patch('subprocess.check_output')
|
@patch('subprocess.check_output')
|
||||||
def test_openstack_upgrade_false(self, _check_output, log, upgrade_avail,
|
def test_openstack_upgrade_false(self, _check_output, log, upgrade_avail,
|
||||||
action_set, config):
|
action_set, config):
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
from mock import patch, MagicMock
|
from mock import patch, MagicMock
|
||||||
|
|
||||||
from hooks import glance_contexts as contexts
|
import glance_contexts as contexts
|
||||||
from test_utils import (
|
from test_utils import (
|
||||||
CharmTestCase
|
CharmTestCase
|
||||||
)
|
)
|
||||||
|
@@ -25,28 +25,27 @@ sys.modules['apt'] = mock_apt
|
|||||||
mock_apt.apt_pkg = MagicMock()
|
mock_apt.apt_pkg = MagicMock()
|
||||||
|
|
||||||
os.environ['JUJU_UNIT_NAME'] = 'glance'
|
os.environ['JUJU_UNIT_NAME'] = 'glance'
|
||||||
import hooks.glance_utils as utils # noqa
|
|
||||||
|
|
||||||
_reg = utils.register_configs
|
|
||||||
_map = utils.restart_map
|
|
||||||
|
|
||||||
utils.register_configs = MagicMock()
|
with patch('charmhelpers.contrib.openstack.utils.'
|
||||||
utils.restart_map = MagicMock()
|
'pausable_restart_on_change') as mock_on_change, \
|
||||||
|
patch('charmhelpers.contrib.hardening.harden.harden') as mock_dec, \
|
||||||
with patch('hooks.charmhelpers.contrib.hardening.harden.harden') as mock_dec:
|
patch('charmhelpers.contrib.openstack.'
|
||||||
with patch('hooks.charmhelpers.contrib.openstack.'
|
'utils.os_requires_version') as mock_os, \
|
||||||
'utils.os_requires_version') as mock_os:
|
patch('glance_utils.register_configs') as mock_register, \
|
||||||
mock_dec.side_effect = (lambda *dargs, **dkwargs: lambda f:
|
patch('glance_utils.restart_map') as mock_map, \
|
||||||
lambda *args, **kwargs: f(*args, **kwargs))
|
patch('glance_utils.config'):
|
||||||
mock_os.side_effect = (lambda *dargs, **dkwargs: lambda f:
|
mock_on_change.side_effect = (lambda *dargs, **dkwargs: lambda f:
|
||||||
lambda *args, **kwargs: f(*args, **kwargs))
|
lambda *args, **kwargs: f(*args,
|
||||||
import hooks.glance_relations as relations
|
**kwargs))
|
||||||
|
mock_dec.side_effect = (lambda *dargs, **dkwargs: lambda f:
|
||||||
|
lambda *args, **kwargs: f(*args, **kwargs))
|
||||||
|
mock_os.side_effect = (lambda *dargs, **dkwargs: lambda f:
|
||||||
|
lambda *args, **kwargs: f(*args, **kwargs))
|
||||||
|
import glance_relations as relations
|
||||||
|
|
||||||
relations.hooks._config_save = False
|
relations.hooks._config_save = False
|
||||||
|
|
||||||
utils.register_configs = _reg
|
|
||||||
utils.restart_map = _map
|
|
||||||
|
|
||||||
TO_PATCH = [
|
TO_PATCH = [
|
||||||
# charmhelpers.core.hookenv
|
# charmhelpers.core.hookenv
|
||||||
'Hooks',
|
'Hooks',
|
||||||
@@ -75,7 +74,7 @@ TO_PATCH = [
|
|||||||
'is_clustered',
|
'is_clustered',
|
||||||
# charmhelpers.contrib.hahelpers.cluster_utils
|
# charmhelpers.contrib.hahelpers.cluster_utils
|
||||||
'is_elected_leader',
|
'is_elected_leader',
|
||||||
# hooks.glance_utils
|
# glance_utils
|
||||||
'restart_map',
|
'restart_map',
|
||||||
'register_configs',
|
'register_configs',
|
||||||
'do_openstack_upgrade',
|
'do_openstack_upgrade',
|
||||||
@@ -84,6 +83,7 @@ TO_PATCH = [
|
|||||||
'ceph_config_file',
|
'ceph_config_file',
|
||||||
'update_nrpe_config',
|
'update_nrpe_config',
|
||||||
'reinstall_paste_ini',
|
'reinstall_paste_ini',
|
||||||
|
'determine_packages',
|
||||||
# other
|
# other
|
||||||
'call',
|
'call',
|
||||||
'check_call',
|
'check_call',
|
||||||
@@ -104,28 +104,23 @@ class GlanceRelationTests(CharmTestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(GlanceRelationTests, self).setUp(relations, TO_PATCH)
|
super(GlanceRelationTests, self).setUp(relations, TO_PATCH)
|
||||||
self.config.side_effect = self.test_config.get
|
self.config.side_effect = self.test_config.get
|
||||||
|
self.restart_on_change.return_value = None
|
||||||
|
|
||||||
@patch.object(utils, 'config')
|
def test_install_hook(self):
|
||||||
@patch.object(utils, 'token_cache_pkgs')
|
|
||||||
def test_install_hook(self, token_cache_pkgs, util_config):
|
|
||||||
token_cache_pkgs.return_value = ['memcached']
|
|
||||||
repo = 'cloud:precise-grizzly'
|
repo = 'cloud:precise-grizzly'
|
||||||
|
_packages = ['apache2', 'glance', 'haproxy', 'memcached',
|
||||||
|
'python-keystone', 'python-mysqldb', 'python-psycopg2',
|
||||||
|
'python-six', 'python-swiftclient', 'uuid']
|
||||||
self.test_config.set('openstack-origin', repo)
|
self.test_config.set('openstack-origin', repo)
|
||||||
self.service_stop.return_value = True
|
self.service_stop.return_value = True
|
||||||
|
self.determine_packages.return_value = _packages
|
||||||
relations.install_hook()
|
relations.install_hook()
|
||||||
self.configure_installation_source.assert_called_with(repo)
|
self.configure_installation_source.assert_called_with(repo)
|
||||||
self.apt_update.assert_called_with(fatal=True)
|
self.apt_update.assert_called_with(fatal=True)
|
||||||
self.apt_install.assert_called_with(
|
self.apt_install.assert_called_with(_packages, fatal=True)
|
||||||
['apache2', 'glance', 'haproxy', 'memcached', 'python-keystone',
|
|
||||||
'python-mysqldb', 'python-psycopg2', 'python-six',
|
|
||||||
'python-swiftclient', 'uuid'], fatal=True)
|
|
||||||
self.assertTrue(self.execd_preinstall.called)
|
self.assertTrue(self.execd_preinstall.called)
|
||||||
|
|
||||||
@patch.object(utils, 'config')
|
def test_install_hook_precise_distro(self):
|
||||||
@patch.object(utils, 'token_cache_pkgs')
|
|
||||||
def test_install_hook_precise_distro(self, token_cache_pkgs,
|
|
||||||
util_config):
|
|
||||||
token_cache_pkgs.return_value = []
|
|
||||||
self.test_config.set('openstack-origin', 'distro')
|
self.test_config.set('openstack-origin', 'distro')
|
||||||
self.lsb_release.return_value = {'DISTRIB_RELEASE': 12.04,
|
self.lsb_release.return_value = {'DISTRIB_RELEASE': 12.04,
|
||||||
'DISTRIB_CODENAME': 'precise'}
|
'DISTRIB_CODENAME': 'precise'}
|
||||||
@@ -338,9 +333,9 @@ class GlanceRelationTests(CharmTestCase):
|
|||||||
for c in [call('/etc/glance/glance.conf')]:
|
for c in [call('/etc/glance/glance.conf')]:
|
||||||
self.assertNotIn(c, configs.write.call_args_list)
|
self.assertNotIn(c, configs.write.call_args_list)
|
||||||
|
|
||||||
@patch('hooks.charmhelpers.contrib.storage.linux.ceph.CephBrokerRq'
|
@patch('charmhelpers.contrib.storage.linux.ceph.CephBrokerRq'
|
||||||
'.add_op_request_access_to_group')
|
'.add_op_request_access_to_group')
|
||||||
@patch('hooks.charmhelpers.contrib.storage.linux.ceph.CephBrokerRq'
|
@patch('charmhelpers.contrib.storage.linux.ceph.CephBrokerRq'
|
||||||
'.add_op_create_pool')
|
'.add_op_create_pool')
|
||||||
def test_create_pool_op(self, mock_create_pool,
|
def test_create_pool_op(self, mock_create_pool,
|
||||||
mock_request_access):
|
mock_request_access):
|
||||||
@@ -564,11 +559,8 @@ class GlanceRelationTests(CharmTestCase):
|
|||||||
configs.write.call_args_list)
|
configs.write.call_args_list)
|
||||||
|
|
||||||
@patch.object(relations, 'update_image_location_policy')
|
@patch.object(relations, 'update_image_location_policy')
|
||||||
@patch.object(utils, 'config')
|
|
||||||
@patch.object(utils, 'token_cache_pkgs')
|
|
||||||
@patch.object(relations, 'CONFIGS')
|
@patch.object(relations, 'CONFIGS')
|
||||||
def test_upgrade_charm(self, configs, token_cache_pkgs,
|
def test_upgrade_charm(self, configs, mock_update_image_location_policy):
|
||||||
util_config, mock_update_image_location_policy):
|
|
||||||
self.filter_installed_packages.return_value = ['test']
|
self.filter_installed_packages.return_value = ['test']
|
||||||
relations.upgrade_charm()
|
relations.upgrade_charm()
|
||||||
self.apt_install.assert_called_with(['test'], fatal=True)
|
self.apt_install.assert_called_with(['test'], fatal=True)
|
||||||
|
@@ -18,7 +18,7 @@ from collections import OrderedDict
|
|||||||
from mock import patch, call, MagicMock, mock_open
|
from mock import patch, call, MagicMock, mock_open
|
||||||
|
|
||||||
os.environ['JUJU_UNIT_NAME'] = 'glance'
|
os.environ['JUJU_UNIT_NAME'] = 'glance'
|
||||||
import hooks.glance_utils as utils
|
import glance_utils as utils
|
||||||
|
|
||||||
from test_utils import (
|
from test_utils import (
|
||||||
CharmTestCase,
|
CharmTestCase,
|
||||||
|
@@ -20,9 +20,9 @@ import yaml
|
|||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from mock import patch, MagicMock
|
from mock import patch, MagicMock
|
||||||
|
|
||||||
patch('hooks.charmhelpers.contrib.openstack.utils.'
|
patch('charmhelpers.contrib.openstack.utils.'
|
||||||
'set_os_workload_status').start()
|
'set_os_workload_status').start()
|
||||||
patch('hooks.charmhelpers.core.hookenv.status_set').start()
|
patch('charmhelpers.core.hookenv.status_set').start()
|
||||||
|
|
||||||
|
|
||||||
def load_config():
|
def load_config():
|
||||||
|
Reference in New Issue
Block a user