Update for Python 3 execution
Charm helpers sync Change-Id: I6cfa96b23354f68521a3179eb690340e1c9f23bf
This commit is contained in:
parent
93ca07ac92
commit
c7fea4af07
3
.gitignore
vendored
3
.gitignore
vendored
@ -10,3 +10,6 @@ trusty/
|
|||||||
xenial/
|
xenial/
|
||||||
.unit-state.db
|
.unit-state.db
|
||||||
tests/cirros-*-disk.img
|
tests/cirros-*-disk.img
|
||||||
|
func-results.json
|
||||||
|
__pycache__
|
||||||
|
.stestr/
|
||||||
|
3
.stestr.conf
Normal file
3
.stestr.conf
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[DEFAULT]
|
||||||
|
test_path=./unit_tests
|
||||||
|
top_dir=./
|
@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/env python3
|
||||||
#
|
#
|
||||||
# Copyright 2016 Canonical Ltd
|
# Copyright 2016 Canonical Ltd
|
||||||
#
|
#
|
||||||
@ -14,9 +14,19 @@
|
|||||||
# 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
|
||||||
|
|
||||||
sys.path.append('hooks/')
|
_path = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
_root = os.path.abspath(os.path.join(_path, '..'))
|
||||||
|
|
||||||
|
|
||||||
|
def _add_path(path):
|
||||||
|
if path not in sys.path:
|
||||||
|
sys.path.insert(1, path)
|
||||||
|
|
||||||
|
|
||||||
|
_add_path('hooks')
|
||||||
|
|
||||||
from charmhelpers.contrib.openstack.utils import (
|
from charmhelpers.contrib.openstack.utils import (
|
||||||
do_action_openstack_upgrade,
|
do_action_openstack_upgrade,
|
||||||
|
@ -27,6 +27,7 @@ clustering-related helpers.
|
|||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import os
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
from socket import gethostname as get_unit_hostname
|
from socket import gethostname as get_unit_hostname
|
||||||
|
|
||||||
@ -45,6 +46,9 @@ from charmhelpers.core.hookenv import (
|
|||||||
is_leader as juju_is_leader,
|
is_leader as juju_is_leader,
|
||||||
status_set,
|
status_set,
|
||||||
)
|
)
|
||||||
|
from charmhelpers.core.host import (
|
||||||
|
modulo_distribution,
|
||||||
|
)
|
||||||
from charmhelpers.core.decorators import (
|
from charmhelpers.core.decorators import (
|
||||||
retry_on_exception,
|
retry_on_exception,
|
||||||
)
|
)
|
||||||
@ -361,3 +365,29 @@ def canonical_url(configs, vip_setting='vip'):
|
|||||||
else:
|
else:
|
||||||
addr = unit_get('private-address')
|
addr = unit_get('private-address')
|
||||||
return '%s://%s' % (scheme, addr)
|
return '%s://%s' % (scheme, addr)
|
||||||
|
|
||||||
|
|
||||||
|
def distributed_wait(modulo=None, wait=None, operation_name='operation'):
|
||||||
|
''' Distribute operations by waiting based on modulo_distribution
|
||||||
|
|
||||||
|
If modulo and or wait are not set, check config_get for those values.
|
||||||
|
|
||||||
|
:param modulo: int The modulo number creates the group distribution
|
||||||
|
:param wait: int The constant time wait value
|
||||||
|
:param operation_name: string Operation name for status message
|
||||||
|
i.e. 'restart'
|
||||||
|
:side effect: Calls config_get()
|
||||||
|
:side effect: Calls log()
|
||||||
|
:side effect: Calls status_set()
|
||||||
|
:side effect: Calls time.sleep()
|
||||||
|
'''
|
||||||
|
if modulo is None:
|
||||||
|
modulo = config_get('modulo-nodes')
|
||||||
|
if wait is None:
|
||||||
|
wait = config_get('known-wait')
|
||||||
|
calculated_wait = modulo_distribution(modulo=modulo, wait=wait)
|
||||||
|
msg = "Waiting {} seconds for {} ...".format(calculated_wait,
|
||||||
|
operation_name)
|
||||||
|
log(msg, DEBUG)
|
||||||
|
status_set('maintenance', msg)
|
||||||
|
time.sleep(calculated_wait)
|
||||||
|
@ -70,12 +70,12 @@ class DisabledModuleAudit(BaseAudit):
|
|||||||
"""Returns the modules which are enabled in Apache."""
|
"""Returns the modules which are enabled in Apache."""
|
||||||
output = subprocess.check_output(['apache2ctl', '-M'])
|
output = subprocess.check_output(['apache2ctl', '-M'])
|
||||||
modules = []
|
modules = []
|
||||||
for line in output.strip().split():
|
for line in output.splitlines():
|
||||||
# Each line of the enabled module output looks like:
|
# Each line of the enabled module output looks like:
|
||||||
# module_name (static|shared)
|
# module_name (static|shared)
|
||||||
# Plus a header line at the top of the output which is stripped
|
# Plus a header line at the top of the output which is stripped
|
||||||
# out by the regex.
|
# out by the regex.
|
||||||
matcher = re.search(r'^ (\S*)', line)
|
matcher = re.search(r'^ (\S*)_module (\S*)', line)
|
||||||
if matcher:
|
if matcher:
|
||||||
modules.append(matcher.group(1))
|
modules.append(matcher.group(1))
|
||||||
return modules
|
return modules
|
||||||
|
@ -490,7 +490,7 @@ def get_host_ip(hostname, fallback=None):
|
|||||||
if not ip_addr:
|
if not ip_addr:
|
||||||
try:
|
try:
|
||||||
ip_addr = socket.gethostbyname(hostname)
|
ip_addr = socket.gethostbyname(hostname)
|
||||||
except:
|
except Exception:
|
||||||
log("Failed to resolve hostname '%s'" % (hostname),
|
log("Failed to resolve hostname '%s'" % (hostname),
|
||||||
level=WARNING)
|
level=WARNING)
|
||||||
return fallback
|
return fallback
|
||||||
@ -518,7 +518,7 @@ def get_hostname(address, fqdn=True):
|
|||||||
if not result:
|
if not result:
|
||||||
try:
|
try:
|
||||||
result = socket.gethostbyaddr(address)[0]
|
result = socket.gethostbyaddr(address)[0]
|
||||||
except:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
result = address
|
result = address
|
||||||
|
@ -29,3 +29,16 @@ def install_alternative(name, target, source, priority=50):
|
|||||||
target, name, source, str(priority)
|
target, name, source, str(priority)
|
||||||
]
|
]
|
||||||
subprocess.check_call(cmd)
|
subprocess.check_call(cmd)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_alternative(name, source):
|
||||||
|
"""Remove an installed alternative configuration file
|
||||||
|
|
||||||
|
:param name: string name of the alternative to remove
|
||||||
|
:param source: string full path to alternative to remove
|
||||||
|
"""
|
||||||
|
cmd = [
|
||||||
|
'update-alternatives', '--remove',
|
||||||
|
name, source
|
||||||
|
]
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
@ -250,7 +250,14 @@ class OpenStackAmuletDeployment(AmuletDeployment):
|
|||||||
self.log.debug('Waiting up to {}s for extended status on services: '
|
self.log.debug('Waiting up to {}s for extended status on services: '
|
||||||
'{}'.format(timeout, services))
|
'{}'.format(timeout, services))
|
||||||
service_messages = {service: message for service in services}
|
service_messages = {service: message for service in services}
|
||||||
|
|
||||||
|
# Check for idleness
|
||||||
|
self.d.sentry.wait()
|
||||||
|
# Check for error states and bail early
|
||||||
|
self.d.sentry.wait_for_status(self.d.juju_env, services)
|
||||||
|
# Check for ready messages
|
||||||
self.d.sentry.wait_for_messages(service_messages, timeout=timeout)
|
self.d.sentry.wait_for_messages(service_messages, timeout=timeout)
|
||||||
|
|
||||||
self.log.info('OK')
|
self.log.info('OK')
|
||||||
|
|
||||||
def _get_openstack_release(self):
|
def _get_openstack_release(self):
|
||||||
@ -263,7 +270,8 @@ class OpenStackAmuletDeployment(AmuletDeployment):
|
|||||||
(self.trusty_icehouse, self.trusty_kilo, self.trusty_liberty,
|
(self.trusty_icehouse, self.trusty_kilo, self.trusty_liberty,
|
||||||
self.trusty_mitaka, self.xenial_mitaka, self.xenial_newton,
|
self.trusty_mitaka, self.xenial_mitaka, self.xenial_newton,
|
||||||
self.yakkety_newton, self.xenial_ocata, self.zesty_ocata,
|
self.yakkety_newton, self.xenial_ocata, self.zesty_ocata,
|
||||||
self.xenial_pike, self.artful_pike) = range(11)
|
self.xenial_pike, self.artful_pike, self.xenial_queens,
|
||||||
|
self.bionic_queens,) = range(13)
|
||||||
|
|
||||||
releases = {
|
releases = {
|
||||||
('trusty', None): self.trusty_icehouse,
|
('trusty', None): self.trusty_icehouse,
|
||||||
@ -274,9 +282,11 @@ class OpenStackAmuletDeployment(AmuletDeployment):
|
|||||||
('xenial', 'cloud:xenial-newton'): self.xenial_newton,
|
('xenial', 'cloud:xenial-newton'): self.xenial_newton,
|
||||||
('xenial', 'cloud:xenial-ocata'): self.xenial_ocata,
|
('xenial', 'cloud:xenial-ocata'): self.xenial_ocata,
|
||||||
('xenial', 'cloud:xenial-pike'): self.xenial_pike,
|
('xenial', 'cloud:xenial-pike'): self.xenial_pike,
|
||||||
|
('xenial', 'cloud:xenial-queens'): self.xenial_queens,
|
||||||
('yakkety', None): self.yakkety_newton,
|
('yakkety', None): self.yakkety_newton,
|
||||||
('zesty', None): self.zesty_ocata,
|
('zesty', None): self.zesty_ocata,
|
||||||
('artful', None): self.artful_pike,
|
('artful', None): self.artful_pike,
|
||||||
|
('bionic', None): self.bionic_queens,
|
||||||
}
|
}
|
||||||
return releases[(self.series, self.openstack)]
|
return releases[(self.series, self.openstack)]
|
||||||
|
|
||||||
@ -291,6 +301,7 @@ class OpenStackAmuletDeployment(AmuletDeployment):
|
|||||||
('yakkety', 'newton'),
|
('yakkety', 'newton'),
|
||||||
('zesty', 'ocata'),
|
('zesty', 'ocata'),
|
||||||
('artful', 'pike'),
|
('artful', 'pike'),
|
||||||
|
('bionic', 'queens'),
|
||||||
])
|
])
|
||||||
if self.openstack:
|
if self.openstack:
|
||||||
os_origin = self.openstack.split(':')[1]
|
os_origin = self.openstack.split(':')[1]
|
||||||
@ -303,20 +314,27 @@ class OpenStackAmuletDeployment(AmuletDeployment):
|
|||||||
test scenario, based on OpenStack release and whether ceph radosgw
|
test scenario, based on OpenStack release and whether ceph radosgw
|
||||||
is flagged as present or not."""
|
is flagged as present or not."""
|
||||||
|
|
||||||
if self._get_openstack_release() >= self.trusty_kilo:
|
if self._get_openstack_release() == self.trusty_icehouse:
|
||||||
# Kilo or later
|
# Icehouse
|
||||||
pools = [
|
|
||||||
'rbd',
|
|
||||||
'cinder',
|
|
||||||
'glance'
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
# Juno or earlier
|
|
||||||
pools = [
|
pools = [
|
||||||
'data',
|
'data',
|
||||||
'metadata',
|
'metadata',
|
||||||
'rbd',
|
'rbd',
|
||||||
'cinder',
|
'cinder-ceph',
|
||||||
|
'glance'
|
||||||
|
]
|
||||||
|
elif (self.trusty_kilo <= self._get_openstack_release() <=
|
||||||
|
self.zesty_ocata):
|
||||||
|
# Kilo through Ocata
|
||||||
|
pools = [
|
||||||
|
'rbd',
|
||||||
|
'cinder-ceph',
|
||||||
|
'glance'
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
# Pike and later
|
||||||
|
pools = [
|
||||||
|
'cinder-ceph',
|
||||||
'glance'
|
'glance'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ import urllib
|
|||||||
import urlparse
|
import urlparse
|
||||||
|
|
||||||
import cinderclient.v1.client as cinder_client
|
import cinderclient.v1.client as cinder_client
|
||||||
|
import cinderclient.v2.client as cinder_clientv2
|
||||||
import glanceclient.v1.client as glance_client
|
import glanceclient.v1.client as glance_client
|
||||||
import heatclient.v1.client as heat_client
|
import heatclient.v1.client as heat_client
|
||||||
from keystoneclient.v2_0 import client as keystone_client
|
from keystoneclient.v2_0 import client as keystone_client
|
||||||
@ -42,7 +43,6 @@ import swiftclient
|
|||||||
from charmhelpers.contrib.amulet.utils import (
|
from charmhelpers.contrib.amulet.utils import (
|
||||||
AmuletUtils
|
AmuletUtils
|
||||||
)
|
)
|
||||||
from charmhelpers.core.decorators import retry_on_exception
|
|
||||||
from charmhelpers.core.host import CompareHostReleases
|
from charmhelpers.core.host import CompareHostReleases
|
||||||
|
|
||||||
DEBUG = logging.DEBUG
|
DEBUG = logging.DEBUG
|
||||||
@ -310,7 +310,6 @@ class OpenStackAmuletUtils(AmuletUtils):
|
|||||||
self.log.debug('Checking if tenant exists ({})...'.format(tenant))
|
self.log.debug('Checking if tenant exists ({})...'.format(tenant))
|
||||||
return tenant in [t.name for t in keystone.tenants.list()]
|
return tenant in [t.name for t in keystone.tenants.list()]
|
||||||
|
|
||||||
@retry_on_exception(5, base_delay=10)
|
|
||||||
def keystone_wait_for_propagation(self, sentry_relation_pairs,
|
def keystone_wait_for_propagation(self, sentry_relation_pairs,
|
||||||
api_version):
|
api_version):
|
||||||
"""Iterate over list of sentry and relation tuples and verify that
|
"""Iterate over list of sentry and relation tuples and verify that
|
||||||
@ -326,7 +325,7 @@ class OpenStackAmuletUtils(AmuletUtils):
|
|||||||
rel = sentry.relation('identity-service',
|
rel = sentry.relation('identity-service',
|
||||||
relation_name)
|
relation_name)
|
||||||
self.log.debug('keystone relation data: {}'.format(rel))
|
self.log.debug('keystone relation data: {}'.format(rel))
|
||||||
if rel['api_version'] != str(api_version):
|
if rel.get('api_version') != str(api_version):
|
||||||
raise Exception("api_version not propagated through relation"
|
raise Exception("api_version not propagated through relation"
|
||||||
" data yet ('{}' != '{}')."
|
" data yet ('{}' != '{}')."
|
||||||
"".format(rel['api_version'], api_version))
|
"".format(rel['api_version'], api_version))
|
||||||
@ -348,15 +347,19 @@ class OpenStackAmuletUtils(AmuletUtils):
|
|||||||
|
|
||||||
config = {'preferred-api-version': api_version}
|
config = {'preferred-api-version': api_version}
|
||||||
deployment.d.configure('keystone', config)
|
deployment.d.configure('keystone', config)
|
||||||
|
deployment._auto_wait_for_status()
|
||||||
self.keystone_wait_for_propagation(sentry_relation_pairs, api_version)
|
self.keystone_wait_for_propagation(sentry_relation_pairs, api_version)
|
||||||
|
|
||||||
def authenticate_cinder_admin(self, keystone_sentry, username,
|
def authenticate_cinder_admin(self, keystone_sentry, username,
|
||||||
password, tenant):
|
password, tenant, api_version=2):
|
||||||
"""Authenticates admin user with cinder."""
|
"""Authenticates admin user with cinder."""
|
||||||
# NOTE(beisner): cinder python client doesn't accept tokens.
|
# NOTE(beisner): cinder python client doesn't accept tokens.
|
||||||
keystone_ip = keystone_sentry.info['public-address']
|
keystone_ip = keystone_sentry.info['public-address']
|
||||||
ept = "http://{}:5000/v2.0".format(keystone_ip.strip().decode('utf-8'))
|
ept = "http://{}:5000/v2.0".format(keystone_ip.strip().decode('utf-8'))
|
||||||
return cinder_client.Client(username, password, tenant, ept)
|
_clients = {
|
||||||
|
1: cinder_client.Client,
|
||||||
|
2: cinder_clientv2.Client}
|
||||||
|
return _clients[api_version](username, password, tenant, ept)
|
||||||
|
|
||||||
def authenticate_keystone(self, keystone_ip, username, password,
|
def authenticate_keystone(self, keystone_ip, username, password,
|
||||||
api_version=False, admin_port=False,
|
api_version=False, admin_port=False,
|
||||||
@ -617,13 +620,25 @@ class OpenStackAmuletUtils(AmuletUtils):
|
|||||||
self.log.debug('Keypair ({}) already exists, '
|
self.log.debug('Keypair ({}) already exists, '
|
||||||
'using it.'.format(keypair_name))
|
'using it.'.format(keypair_name))
|
||||||
return _keypair
|
return _keypair
|
||||||
except:
|
except Exception:
|
||||||
self.log.debug('Keypair ({}) does not exist, '
|
self.log.debug('Keypair ({}) does not exist, '
|
||||||
'creating it.'.format(keypair_name))
|
'creating it.'.format(keypair_name))
|
||||||
|
|
||||||
_keypair = nova.keypairs.create(name=keypair_name)
|
_keypair = nova.keypairs.create(name=keypair_name)
|
||||||
return _keypair
|
return _keypair
|
||||||
|
|
||||||
|
def _get_cinder_obj_name(self, cinder_object):
|
||||||
|
"""Retrieve name of cinder object.
|
||||||
|
|
||||||
|
:param cinder_object: cinder snapshot or volume object
|
||||||
|
:returns: str cinder object name
|
||||||
|
"""
|
||||||
|
# v1 objects store name in 'display_name' attr but v2+ use 'name'
|
||||||
|
try:
|
||||||
|
return cinder_object.display_name
|
||||||
|
except AttributeError:
|
||||||
|
return cinder_object.name
|
||||||
|
|
||||||
def create_cinder_volume(self, cinder, vol_name="demo-vol", vol_size=1,
|
def create_cinder_volume(self, cinder, vol_name="demo-vol", vol_size=1,
|
||||||
img_id=None, src_vol_id=None, snap_id=None):
|
img_id=None, src_vol_id=None, snap_id=None):
|
||||||
"""Create cinder volume, optionally from a glance image, OR
|
"""Create cinder volume, optionally from a glance image, OR
|
||||||
@ -674,6 +689,13 @@ class OpenStackAmuletUtils(AmuletUtils):
|
|||||||
source_volid=src_vol_id,
|
source_volid=src_vol_id,
|
||||||
snapshot_id=snap_id)
|
snapshot_id=snap_id)
|
||||||
vol_id = vol_new.id
|
vol_id = vol_new.id
|
||||||
|
except TypeError:
|
||||||
|
vol_new = cinder.volumes.create(name=vol_name,
|
||||||
|
imageRef=img_id,
|
||||||
|
size=vol_size,
|
||||||
|
source_volid=src_vol_id,
|
||||||
|
snapshot_id=snap_id)
|
||||||
|
vol_id = vol_new.id
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
msg = 'Failed to create volume: {}'.format(e)
|
msg = 'Failed to create volume: {}'.format(e)
|
||||||
amulet.raise_status(amulet.FAIL, msg=msg)
|
amulet.raise_status(amulet.FAIL, msg=msg)
|
||||||
@ -688,7 +710,7 @@ class OpenStackAmuletUtils(AmuletUtils):
|
|||||||
|
|
||||||
# Re-validate new volume
|
# Re-validate new volume
|
||||||
self.log.debug('Validating volume attributes...')
|
self.log.debug('Validating volume attributes...')
|
||||||
val_vol_name = cinder.volumes.get(vol_id).display_name
|
val_vol_name = self._get_cinder_obj_name(cinder.volumes.get(vol_id))
|
||||||
val_vol_boot = cinder.volumes.get(vol_id).bootable
|
val_vol_boot = cinder.volumes.get(vol_id).bootable
|
||||||
val_vol_stat = cinder.volumes.get(vol_id).status
|
val_vol_stat = cinder.volumes.get(vol_id).status
|
||||||
val_vol_size = cinder.volumes.get(vol_id).size
|
val_vol_size = cinder.volumes.get(vol_id).size
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
# 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 collections
|
||||||
import glob
|
import glob
|
||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
@ -578,11 +579,14 @@ class HAProxyContext(OSContextGenerator):
|
|||||||
laddr = get_address_in_network(config(cfg_opt))
|
laddr = get_address_in_network(config(cfg_opt))
|
||||||
if laddr:
|
if laddr:
|
||||||
netmask = get_netmask_for_address(laddr)
|
netmask = get_netmask_for_address(laddr)
|
||||||
cluster_hosts[laddr] = {'network': "{}/{}".format(laddr,
|
cluster_hosts[laddr] = {
|
||||||
netmask),
|
'network': "{}/{}".format(laddr,
|
||||||
'backends': {l_unit: laddr}}
|
netmask),
|
||||||
|
'backends': collections.OrderedDict([(l_unit,
|
||||||
|
laddr)])
|
||||||
|
}
|
||||||
for rid in relation_ids('cluster'):
|
for rid in relation_ids('cluster'):
|
||||||
for unit in related_units(rid):
|
for unit in sorted(related_units(rid)):
|
||||||
_laddr = relation_get('{}-address'.format(addr_type),
|
_laddr = relation_get('{}-address'.format(addr_type),
|
||||||
rid=rid, unit=unit)
|
rid=rid, unit=unit)
|
||||||
if _laddr:
|
if _laddr:
|
||||||
@ -594,10 +598,13 @@ class HAProxyContext(OSContextGenerator):
|
|||||||
# match in the frontend
|
# match in the frontend
|
||||||
cluster_hosts[addr] = {}
|
cluster_hosts[addr] = {}
|
||||||
netmask = get_netmask_for_address(addr)
|
netmask = get_netmask_for_address(addr)
|
||||||
cluster_hosts[addr] = {'network': "{}/{}".format(addr, netmask),
|
cluster_hosts[addr] = {
|
||||||
'backends': {l_unit: addr}}
|
'network': "{}/{}".format(addr, netmask),
|
||||||
|
'backends': collections.OrderedDict([(l_unit,
|
||||||
|
addr)])
|
||||||
|
}
|
||||||
for rid in relation_ids('cluster'):
|
for rid in relation_ids('cluster'):
|
||||||
for unit in related_units(rid):
|
for unit in sorted(related_units(rid)):
|
||||||
_laddr = relation_get('private-address',
|
_laddr = relation_get('private-address',
|
||||||
rid=rid, unit=unit)
|
rid=rid, unit=unit)
|
||||||
if _laddr:
|
if _laddr:
|
||||||
@ -628,6 +635,8 @@ class HAProxyContext(OSContextGenerator):
|
|||||||
ctxt['local_host'] = '127.0.0.1'
|
ctxt['local_host'] = '127.0.0.1'
|
||||||
ctxt['haproxy_host'] = '0.0.0.0'
|
ctxt['haproxy_host'] = '0.0.0.0'
|
||||||
|
|
||||||
|
ctxt['ipv6_enabled'] = not is_ipv6_disabled()
|
||||||
|
|
||||||
ctxt['stat_port'] = '8888'
|
ctxt['stat_port'] = '8888'
|
||||||
|
|
||||||
db = kv()
|
db = kv()
|
||||||
@ -802,8 +811,9 @@ class ApacheSSLContext(OSContextGenerator):
|
|||||||
else:
|
else:
|
||||||
# Expect cert/key provided in config (currently assumed that ca
|
# Expect cert/key provided in config (currently assumed that ca
|
||||||
# uses ip for cn)
|
# uses ip for cn)
|
||||||
cn = resolve_address(endpoint_type=INTERNAL)
|
for net_type in (INTERNAL, ADMIN, PUBLIC):
|
||||||
self.configure_cert(cn)
|
cn = resolve_address(endpoint_type=net_type)
|
||||||
|
self.configure_cert(cn)
|
||||||
|
|
||||||
addresses = self.get_network_addresses()
|
addresses = self.get_network_addresses()
|
||||||
for address, endpoint in addresses:
|
for address, endpoint in addresses:
|
||||||
@ -843,15 +853,6 @@ class NeutronContext(OSContextGenerator):
|
|||||||
for pkgs in self.packages:
|
for pkgs in self.packages:
|
||||||
ensure_packages(pkgs)
|
ensure_packages(pkgs)
|
||||||
|
|
||||||
def _save_flag_file(self):
|
|
||||||
if self.network_manager == 'quantum':
|
|
||||||
_file = '/etc/nova/quantum_plugin.conf'
|
|
||||||
else:
|
|
||||||
_file = '/etc/nova/neutron_plugin.conf'
|
|
||||||
|
|
||||||
with open(_file, 'wb') as out:
|
|
||||||
out.write(self.plugin + '\n')
|
|
||||||
|
|
||||||
def ovs_ctxt(self):
|
def ovs_ctxt(self):
|
||||||
driver = neutron_plugin_attribute(self.plugin, 'driver',
|
driver = neutron_plugin_attribute(self.plugin, 'driver',
|
||||||
self.network_manager)
|
self.network_manager)
|
||||||
@ -996,7 +997,6 @@ class NeutronContext(OSContextGenerator):
|
|||||||
flags = config_flags_parser(alchemy_flags)
|
flags = config_flags_parser(alchemy_flags)
|
||||||
ctxt['neutron_alchemy_flags'] = flags
|
ctxt['neutron_alchemy_flags'] = flags
|
||||||
|
|
||||||
self._save_flag_file()
|
|
||||||
return ctxt
|
return ctxt
|
||||||
|
|
||||||
|
|
||||||
@ -1176,7 +1176,7 @@ class SubordinateConfigContext(OSContextGenerator):
|
|||||||
if sub_config and sub_config != '':
|
if sub_config and sub_config != '':
|
||||||
try:
|
try:
|
||||||
sub_config = json.loads(sub_config)
|
sub_config = json.loads(sub_config)
|
||||||
except:
|
except Exception:
|
||||||
log('Could not parse JSON from '
|
log('Could not parse JSON from '
|
||||||
'subordinate_configuration setting from %s'
|
'subordinate_configuration setting from %s'
|
||||||
% rid, level=ERROR)
|
% rid, level=ERROR)
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
CRITICAL=0
|
CRITICAL=0
|
||||||
NOTACTIVE=''
|
NOTACTIVE=''
|
||||||
LOGFILE=/var/log/nagios/check_haproxy.log
|
LOGFILE=/var/log/nagios/check_haproxy.log
|
||||||
AUTH=$(grep -r "stats auth" /etc/haproxy | awk 'NR=1{print $4}')
|
AUTH=$(grep -r "stats auth" /etc/haproxy/haproxy.cfg | awk 'NR=1{print $4}')
|
||||||
|
|
||||||
typeset -i N_INSTANCES=0
|
typeset -i N_INSTANCES=0
|
||||||
for appserver in $(awk '/^\s+server/{print $2}' /etc/haproxy/haproxy.cfg)
|
for appserver in $(awk '/^\s+server/{print $2}' /etc/haproxy/haproxy.cfg)
|
||||||
|
@ -82,15 +82,18 @@ def update_dns_ha_resource_params(resources, resource_params,
|
|||||||
continue
|
continue
|
||||||
m = re.search('os-(.+?)-hostname', setting)
|
m = re.search('os-(.+?)-hostname', setting)
|
||||||
if m:
|
if m:
|
||||||
networkspace = m.group(1)
|
endpoint_type = m.group(1)
|
||||||
|
# resolve_address's ADDRESS_MAP uses 'int' not 'internal'
|
||||||
|
if endpoint_type == 'internal':
|
||||||
|
endpoint_type = 'int'
|
||||||
else:
|
else:
|
||||||
msg = ('Unexpected DNS hostname setting: {}. '
|
msg = ('Unexpected DNS hostname setting: {}. '
|
||||||
'Cannot determine network space name'
|
'Cannot determine endpoint_type name'
|
||||||
''.format(setting))
|
''.format(setting))
|
||||||
status_set('blocked', msg)
|
status_set('blocked', msg)
|
||||||
raise DNSHAException(msg)
|
raise DNSHAException(msg)
|
||||||
|
|
||||||
hostname_key = 'res_{}_{}_hostname'.format(charm_name(), networkspace)
|
hostname_key = 'res_{}_{}_hostname'.format(charm_name(), endpoint_type)
|
||||||
if hostname_key in hostname_group:
|
if hostname_key in hostname_group:
|
||||||
log('DNS HA: Resource {}: {} already exists in '
|
log('DNS HA: Resource {}: {} already exists in '
|
||||||
'hostname group - skipping'.format(hostname_key, hostname),
|
'hostname group - skipping'.format(hostname_key, hostname),
|
||||||
@ -101,7 +104,7 @@ def update_dns_ha_resource_params(resources, resource_params,
|
|||||||
resources[hostname_key] = crm_ocf
|
resources[hostname_key] = crm_ocf
|
||||||
resource_params[hostname_key] = (
|
resource_params[hostname_key] = (
|
||||||
'params fqdn="{}" ip_address="{}" '
|
'params fqdn="{}" ip_address="{}" '
|
||||||
''.format(hostname, resolve_address(endpoint_type=networkspace,
|
''.format(hostname, resolve_address(endpoint_type=endpoint_type,
|
||||||
override=False)))
|
override=False)))
|
||||||
|
|
||||||
if len(hostname_group) >= 1:
|
if len(hostname_group) >= 1:
|
||||||
|
@ -59,18 +59,13 @@ def determine_dkms_package():
|
|||||||
|
|
||||||
|
|
||||||
def quantum_plugins():
|
def quantum_plugins():
|
||||||
from charmhelpers.contrib.openstack import context
|
|
||||||
return {
|
return {
|
||||||
'ovs': {
|
'ovs': {
|
||||||
'config': '/etc/quantum/plugins/openvswitch/'
|
'config': '/etc/quantum/plugins/openvswitch/'
|
||||||
'ovs_quantum_plugin.ini',
|
'ovs_quantum_plugin.ini',
|
||||||
'driver': 'quantum.plugins.openvswitch.ovs_quantum_plugin.'
|
'driver': 'quantum.plugins.openvswitch.ovs_quantum_plugin.'
|
||||||
'OVSQuantumPluginV2',
|
'OVSQuantumPluginV2',
|
||||||
'contexts': [
|
'contexts': [],
|
||||||
context.SharedDBContext(user=config('neutron-database-user'),
|
|
||||||
database=config('neutron-database'),
|
|
||||||
relation_prefix='neutron',
|
|
||||||
ssl_dir=QUANTUM_CONF_DIR)],
|
|
||||||
'services': ['quantum-plugin-openvswitch-agent'],
|
'services': ['quantum-plugin-openvswitch-agent'],
|
||||||
'packages': [determine_dkms_package(),
|
'packages': [determine_dkms_package(),
|
||||||
['quantum-plugin-openvswitch-agent']],
|
['quantum-plugin-openvswitch-agent']],
|
||||||
@ -82,11 +77,7 @@ def quantum_plugins():
|
|||||||
'config': '/etc/quantum/plugins/nicira/nvp.ini',
|
'config': '/etc/quantum/plugins/nicira/nvp.ini',
|
||||||
'driver': 'quantum.plugins.nicira.nicira_nvp_plugin.'
|
'driver': 'quantum.plugins.nicira.nicira_nvp_plugin.'
|
||||||
'QuantumPlugin.NvpPluginV2',
|
'QuantumPlugin.NvpPluginV2',
|
||||||
'contexts': [
|
'contexts': [],
|
||||||
context.SharedDBContext(user=config('neutron-database-user'),
|
|
||||||
database=config('neutron-database'),
|
|
||||||
relation_prefix='neutron',
|
|
||||||
ssl_dir=QUANTUM_CONF_DIR)],
|
|
||||||
'services': [],
|
'services': [],
|
||||||
'packages': [],
|
'packages': [],
|
||||||
'server_packages': ['quantum-server',
|
'server_packages': ['quantum-server',
|
||||||
@ -100,7 +91,6 @@ NEUTRON_CONF_DIR = '/etc/neutron'
|
|||||||
|
|
||||||
|
|
||||||
def neutron_plugins():
|
def neutron_plugins():
|
||||||
from charmhelpers.contrib.openstack import context
|
|
||||||
release = os_release('nova-common')
|
release = os_release('nova-common')
|
||||||
plugins = {
|
plugins = {
|
||||||
'ovs': {
|
'ovs': {
|
||||||
@ -108,11 +98,7 @@ def neutron_plugins():
|
|||||||
'ovs_neutron_plugin.ini',
|
'ovs_neutron_plugin.ini',
|
||||||
'driver': 'neutron.plugins.openvswitch.ovs_neutron_plugin.'
|
'driver': 'neutron.plugins.openvswitch.ovs_neutron_plugin.'
|
||||||
'OVSNeutronPluginV2',
|
'OVSNeutronPluginV2',
|
||||||
'contexts': [
|
'contexts': [],
|
||||||
context.SharedDBContext(user=config('neutron-database-user'),
|
|
||||||
database=config('neutron-database'),
|
|
||||||
relation_prefix='neutron',
|
|
||||||
ssl_dir=NEUTRON_CONF_DIR)],
|
|
||||||
'services': ['neutron-plugin-openvswitch-agent'],
|
'services': ['neutron-plugin-openvswitch-agent'],
|
||||||
'packages': [determine_dkms_package(),
|
'packages': [determine_dkms_package(),
|
||||||
['neutron-plugin-openvswitch-agent']],
|
['neutron-plugin-openvswitch-agent']],
|
||||||
@ -124,11 +110,7 @@ def neutron_plugins():
|
|||||||
'config': '/etc/neutron/plugins/nicira/nvp.ini',
|
'config': '/etc/neutron/plugins/nicira/nvp.ini',
|
||||||
'driver': 'neutron.plugins.nicira.nicira_nvp_plugin.'
|
'driver': 'neutron.plugins.nicira.nicira_nvp_plugin.'
|
||||||
'NeutronPlugin.NvpPluginV2',
|
'NeutronPlugin.NvpPluginV2',
|
||||||
'contexts': [
|
'contexts': [],
|
||||||
context.SharedDBContext(user=config('neutron-database-user'),
|
|
||||||
database=config('neutron-database'),
|
|
||||||
relation_prefix='neutron',
|
|
||||||
ssl_dir=NEUTRON_CONF_DIR)],
|
|
||||||
'services': [],
|
'services': [],
|
||||||
'packages': [],
|
'packages': [],
|
||||||
'server_packages': ['neutron-server',
|
'server_packages': ['neutron-server',
|
||||||
@ -138,11 +120,7 @@ def neutron_plugins():
|
|||||||
'nsx': {
|
'nsx': {
|
||||||
'config': '/etc/neutron/plugins/vmware/nsx.ini',
|
'config': '/etc/neutron/plugins/vmware/nsx.ini',
|
||||||
'driver': 'vmware',
|
'driver': 'vmware',
|
||||||
'contexts': [
|
'contexts': [],
|
||||||
context.SharedDBContext(user=config('neutron-database-user'),
|
|
||||||
database=config('neutron-database'),
|
|
||||||
relation_prefix='neutron',
|
|
||||||
ssl_dir=NEUTRON_CONF_DIR)],
|
|
||||||
'services': [],
|
'services': [],
|
||||||
'packages': [],
|
'packages': [],
|
||||||
'server_packages': ['neutron-server',
|
'server_packages': ['neutron-server',
|
||||||
@ -152,11 +130,7 @@ def neutron_plugins():
|
|||||||
'n1kv': {
|
'n1kv': {
|
||||||
'config': '/etc/neutron/plugins/cisco/cisco_plugins.ini',
|
'config': '/etc/neutron/plugins/cisco/cisco_plugins.ini',
|
||||||
'driver': 'neutron.plugins.cisco.network_plugin.PluginV2',
|
'driver': 'neutron.plugins.cisco.network_plugin.PluginV2',
|
||||||
'contexts': [
|
'contexts': [],
|
||||||
context.SharedDBContext(user=config('neutron-database-user'),
|
|
||||||
database=config('neutron-database'),
|
|
||||||
relation_prefix='neutron',
|
|
||||||
ssl_dir=NEUTRON_CONF_DIR)],
|
|
||||||
'services': [],
|
'services': [],
|
||||||
'packages': [determine_dkms_package(),
|
'packages': [determine_dkms_package(),
|
||||||
['neutron-plugin-cisco']],
|
['neutron-plugin-cisco']],
|
||||||
@ -167,11 +141,7 @@ def neutron_plugins():
|
|||||||
'Calico': {
|
'Calico': {
|
||||||
'config': '/etc/neutron/plugins/ml2/ml2_conf.ini',
|
'config': '/etc/neutron/plugins/ml2/ml2_conf.ini',
|
||||||
'driver': 'neutron.plugins.ml2.plugin.Ml2Plugin',
|
'driver': 'neutron.plugins.ml2.plugin.Ml2Plugin',
|
||||||
'contexts': [
|
'contexts': [],
|
||||||
context.SharedDBContext(user=config('neutron-database-user'),
|
|
||||||
database=config('neutron-database'),
|
|
||||||
relation_prefix='neutron',
|
|
||||||
ssl_dir=NEUTRON_CONF_DIR)],
|
|
||||||
'services': ['calico-felix',
|
'services': ['calico-felix',
|
||||||
'bird',
|
'bird',
|
||||||
'neutron-dhcp-agent',
|
'neutron-dhcp-agent',
|
||||||
@ -189,11 +159,7 @@ def neutron_plugins():
|
|||||||
'vsp': {
|
'vsp': {
|
||||||
'config': '/etc/neutron/plugins/nuage/nuage_plugin.ini',
|
'config': '/etc/neutron/plugins/nuage/nuage_plugin.ini',
|
||||||
'driver': 'neutron.plugins.nuage.plugin.NuagePlugin',
|
'driver': 'neutron.plugins.nuage.plugin.NuagePlugin',
|
||||||
'contexts': [
|
'contexts': [],
|
||||||
context.SharedDBContext(user=config('neutron-database-user'),
|
|
||||||
database=config('neutron-database'),
|
|
||||||
relation_prefix='neutron',
|
|
||||||
ssl_dir=NEUTRON_CONF_DIR)],
|
|
||||||
'services': [],
|
'services': [],
|
||||||
'packages': [],
|
'packages': [],
|
||||||
'server_packages': ['neutron-server', 'neutron-plugin-nuage'],
|
'server_packages': ['neutron-server', 'neutron-plugin-nuage'],
|
||||||
@ -203,10 +169,7 @@ def neutron_plugins():
|
|||||||
'config': '/etc/neutron/plugins/plumgrid/plumgrid.ini',
|
'config': '/etc/neutron/plugins/plumgrid/plumgrid.ini',
|
||||||
'driver': ('neutron.plugins.plumgrid.plumgrid_plugin'
|
'driver': ('neutron.plugins.plumgrid.plumgrid_plugin'
|
||||||
'.plumgrid_plugin.NeutronPluginPLUMgridV2'),
|
'.plumgrid_plugin.NeutronPluginPLUMgridV2'),
|
||||||
'contexts': [
|
'contexts': [],
|
||||||
context.SharedDBContext(user=config('database-user'),
|
|
||||||
database=config('database'),
|
|
||||||
ssl_dir=NEUTRON_CONF_DIR)],
|
|
||||||
'services': [],
|
'services': [],
|
||||||
'packages': ['plumgrid-lxc',
|
'packages': ['plumgrid-lxc',
|
||||||
'iovisor-dkms'],
|
'iovisor-dkms'],
|
||||||
@ -217,11 +180,7 @@ def neutron_plugins():
|
|||||||
'midonet': {
|
'midonet': {
|
||||||
'config': '/etc/neutron/plugins/midonet/midonet.ini',
|
'config': '/etc/neutron/plugins/midonet/midonet.ini',
|
||||||
'driver': 'midonet.neutron.plugin.MidonetPluginV2',
|
'driver': 'midonet.neutron.plugin.MidonetPluginV2',
|
||||||
'contexts': [
|
'contexts': [],
|
||||||
context.SharedDBContext(user=config('neutron-database-user'),
|
|
||||||
database=config('neutron-database'),
|
|
||||||
relation_prefix='neutron',
|
|
||||||
ssl_dir=NEUTRON_CONF_DIR)],
|
|
||||||
'services': [],
|
'services': [],
|
||||||
'packages': [determine_dkms_package()],
|
'packages': [determine_dkms_package()],
|
||||||
'server_packages': ['neutron-server',
|
'server_packages': ['neutron-server',
|
||||||
|
@ -18,7 +18,7 @@ rbd default features = {{ rbd_features }}
|
|||||||
|
|
||||||
[client]
|
[client]
|
||||||
{% if rbd_client_cache_settings -%}
|
{% if rbd_client_cache_settings -%}
|
||||||
{% for key, value in rbd_client_cache_settings.iteritems() -%}
|
{% for key, value in rbd_client_cache_settings.items() -%}
|
||||||
{{ key }} = {{ value }}
|
{{ key }} = {{ value }}
|
||||||
{% endfor -%}
|
{% endfor -%}
|
||||||
{%- endif %}
|
{%- endif %}
|
@ -48,7 +48,9 @@ listen stats
|
|||||||
{% for service, ports in service_ports.items() -%}
|
{% for service, ports in service_ports.items() -%}
|
||||||
frontend tcp-in_{{ service }}
|
frontend tcp-in_{{ service }}
|
||||||
bind *:{{ ports[0] }}
|
bind *:{{ ports[0] }}
|
||||||
|
{% if ipv6_enabled -%}
|
||||||
bind :::{{ ports[0] }}
|
bind :::{{ ports[0] }}
|
||||||
|
{% endif -%}
|
||||||
{% for frontend in frontends -%}
|
{% for frontend in frontends -%}
|
||||||
acl net_{{ frontend }} dst {{ frontends[frontend]['network'] }}
|
acl net_{{ frontend }} dst {{ frontends[frontend]['network'] }}
|
||||||
use_backend {{ service }}_{{ frontend }} if net_{{ frontend }}
|
use_backend {{ service }}_{{ frontend }} if net_{{ frontend }}
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
[cache]
|
||||||
|
{% if memcache_url %}
|
||||||
|
enabled = true
|
||||||
|
backend = oslo_cache.memcache_pool
|
||||||
|
memcache_servers = {{ memcache_url }}
|
||||||
|
{% endif %}
|
@ -272,6 +272,8 @@ class OSConfigRenderer(object):
|
|||||||
raise OSConfigException
|
raise OSConfigException
|
||||||
|
|
||||||
_out = self.render(config_file)
|
_out = self.render(config_file)
|
||||||
|
if six.PY3:
|
||||||
|
_out = _out.encode('UTF-8')
|
||||||
|
|
||||||
with open(config_file, 'wb') as out:
|
with open(config_file, 'wb') as out:
|
||||||
out.write(_out)
|
out.write(_out)
|
||||||
|
@ -95,7 +95,7 @@ from charmhelpers.fetch import (
|
|||||||
from charmhelpers.fetch.snap import (
|
from charmhelpers.fetch.snap import (
|
||||||
snap_install,
|
snap_install,
|
||||||
snap_refresh,
|
snap_refresh,
|
||||||
SNAP_CHANNELS,
|
valid_snap_channel,
|
||||||
)
|
)
|
||||||
|
|
||||||
from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk
|
from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk
|
||||||
@ -140,6 +140,7 @@ UBUNTU_OPENSTACK_RELEASE = OrderedDict([
|
|||||||
('yakkety', 'newton'),
|
('yakkety', 'newton'),
|
||||||
('zesty', 'ocata'),
|
('zesty', 'ocata'),
|
||||||
('artful', 'pike'),
|
('artful', 'pike'),
|
||||||
|
('bionic', 'queens'),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
@ -157,6 +158,7 @@ OPENSTACK_CODENAMES = OrderedDict([
|
|||||||
('2016.2', 'newton'),
|
('2016.2', 'newton'),
|
||||||
('2017.1', 'ocata'),
|
('2017.1', 'ocata'),
|
||||||
('2017.2', 'pike'),
|
('2017.2', 'pike'),
|
||||||
|
('2018.1', 'queens'),
|
||||||
])
|
])
|
||||||
|
|
||||||
# The ugly duckling - must list releases oldest to newest
|
# The ugly duckling - must list releases oldest to newest
|
||||||
@ -187,6 +189,8 @@ SWIFT_CODENAMES = OrderedDict([
|
|||||||
['2.11.0', '2.12.0', '2.13.0']),
|
['2.11.0', '2.12.0', '2.13.0']),
|
||||||
('pike',
|
('pike',
|
||||||
['2.13.0', '2.15.0']),
|
['2.13.0', '2.15.0']),
|
||||||
|
('queens',
|
||||||
|
['2.16.0']),
|
||||||
])
|
])
|
||||||
|
|
||||||
# >= Liberty version->codename mapping
|
# >= Liberty version->codename mapping
|
||||||
@ -412,6 +416,8 @@ def get_os_codename_package(package, fatal=True):
|
|||||||
cmd = ['snap', 'list', package]
|
cmd = ['snap', 'list', package]
|
||||||
try:
|
try:
|
||||||
out = subprocess.check_output(cmd)
|
out = subprocess.check_output(cmd)
|
||||||
|
if six.PY3:
|
||||||
|
out = out.decode('UTF-8')
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
return None
|
return None
|
||||||
lines = out.split('\n')
|
lines = out.split('\n')
|
||||||
@ -426,7 +432,7 @@ def get_os_codename_package(package, fatal=True):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
pkg = cache[package]
|
pkg = cache[package]
|
||||||
except:
|
except Exception:
|
||||||
if not fatal:
|
if not fatal:
|
||||||
return None
|
return None
|
||||||
# the package is unknown to the current apt cache.
|
# the package is unknown to the current apt cache.
|
||||||
@ -579,6 +585,9 @@ def configure_installation_source(source_plus_key):
|
|||||||
Note that the behaviour on error is to log the error to the juju log and
|
Note that the behaviour on error is to log the error to the juju log and
|
||||||
then call sys.exit(1).
|
then call sys.exit(1).
|
||||||
"""
|
"""
|
||||||
|
if source_plus_key.startswith('snap'):
|
||||||
|
# Do nothing for snap installs
|
||||||
|
return
|
||||||
# extract the key if there is one, denoted by a '|' in the rel
|
# extract the key if there is one, denoted by a '|' in the rel
|
||||||
source, key = get_source_and_pgp_key(source_plus_key)
|
source, key = get_source_and_pgp_key(source_plus_key)
|
||||||
|
|
||||||
@ -615,7 +624,7 @@ def save_script_rc(script_path="scripts/scriptrc", **env_vars):
|
|||||||
juju_rc_path = "%s/%s" % (charm_dir(), script_path)
|
juju_rc_path = "%s/%s" % (charm_dir(), script_path)
|
||||||
if not os.path.exists(os.path.dirname(juju_rc_path)):
|
if not os.path.exists(os.path.dirname(juju_rc_path)):
|
||||||
os.mkdir(os.path.dirname(juju_rc_path))
|
os.mkdir(os.path.dirname(juju_rc_path))
|
||||||
with open(juju_rc_path, 'wb') as rc_script:
|
with open(juju_rc_path, 'wt') as rc_script:
|
||||||
rc_script.write(
|
rc_script.write(
|
||||||
"#!/bin/bash\n")
|
"#!/bin/bash\n")
|
||||||
[rc_script.write('export %s=%s\n' % (u, p))
|
[rc_script.write('export %s=%s\n' % (u, p))
|
||||||
@ -794,7 +803,7 @@ def git_default_repos(projects_yaml):
|
|||||||
service = service_name()
|
service = service_name()
|
||||||
core_project = service
|
core_project = service
|
||||||
|
|
||||||
for default, branch in GIT_DEFAULT_BRANCHES.iteritems():
|
for default, branch in six.iteritems(GIT_DEFAULT_BRANCHES):
|
||||||
if projects_yaml == default:
|
if projects_yaml == default:
|
||||||
|
|
||||||
# add the requirements repo first
|
# add the requirements repo first
|
||||||
@ -1615,7 +1624,7 @@ def do_action_openstack_upgrade(package, upgrade_callback, configs):
|
|||||||
upgrade_callback(configs=configs)
|
upgrade_callback(configs=configs)
|
||||||
action_set({'outcome': 'success, upgrade completed.'})
|
action_set({'outcome': 'success, upgrade completed.'})
|
||||||
ret = True
|
ret = True
|
||||||
except:
|
except Exception:
|
||||||
action_set({'outcome': 'upgrade failed, see traceback.'})
|
action_set({'outcome': 'upgrade failed, see traceback.'})
|
||||||
action_set({'traceback': traceback.format_exc()})
|
action_set({'traceback': traceback.format_exc()})
|
||||||
action_fail('do_openstack_upgrade resulted in an '
|
action_fail('do_openstack_upgrade resulted in an '
|
||||||
@ -1720,7 +1729,7 @@ def is_unit_paused_set():
|
|||||||
kv = t[0]
|
kv = t[0]
|
||||||
# transform something truth-y into a Boolean.
|
# transform something truth-y into a Boolean.
|
||||||
return not(not(kv.get('unit-paused')))
|
return not(not(kv.get('unit-paused')))
|
||||||
except:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@ -2048,7 +2057,7 @@ def update_json_file(filename, items):
|
|||||||
def snap_install_requested():
|
def snap_install_requested():
|
||||||
""" Determine if installing from snaps
|
""" Determine if installing from snaps
|
||||||
|
|
||||||
If openstack-origin is of the form snap:channel-series-release
|
If openstack-origin is of the form snap:track/channel[/branch]
|
||||||
and channel is in SNAPS_CHANNELS return True.
|
and channel is in SNAPS_CHANNELS return True.
|
||||||
"""
|
"""
|
||||||
origin = config('openstack-origin') or ""
|
origin = config('openstack-origin') or ""
|
||||||
@ -2056,10 +2065,12 @@ def snap_install_requested():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
_src = origin[5:]
|
_src = origin[5:]
|
||||||
channel, series, release = _src.split('-')
|
if '/' in _src:
|
||||||
if channel.lower() in SNAP_CHANNELS:
|
channel = _src.split('/')[1]
|
||||||
return True
|
else:
|
||||||
return False
|
# Handle snap:track with no channel
|
||||||
|
channel = 'stable'
|
||||||
|
return valid_snap_channel(channel)
|
||||||
|
|
||||||
|
|
||||||
def get_snaps_install_info_from_origin(snaps, src, mode='classic'):
|
def get_snaps_install_info_from_origin(snaps, src, mode='classic'):
|
||||||
@ -2067,7 +2078,7 @@ def get_snaps_install_info_from_origin(snaps, src, mode='classic'):
|
|||||||
|
|
||||||
@param snaps: List of snaps
|
@param snaps: List of snaps
|
||||||
@param src: String of openstack-origin or source of the form
|
@param src: String of openstack-origin or source of the form
|
||||||
snap:channel-series-track
|
snap:track/channel
|
||||||
@param mode: String classic, devmode or jailmode
|
@param mode: String classic, devmode or jailmode
|
||||||
@returns: Dictionary of snaps with channels and modes
|
@returns: Dictionary of snaps with channels and modes
|
||||||
"""
|
"""
|
||||||
@ -2077,8 +2088,7 @@ def get_snaps_install_info_from_origin(snaps, src, mode='classic'):
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
_src = src[5:]
|
_src = src[5:]
|
||||||
_channel, _series, _release = _src.split('-')
|
channel = '--channel={}'.format(_src)
|
||||||
channel = '--channel={}/{}'.format(_release, _channel)
|
|
||||||
|
|
||||||
return {snap: {'channel': channel, 'mode': mode}
|
return {snap: {'channel': channel, 'mode': mode}
|
||||||
for snap in snaps}
|
for snap in snaps}
|
||||||
@ -2090,8 +2100,8 @@ def install_os_snaps(snaps, refresh=False):
|
|||||||
@param snaps: Dictionary of snaps with channels and modes of the form:
|
@param snaps: Dictionary of snaps with channels and modes of the form:
|
||||||
{'snap_name': {'channel': 'snap_channel',
|
{'snap_name': {'channel': 'snap_channel',
|
||||||
'mode': 'snap_mode'}}
|
'mode': 'snap_mode'}}
|
||||||
Where channel a snapstore channel and mode is --classic, --devmode or
|
Where channel is a snapstore channel and mode is --classic, --devmode
|
||||||
--jailmode.
|
or --jailmode.
|
||||||
@param post_snap_install: Callback function to run after snaps have been
|
@param post_snap_install: Callback function to run after snaps have been
|
||||||
installed
|
installed
|
||||||
"""
|
"""
|
||||||
|
@ -370,9 +370,10 @@ def get_mon_map(service):
|
|||||||
Also raises CalledProcessError if our ceph command fails
|
Also raises CalledProcessError if our ceph command fails
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
mon_status = check_output(
|
mon_status = check_output(['ceph', '--id', service,
|
||||||
['ceph', '--id', service,
|
'mon_status', '--format=json'])
|
||||||
'mon_status', '--format=json'])
|
if six.PY3:
|
||||||
|
mon_status = mon_status.decode('UTF-8')
|
||||||
try:
|
try:
|
||||||
return json.loads(mon_status)
|
return json.loads(mon_status)
|
||||||
except ValueError as v:
|
except ValueError as v:
|
||||||
@ -457,7 +458,7 @@ def monitor_key_get(service, key):
|
|||||||
try:
|
try:
|
||||||
output = check_output(
|
output = check_output(
|
||||||
['ceph', '--id', service,
|
['ceph', '--id', service,
|
||||||
'config-key', 'get', str(key)])
|
'config-key', 'get', str(key)]).decode('UTF-8')
|
||||||
return output
|
return output
|
||||||
except CalledProcessError as e:
|
except CalledProcessError as e:
|
||||||
log("Monitor config-key get failed with message: {}".format(
|
log("Monitor config-key get failed with message: {}".format(
|
||||||
@ -500,6 +501,8 @@ def get_erasure_profile(service, name):
|
|||||||
out = check_output(['ceph', '--id', service,
|
out = check_output(['ceph', '--id', service,
|
||||||
'osd', 'erasure-code-profile', 'get',
|
'osd', 'erasure-code-profile', 'get',
|
||||||
name, '--format=json'])
|
name, '--format=json'])
|
||||||
|
if six.PY3:
|
||||||
|
out = out.decode('UTF-8')
|
||||||
return json.loads(out)
|
return json.loads(out)
|
||||||
except (CalledProcessError, OSError, ValueError):
|
except (CalledProcessError, OSError, ValueError):
|
||||||
return None
|
return None
|
||||||
@ -686,7 +689,10 @@ def get_cache_mode(service, pool_name):
|
|||||||
"""
|
"""
|
||||||
validator(value=service, valid_type=six.string_types)
|
validator(value=service, valid_type=six.string_types)
|
||||||
validator(value=pool_name, valid_type=six.string_types)
|
validator(value=pool_name, valid_type=six.string_types)
|
||||||
out = check_output(['ceph', '--id', service, 'osd', 'dump', '--format=json'])
|
out = check_output(['ceph', '--id', service,
|
||||||
|
'osd', 'dump', '--format=json'])
|
||||||
|
if six.PY3:
|
||||||
|
out = out.decode('UTF-8')
|
||||||
try:
|
try:
|
||||||
osd_json = json.loads(out)
|
osd_json = json.loads(out)
|
||||||
for pool in osd_json['pools']:
|
for pool in osd_json['pools']:
|
||||||
@ -700,8 +706,9 @@ def get_cache_mode(service, pool_name):
|
|||||||
def pool_exists(service, name):
|
def pool_exists(service, name):
|
||||||
"""Check to see if a RADOS pool already exists."""
|
"""Check to see if a RADOS pool already exists."""
|
||||||
try:
|
try:
|
||||||
out = check_output(['rados', '--id', service,
|
out = check_output(['rados', '--id', service, 'lspools'])
|
||||||
'lspools']).decode('UTF-8')
|
if six.PY3:
|
||||||
|
out = out.decode('UTF-8')
|
||||||
except CalledProcessError:
|
except CalledProcessError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -714,9 +721,12 @@ def get_osds(service):
|
|||||||
"""
|
"""
|
||||||
version = ceph_version()
|
version = ceph_version()
|
||||||
if version and version >= '0.56':
|
if version and version >= '0.56':
|
||||||
return json.loads(check_output(['ceph', '--id', service,
|
out = check_output(['ceph', '--id', service,
|
||||||
'osd', 'ls',
|
'osd', 'ls',
|
||||||
'--format=json']).decode('UTF-8'))
|
'--format=json'])
|
||||||
|
if six.PY3:
|
||||||
|
out = out.decode('UTF-8')
|
||||||
|
return json.loads(out)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -734,7 +744,9 @@ def rbd_exists(service, pool, rbd_img):
|
|||||||
"""Check to see if a RADOS block device exists."""
|
"""Check to see if a RADOS block device exists."""
|
||||||
try:
|
try:
|
||||||
out = check_output(['rbd', 'list', '--id',
|
out = check_output(['rbd', 'list', '--id',
|
||||||
service, '--pool', pool]).decode('UTF-8')
|
service, '--pool', pool])
|
||||||
|
if six.PY3:
|
||||||
|
out = out.decode('UTF-8')
|
||||||
except CalledProcessError:
|
except CalledProcessError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -859,7 +871,9 @@ def configure(service, key, auth, use_syslog):
|
|||||||
def image_mapped(name):
|
def image_mapped(name):
|
||||||
"""Determine whether a RADOS block device is mapped locally."""
|
"""Determine whether a RADOS block device is mapped locally."""
|
||||||
try:
|
try:
|
||||||
out = check_output(['rbd', 'showmapped']).decode('UTF-8')
|
out = check_output(['rbd', 'showmapped'])
|
||||||
|
if six.PY3:
|
||||||
|
out = out.decode('UTF-8')
|
||||||
except CalledProcessError:
|
except CalledProcessError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -1018,7 +1032,9 @@ def ceph_version():
|
|||||||
"""Retrieve the local version of ceph."""
|
"""Retrieve the local version of ceph."""
|
||||||
if os.path.exists('/usr/bin/ceph'):
|
if os.path.exists('/usr/bin/ceph'):
|
||||||
cmd = ['ceph', '-v']
|
cmd = ['ceph', '-v']
|
||||||
output = check_output(cmd).decode('US-ASCII')
|
output = check_output(cmd)
|
||||||
|
if six.PY3:
|
||||||
|
output = output.decode('UTF-8')
|
||||||
output = output.split()
|
output = output.split()
|
||||||
if len(output) > 3:
|
if len(output) > 3:
|
||||||
return output[2]
|
return output[2]
|
||||||
|
@ -74,10 +74,10 @@ def list_lvm_volume_group(block_device):
|
|||||||
'''
|
'''
|
||||||
vg = None
|
vg = None
|
||||||
pvd = check_output(['pvdisplay', block_device]).splitlines()
|
pvd = check_output(['pvdisplay', block_device]).splitlines()
|
||||||
for l in pvd:
|
for lvm in pvd:
|
||||||
l = l.decode('UTF-8')
|
lvm = lvm.decode('UTF-8')
|
||||||
if l.strip().startswith('VG Name'):
|
if lvm.strip().startswith('VG Name'):
|
||||||
vg = ' '.join(l.strip().split()[2:])
|
vg = ' '.join(lvm.strip().split()[2:])
|
||||||
return vg
|
return vg
|
||||||
|
|
||||||
|
|
||||||
|
@ -64,6 +64,6 @@ def is_device_mounted(device):
|
|||||||
'''
|
'''
|
||||||
try:
|
try:
|
||||||
out = check_output(['lsblk', '-P', device]).decode('UTF-8')
|
out = check_output(['lsblk', '-P', device]).decode('UTF-8')
|
||||||
except:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
return bool(re.search(r'MOUNTPOINT=".+"', out))
|
return bool(re.search(r'MOUNTPOINT=".+"', out))
|
||||||
|
@ -22,6 +22,7 @@ from __future__ import print_function
|
|||||||
import copy
|
import copy
|
||||||
from distutils.version import LooseVersion
|
from distutils.version import LooseVersion
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
from collections import namedtuple
|
||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
@ -218,6 +219,8 @@ def principal_unit():
|
|||||||
for rid in relation_ids(reltype):
|
for rid in relation_ids(reltype):
|
||||||
for unit in related_units(rid):
|
for unit in related_units(rid):
|
||||||
md = _metadata_unit(unit)
|
md = _metadata_unit(unit)
|
||||||
|
if not md:
|
||||||
|
continue
|
||||||
subordinate = md.pop('subordinate', None)
|
subordinate = md.pop('subordinate', None)
|
||||||
if not subordinate:
|
if not subordinate:
|
||||||
return unit
|
return unit
|
||||||
@ -511,7 +514,10 @@ def _metadata_unit(unit):
|
|||||||
"""
|
"""
|
||||||
basedir = os.sep.join(charm_dir().split(os.sep)[:-2])
|
basedir = os.sep.join(charm_dir().split(os.sep)[:-2])
|
||||||
unitdir = 'unit-{}'.format(unit.replace(os.sep, '-'))
|
unitdir = 'unit-{}'.format(unit.replace(os.sep, '-'))
|
||||||
with open(os.path.join(basedir, unitdir, 'charm', 'metadata.yaml')) as md:
|
joineddir = os.path.join(basedir, unitdir, 'charm', 'metadata.yaml')
|
||||||
|
if not os.path.exists(joineddir):
|
||||||
|
return None
|
||||||
|
with open(joineddir) as md:
|
||||||
return yaml.safe_load(md)
|
return yaml.safe_load(md)
|
||||||
|
|
||||||
|
|
||||||
@ -639,18 +645,31 @@ def is_relation_made(relation, keys='private-address'):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _port_op(op_name, port, protocol="TCP"):
|
||||||
|
"""Open or close a service network port"""
|
||||||
|
_args = [op_name]
|
||||||
|
icmp = protocol.upper() == "ICMP"
|
||||||
|
if icmp:
|
||||||
|
_args.append(protocol)
|
||||||
|
else:
|
||||||
|
_args.append('{}/{}'.format(port, protocol))
|
||||||
|
try:
|
||||||
|
subprocess.check_call(_args)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
# Older Juju pre 2.3 doesn't support ICMP
|
||||||
|
# so treat it as a no-op if it fails.
|
||||||
|
if not icmp:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
def open_port(port, protocol="TCP"):
|
def open_port(port, protocol="TCP"):
|
||||||
"""Open a service network port"""
|
"""Open a service network port"""
|
||||||
_args = ['open-port']
|
_port_op('open-port', port, protocol)
|
||||||
_args.append('{}/{}'.format(port, protocol))
|
|
||||||
subprocess.check_call(_args)
|
|
||||||
|
|
||||||
|
|
||||||
def close_port(port, protocol="TCP"):
|
def close_port(port, protocol="TCP"):
|
||||||
"""Close a service network port"""
|
"""Close a service network port"""
|
||||||
_args = ['close-port']
|
_port_op('close-port', port, protocol)
|
||||||
_args.append('{}/{}'.format(port, protocol))
|
|
||||||
subprocess.check_call(_args)
|
|
||||||
|
|
||||||
|
|
||||||
def open_ports(start, end, protocol="TCP"):
|
def open_ports(start, end, protocol="TCP"):
|
||||||
@ -667,6 +686,17 @@ def close_ports(start, end, protocol="TCP"):
|
|||||||
subprocess.check_call(_args)
|
subprocess.check_call(_args)
|
||||||
|
|
||||||
|
|
||||||
|
def opened_ports():
|
||||||
|
"""Get the opened ports
|
||||||
|
|
||||||
|
*Note that this will only show ports opened in a previous hook*
|
||||||
|
|
||||||
|
:returns: Opened ports as a list of strings: ``['8080/tcp', '8081-8083/tcp']``
|
||||||
|
"""
|
||||||
|
_args = ['opened-ports', '--format=json']
|
||||||
|
return json.loads(subprocess.check_output(_args).decode('UTF-8'))
|
||||||
|
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
def unit_get(attribute):
|
def unit_get(attribute):
|
||||||
"""Get the unit ID for the remote unit"""
|
"""Get the unit ID for the remote unit"""
|
||||||
@ -1077,6 +1107,35 @@ def network_get_primary_address(binding):
|
|||||||
return subprocess.check_output(cmd).decode('UTF-8').strip()
|
return subprocess.check_output(cmd).decode('UTF-8').strip()
|
||||||
|
|
||||||
|
|
||||||
|
@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
|
||||||
|
def network_get(endpoint, relation_id=None):
|
||||||
|
"""
|
||||||
|
Retrieve the network details for a relation endpoint
|
||||||
|
|
||||||
|
:param endpoint: string. The name of a relation endpoint
|
||||||
|
:param relation_id: int. The ID of the relation for the current context.
|
||||||
|
:return: dict. The loaded YAML output of the network-get query.
|
||||||
|
:raise: NotImplementedError if run on Juju < 2.1
|
||||||
|
"""
|
||||||
|
cmd = ['network-get', endpoint, '--format', 'yaml']
|
||||||
|
if relation_id:
|
||||||
|
cmd.append('-r')
|
||||||
|
cmd.append(relation_id)
|
||||||
|
try:
|
||||||
|
response = subprocess.check_output(
|
||||||
|
cmd,
|
||||||
|
stderr=subprocess.STDOUT).decode('UTF-8').strip()
|
||||||
|
except CalledProcessError as e:
|
||||||
|
# Early versions of Juju 2.0.x required the --primary-address argument.
|
||||||
|
# We catch that condition here and raise NotImplementedError since
|
||||||
|
# the requested semantics are not available - the caller can then
|
||||||
|
# use the network_get_primary_address() method instead.
|
||||||
|
if '--primary-address is currently required' in e.output.decode('UTF-8'):
|
||||||
|
raise NotImplementedError
|
||||||
|
raise
|
||||||
|
return yaml.safe_load(response)
|
||||||
|
|
||||||
|
|
||||||
def add_metric(*args, **kwargs):
|
def add_metric(*args, **kwargs):
|
||||||
"""Add metric values. Values may be expressed with keyword arguments. For
|
"""Add metric values. Values may be expressed with keyword arguments. For
|
||||||
metric names containing dashes, these may be expressed as one or more
|
metric names containing dashes, these may be expressed as one or more
|
||||||
@ -1106,3 +1165,42 @@ def meter_info():
|
|||||||
"""Get the meter status information, if running in the meter-status-changed
|
"""Get the meter status information, if running in the meter-status-changed
|
||||||
hook."""
|
hook."""
|
||||||
return os.environ.get('JUJU_METER_INFO')
|
return os.environ.get('JUJU_METER_INFO')
|
||||||
|
|
||||||
|
|
||||||
|
def iter_units_for_relation_name(relation_name):
|
||||||
|
"""Iterate through all units in a relation
|
||||||
|
|
||||||
|
Generator that iterates through all the units in a relation and yields
|
||||||
|
a named tuple with rid and unit field names.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
data = [(u.rid, u.unit)
|
||||||
|
for u in iter_units_for_relation_name(relation_name)]
|
||||||
|
|
||||||
|
:param relation_name: string relation name
|
||||||
|
:yield: Named Tuple with rid and unit field names
|
||||||
|
"""
|
||||||
|
RelatedUnit = namedtuple('RelatedUnit', 'rid, unit')
|
||||||
|
for rid in relation_ids(relation_name):
|
||||||
|
for unit in related_units(rid):
|
||||||
|
yield RelatedUnit(rid, unit)
|
||||||
|
|
||||||
|
|
||||||
|
def ingress_address(rid=None, unit=None):
|
||||||
|
"""
|
||||||
|
Retrieve the ingress-address from a relation when available. Otherwise,
|
||||||
|
return the private-address. This function is to be used on the consuming
|
||||||
|
side of the relation.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
addresses = [ingress_address(rid=u.rid, unit=u.unit)
|
||||||
|
for u in iter_units_for_relation_name(relation_name)]
|
||||||
|
|
||||||
|
:param rid: string relation id
|
||||||
|
:param unit: string unit name
|
||||||
|
:side effect: calls relation_get
|
||||||
|
:return: string IP address
|
||||||
|
"""
|
||||||
|
settings = relation_get(rid=rid, unit=unit)
|
||||||
|
return (settings.get('ingress-address') or
|
||||||
|
settings.get('private-address'))
|
||||||
|
@ -34,7 +34,7 @@ import six
|
|||||||
|
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from .hookenv import log, DEBUG
|
from .hookenv import log, DEBUG, local_unit
|
||||||
from .fstab import Fstab
|
from .fstab import Fstab
|
||||||
from charmhelpers.osplatform import get_platform
|
from charmhelpers.osplatform import get_platform
|
||||||
|
|
||||||
@ -441,6 +441,49 @@ def add_user_to_group(username, group):
|
|||||||
subprocess.check_call(cmd)
|
subprocess.check_call(cmd)
|
||||||
|
|
||||||
|
|
||||||
|
def chage(username, lastday=None, expiredate=None, inactive=None,
|
||||||
|
mindays=None, maxdays=None, root=None, warndays=None):
|
||||||
|
"""Change user password expiry information
|
||||||
|
|
||||||
|
:param str username: User to update
|
||||||
|
:param str lastday: Set when password was changed in YYYY-MM-DD format
|
||||||
|
:param str expiredate: Set when user's account will no longer be
|
||||||
|
accessible in YYYY-MM-DD format.
|
||||||
|
-1 will remove an account expiration date.
|
||||||
|
:param str inactive: Set the number of days of inactivity after a password
|
||||||
|
has expired before the account is locked.
|
||||||
|
-1 will remove an account's inactivity.
|
||||||
|
:param str mindays: Set the minimum number of days between password
|
||||||
|
changes to MIN_DAYS.
|
||||||
|
0 indicates the password can be changed anytime.
|
||||||
|
:param str maxdays: Set the maximum number of days during which a
|
||||||
|
password is valid.
|
||||||
|
-1 as MAX_DAYS will remove checking maxdays
|
||||||
|
:param str root: Apply changes in the CHROOT_DIR directory
|
||||||
|
:param str warndays: Set the number of days of warning before a password
|
||||||
|
change is required
|
||||||
|
:raises subprocess.CalledProcessError: if call to chage fails
|
||||||
|
"""
|
||||||
|
cmd = ['chage']
|
||||||
|
if root:
|
||||||
|
cmd.extend(['--root', root])
|
||||||
|
if lastday:
|
||||||
|
cmd.extend(['--lastday', lastday])
|
||||||
|
if expiredate:
|
||||||
|
cmd.extend(['--expiredate', expiredate])
|
||||||
|
if inactive:
|
||||||
|
cmd.extend(['--inactive', inactive])
|
||||||
|
if mindays:
|
||||||
|
cmd.extend(['--mindays', mindays])
|
||||||
|
if maxdays:
|
||||||
|
cmd.extend(['--maxdays', maxdays])
|
||||||
|
if warndays:
|
||||||
|
cmd.extend(['--warndays', warndays])
|
||||||
|
cmd.append(username)
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
|
||||||
|
remove_password_expiry = functools.partial(chage, expiredate='-1', inactive='-1', mindays='0', maxdays='-1')
|
||||||
|
|
||||||
def rsync(from_path, to_path, flags='-r', options=None, timeout=None):
|
def rsync(from_path, to_path, flags='-r', options=None, timeout=None):
|
||||||
"""Replicate the contents of a path"""
|
"""Replicate the contents of a path"""
|
||||||
options = options or ['--delete', '--executability']
|
options = options or ['--delete', '--executability']
|
||||||
@ -946,3 +989,31 @@ def updatedb(updatedb_text, new_path):
|
|||||||
lines[i] = 'PRUNEPATHS="{}"'.format(' '.join(paths))
|
lines[i] = 'PRUNEPATHS="{}"'.format(' '.join(paths))
|
||||||
output = "\n".join(lines)
|
output = "\n".join(lines)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def modulo_distribution(modulo=3, wait=30):
|
||||||
|
""" Modulo distribution
|
||||||
|
|
||||||
|
This helper uses the unit number, a modulo value and a constant wait time
|
||||||
|
to produce a calculated wait time distribution. This is useful in large
|
||||||
|
scale deployments to distribute load during an expensive operation such as
|
||||||
|
service restarts.
|
||||||
|
|
||||||
|
If you have 1000 nodes that need to restart 100 at a time 1 minute at a
|
||||||
|
time:
|
||||||
|
|
||||||
|
time.wait(modulo_distribution(modulo=100, wait=60))
|
||||||
|
restart()
|
||||||
|
|
||||||
|
If you need restarts to happen serially set modulo to the exact number of
|
||||||
|
nodes and set a high constant wait time:
|
||||||
|
|
||||||
|
time.wait(modulo_distribution(modulo=10, wait=120))
|
||||||
|
restart()
|
||||||
|
|
||||||
|
@param modulo: int The modulo number creates the group distribution
|
||||||
|
@param wait: int The constant time wait value
|
||||||
|
@return: int Calculated time to wait for unit operation
|
||||||
|
"""
|
||||||
|
unit_number = int(local_unit().split('/')[1])
|
||||||
|
return (unit_number % modulo) * wait
|
||||||
|
@ -61,13 +61,19 @@ def bytes_from_string(value):
|
|||||||
if isinstance(value, six.string_types):
|
if isinstance(value, six.string_types):
|
||||||
value = six.text_type(value)
|
value = six.text_type(value)
|
||||||
else:
|
else:
|
||||||
msg = "Unable to interpret non-string value '%s' as boolean" % (value)
|
msg = "Unable to interpret non-string value '%s' as bytes" % (value)
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
matches = re.match("([0-9]+)([a-zA-Z]+)", value)
|
matches = re.match("([0-9]+)([a-zA-Z]+)", value)
|
||||||
if not matches:
|
if matches:
|
||||||
msg = "Unable to interpret string value '%s' as bytes" % (value)
|
size = int(matches.group(1)) * (1024 ** BYTE_POWER[matches.group(2)])
|
||||||
raise ValueError(msg)
|
else:
|
||||||
return int(matches.group(1)) * (1024 ** BYTE_POWER[matches.group(2)])
|
# Assume that value passed in is bytes
|
||||||
|
try:
|
||||||
|
size = int(value)
|
||||||
|
except ValueError:
|
||||||
|
msg = "Unable to interpret string value '%s' as bytes" % (value)
|
||||||
|
raise ValueError(msg)
|
||||||
|
return size
|
||||||
|
|
||||||
|
|
||||||
class BasicStringComparator(object):
|
class BasicStringComparator(object):
|
||||||
|
@ -358,7 +358,7 @@ class Storage(object):
|
|||||||
try:
|
try:
|
||||||
yield self.revision
|
yield self.revision
|
||||||
self.revision = None
|
self.revision = None
|
||||||
except:
|
except Exception:
|
||||||
self.flush(False)
|
self.flush(False)
|
||||||
self.revision = None
|
self.revision = None
|
||||||
raise
|
raise
|
||||||
|
@ -41,6 +41,10 @@ class CouldNotAcquireLockException(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidSnapChannel(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _snap_exec(commands):
|
def _snap_exec(commands):
|
||||||
"""
|
"""
|
||||||
Execute snap commands.
|
Execute snap commands.
|
||||||
@ -132,3 +136,15 @@ def snap_refresh(packages, *flags):
|
|||||||
|
|
||||||
log(message, level='INFO')
|
log(message, level='INFO')
|
||||||
return _snap_exec(['refresh'] + flags + packages)
|
return _snap_exec(['refresh'] + flags + packages)
|
||||||
|
|
||||||
|
|
||||||
|
def valid_snap_channel(channel):
|
||||||
|
""" Validate snap channel exists
|
||||||
|
|
||||||
|
:raises InvalidSnapChannel: When channel does not exist
|
||||||
|
:return: Boolean
|
||||||
|
"""
|
||||||
|
if channel.lower() in SNAP_CHANNELS:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
raise InvalidSnapChannel("Invalid Snap Channel: {}".format(channel))
|
||||||
|
@ -572,7 +572,7 @@ def get_upstream_version(package):
|
|||||||
cache = apt_cache()
|
cache = apt_cache()
|
||||||
try:
|
try:
|
||||||
pkg = cache[package]
|
pkg = cache[package]
|
||||||
except:
|
except Exception:
|
||||||
# the package is unknown to the current apt cache.
|
# the package is unknown to the current apt cache.
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/env python3
|
||||||
#
|
#
|
||||||
# Copyright 2016 Canonical Ltd
|
# Copyright 2016 Canonical Ltd
|
||||||
#
|
#
|
||||||
@ -120,7 +120,7 @@ def install():
|
|||||||
if os.path.isdir(_files):
|
if os.path.isdir(_files):
|
||||||
for f in os.listdir(_files):
|
for f in os.listdir(_files):
|
||||||
f = os.path.join(_files, f)
|
f = os.path.join(_files, f)
|
||||||
log('Installing %s to /usr/bin' % f)
|
log('Installing {} to /usr/bin'.format(f))
|
||||||
shutil.copy2(f, '/usr/bin')
|
shutil.copy2(f, '/usr/bin')
|
||||||
|
|
||||||
for port in API_PORTS.values():
|
for port in API_PORTS.values():
|
||||||
@ -228,7 +228,7 @@ def db_changed():
|
|||||||
migrate_database()
|
migrate_database()
|
||||||
else:
|
else:
|
||||||
log('allowed_units either not presented, or local unit '
|
log('allowed_units either not presented, or local unit '
|
||||||
'not in acl list: %s' % repr(allowed_units))
|
'not in acl list: {}'.format(repr(allowed_units)))
|
||||||
|
|
||||||
|
|
||||||
def configure_https():
|
def configure_https():
|
||||||
@ -257,15 +257,15 @@ def identity_joined(rid=None):
|
|||||||
internal_url_base = canonical_url(CONFIGS, INTERNAL)
|
internal_url_base = canonical_url(CONFIGS, INTERNAL)
|
||||||
admin_url_base = canonical_url(CONFIGS, ADMIN)
|
admin_url_base = canonical_url(CONFIGS, ADMIN)
|
||||||
|
|
||||||
api_url_template = '%s:8004/v1/$(tenant_id)s'
|
api_url_template = '{}:8004/v1/$(tenant_id)s'
|
||||||
public_api_endpoint = (api_url_template % public_url_base)
|
public_api_endpoint = (api_url_template.format(public_url_base))
|
||||||
internal_api_endpoint = (api_url_template % internal_url_base)
|
internal_api_endpoint = (api_url_template.format(internal_url_base))
|
||||||
admin_api_endpoint = (api_url_template % admin_url_base)
|
admin_api_endpoint = (api_url_template.format(admin_url_base))
|
||||||
|
|
||||||
cfn_url_template = '%s:8000/v1'
|
cfn_url_template = '{}:8000/v1'
|
||||||
public_cfn_endpoint = (cfn_url_template % public_url_base)
|
public_cfn_endpoint = (cfn_url_template.format(public_url_base))
|
||||||
internal_cfn_endpoint = (cfn_url_template % internal_url_base)
|
internal_cfn_endpoint = (cfn_url_template.format(internal_url_base))
|
||||||
admin_cfn_endpoint = (cfn_url_template % admin_url_base)
|
admin_cfn_endpoint = (cfn_url_template.format(admin_url_base))
|
||||||
|
|
||||||
relation_data = {
|
relation_data = {
|
||||||
'heat_service': 'heat',
|
'heat_service': 'heat',
|
||||||
@ -378,8 +378,9 @@ def ha_joined(relation_id=None):
|
|||||||
if vip not in resource_params[vip_key]:
|
if vip not in resource_params[vip_key]:
|
||||||
vip_key = '{}_{}'.format(vip_key, vip_params)
|
vip_key = '{}_{}'.format(vip_key, vip_params)
|
||||||
else:
|
else:
|
||||||
log("Resource '%s' (vip='%s') already exists in "
|
log("Resource '{}' (vip='{}') already exists in "
|
||||||
"vip group - skipping" % (vip_key, vip), WARNING)
|
"vip group - skipping".format(vip_key, vip),
|
||||||
|
WARNING)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
resources[vip_key] = res_heat_vip
|
resources[vip_key] = res_heat_vip
|
||||||
|
@ -218,7 +218,7 @@ def restart_map():
|
|||||||
that should be restarted when file changes.
|
that should be restarted when file changes.
|
||||||
"""
|
"""
|
||||||
_map = []
|
_map = []
|
||||||
for f, ctxt in CONFIG_FILES.iteritems():
|
for f, ctxt in CONFIG_FILES.items():
|
||||||
svcs = []
|
svcs = []
|
||||||
for svc in ctxt['services']:
|
for svc in ctxt['services']:
|
||||||
svcs.append(svc)
|
svcs.append(svc)
|
||||||
|
@ -14,7 +14,7 @@ host=heat
|
|||||||
auth_encryption_key={{ encryption_key }}
|
auth_encryption_key={{ encryption_key }}
|
||||||
num_engine_workers = {{ workers }}
|
num_engine_workers = {{ workers }}
|
||||||
{% if user_config_flags -%}
|
{% if user_config_flags -%}
|
||||||
{% for key, value in user_config_flags.iteritems() -%}
|
{% for key, value in user_config_flags.items() -%}
|
||||||
{{ key }} = {{ value }}
|
{{ key }} = {{ value }}
|
||||||
{% endfor -%}
|
{% endfor -%}
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
|
@ -15,7 +15,7 @@ stack_domain_admin_password = {{ heat_domain_admin_passwd }}
|
|||||||
stack_user_domain_name = heat
|
stack_user_domain_name = heat
|
||||||
num_engine_workers = {{ workers }}
|
num_engine_workers = {{ workers }}
|
||||||
{% if user_config_flags -%}
|
{% if user_config_flags -%}
|
||||||
{% for key, value in user_config_flags.iteritems() -%}
|
{% for key, value in user_config_flags.items() -%}
|
||||||
{{ key }} = {{ value }}
|
{{ key }} = {{ value }}
|
||||||
{% endfor -%}
|
{% endfor -%}
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
|
@ -15,7 +15,7 @@ stack_domain_admin_password = {{ heat_domain_admin_passwd }}
|
|||||||
stack_user_domain_name = heat
|
stack_user_domain_name = heat
|
||||||
num_engine_workers = {{ workers }}
|
num_engine_workers = {{ workers }}
|
||||||
{% if user_config_flags -%}
|
{% if user_config_flags -%}
|
||||||
{% for key, value in user_config_flags.iteritems() -%}
|
{% for key, value in user_config_flags.items() -%}
|
||||||
{{ key }} = {{ value }}
|
{{ key }} = {{ value }}
|
||||||
{% endfor -%}
|
{% endfor -%}
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
|
@ -15,7 +15,7 @@ stack_domain_admin_password = {{ heat_domain_admin_passwd }}
|
|||||||
stack_user_domain_name = heat
|
stack_user_domain_name = heat
|
||||||
num_engine_workers = {{ workers }}
|
num_engine_workers = {{ workers }}
|
||||||
{% if user_config_flags -%}
|
{% if user_config_flags -%}
|
||||||
{% for key, value in user_config_flags.iteritems() -%}
|
{% for key, value in user_config_flags.items() -%}
|
||||||
{{ key }} = {{ value }}
|
{{ key }} = {{ value }}
|
||||||
{% endfor -%}
|
{% endfor -%}
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
|
@ -5,12 +5,12 @@ coverage>=3.6
|
|||||||
mock>=1.2
|
mock>=1.2
|
||||||
flake8>=2.2.4,<=2.4.1
|
flake8>=2.2.4,<=2.4.1
|
||||||
os-testr>=0.4.1
|
os-testr>=0.4.1
|
||||||
charm-tools>=2.0.0
|
charm-tools>=2.0.0;python_version=='2.7'
|
||||||
requests==2.6.0
|
requests==2.6.0
|
||||||
# BEGIN: Amulet OpenStack Charm Helper Requirements
|
# BEGIN: Amulet OpenStack Charm Helper Requirements
|
||||||
# Liberty client lower constraints
|
# Liberty client lower constraints
|
||||||
amulet>=1.14.3,<2.0
|
amulet>=1.14.3,<2.0;python_version=='2.7'
|
||||||
bundletester>=0.6.1,<1.0
|
bundletester>=0.6.1,<1.0;python_version=='2.7'
|
||||||
python-ceilometerclient>=1.5.0
|
python-ceilometerclient>=1.5.0
|
||||||
python-cinderclient>=1.4.0
|
python-cinderclient>=1.4.0
|
||||||
python-glanceclient>=1.1.0
|
python-glanceclient>=1.1.0
|
||||||
|
@ -556,7 +556,7 @@ class HeatBasicDeployment(OpenStackAmuletDeployment):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for section, pairs in expected.iteritems():
|
for section, pairs in expected.items():
|
||||||
ret = u.validate_config_data(unit, conf, section, pairs)
|
ret = u.validate_config_data(unit, conf, section, pairs)
|
||||||
if ret:
|
if ret:
|
||||||
message = "heat config error: {}".format(ret)
|
message = "heat config error: {}".format(ret)
|
||||||
@ -687,7 +687,7 @@ class HeatBasicDeployment(OpenStackAmuletDeployment):
|
|||||||
self.d.configure(juju_service, set_alternate)
|
self.d.configure(juju_service, set_alternate)
|
||||||
|
|
||||||
sleep_time = 30
|
sleep_time = 30
|
||||||
for s, conf_file in services.iteritems():
|
for s, conf_file in services.items():
|
||||||
u.log.debug("Checking that service restarted: {}".format(s))
|
u.log.debug("Checking that service restarted: {}".format(s))
|
||||||
if not u.validate_service_config_changed(sentry, mtime, s,
|
if not u.validate_service_config_changed(sentry, mtime, s,
|
||||||
conf_file,
|
conf_file,
|
||||||
|
@ -250,7 +250,14 @@ class OpenStackAmuletDeployment(AmuletDeployment):
|
|||||||
self.log.debug('Waiting up to {}s for extended status on services: '
|
self.log.debug('Waiting up to {}s for extended status on services: '
|
||||||
'{}'.format(timeout, services))
|
'{}'.format(timeout, services))
|
||||||
service_messages = {service: message for service in services}
|
service_messages = {service: message for service in services}
|
||||||
|
|
||||||
|
# Check for idleness
|
||||||
|
self.d.sentry.wait()
|
||||||
|
# Check for error states and bail early
|
||||||
|
self.d.sentry.wait_for_status(self.d.juju_env, services)
|
||||||
|
# Check for ready messages
|
||||||
self.d.sentry.wait_for_messages(service_messages, timeout=timeout)
|
self.d.sentry.wait_for_messages(service_messages, timeout=timeout)
|
||||||
|
|
||||||
self.log.info('OK')
|
self.log.info('OK')
|
||||||
|
|
||||||
def _get_openstack_release(self):
|
def _get_openstack_release(self):
|
||||||
@ -263,7 +270,8 @@ class OpenStackAmuletDeployment(AmuletDeployment):
|
|||||||
(self.trusty_icehouse, self.trusty_kilo, self.trusty_liberty,
|
(self.trusty_icehouse, self.trusty_kilo, self.trusty_liberty,
|
||||||
self.trusty_mitaka, self.xenial_mitaka, self.xenial_newton,
|
self.trusty_mitaka, self.xenial_mitaka, self.xenial_newton,
|
||||||
self.yakkety_newton, self.xenial_ocata, self.zesty_ocata,
|
self.yakkety_newton, self.xenial_ocata, self.zesty_ocata,
|
||||||
self.xenial_pike, self.artful_pike) = range(11)
|
self.xenial_pike, self.artful_pike, self.xenial_queens,
|
||||||
|
self.bionic_queens,) = range(13)
|
||||||
|
|
||||||
releases = {
|
releases = {
|
||||||
('trusty', None): self.trusty_icehouse,
|
('trusty', None): self.trusty_icehouse,
|
||||||
@ -274,9 +282,11 @@ class OpenStackAmuletDeployment(AmuletDeployment):
|
|||||||
('xenial', 'cloud:xenial-newton'): self.xenial_newton,
|
('xenial', 'cloud:xenial-newton'): self.xenial_newton,
|
||||||
('xenial', 'cloud:xenial-ocata'): self.xenial_ocata,
|
('xenial', 'cloud:xenial-ocata'): self.xenial_ocata,
|
||||||
('xenial', 'cloud:xenial-pike'): self.xenial_pike,
|
('xenial', 'cloud:xenial-pike'): self.xenial_pike,
|
||||||
|
('xenial', 'cloud:xenial-queens'): self.xenial_queens,
|
||||||
('yakkety', None): self.yakkety_newton,
|
('yakkety', None): self.yakkety_newton,
|
||||||
('zesty', None): self.zesty_ocata,
|
('zesty', None): self.zesty_ocata,
|
||||||
('artful', None): self.artful_pike,
|
('artful', None): self.artful_pike,
|
||||||
|
('bionic', None): self.bionic_queens,
|
||||||
}
|
}
|
||||||
return releases[(self.series, self.openstack)]
|
return releases[(self.series, self.openstack)]
|
||||||
|
|
||||||
@ -291,6 +301,7 @@ class OpenStackAmuletDeployment(AmuletDeployment):
|
|||||||
('yakkety', 'newton'),
|
('yakkety', 'newton'),
|
||||||
('zesty', 'ocata'),
|
('zesty', 'ocata'),
|
||||||
('artful', 'pike'),
|
('artful', 'pike'),
|
||||||
|
('bionic', 'queens'),
|
||||||
])
|
])
|
||||||
if self.openstack:
|
if self.openstack:
|
||||||
os_origin = self.openstack.split(':')[1]
|
os_origin = self.openstack.split(':')[1]
|
||||||
@ -303,20 +314,27 @@ class OpenStackAmuletDeployment(AmuletDeployment):
|
|||||||
test scenario, based on OpenStack release and whether ceph radosgw
|
test scenario, based on OpenStack release and whether ceph radosgw
|
||||||
is flagged as present or not."""
|
is flagged as present or not."""
|
||||||
|
|
||||||
if self._get_openstack_release() >= self.trusty_kilo:
|
if self._get_openstack_release() == self.trusty_icehouse:
|
||||||
# Kilo or later
|
# Icehouse
|
||||||
pools = [
|
|
||||||
'rbd',
|
|
||||||
'cinder',
|
|
||||||
'glance'
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
# Juno or earlier
|
|
||||||
pools = [
|
pools = [
|
||||||
'data',
|
'data',
|
||||||
'metadata',
|
'metadata',
|
||||||
'rbd',
|
'rbd',
|
||||||
'cinder',
|
'cinder-ceph',
|
||||||
|
'glance'
|
||||||
|
]
|
||||||
|
elif (self.trusty_kilo <= self._get_openstack_release() <=
|
||||||
|
self.zesty_ocata):
|
||||||
|
# Kilo through Ocata
|
||||||
|
pools = [
|
||||||
|
'rbd',
|
||||||
|
'cinder-ceph',
|
||||||
|
'glance'
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
# Pike and later
|
||||||
|
pools = [
|
||||||
|
'cinder-ceph',
|
||||||
'glance'
|
'glance'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ import urllib
|
|||||||
import urlparse
|
import urlparse
|
||||||
|
|
||||||
import cinderclient.v1.client as cinder_client
|
import cinderclient.v1.client as cinder_client
|
||||||
|
import cinderclient.v2.client as cinder_clientv2
|
||||||
import glanceclient.v1.client as glance_client
|
import glanceclient.v1.client as glance_client
|
||||||
import heatclient.v1.client as heat_client
|
import heatclient.v1.client as heat_client
|
||||||
from keystoneclient.v2_0 import client as keystone_client
|
from keystoneclient.v2_0 import client as keystone_client
|
||||||
@ -42,7 +43,6 @@ import swiftclient
|
|||||||
from charmhelpers.contrib.amulet.utils import (
|
from charmhelpers.contrib.amulet.utils import (
|
||||||
AmuletUtils
|
AmuletUtils
|
||||||
)
|
)
|
||||||
from charmhelpers.core.decorators import retry_on_exception
|
|
||||||
from charmhelpers.core.host import CompareHostReleases
|
from charmhelpers.core.host import CompareHostReleases
|
||||||
|
|
||||||
DEBUG = logging.DEBUG
|
DEBUG = logging.DEBUG
|
||||||
@ -310,7 +310,6 @@ class OpenStackAmuletUtils(AmuletUtils):
|
|||||||
self.log.debug('Checking if tenant exists ({})...'.format(tenant))
|
self.log.debug('Checking if tenant exists ({})...'.format(tenant))
|
||||||
return tenant in [t.name for t in keystone.tenants.list()]
|
return tenant in [t.name for t in keystone.tenants.list()]
|
||||||
|
|
||||||
@retry_on_exception(5, base_delay=10)
|
|
||||||
def keystone_wait_for_propagation(self, sentry_relation_pairs,
|
def keystone_wait_for_propagation(self, sentry_relation_pairs,
|
||||||
api_version):
|
api_version):
|
||||||
"""Iterate over list of sentry and relation tuples and verify that
|
"""Iterate over list of sentry and relation tuples and verify that
|
||||||
@ -326,7 +325,7 @@ class OpenStackAmuletUtils(AmuletUtils):
|
|||||||
rel = sentry.relation('identity-service',
|
rel = sentry.relation('identity-service',
|
||||||
relation_name)
|
relation_name)
|
||||||
self.log.debug('keystone relation data: {}'.format(rel))
|
self.log.debug('keystone relation data: {}'.format(rel))
|
||||||
if rel['api_version'] != str(api_version):
|
if rel.get('api_version') != str(api_version):
|
||||||
raise Exception("api_version not propagated through relation"
|
raise Exception("api_version not propagated through relation"
|
||||||
" data yet ('{}' != '{}')."
|
" data yet ('{}' != '{}')."
|
||||||
"".format(rel['api_version'], api_version))
|
"".format(rel['api_version'], api_version))
|
||||||
@ -348,15 +347,19 @@ class OpenStackAmuletUtils(AmuletUtils):
|
|||||||
|
|
||||||
config = {'preferred-api-version': api_version}
|
config = {'preferred-api-version': api_version}
|
||||||
deployment.d.configure('keystone', config)
|
deployment.d.configure('keystone', config)
|
||||||
|
deployment._auto_wait_for_status()
|
||||||
self.keystone_wait_for_propagation(sentry_relation_pairs, api_version)
|
self.keystone_wait_for_propagation(sentry_relation_pairs, api_version)
|
||||||
|
|
||||||
def authenticate_cinder_admin(self, keystone_sentry, username,
|
def authenticate_cinder_admin(self, keystone_sentry, username,
|
||||||
password, tenant):
|
password, tenant, api_version=2):
|
||||||
"""Authenticates admin user with cinder."""
|
"""Authenticates admin user with cinder."""
|
||||||
# NOTE(beisner): cinder python client doesn't accept tokens.
|
# NOTE(beisner): cinder python client doesn't accept tokens.
|
||||||
keystone_ip = keystone_sentry.info['public-address']
|
keystone_ip = keystone_sentry.info['public-address']
|
||||||
ept = "http://{}:5000/v2.0".format(keystone_ip.strip().decode('utf-8'))
|
ept = "http://{}:5000/v2.0".format(keystone_ip.strip().decode('utf-8'))
|
||||||
return cinder_client.Client(username, password, tenant, ept)
|
_clients = {
|
||||||
|
1: cinder_client.Client,
|
||||||
|
2: cinder_clientv2.Client}
|
||||||
|
return _clients[api_version](username, password, tenant, ept)
|
||||||
|
|
||||||
def authenticate_keystone(self, keystone_ip, username, password,
|
def authenticate_keystone(self, keystone_ip, username, password,
|
||||||
api_version=False, admin_port=False,
|
api_version=False, admin_port=False,
|
||||||
@ -617,13 +620,25 @@ class OpenStackAmuletUtils(AmuletUtils):
|
|||||||
self.log.debug('Keypair ({}) already exists, '
|
self.log.debug('Keypair ({}) already exists, '
|
||||||
'using it.'.format(keypair_name))
|
'using it.'.format(keypair_name))
|
||||||
return _keypair
|
return _keypair
|
||||||
except:
|
except Exception:
|
||||||
self.log.debug('Keypair ({}) does not exist, '
|
self.log.debug('Keypair ({}) does not exist, '
|
||||||
'creating it.'.format(keypair_name))
|
'creating it.'.format(keypair_name))
|
||||||
|
|
||||||
_keypair = nova.keypairs.create(name=keypair_name)
|
_keypair = nova.keypairs.create(name=keypair_name)
|
||||||
return _keypair
|
return _keypair
|
||||||
|
|
||||||
|
def _get_cinder_obj_name(self, cinder_object):
|
||||||
|
"""Retrieve name of cinder object.
|
||||||
|
|
||||||
|
:param cinder_object: cinder snapshot or volume object
|
||||||
|
:returns: str cinder object name
|
||||||
|
"""
|
||||||
|
# v1 objects store name in 'display_name' attr but v2+ use 'name'
|
||||||
|
try:
|
||||||
|
return cinder_object.display_name
|
||||||
|
except AttributeError:
|
||||||
|
return cinder_object.name
|
||||||
|
|
||||||
def create_cinder_volume(self, cinder, vol_name="demo-vol", vol_size=1,
|
def create_cinder_volume(self, cinder, vol_name="demo-vol", vol_size=1,
|
||||||
img_id=None, src_vol_id=None, snap_id=None):
|
img_id=None, src_vol_id=None, snap_id=None):
|
||||||
"""Create cinder volume, optionally from a glance image, OR
|
"""Create cinder volume, optionally from a glance image, OR
|
||||||
@ -674,6 +689,13 @@ class OpenStackAmuletUtils(AmuletUtils):
|
|||||||
source_volid=src_vol_id,
|
source_volid=src_vol_id,
|
||||||
snapshot_id=snap_id)
|
snapshot_id=snap_id)
|
||||||
vol_id = vol_new.id
|
vol_id = vol_new.id
|
||||||
|
except TypeError:
|
||||||
|
vol_new = cinder.volumes.create(name=vol_name,
|
||||||
|
imageRef=img_id,
|
||||||
|
size=vol_size,
|
||||||
|
source_volid=src_vol_id,
|
||||||
|
snapshot_id=snap_id)
|
||||||
|
vol_id = vol_new.id
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
msg = 'Failed to create volume: {}'.format(e)
|
msg = 'Failed to create volume: {}'.format(e)
|
||||||
amulet.raise_status(amulet.FAIL, msg=msg)
|
amulet.raise_status(amulet.FAIL, msg=msg)
|
||||||
@ -688,7 +710,7 @@ class OpenStackAmuletUtils(AmuletUtils):
|
|||||||
|
|
||||||
# Re-validate new volume
|
# Re-validate new volume
|
||||||
self.log.debug('Validating volume attributes...')
|
self.log.debug('Validating volume attributes...')
|
||||||
val_vol_name = cinder.volumes.get(vol_id).display_name
|
val_vol_name = self._get_cinder_obj_name(cinder.volumes.get(vol_id))
|
||||||
val_vol_boot = cinder.volumes.get(vol_id).bootable
|
val_vol_boot = cinder.volumes.get(vol_id).bootable
|
||||||
val_vol_stat = cinder.volumes.get(vol_id).status
|
val_vol_stat = cinder.volumes.get(vol_id).status
|
||||||
val_vol_size = cinder.volumes.get(vol_id).size
|
val_vol_size = cinder.volumes.get(vol_id).size
|
||||||
|
@ -22,6 +22,7 @@ from __future__ import print_function
|
|||||||
import copy
|
import copy
|
||||||
from distutils.version import LooseVersion
|
from distutils.version import LooseVersion
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
from collections import namedtuple
|
||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
@ -218,6 +219,8 @@ def principal_unit():
|
|||||||
for rid in relation_ids(reltype):
|
for rid in relation_ids(reltype):
|
||||||
for unit in related_units(rid):
|
for unit in related_units(rid):
|
||||||
md = _metadata_unit(unit)
|
md = _metadata_unit(unit)
|
||||||
|
if not md:
|
||||||
|
continue
|
||||||
subordinate = md.pop('subordinate', None)
|
subordinate = md.pop('subordinate', None)
|
||||||
if not subordinate:
|
if not subordinate:
|
||||||
return unit
|
return unit
|
||||||
@ -511,7 +514,10 @@ def _metadata_unit(unit):
|
|||||||
"""
|
"""
|
||||||
basedir = os.sep.join(charm_dir().split(os.sep)[:-2])
|
basedir = os.sep.join(charm_dir().split(os.sep)[:-2])
|
||||||
unitdir = 'unit-{}'.format(unit.replace(os.sep, '-'))
|
unitdir = 'unit-{}'.format(unit.replace(os.sep, '-'))
|
||||||
with open(os.path.join(basedir, unitdir, 'charm', 'metadata.yaml')) as md:
|
joineddir = os.path.join(basedir, unitdir, 'charm', 'metadata.yaml')
|
||||||
|
if not os.path.exists(joineddir):
|
||||||
|
return None
|
||||||
|
with open(joineddir) as md:
|
||||||
return yaml.safe_load(md)
|
return yaml.safe_load(md)
|
||||||
|
|
||||||
|
|
||||||
@ -639,18 +645,31 @@ def is_relation_made(relation, keys='private-address'):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _port_op(op_name, port, protocol="TCP"):
|
||||||
|
"""Open or close a service network port"""
|
||||||
|
_args = [op_name]
|
||||||
|
icmp = protocol.upper() == "ICMP"
|
||||||
|
if icmp:
|
||||||
|
_args.append(protocol)
|
||||||
|
else:
|
||||||
|
_args.append('{}/{}'.format(port, protocol))
|
||||||
|
try:
|
||||||
|
subprocess.check_call(_args)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
# Older Juju pre 2.3 doesn't support ICMP
|
||||||
|
# so treat it as a no-op if it fails.
|
||||||
|
if not icmp:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
def open_port(port, protocol="TCP"):
|
def open_port(port, protocol="TCP"):
|
||||||
"""Open a service network port"""
|
"""Open a service network port"""
|
||||||
_args = ['open-port']
|
_port_op('open-port', port, protocol)
|
||||||
_args.append('{}/{}'.format(port, protocol))
|
|
||||||
subprocess.check_call(_args)
|
|
||||||
|
|
||||||
|
|
||||||
def close_port(port, protocol="TCP"):
|
def close_port(port, protocol="TCP"):
|
||||||
"""Close a service network port"""
|
"""Close a service network port"""
|
||||||
_args = ['close-port']
|
_port_op('close-port', port, protocol)
|
||||||
_args.append('{}/{}'.format(port, protocol))
|
|
||||||
subprocess.check_call(_args)
|
|
||||||
|
|
||||||
|
|
||||||
def open_ports(start, end, protocol="TCP"):
|
def open_ports(start, end, protocol="TCP"):
|
||||||
@ -667,6 +686,17 @@ def close_ports(start, end, protocol="TCP"):
|
|||||||
subprocess.check_call(_args)
|
subprocess.check_call(_args)
|
||||||
|
|
||||||
|
|
||||||
|
def opened_ports():
|
||||||
|
"""Get the opened ports
|
||||||
|
|
||||||
|
*Note that this will only show ports opened in a previous hook*
|
||||||
|
|
||||||
|
:returns: Opened ports as a list of strings: ``['8080/tcp', '8081-8083/tcp']``
|
||||||
|
"""
|
||||||
|
_args = ['opened-ports', '--format=json']
|
||||||
|
return json.loads(subprocess.check_output(_args).decode('UTF-8'))
|
||||||
|
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
def unit_get(attribute):
|
def unit_get(attribute):
|
||||||
"""Get the unit ID for the remote unit"""
|
"""Get the unit ID for the remote unit"""
|
||||||
@ -1077,6 +1107,35 @@ def network_get_primary_address(binding):
|
|||||||
return subprocess.check_output(cmd).decode('UTF-8').strip()
|
return subprocess.check_output(cmd).decode('UTF-8').strip()
|
||||||
|
|
||||||
|
|
||||||
|
@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
|
||||||
|
def network_get(endpoint, relation_id=None):
|
||||||
|
"""
|
||||||
|
Retrieve the network details for a relation endpoint
|
||||||
|
|
||||||
|
:param endpoint: string. The name of a relation endpoint
|
||||||
|
:param relation_id: int. The ID of the relation for the current context.
|
||||||
|
:return: dict. The loaded YAML output of the network-get query.
|
||||||
|
:raise: NotImplementedError if run on Juju < 2.1
|
||||||
|
"""
|
||||||
|
cmd = ['network-get', endpoint, '--format', 'yaml']
|
||||||
|
if relation_id:
|
||||||
|
cmd.append('-r')
|
||||||
|
cmd.append(relation_id)
|
||||||
|
try:
|
||||||
|
response = subprocess.check_output(
|
||||||
|
cmd,
|
||||||
|
stderr=subprocess.STDOUT).decode('UTF-8').strip()
|
||||||
|
except CalledProcessError as e:
|
||||||
|
# Early versions of Juju 2.0.x required the --primary-address argument.
|
||||||
|
# We catch that condition here and raise NotImplementedError since
|
||||||
|
# the requested semantics are not available - the caller can then
|
||||||
|
# use the network_get_primary_address() method instead.
|
||||||
|
if '--primary-address is currently required' in e.output.decode('UTF-8'):
|
||||||
|
raise NotImplementedError
|
||||||
|
raise
|
||||||
|
return yaml.safe_load(response)
|
||||||
|
|
||||||
|
|
||||||
def add_metric(*args, **kwargs):
|
def add_metric(*args, **kwargs):
|
||||||
"""Add metric values. Values may be expressed with keyword arguments. For
|
"""Add metric values. Values may be expressed with keyword arguments. For
|
||||||
metric names containing dashes, these may be expressed as one or more
|
metric names containing dashes, these may be expressed as one or more
|
||||||
@ -1106,3 +1165,42 @@ def meter_info():
|
|||||||
"""Get the meter status information, if running in the meter-status-changed
|
"""Get the meter status information, if running in the meter-status-changed
|
||||||
hook."""
|
hook."""
|
||||||
return os.environ.get('JUJU_METER_INFO')
|
return os.environ.get('JUJU_METER_INFO')
|
||||||
|
|
||||||
|
|
||||||
|
def iter_units_for_relation_name(relation_name):
|
||||||
|
"""Iterate through all units in a relation
|
||||||
|
|
||||||
|
Generator that iterates through all the units in a relation and yields
|
||||||
|
a named tuple with rid and unit field names.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
data = [(u.rid, u.unit)
|
||||||
|
for u in iter_units_for_relation_name(relation_name)]
|
||||||
|
|
||||||
|
:param relation_name: string relation name
|
||||||
|
:yield: Named Tuple with rid and unit field names
|
||||||
|
"""
|
||||||
|
RelatedUnit = namedtuple('RelatedUnit', 'rid, unit')
|
||||||
|
for rid in relation_ids(relation_name):
|
||||||
|
for unit in related_units(rid):
|
||||||
|
yield RelatedUnit(rid, unit)
|
||||||
|
|
||||||
|
|
||||||
|
def ingress_address(rid=None, unit=None):
|
||||||
|
"""
|
||||||
|
Retrieve the ingress-address from a relation when available. Otherwise,
|
||||||
|
return the private-address. This function is to be used on the consuming
|
||||||
|
side of the relation.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
addresses = [ingress_address(rid=u.rid, unit=u.unit)
|
||||||
|
for u in iter_units_for_relation_name(relation_name)]
|
||||||
|
|
||||||
|
:param rid: string relation id
|
||||||
|
:param unit: string unit name
|
||||||
|
:side effect: calls relation_get
|
||||||
|
:return: string IP address
|
||||||
|
"""
|
||||||
|
settings = relation_get(rid=rid, unit=unit)
|
||||||
|
return (settings.get('ingress-address') or
|
||||||
|
settings.get('private-address'))
|
||||||
|
@ -34,7 +34,7 @@ import six
|
|||||||
|
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from .hookenv import log, DEBUG
|
from .hookenv import log, DEBUG, local_unit
|
||||||
from .fstab import Fstab
|
from .fstab import Fstab
|
||||||
from charmhelpers.osplatform import get_platform
|
from charmhelpers.osplatform import get_platform
|
||||||
|
|
||||||
@ -441,6 +441,49 @@ def add_user_to_group(username, group):
|
|||||||
subprocess.check_call(cmd)
|
subprocess.check_call(cmd)
|
||||||
|
|
||||||
|
|
||||||
|
def chage(username, lastday=None, expiredate=None, inactive=None,
|
||||||
|
mindays=None, maxdays=None, root=None, warndays=None):
|
||||||
|
"""Change user password expiry information
|
||||||
|
|
||||||
|
:param str username: User to update
|
||||||
|
:param str lastday: Set when password was changed in YYYY-MM-DD format
|
||||||
|
:param str expiredate: Set when user's account will no longer be
|
||||||
|
accessible in YYYY-MM-DD format.
|
||||||
|
-1 will remove an account expiration date.
|
||||||
|
:param str inactive: Set the number of days of inactivity after a password
|
||||||
|
has expired before the account is locked.
|
||||||
|
-1 will remove an account's inactivity.
|
||||||
|
:param str mindays: Set the minimum number of days between password
|
||||||
|
changes to MIN_DAYS.
|
||||||
|
0 indicates the password can be changed anytime.
|
||||||
|
:param str maxdays: Set the maximum number of days during which a
|
||||||
|
password is valid.
|
||||||
|
-1 as MAX_DAYS will remove checking maxdays
|
||||||
|
:param str root: Apply changes in the CHROOT_DIR directory
|
||||||
|
:param str warndays: Set the number of days of warning before a password
|
||||||
|
change is required
|
||||||
|
:raises subprocess.CalledProcessError: if call to chage fails
|
||||||
|
"""
|
||||||
|
cmd = ['chage']
|
||||||
|
if root:
|
||||||
|
cmd.extend(['--root', root])
|
||||||
|
if lastday:
|
||||||
|
cmd.extend(['--lastday', lastday])
|
||||||
|
if expiredate:
|
||||||
|
cmd.extend(['--expiredate', expiredate])
|
||||||
|
if inactive:
|
||||||
|
cmd.extend(['--inactive', inactive])
|
||||||
|
if mindays:
|
||||||
|
cmd.extend(['--mindays', mindays])
|
||||||
|
if maxdays:
|
||||||
|
cmd.extend(['--maxdays', maxdays])
|
||||||
|
if warndays:
|
||||||
|
cmd.extend(['--warndays', warndays])
|
||||||
|
cmd.append(username)
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
|
||||||
|
remove_password_expiry = functools.partial(chage, expiredate='-1', inactive='-1', mindays='0', maxdays='-1')
|
||||||
|
|
||||||
def rsync(from_path, to_path, flags='-r', options=None, timeout=None):
|
def rsync(from_path, to_path, flags='-r', options=None, timeout=None):
|
||||||
"""Replicate the contents of a path"""
|
"""Replicate the contents of a path"""
|
||||||
options = options or ['--delete', '--executability']
|
options = options or ['--delete', '--executability']
|
||||||
@ -946,3 +989,31 @@ def updatedb(updatedb_text, new_path):
|
|||||||
lines[i] = 'PRUNEPATHS="{}"'.format(' '.join(paths))
|
lines[i] = 'PRUNEPATHS="{}"'.format(' '.join(paths))
|
||||||
output = "\n".join(lines)
|
output = "\n".join(lines)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def modulo_distribution(modulo=3, wait=30):
|
||||||
|
""" Modulo distribution
|
||||||
|
|
||||||
|
This helper uses the unit number, a modulo value and a constant wait time
|
||||||
|
to produce a calculated wait time distribution. This is useful in large
|
||||||
|
scale deployments to distribute load during an expensive operation such as
|
||||||
|
service restarts.
|
||||||
|
|
||||||
|
If you have 1000 nodes that need to restart 100 at a time 1 minute at a
|
||||||
|
time:
|
||||||
|
|
||||||
|
time.wait(modulo_distribution(modulo=100, wait=60))
|
||||||
|
restart()
|
||||||
|
|
||||||
|
If you need restarts to happen serially set modulo to the exact number of
|
||||||
|
nodes and set a high constant wait time:
|
||||||
|
|
||||||
|
time.wait(modulo_distribution(modulo=10, wait=120))
|
||||||
|
restart()
|
||||||
|
|
||||||
|
@param modulo: int The modulo number creates the group distribution
|
||||||
|
@param wait: int The constant time wait value
|
||||||
|
@return: int Calculated time to wait for unit operation
|
||||||
|
"""
|
||||||
|
unit_number = int(local_unit().split('/')[1])
|
||||||
|
return (unit_number % modulo) * wait
|
||||||
|
@ -61,13 +61,19 @@ def bytes_from_string(value):
|
|||||||
if isinstance(value, six.string_types):
|
if isinstance(value, six.string_types):
|
||||||
value = six.text_type(value)
|
value = six.text_type(value)
|
||||||
else:
|
else:
|
||||||
msg = "Unable to interpret non-string value '%s' as boolean" % (value)
|
msg = "Unable to interpret non-string value '%s' as bytes" % (value)
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
matches = re.match("([0-9]+)([a-zA-Z]+)", value)
|
matches = re.match("([0-9]+)([a-zA-Z]+)", value)
|
||||||
if not matches:
|
if matches:
|
||||||
msg = "Unable to interpret string value '%s' as bytes" % (value)
|
size = int(matches.group(1)) * (1024 ** BYTE_POWER[matches.group(2)])
|
||||||
raise ValueError(msg)
|
else:
|
||||||
return int(matches.group(1)) * (1024 ** BYTE_POWER[matches.group(2)])
|
# Assume that value passed in is bytes
|
||||||
|
try:
|
||||||
|
size = int(value)
|
||||||
|
except ValueError:
|
||||||
|
msg = "Unable to interpret string value '%s' as bytes" % (value)
|
||||||
|
raise ValueError(msg)
|
||||||
|
return size
|
||||||
|
|
||||||
|
|
||||||
class BasicStringComparator(object):
|
class BasicStringComparator(object):
|
||||||
|
@ -358,7 +358,7 @@ class Storage(object):
|
|||||||
try:
|
try:
|
||||||
yield self.revision
|
yield self.revision
|
||||||
self.revision = None
|
self.revision = None
|
||||||
except:
|
except Exception:
|
||||||
self.flush(False)
|
self.flush(False)
|
||||||
self.revision = None
|
self.revision = None
|
||||||
raise
|
raise
|
||||||
|
1
tox.ini
1
tox.ini
@ -20,6 +20,7 @@ passenv = HOME TERM AMULET_* CS_API_*
|
|||||||
basepython = python2.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 = /bin/true
|
||||||
|
|
||||||
[testenv:py35]
|
[testenv:py35]
|
||||||
basepython = python3.5
|
basepython = python3.5
|
||||||
|
@ -12,6 +12,18 @@
|
|||||||
# 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
|
||||||
sys.path.append('actions/')
|
|
||||||
sys.path.append('hooks/')
|
_path = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
_root = os.path.abspath(os.path.join(_path, '..'))
|
||||||
|
|
||||||
|
|
||||||
|
def _add_path(path):
|
||||||
|
if path not in sys.path:
|
||||||
|
sys.path.insert(1, path)
|
||||||
|
|
||||||
|
|
||||||
|
_add_path(_path)
|
||||||
|
_add_path('actions')
|
||||||
|
_add_path('hooks')
|
||||||
|
@ -145,11 +145,11 @@ class HeatRelationTests(CharmTestCase):
|
|||||||
with patch_open() as (mock_open, mock_file):
|
with patch_open() as (mock_open, mock_file):
|
||||||
mock_file.read.return_value = "abc"
|
mock_file.read.return_value = "abc"
|
||||||
relations.upgrade_charm()
|
relations.upgrade_charm()
|
||||||
file = os.path.join(relations.HEAT_PATH, 'encryption-key')
|
filename = os.path.join(relations.HEAT_PATH, 'encryption-key')
|
||||||
mock_open.assert_called_once_with(file, 'r')
|
mock_open.assert_called_once_with(filename, 'r')
|
||||||
self.leader_set.assert_called_once_with(
|
self.leader_set.assert_called_once_with(
|
||||||
{'heat-auth-encryption-key': 'abc'})
|
{'heat-auth-encryption-key': 'abc'})
|
||||||
os_remove.assert_called_once_with(file)
|
os_remove.assert_called_once_with(filename)
|
||||||
|
|
||||||
def test_db_joined(self):
|
def test_db_joined(self):
|
||||||
self.get_relation_ip.return_value = '192.168.20.1'
|
self.get_relation_ip.return_value = '192.168.20.1'
|
||||||
|
@ -75,7 +75,7 @@ class HeatUtilsTests(CharmTestCase):
|
|||||||
pkgs = utils.determine_packages()
|
pkgs = utils.determine_packages()
|
||||||
ex = list(set(utils.BASE_PACKAGES + ['memcached'] +
|
ex = list(set(utils.BASE_PACKAGES + ['memcached'] +
|
||||||
utils.BASE_SERVICES))
|
utils.BASE_SERVICES))
|
||||||
self.assertEqual(ex, pkgs)
|
self.assertEqual(sorted(ex), sorted(pkgs))
|
||||||
|
|
||||||
def test_restart_map(self):
|
def test_restart_map(self):
|
||||||
self.assertEqual(RESTART_MAP, utils.restart_map())
|
self.assertEqual(RESTART_MAP, utils.restart_map())
|
||||||
|
@ -49,7 +49,7 @@ def get_default_config():
|
|||||||
"""
|
"""
|
||||||
default_config = {}
|
default_config = {}
|
||||||
config = load_config()
|
config = load_config()
|
||||||
for k, v in config.iteritems():
|
for k, v in config.items():
|
||||||
if 'default' in v:
|
if 'default' in v:
|
||||||
default_config[k] = v['default']
|
default_config[k] = v['default']
|
||||||
else:
|
else:
|
||||||
@ -124,12 +124,12 @@ def patch_open():
|
|||||||
Yields the mock for "open" and "file", respectively.
|
Yields the mock for "open" and "file", respectively.
|
||||||
'''
|
'''
|
||||||
mock_open = MagicMock(spec=open)
|
mock_open = MagicMock(spec=open)
|
||||||
mock_file = MagicMock(spec=file)
|
mock_file = MagicMock()
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def stub_open(*args, **kwargs):
|
def stub_open(*args, **kwargs):
|
||||||
mock_open(*args, **kwargs)
|
mock_open(*args, **kwargs)
|
||||||
yield mock_file
|
yield mock_file
|
||||||
|
|
||||||
with patch('__builtin__.open', stub_open):
|
with patch('builtins.open', stub_open):
|
||||||
yield mock_open, mock_file
|
yield mock_open, mock_file
|
||||||
|
Loading…
Reference in New Issue
Block a user