Fix alphanumeric comparisons for openstack and ubuntu releases
- sync charmhelpers with fix-alpha helpers - fix up code where the alpha comparisons are done Change-Id: If7d31bbd11cc1c445fc1ac82bd0d4a5097b3f52a Related-Bug: #1659575
This commit is contained in:
parent
239435b26e
commit
b29534d990
|
@ -4,3 +4,4 @@ include:
|
|||
- contrib.amulet
|
||||
- contrib.openstack.amulet
|
||||
- core
|
||||
- osplatform
|
||||
|
|
|
@ -20,25 +20,38 @@ import socket
|
|||
|
||||
from functools import partial
|
||||
|
||||
from charmhelpers.core.hookenv import unit_get
|
||||
from charmhelpers.fetch import apt_install, apt_update
|
||||
from charmhelpers.core.hookenv import (
|
||||
config,
|
||||
log,
|
||||
network_get_primary_address,
|
||||
unit_get,
|
||||
WARNING,
|
||||
)
|
||||
|
||||
from charmhelpers.core.host import (
|
||||
lsb_release,
|
||||
CompareHostReleases,
|
||||
)
|
||||
|
||||
try:
|
||||
import netifaces
|
||||
except ImportError:
|
||||
apt_update(fatal=True)
|
||||
apt_install('python-netifaces', fatal=True)
|
||||
if six.PY2:
|
||||
apt_install('python-netifaces', fatal=True)
|
||||
else:
|
||||
apt_install('python3-netifaces', fatal=True)
|
||||
import netifaces
|
||||
|
||||
try:
|
||||
import netaddr
|
||||
except ImportError:
|
||||
apt_update(fatal=True)
|
||||
apt_install('python-netaddr', fatal=True)
|
||||
if six.PY2:
|
||||
apt_install('python-netaddr', fatal=True)
|
||||
else:
|
||||
apt_install('python3-netaddr', fatal=True)
|
||||
import netaddr
|
||||
|
||||
|
||||
|
@ -55,6 +68,24 @@ def no_ip_found_error_out(network):
|
|||
raise ValueError(errmsg)
|
||||
|
||||
|
||||
def _get_ipv6_network_from_address(address):
|
||||
"""Get an netaddr.IPNetwork for the given IPv6 address
|
||||
:param address: a dict as returned by netifaces.ifaddresses
|
||||
:returns netaddr.IPNetwork: None if the address is a link local or loopback
|
||||
address
|
||||
"""
|
||||
if address['addr'].startswith('fe80') or address['addr'] == "::1":
|
||||
return None
|
||||
|
||||
prefix = address['netmask'].split("/")
|
||||
if len(prefix) > 1:
|
||||
netmask = prefix[1]
|
||||
else:
|
||||
netmask = address['netmask']
|
||||
return netaddr.IPNetwork("%s/%s" % (address['addr'],
|
||||
netmask))
|
||||
|
||||
|
||||
def get_address_in_network(network, fallback=None, fatal=False):
|
||||
"""Get an IPv4 or IPv6 address within the network from the host.
|
||||
|
||||
|
@ -88,11 +119,9 @@ def get_address_in_network(network, fallback=None, fatal=False):
|
|||
|
||||
if network.version == 6 and netifaces.AF_INET6 in addresses:
|
||||
for addr in addresses[netifaces.AF_INET6]:
|
||||
if not addr['addr'].startswith('fe80'):
|
||||
cidr = netaddr.IPNetwork("%s/%s" % (addr['addr'],
|
||||
addr['netmask']))
|
||||
if cidr in network:
|
||||
return str(cidr.ip)
|
||||
cidr = _get_ipv6_network_from_address(addr)
|
||||
if cidr and cidr in network:
|
||||
return str(cidr.ip)
|
||||
|
||||
if fallback is not None:
|
||||
return fallback
|
||||
|
@ -168,18 +197,18 @@ def _get_for_address(address, key):
|
|||
|
||||
if address.version == 6 and netifaces.AF_INET6 in addresses:
|
||||
for addr in addresses[netifaces.AF_INET6]:
|
||||
if not addr['addr'].startswith('fe80'):
|
||||
network = netaddr.IPNetwork("%s/%s" % (addr['addr'],
|
||||
addr['netmask']))
|
||||
cidr = network.cidr
|
||||
if address in cidr:
|
||||
if key == 'iface':
|
||||
return iface
|
||||
elif key == 'netmask' and cidr:
|
||||
return str(cidr).split('/')[1]
|
||||
else:
|
||||
return addr[key]
|
||||
network = _get_ipv6_network_from_address(addr)
|
||||
if not network:
|
||||
continue
|
||||
|
||||
cidr = network.cidr
|
||||
if address in cidr:
|
||||
if key == 'iface':
|
||||
return iface
|
||||
elif key == 'netmask' and cidr:
|
||||
return str(cidr).split('/')[1]
|
||||
else:
|
||||
return addr[key]
|
||||
return None
|
||||
|
||||
|
||||
|
@ -414,7 +443,10 @@ def ns_query(address):
|
|||
try:
|
||||
import dns.resolver
|
||||
except ImportError:
|
||||
apt_install('python-dnspython', fatal=True)
|
||||
if six.PY2:
|
||||
apt_install('python-dnspython', fatal=True)
|
||||
else:
|
||||
apt_install('python3-dnspython', fatal=True)
|
||||
import dns.resolver
|
||||
|
||||
if isinstance(address, dns.name.Name):
|
||||
|
@ -462,7 +494,10 @@ def get_hostname(address, fqdn=True):
|
|||
try:
|
||||
import dns.reversename
|
||||
except ImportError:
|
||||
apt_install("python-dnspython", fatal=True)
|
||||
if six.PY2:
|
||||
apt_install("python-dnspython", fatal=True)
|
||||
else:
|
||||
apt_install("python3-dnspython", fatal=True)
|
||||
import dns.reversename
|
||||
|
||||
rev = dns.reversename.from_address(address)
|
||||
|
@ -499,3 +534,41 @@ def port_has_listener(address, port):
|
|||
cmd = ['nc', '-z', address, str(port)]
|
||||
result = subprocess.call(cmd)
|
||||
return not(bool(result))
|
||||
|
||||
|
||||
def assert_charm_supports_ipv6():
|
||||
"""Check whether we are able to support charms ipv6."""
|
||||
release = lsb_release()['DISTRIB_CODENAME'].lower()
|
||||
if CompareHostReleases(release) < "trusty":
|
||||
raise Exception("IPv6 is not supported in the charms for Ubuntu "
|
||||
"versions less than Trusty 14.04")
|
||||
|
||||
|
||||
def get_relation_ip(interface, config_override=None):
|
||||
"""Return this unit's IP for the given relation.
|
||||
|
||||
Allow for an arbitrary interface to use with network-get to select an IP.
|
||||
Handle all address selection options including configuration parameter
|
||||
override and IPv6.
|
||||
|
||||
Usage: get_relation_ip('amqp', config_override='access-network')
|
||||
|
||||
@param interface: string name of the relation.
|
||||
@param config_override: string name of the config option for network
|
||||
override. Supports legacy network override configuration parameters.
|
||||
@raises Exception if prefer-ipv6 is configured but IPv6 unsupported.
|
||||
@returns IPv6 or IPv4 address
|
||||
"""
|
||||
|
||||
fallback = get_host_ip(unit_get('private-address'))
|
||||
if config('prefer-ipv6'):
|
||||
assert_charm_supports_ipv6()
|
||||
return get_ipv6_addr()[0]
|
||||
elif config_override and config(config_override):
|
||||
return get_address_in_network(config(config_override),
|
||||
fallback)
|
||||
else:
|
||||
try:
|
||||
return network_get_primary_address(interface)
|
||||
except NotImplementedError:
|
||||
return fallback
|
||||
|
|
|
@ -32,6 +32,7 @@ from keystoneclient.v3 import client as keystone_client_v3
|
|||
from novaclient import exceptions
|
||||
|
||||
import novaclient.client as nova_client
|
||||
import novaclient
|
||||
import pika
|
||||
import swiftclient
|
||||
|
||||
|
@ -39,6 +40,7 @@ from charmhelpers.contrib.amulet.utils import (
|
|||
AmuletUtils
|
||||
)
|
||||
from charmhelpers.core.decorators import retry_on_exception
|
||||
from charmhelpers.core.host import CompareHostReleases
|
||||
|
||||
DEBUG = logging.DEBUG
|
||||
ERROR = logging.ERROR
|
||||
|
@ -434,9 +436,14 @@ class OpenStackAmuletUtils(AmuletUtils):
|
|||
self.log.debug('Authenticating nova user ({})...'.format(user))
|
||||
ep = keystone.service_catalog.url_for(service_type='identity',
|
||||
endpoint_type='publicURL')
|
||||
return nova_client.Client(NOVA_CLIENT_VERSION,
|
||||
username=user, api_key=password,
|
||||
project_id=tenant, auth_url=ep)
|
||||
if novaclient.__version__[0] >= "7":
|
||||
return nova_client.Client(NOVA_CLIENT_VERSION,
|
||||
username=user, password=password,
|
||||
project_name=tenant, auth_url=ep)
|
||||
else:
|
||||
return nova_client.Client(NOVA_CLIENT_VERSION,
|
||||
username=user, api_key=password,
|
||||
project_id=tenant, auth_url=ep)
|
||||
|
||||
def authenticate_swift_user(self, keystone, user, password, tenant):
|
||||
"""Authenticates a regular user with swift api."""
|
||||
|
@ -1249,7 +1256,7 @@ class OpenStackAmuletUtils(AmuletUtils):
|
|||
contents = self.file_contents_safe(sentry_unit, '/etc/memcached.conf',
|
||||
fatal=True)
|
||||
ubuntu_release, _ = self.run_cmd_unit(sentry_unit, 'lsb_release -cs')
|
||||
if ubuntu_release <= 'trusty':
|
||||
if CompareHostReleases(ubuntu_release) <= 'trusty':
|
||||
memcache_listen_addr = 'ip6-localhost'
|
||||
else:
|
||||
memcache_listen_addr = '::1'
|
||||
|
|
|
@ -59,6 +59,7 @@ from charmhelpers.core.host import (
|
|||
write_file,
|
||||
pwgen,
|
||||
lsb_release,
|
||||
CompareHostReleases,
|
||||
)
|
||||
from charmhelpers.contrib.hahelpers.cluster import (
|
||||
determine_apache_port,
|
||||
|
@ -100,7 +101,10 @@ from charmhelpers.core.unitdata import kv
|
|||
try:
|
||||
import psutil
|
||||
except ImportError:
|
||||
apt_install('python-psutil', fatal=True)
|
||||
if six.PY2:
|
||||
apt_install('python-psutil', fatal=True)
|
||||
else:
|
||||
apt_install('python3-psutil', fatal=True)
|
||||
import psutil
|
||||
|
||||
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
|
||||
|
@ -152,7 +156,8 @@ class OSContextGenerator(object):
|
|||
|
||||
if self.missing_data:
|
||||
self.complete = False
|
||||
log('Missing required data: %s' % ' '.join(self.missing_data), level=INFO)
|
||||
log('Missing required data: %s' % ' '.join(self.missing_data),
|
||||
level=INFO)
|
||||
else:
|
||||
self.complete = True
|
||||
return self.complete
|
||||
|
@ -210,8 +215,9 @@ class SharedDBContext(OSContextGenerator):
|
|||
hostname_key = "{}_hostname".format(self.relation_prefix)
|
||||
else:
|
||||
hostname_key = "hostname"
|
||||
access_hostname = get_address_in_network(access_network,
|
||||
unit_get('private-address'))
|
||||
access_hostname = get_address_in_network(
|
||||
access_network,
|
||||
unit_get('private-address'))
|
||||
set_hostname = relation_get(attribute=hostname_key,
|
||||
unit=local_unit())
|
||||
if set_hostname != access_hostname:
|
||||
|
@ -305,7 +311,10 @@ def db_ssl(rdata, ctxt, ssl_dir):
|
|||
|
||||
class IdentityServiceContext(OSContextGenerator):
|
||||
|
||||
def __init__(self, service=None, service_user=None, rel_name='identity-service'):
|
||||
def __init__(self,
|
||||
service=None,
|
||||
service_user=None,
|
||||
rel_name='identity-service'):
|
||||
self.service = service
|
||||
self.service_user = service_user
|
||||
self.rel_name = rel_name
|
||||
|
@ -392,16 +401,20 @@ class AMQPContext(OSContextGenerator):
|
|||
for rid in relation_ids(self.rel_name):
|
||||
ha_vip_only = False
|
||||
self.related = True
|
||||
transport_hosts = None
|
||||
rabbitmq_port = '5672'
|
||||
for unit in related_units(rid):
|
||||
if relation_get('clustered', rid=rid, unit=unit):
|
||||
ctxt['clustered'] = True
|
||||
vip = relation_get('vip', rid=rid, unit=unit)
|
||||
vip = format_ipv6_addr(vip) or vip
|
||||
ctxt['rabbitmq_host'] = vip
|
||||
transport_hosts = [vip]
|
||||
else:
|
||||
host = relation_get('private-address', rid=rid, unit=unit)
|
||||
host = format_ipv6_addr(host) or host
|
||||
ctxt['rabbitmq_host'] = host
|
||||
transport_hosts = [host]
|
||||
|
||||
ctxt.update({
|
||||
'rabbitmq_user': username,
|
||||
|
@ -413,6 +426,7 @@ class AMQPContext(OSContextGenerator):
|
|||
ssl_port = relation_get('ssl_port', rid=rid, unit=unit)
|
||||
if ssl_port:
|
||||
ctxt['rabbit_ssl_port'] = ssl_port
|
||||
rabbitmq_port = ssl_port
|
||||
|
||||
ssl_ca = relation_get('ssl_ca', rid=rid, unit=unit)
|
||||
if ssl_ca:
|
||||
|
@ -449,7 +463,19 @@ class AMQPContext(OSContextGenerator):
|
|||
host = format_ipv6_addr(host) or host
|
||||
rabbitmq_hosts.append(host)
|
||||
|
||||
ctxt['rabbitmq_hosts'] = ','.join(sorted(rabbitmq_hosts))
|
||||
rabbitmq_hosts = sorted(rabbitmq_hosts)
|
||||
ctxt['rabbitmq_hosts'] = ','.join(rabbitmq_hosts)
|
||||
transport_hosts = rabbitmq_hosts
|
||||
|
||||
if transport_hosts:
|
||||
transport_url_hosts = ','.join([
|
||||
"{}:{}@{}:{}".format(ctxt['rabbitmq_user'],
|
||||
ctxt['rabbitmq_password'],
|
||||
host_,
|
||||
rabbitmq_port)
|
||||
for host_ in transport_hosts])
|
||||
ctxt['transport_url'] = "rabbit://{}/{}".format(
|
||||
transport_url_hosts, vhost)
|
||||
|
||||
oslo_messaging_flags = conf.get('oslo-messaging-flags', None)
|
||||
if oslo_messaging_flags:
|
||||
|
@ -481,13 +507,16 @@ class CephContext(OSContextGenerator):
|
|||
ctxt['auth'] = relation_get('auth', rid=rid, unit=unit)
|
||||
if not ctxt.get('key'):
|
||||
ctxt['key'] = relation_get('key', rid=rid, unit=unit)
|
||||
ceph_pub_addr = relation_get('ceph-public-address', rid=rid,
|
||||
|
||||
ceph_addrs = relation_get('ceph-public-address', rid=rid,
|
||||
unit=unit)
|
||||
if ceph_addrs:
|
||||
for addr in ceph_addrs.split(' '):
|
||||
mon_hosts.append(format_ipv6_addr(addr) or addr)
|
||||
else:
|
||||
priv_addr = relation_get('private-address', rid=rid,
|
||||
unit=unit)
|
||||
unit_priv_addr = relation_get('private-address', rid=rid,
|
||||
unit=unit)
|
||||
ceph_addr = ceph_pub_addr or unit_priv_addr
|
||||
ceph_addr = format_ipv6_addr(ceph_addr) or ceph_addr
|
||||
mon_hosts.append(ceph_addr)
|
||||
mon_hosts.append(format_ipv6_addr(priv_addr) or priv_addr)
|
||||
|
||||
ctxt['mon_hosts'] = ' '.join(sorted(mon_hosts))
|
||||
|
||||
|
@ -1576,7 +1605,8 @@ class MemcacheContext(OSContextGenerator):
|
|||
if ctxt['use_memcache']:
|
||||
# Trusty version of memcached does not support ::1 as a listen
|
||||
# address so use host file entry instead
|
||||
if lsb_release()['DISTRIB_CODENAME'].lower() > 'trusty':
|
||||
release = lsb_release()['DISTRIB_CODENAME'].lower()
|
||||
if CompareHostReleases(release) > 'trusty':
|
||||
ctxt['memcache_server'] = '::1'
|
||||
else:
|
||||
ctxt['memcache_server'] = 'ip6-localhost'
|
||||
|
|
|
@ -126,3 +126,14 @@ def assert_charm_supports_dns_ha():
|
|||
status_set('blocked', msg)
|
||||
raise DNSHAException(msg)
|
||||
return True
|
||||
|
||||
|
||||
def expect_ha():
|
||||
""" Determine if the unit expects to be in HA
|
||||
|
||||
Check for VIP or dns-ha settings which indicate the unit should expect to
|
||||
be related to hacluster.
|
||||
|
||||
@returns boolean
|
||||
"""
|
||||
return config('vip') or config('dns-ha')
|
||||
|
|
|
@ -0,0 +1,178 @@
|
|||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright 2017 Canonical Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import six
|
||||
from charmhelpers.fetch import apt_install
|
||||
from charmhelpers.contrib.openstack.context import IdentityServiceContext
|
||||
from charmhelpers.core.hookenv import (
|
||||
log,
|
||||
ERROR,
|
||||
)
|
||||
|
||||
|
||||
def get_api_suffix(api_version):
|
||||
"""Return the formatted api suffix for the given version
|
||||
@param api_version: version of the keystone endpoint
|
||||
@returns the api suffix formatted according to the given api
|
||||
version
|
||||
"""
|
||||
return 'v2.0' if api_version in (2, "2.0") else 'v3'
|
||||
|
||||
|
||||
def format_endpoint(schema, addr, port, api_version):
|
||||
"""Return a formatted keystone endpoint
|
||||
@param schema: http or https
|
||||
@param addr: ipv4/ipv6 host of the keystone service
|
||||
@param port: port of the keystone service
|
||||
@param api_version: 2 or 3
|
||||
@returns a fully formatted keystone endpoint
|
||||
"""
|
||||
return '{}://{}:{}/{}/'.format(schema, addr, port,
|
||||
get_api_suffix(api_version))
|
||||
|
||||
|
||||
def get_keystone_manager(endpoint, api_version, **kwargs):
|
||||
"""Return a keystonemanager for the correct API version
|
||||
|
||||
@param endpoint: the keystone endpoint to point client at
|
||||
@param api_version: version of the keystone api the client should use
|
||||
@param kwargs: token or username/tenant/password information
|
||||
@returns keystonemanager class used for interrogating keystone
|
||||
"""
|
||||
if api_version == 2:
|
||||
return KeystoneManager2(endpoint, **kwargs)
|
||||
if api_version == 3:
|
||||
return KeystoneManager3(endpoint, **kwargs)
|
||||
raise ValueError('No manager found for api version {}'.format(api_version))
|
||||
|
||||
|
||||
def get_keystone_manager_from_identity_service_context():
|
||||
"""Return a keystonmanager generated from a
|
||||
instance of charmhelpers.contrib.openstack.context.IdentityServiceContext
|
||||
@returns keystonamenager instance
|
||||
"""
|
||||
context = IdentityServiceContext()()
|
||||
if not context:
|
||||
msg = "Identity service context cannot be generated"
|
||||
log(msg, level=ERROR)
|
||||
raise ValueError(msg)
|
||||
|
||||
endpoint = format_endpoint(context['service_protocol'],
|
||||
context['service_host'],
|
||||
context['service_port'],
|
||||
context['api_version'])
|
||||
|
||||
if context['api_version'] in (2, "2.0"):
|
||||
api_version = 2
|
||||
else:
|
||||
api_version = 3
|
||||
|
||||
return get_keystone_manager(endpoint, api_version,
|
||||
username=context['admin_user'],
|
||||
password=context['admin_password'],
|
||||
tenant_name=context['admin_tenant_name'])
|
||||
|
||||
|
||||
class KeystoneManager(object):
|
||||
|
||||
def resolve_service_id(self, service_name=None, service_type=None):
|
||||
"""Find the service_id of a given service"""
|
||||
services = [s._info for s in self.api.services.list()]
|
||||
|
||||
service_name = service_name.lower()
|
||||
for s in services:
|
||||
name = s['name'].lower()
|
||||
if service_type and service_name:
|
||||
if (service_name == name and service_type == s['type']):
|
||||
return s['id']
|
||||
elif service_name and service_name == name:
|
||||
return s['id']
|
||||
elif service_type and service_type == s['type']:
|
||||
return s['id']
|
||||
return None
|
||||
|
||||
def service_exists(self, service_name=None, service_type=None):
|
||||
"""Determine if the given service exists on the service list"""
|
||||
return self.resolve_service_id(service_name, service_type) is not None
|
||||
|
||||
|
||||
class KeystoneManager2(KeystoneManager):
|
||||
|
||||
def __init__(self, endpoint, **kwargs):
|
||||
try:
|
||||
from keystoneclient.v2_0 import client
|
||||
from keystoneclient.auth.identity import v2
|
||||
from keystoneclient import session
|
||||
except ImportError:
|
||||
if six.PY2:
|
||||
apt_install(["python-keystoneclient"], fatal=True)
|
||||
else:
|
||||
apt_install(["python3-keystoneclient"], fatal=True)
|
||||
|
||||
from keystoneclient.v2_0 import client
|
||||
from keystoneclient.auth.identity import v2
|
||||
from keystoneclient import session
|
||||
|
||||
self.api_version = 2
|
||||
|
||||
token = kwargs.get("token", None)
|
||||
if token:
|
||||
api = client.Client(endpoint=endpoint, token=token)
|
||||
else:
|
||||
auth = v2.Password(username=kwargs.get("username"),
|
||||
password=kwargs.get("password"),
|
||||
tenant_name=kwargs.get("tenant_name"),
|
||||
auth_url=endpoint)
|
||||
sess = session.Session(auth=auth)
|
||||
api = client.Client(session=sess)
|
||||
|
||||
self.api = api
|
||||
|
||||
|
||||
class KeystoneManager3(KeystoneManager):
|
||||
|
||||
def __init__(self, endpoint, **kwargs):
|
||||
try:
|
||||
from keystoneclient.v3 import client
|
||||
from keystoneclient.auth import token_endpoint
|
||||
from keystoneclient import session
|
||||
from keystoneclient.auth.identity import v3
|
||||
except ImportError:
|
||||
if six.PY2:
|
||||
apt_install(["python-keystoneclient"], fatal=True)
|
||||
else:
|
||||
apt_install(["python3-keystoneclient"], fatal=True)
|
||||
|
||||
from keystoneclient.v3 import client
|
||||
from keystoneclient.auth import token_endpoint
|
||||
from keystoneclient import session
|
||||
from keystoneclient.auth.identity import v3
|
||||
|
||||
self.api_version = 3
|
||||
|
||||
token = kwargs.get("token", None)
|
||||
if token:
|
||||
auth = token_endpoint.Token(endpoint=endpoint,
|
||||
token=token)
|
||||
sess = session.Session(auth=auth)
|
||||
else:
|
||||
auth = v3.Password(auth_url=endpoint,
|
||||
user_id=kwargs.get("username"),
|
||||
password=kwargs.get("password"),
|
||||
project_id=kwargs.get("tenant_name"))
|
||||
sess = session.Session(auth=auth)
|
||||
|
||||
self.api = client.Client(session=sess)
|
|
@ -23,7 +23,10 @@ from charmhelpers.core.hookenv import (
|
|||
ERROR,
|
||||
)
|
||||
|
||||
from charmhelpers.contrib.openstack.utils import os_release
|
||||
from charmhelpers.contrib.openstack.utils import (
|
||||
os_release,
|
||||
CompareOpenStackReleases,
|
||||
)
|
||||
|
||||
|
||||
def headers_package():
|
||||
|
@ -198,7 +201,8 @@ def neutron_plugins():
|
|||
},
|
||||
'plumgrid': {
|
||||
'config': '/etc/neutron/plugins/plumgrid/plumgrid.ini',
|
||||
'driver': 'neutron.plugins.plumgrid.plumgrid_plugin.plumgrid_plugin.NeutronPluginPLUMgridV2',
|
||||
'driver': ('neutron.plugins.plumgrid.plumgrid_plugin'
|
||||
'.plumgrid_plugin.NeutronPluginPLUMgridV2'),
|
||||
'contexts': [
|
||||
context.SharedDBContext(user=config('database-user'),
|
||||
database=config('database'),
|
||||
|
@ -225,7 +229,7 @@ def neutron_plugins():
|
|||
'server_services': ['neutron-server']
|
||||
}
|
||||
}
|
||||
if release >= 'icehouse':
|
||||
if CompareOpenStackReleases(release) >= 'icehouse':
|
||||
# NOTE: patch in ml2 plugin for icehouse onwards
|
||||
plugins['ovs']['config'] = '/etc/neutron/plugins/ml2/ml2_conf.ini'
|
||||
plugins['ovs']['driver'] = 'neutron.plugins.ml2.plugin.Ml2Plugin'
|
||||
|
@ -233,10 +237,10 @@ def neutron_plugins():
|
|||
'neutron-plugin-ml2']
|
||||
# NOTE: patch in vmware renames nvp->nsx for icehouse onwards
|
||||
plugins['nvp'] = plugins['nsx']
|
||||
if release >= 'kilo':
|
||||
if CompareOpenStackReleases(release) >= 'kilo':
|
||||
plugins['midonet']['driver'] = (
|
||||
'neutron.plugins.midonet.plugin.MidonetPluginV2')
|
||||
if release >= 'liberty':
|
||||
if CompareOpenStackReleases(release) >= 'liberty':
|
||||
plugins['midonet']['driver'] = (
|
||||
'midonet.neutron.plugin_v1.MidonetPluginV2')
|
||||
plugins['midonet']['server_packages'].remove(
|
||||
|
@ -244,10 +248,11 @@ def neutron_plugins():
|
|||
plugins['midonet']['server_packages'].append(
|
||||
'python-networking-midonet')
|
||||
plugins['plumgrid']['driver'] = (
|
||||
'networking_plumgrid.neutron.plugins.plugin.NeutronPluginPLUMgridV2')
|
||||
'networking_plumgrid.neutron.plugins'
|
||||
'.plugin.NeutronPluginPLUMgridV2')
|
||||
plugins['plumgrid']['server_packages'].remove(
|
||||
'neutron-plugin-plumgrid')
|
||||
if release >= 'mitaka':
|
||||
if CompareOpenStackReleases(release) >= 'mitaka':
|
||||
plugins['nsx']['server_packages'].remove('neutron-plugin-vmware')
|
||||
plugins['nsx']['server_packages'].append('python-vmware-nsx')
|
||||
plugins['nsx']['config'] = '/etc/neutron/nsx.ini'
|
||||
|
|
|
@ -28,7 +28,10 @@ try:
|
|||
from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions
|
||||
except ImportError:
|
||||
apt_update(fatal=True)
|
||||
apt_install('python-jinja2', fatal=True)
|
||||
if six.PY2:
|
||||
apt_install('python-jinja2', fatal=True)
|
||||
else:
|
||||
apt_install('python3-jinja2', fatal=True)
|
||||
from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions
|
||||
|
||||
|
||||
|
@ -207,7 +210,10 @@ class OSConfigRenderer(object):
|
|||
# if this code is running, the object is created pre-install hook.
|
||||
# jinja2 shouldn't get touched until the module is reloaded on next
|
||||
# hook execution, with proper jinja2 bits successfully imported.
|
||||
apt_install('python-jinja2')
|
||||
if six.PY2:
|
||||
apt_install('python-jinja2')
|
||||
else:
|
||||
apt_install('python3-jinja2')
|
||||
|
||||
def register(self, config_file, contexts):
|
||||
"""
|
||||
|
|
|
@ -33,9 +33,7 @@ import yaml
|
|||
|
||||
from charmhelpers.contrib.network import ip
|
||||
|
||||
from charmhelpers.core import (
|
||||
unitdata,
|
||||
)
|
||||
from charmhelpers.core import unitdata
|
||||
|
||||
from charmhelpers.core.hookenv import (
|
||||
action_fail,
|
||||
|
@ -55,6 +53,8 @@ from charmhelpers.core.hookenv import (
|
|||
application_version_set,
|
||||
)
|
||||
|
||||
from charmhelpers.core.strutils import BasicStringComparator
|
||||
|
||||
from charmhelpers.contrib.storage.linux.lvm import (
|
||||
deactivate_lvm_volume_group,
|
||||
is_lvm_physical_volume,
|
||||
|
@ -97,6 +97,22 @@ CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA'
|
|||
DISTRO_PROPOSED = ('deb http://archive.ubuntu.com/ubuntu/ %s-proposed '
|
||||
'restricted main multiverse universe')
|
||||
|
||||
OPENSTACK_RELEASES = (
|
||||
'diablo',
|
||||
'essex',
|
||||
'folsom',
|
||||
'grizzly',
|
||||
'havana',
|
||||
'icehouse',
|
||||
'juno',
|
||||
'kilo',
|
||||
'liberty',
|
||||
'mitaka',
|
||||
'newton',
|
||||
'ocata',
|
||||
'pike',
|
||||
)
|
||||
|
||||
UBUNTU_OPENSTACK_RELEASE = OrderedDict([
|
||||
('oneiric', 'diablo'),
|
||||
('precise', 'essex'),
|
||||
|
@ -153,7 +169,7 @@ SWIFT_CODENAMES = OrderedDict([
|
|||
('newton',
|
||||
['2.8.0', '2.9.0', '2.10.0']),
|
||||
('ocata',
|
||||
['2.11.0', '2.12.0']),
|
||||
['2.11.0', '2.12.0', '2.13.0']),
|
||||
])
|
||||
|
||||
# >= Liberty version->codename mapping
|
||||
|
@ -238,6 +254,17 @@ GIT_DEFAULT_BRANCHES = {
|
|||
DEFAULT_LOOPBACK_SIZE = '5G'
|
||||
|
||||
|
||||
class CompareOpenStackReleases(BasicStringComparator):
|
||||
"""Provide comparisons of OpenStack releases.
|
||||
|
||||
Use in the form of
|
||||
|
||||
if CompareOpenStackReleases(release) > 'mitaka':
|
||||
# do something with mitaka
|
||||
"""
|
||||
_list = OPENSTACK_RELEASES
|
||||
|
||||
|
||||
def error_out(msg):
|
||||
juju_log("FATAL ERROR: %s" % msg, level='ERROR')
|
||||
sys.exit(1)
|
||||
|
@ -1066,7 +1093,8 @@ def git_generate_systemd_init_files(templates_dir):
|
|||
|
||||
shutil.copyfile(init_in_source, init_source)
|
||||
with open(init_source, 'a') as outfile:
|
||||
template = '/usr/share/openstack-pkg-tools/init-script-template'
|
||||
template = ('/usr/share/openstack-pkg-tools/'
|
||||
'init-script-template')
|
||||
with open(template) as infile:
|
||||
outfile.write('\n\n{}'.format(infile.read()))
|
||||
|
||||
|
@ -1971,9 +1999,7 @@ def enable_memcache(source=None, release=None, package=None):
|
|||
if not _release:
|
||||
_release = get_os_codename_install_source(source)
|
||||
|
||||
# TODO: this should be changed to a numeric comparison using a known list
|
||||
# of releases and comparing by index.
|
||||
return _release >= 'mitaka'
|
||||
return CompareOpenStackReleases(_release) >= 'mitaka'
|
||||
|
||||
|
||||
def token_cache_pkgs(source=None, release=None):
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import six
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
@ -39,7 +40,10 @@ def pip_execute(*args, **kwargs):
|
|||
from pip import main as _pip_execute
|
||||
except ImportError:
|
||||
apt_update()
|
||||
apt_install('python-pip')
|
||||
if six.PY2:
|
||||
apt_install('python-pip')
|
||||
else:
|
||||
apt_install('python3-pip')
|
||||
from pip import main as _pip_execute
|
||||
_pip_execute(*args, **kwargs)
|
||||
finally:
|
||||
|
@ -136,7 +140,10 @@ def pip_list():
|
|||
|
||||
def pip_create_virtualenv(path=None):
|
||||
"""Create an isolated Python environment."""
|
||||
apt_install('python-virtualenv')
|
||||
if six.PY2:
|
||||
apt_install('python-virtualenv')
|
||||
else:
|
||||
apt_install('python3-virtualenv')
|
||||
|
||||
if path:
|
||||
venv_path = path
|
||||
|
|
|
@ -45,6 +45,7 @@ if __platform__ == "ubuntu":
|
|||
add_new_group,
|
||||
lsb_release,
|
||||
cmp_pkgrevno,
|
||||
CompareHostReleases,
|
||||
) # flake8: noqa -- ignore F401 for this import
|
||||
elif __platform__ == "centos":
|
||||
from charmhelpers.core.host_factory.centos import (
|
||||
|
@ -52,6 +53,7 @@ elif __platform__ == "centos":
|
|||
add_new_group,
|
||||
lsb_release,
|
||||
cmp_pkgrevno,
|
||||
CompareHostReleases,
|
||||
) # flake8: noqa -- ignore F401 for this import
|
||||
|
||||
UPDATEDB_PATH = '/etc/updatedb.conf'
|
||||
|
@ -306,6 +308,8 @@ SYSTEMD_SYSTEM = '/run/systemd/system'
|
|||
|
||||
def init_is_systemd():
|
||||
"""Return True if the host system uses systemd, False otherwise."""
|
||||
if lsb_release()['DISTRIB_CODENAME'] == 'trusty':
|
||||
return False
|
||||
return os.path.isdir(SYSTEMD_SYSTEM)
|
||||
|
||||
|
||||
|
|
|
@ -2,6 +2,22 @@ import subprocess
|
|||
import yum
|
||||
import os
|
||||
|
||||
from charmhelpers.core.strutils import BasicStringComparator
|
||||
|
||||
|
||||
class CompareHostReleases(BasicStringComparator):
|
||||
"""Provide comparisons of Host releases.
|
||||
|
||||
Use in the form of
|
||||
|
||||
if CompareHostReleases(release) > 'trusty':
|
||||
# do something with mitaka
|
||||
"""
|
||||
|
||||
def __init__(self, item):
|
||||
raise NotImplementedError(
|
||||
"CompareHostReleases() is not implemented for CentOS")
|
||||
|
||||
|
||||
def service_available(service_name):
|
||||
# """Determine whether a system service is available."""
|
||||
|
|
|
@ -1,5 +1,37 @@
|
|||
import subprocess
|
||||
|
||||
from charmhelpers.core.strutils import BasicStringComparator
|
||||
|
||||
|
||||
UBUNTU_RELEASES = (
|
||||
'lucid',
|
||||
'maverick',
|
||||
'natty',
|
||||
'oneiric',
|
||||
'precise',
|
||||
'quantal',
|
||||
'raring',
|
||||
'saucy',
|
||||
'trusty',
|
||||
'utopic',
|
||||
'vivid',
|
||||
'wily',
|
||||
'xenial',
|
||||
'yakkety',
|
||||
'zesty',
|
||||
)
|
||||
|
||||
|
||||
class CompareHostReleases(BasicStringComparator):
|
||||
"""Provide comparisons of Ubuntu releases.
|
||||
|
||||
Use in the form of
|
||||
|
||||
if CompareHostReleases(release) > 'trusty':
|
||||
# do something with mitaka
|
||||
"""
|
||||
_list = UBUNTU_RELEASES
|
||||
|
||||
|
||||
def service_available(service_name):
|
||||
"""Determine whether a system service is available"""
|
||||
|
|
|
@ -68,3 +68,56 @@ def bytes_from_string(value):
|
|||
msg = "Unable to interpret string value '%s' as bytes" % (value)
|
||||
raise ValueError(msg)
|
||||
return int(matches.group(1)) * (1024 ** BYTE_POWER[matches.group(2)])
|
||||
|
||||
|
||||
class BasicStringComparator(object):
|
||||
"""Provides a class that will compare strings from an iterator type object.
|
||||
Used to provide > and < comparisons on strings that may not necessarily be
|
||||
alphanumerically ordered. e.g. OpenStack or Ubuntu releases AFTER the
|
||||
z-wrap.
|
||||
"""
|
||||
|
||||
_list = None
|
||||
|
||||
def __init__(self, item):
|
||||
if self._list is None:
|
||||
raise Exception("Must define the _list in the class definition!")
|
||||
try:
|
||||
self.index = self._list.index(item)
|
||||
except Exception:
|
||||
raise KeyError("Item '{}' is not in list '{}'"
|
||||
.format(item, self._list))
|
||||
|
||||
def __eq__(self, other):
|
||||
assert isinstance(other, str) or isinstance(other, self.__class__)
|
||||
return self.index == self._list.index(other)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __lt__(self, other):
|
||||
assert isinstance(other, str) or isinstance(other, self.__class__)
|
||||
return self.index < self._list.index(other)
|
||||
|
||||
def __ge__(self, other):
|
||||
return not self.__lt__(other)
|
||||
|
||||
def __gt__(self, other):
|
||||
assert isinstance(other, str) or isinstance(other, self.__class__)
|
||||
return self.index > self._list.index(other)
|
||||
|
||||
def __le__(self, other):
|
||||
return not self.__gt__(other)
|
||||
|
||||
def __str__(self):
|
||||
"""Always give back the item at the index so it can be used in
|
||||
comparisons like:
|
||||
|
||||
s_mitaka = CompareOpenStack('mitaka')
|
||||
s_newton = CompareOpenstack('newton')
|
||||
|
||||
assert s_newton > s_mitaka
|
||||
|
||||
@returns: <string>
|
||||
"""
|
||||
return self._list[self.index]
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
# Copyright 2014-2017 Canonical Limited.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""
|
||||
Charm helpers snap for classic charms.
|
||||
|
||||
If writing reactive charms, use the snap layer:
|
||||
https://lists.ubuntu.com/archives/snapcraft/2016-September/001114.html
|
||||
"""
|
||||
import subprocess
|
||||
from os import environ
|
||||
from time import sleep
|
||||
from charmhelpers.core.hookenv import log
|
||||
|
||||
__author__ = 'Joseph Borg <joseph.borg@canonical.com>'
|
||||
|
||||
SNAP_NO_LOCK = 1 # The return code for "couldn't acquire lock" in Snap (hopefully this will be improved).
|
||||
SNAP_NO_LOCK_RETRY_DELAY = 10 # Wait X seconds between Snap lock checks.
|
||||
SNAP_NO_LOCK_RETRY_COUNT = 30 # Retry to acquire the lock X times.
|
||||
|
||||
|
||||
class CouldNotAcquireLockException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def _snap_exec(commands):
|
||||
"""
|
||||
Execute snap commands.
|
||||
|
||||
:param commands: List commands
|
||||
:return: Integer exit code
|
||||
"""
|
||||
assert type(commands) == list
|
||||
|
||||
retry_count = 0
|
||||
return_code = None
|
||||
|
||||
while return_code is None or return_code == SNAP_NO_LOCK:
|
||||
try:
|
||||
return_code = subprocess.check_call(['snap'] + commands, env=environ)
|
||||
except subprocess.CalledProcessError as e:
|
||||
retry_count += + 1
|
||||
if retry_count > SNAP_NO_LOCK_RETRY_COUNT:
|
||||
raise CouldNotAcquireLockException('Could not aquire lock after %s attempts' % SNAP_NO_LOCK_RETRY_COUNT)
|
||||
return_code = e.returncode
|
||||
log('Snap failed to acquire lock, trying again in %s seconds.' % SNAP_NO_LOCK_RETRY_DELAY, level='WARN')
|
||||
sleep(SNAP_NO_LOCK_RETRY_DELAY)
|
||||
|
||||
return return_code
|
||||
|
||||
|
||||
def snap_install(packages, *flags):
|
||||
"""
|
||||
Install a snap package.
|
||||
|
||||
:param packages: String or List String package name
|
||||
:param flags: List String flags to pass to install command
|
||||
:return: Integer return code from snap
|
||||
"""
|
||||
if type(packages) is not list:
|
||||
packages = [packages]
|
||||
|
||||
flags = list(flags)
|
||||
|
||||
message = 'Installing snap(s) "%s"' % ', '.join(packages)
|
||||
if flags:
|
||||
message += ' with option(s) "%s"' % ', '.join(flags)
|
||||
|
||||
log(message, level='INFO')
|
||||
return _snap_exec(['install'] + flags + packages)
|
||||
|
||||
|
||||
def snap_remove(packages, *flags):
|
||||
"""
|
||||
Remove a snap package.
|
||||
|
||||
:param packages: String or List String package name
|
||||
:param flags: List String flags to pass to remove command
|
||||
:return: Integer return code from snap
|
||||
"""
|
||||
if type(packages) is not list:
|
||||
packages = [packages]
|
||||
|
||||
flags = list(flags)
|
||||
|
||||
message = 'Removing snap(s) "%s"' % ', '.join(packages)
|
||||
if flags:
|
||||
message += ' with options "%s"' % ', '.join(flags)
|
||||
|
||||
log(message, level='INFO')
|
||||
return _snap_exec(['remove'] + flags + packages)
|
||||
|
||||
|
||||
def snap_refresh(packages, *flags):
|
||||
"""
|
||||
Refresh / Update snap package.
|
||||
|
||||
:param packages: String or List String package name
|
||||
:param flags: List String flags to pass to refresh command
|
||||
:return: Integer return code from snap
|
||||
"""
|
||||
if type(packages) is not list:
|
||||
packages = [packages]
|
||||
|
||||
flags = list(flags)
|
||||
|
||||
message = 'Refreshing snap(s) "%s"' % ', '.join(packages)
|
||||
if flags:
|
||||
message += ' with options "%s"' % ', '.join(flags)
|
||||
|
||||
log(message, level='INFO')
|
||||
return _snap_exec(['refresh'] + flags + packages)
|
|
@ -116,8 +116,8 @@ CLOUD_ARCHIVE_POCKETS = {
|
|||
}
|
||||
|
||||
APT_NO_LOCK = 100 # The return code for "couldn't acquire lock" in APT.
|
||||
APT_NO_LOCK_RETRY_DELAY = 10 # Wait 10 seconds between apt lock checks.
|
||||
APT_NO_LOCK_RETRY_COUNT = 30 # Retry to acquire the lock X times.
|
||||
CMD_RETRY_DELAY = 10 # Wait 10 seconds between command retries.
|
||||
CMD_RETRY_COUNT = 30 # Retry a failing fatal command X times.
|
||||
|
||||
|
||||
def filter_installed_packages(packages):
|
||||
|
@ -249,7 +249,8 @@ def add_source(source, key=None):
|
|||
source.startswith('http') or
|
||||
source.startswith('deb ') or
|
||||
source.startswith('cloud-archive:')):
|
||||
subprocess.check_call(['add-apt-repository', '--yes', source])
|
||||
cmd = ['add-apt-repository', '--yes', source]
|
||||
_run_with_retries(cmd)
|
||||
elif source.startswith('cloud:'):
|
||||
install(filter_installed_packages(['ubuntu-cloud-keyring']),
|
||||
fatal=True)
|
||||
|
@ -286,41 +287,60 @@ def add_source(source, key=None):
|
|||
key])
|
||||
|
||||
|
||||
def _run_apt_command(cmd, fatal=False):
|
||||
"""Run an APT command.
|
||||
|
||||
Checks the output and retries if the fatal flag is set
|
||||
to True.
|
||||
def _run_with_retries(cmd, max_retries=CMD_RETRY_COUNT, retry_exitcodes=(1,),
|
||||
retry_message="", cmd_env=None):
|
||||
"""Run a command and retry until success or max_retries is reached.
|
||||
|
||||
:param: cmd: str: The apt command to run.
|
||||
:param: max_retries: int: The number of retries to attempt on a fatal
|
||||
command. Defaults to CMD_RETRY_COUNT.
|
||||
:param: retry_exitcodes: tuple: Optional additional exit codes to retry.
|
||||
Defaults to retry on exit code 1.
|
||||
:param: retry_message: str: Optional log prefix emitted during retries.
|
||||
:param: cmd_env: dict: Environment variables to add to the command run.
|
||||
"""
|
||||
|
||||
env = os.environ.copy()
|
||||
if cmd_env:
|
||||
env.update(cmd_env)
|
||||
|
||||
if not retry_message:
|
||||
retry_message = "Failed executing '{}'".format(" ".join(cmd))
|
||||
retry_message += ". Will retry in {} seconds".format(CMD_RETRY_DELAY)
|
||||
|
||||
retry_count = 0
|
||||
result = None
|
||||
|
||||
retry_results = (None,) + retry_exitcodes
|
||||
while result in retry_results:
|
||||
try:
|
||||
result = subprocess.check_call(cmd, env=env)
|
||||
except subprocess.CalledProcessError as e:
|
||||
retry_count = retry_count + 1
|
||||
if retry_count > max_retries:
|
||||
raise
|
||||
result = e.returncode
|
||||
log(retry_message)
|
||||
time.sleep(CMD_RETRY_DELAY)
|
||||
|
||||
|
||||
def _run_apt_command(cmd, fatal=False):
|
||||
"""Run an apt command with optional retries.
|
||||
|
||||
:param: fatal: bool: Whether the command's output should be checked and
|
||||
retried.
|
||||
"""
|
||||
env = os.environ.copy()
|
||||
|
||||
if 'DEBIAN_FRONTEND' not in env:
|
||||
env['DEBIAN_FRONTEND'] = 'noninteractive'
|
||||
# Provide DEBIAN_FRONTEND=noninteractive if not present in the environment.
|
||||
cmd_env = {
|
||||
'DEBIAN_FRONTEND': os.environ.get('DEBIAN_FRONTEND', 'noninteractive')}
|
||||
|
||||
if fatal:
|
||||
retry_count = 0
|
||||
result = None
|
||||
|
||||
# If the command is considered "fatal", we need to retry if the apt
|
||||
# lock was not acquired.
|
||||
|
||||
while result is None or result == APT_NO_LOCK:
|
||||
try:
|
||||
result = subprocess.check_call(cmd, env=env)
|
||||
except subprocess.CalledProcessError as e:
|
||||
retry_count = retry_count + 1
|
||||
if retry_count > APT_NO_LOCK_RETRY_COUNT:
|
||||
raise
|
||||
result = e.returncode
|
||||
log("Couldn't acquire DPKG lock. Will retry in {} seconds."
|
||||
"".format(APT_NO_LOCK_RETRY_DELAY))
|
||||
time.sleep(APT_NO_LOCK_RETRY_DELAY)
|
||||
|
||||
_run_with_retries(
|
||||
cmd, cmd_env=cmd_env, retry_exitcodes=(1, APT_NO_LOCK,),
|
||||
retry_message="Couldn't acquire DPKG lock")
|
||||
else:
|
||||
env = os.environ.copy()
|
||||
env.update(cmd_env)
|
||||
subprocess.call(cmd, env=env)
|
||||
|
||||
|
||||
|
|
|
@ -19,7 +19,10 @@ from charmhelpers.core.hookenv import (
|
|||
from charmhelpers.contrib.openstack.context import (
|
||||
OSContextGenerator,
|
||||
)
|
||||
from charmhelpers.contrib.openstack.utils import get_os_codename_package
|
||||
from charmhelpers.contrib.openstack.utils import (
|
||||
get_os_codename_package,
|
||||
CompareOpenStackReleases,
|
||||
)
|
||||
|
||||
|
||||
class CephBackupSubordinateContext(OSContextGenerator):
|
||||
|
@ -32,7 +35,8 @@ class CephBackupSubordinateContext(OSContextGenerator):
|
|||
if not is_relation_made('ceph', 'key'):
|
||||
return {}
|
||||
|
||||
if get_os_codename_package('cinder-common') < "icehouse":
|
||||
release = get_os_codename_package('cinder-common')
|
||||
if CompareOpenStackReleases(release) < "icehouse":
|
||||
raise Exception("Unsupported version of Openstack")
|
||||
|
||||
service = service_name()
|
||||
|
|
|
@ -785,37 +785,30 @@ class AmuletUtils(object):
|
|||
generating test messages which need to be unique-ish."""
|
||||
return '[{}-{}]'.format(uuid.uuid4(), time.time())
|
||||
|
||||
# amulet juju action helpers:
|
||||
# amulet juju action helpers:
|
||||
def run_action(self, unit_sentry, action,
|
||||
_check_output=subprocess.check_output,
|
||||
params=None):
|
||||
"""Run the named action on a given unit sentry.
|
||||
"""Translate to amulet's built in run_action(). Deprecated.
|
||||
|
||||
Run the named action on a given unit sentry.
|
||||
|
||||
params a dict of parameters to use
|
||||
_check_output parameter is used for dependency injection.
|
||||
_check_output parameter is no longer used
|
||||
|
||||
@return action_id.
|
||||
"""
|
||||
unit_id = unit_sentry.info["unit_name"]
|
||||
command = ["juju", "action", "do", "--format=json", unit_id, action]
|
||||
if params is not None:
|
||||
for key, value in params.iteritems():
|
||||
command.append("{}={}".format(key, value))
|
||||
self.log.info("Running command: %s\n" % " ".join(command))
|
||||
output = _check_output(command, universal_newlines=True)
|
||||
data = json.loads(output)
|
||||
action_id = data[u'Action queued with id']
|
||||
return action_id
|
||||
self.log.warn('charmhelpers.contrib.amulet.utils.run_action has been '
|
||||
'deprecated for amulet.run_action')
|
||||
return unit_sentry.run_action(action, action_args=params)
|
||||
|
||||
def wait_on_action(self, action_id, _check_output=subprocess.check_output):
|
||||
"""Wait for a given action, returning if it completed or not.
|
||||
|
||||
_check_output parameter is used for dependency injection.
|
||||
action_id a string action uuid
|
||||
_check_output parameter is no longer used
|
||||
"""
|
||||
command = ["juju", "action", "fetch", "--format=json", "--wait=0",
|
||||
action_id]
|
||||
output = _check_output(command, universal_newlines=True)
|
||||
data = json.loads(output)
|
||||
data = amulet.actions.get_action_output(action_id, full_output=True)
|
||||
return data.get(u"status") == "completed"
|
||||
|
||||
def status_get(self, unit):
|
||||
|
|
|
@ -32,6 +32,7 @@ from keystoneclient.v3 import client as keystone_client_v3
|
|||
from novaclient import exceptions
|
||||
|
||||
import novaclient.client as nova_client
|
||||
import novaclient
|
||||
import pika
|
||||
import swiftclient
|
||||
|
||||
|
@ -39,6 +40,7 @@ from charmhelpers.contrib.amulet.utils import (
|
|||
AmuletUtils
|
||||
)
|
||||
from charmhelpers.core.decorators import retry_on_exception
|
||||
from charmhelpers.core.host import CompareHostReleases
|
||||
|
||||
DEBUG = logging.DEBUG
|
||||
ERROR = logging.ERROR
|
||||
|
@ -434,9 +436,14 @@ class OpenStackAmuletUtils(AmuletUtils):
|
|||
self.log.debug('Authenticating nova user ({})...'.format(user))
|
||||
ep = keystone.service_catalog.url_for(service_type='identity',
|
||||
endpoint_type='publicURL')
|
||||
return nova_client.Client(NOVA_CLIENT_VERSION,
|
||||
username=user, api_key=password,
|
||||
project_id=tenant, auth_url=ep)
|
||||
if novaclient.__version__[0] >= "7":
|
||||
return nova_client.Client(NOVA_CLIENT_VERSION,
|
||||
username=user, password=password,
|
||||
project_name=tenant, auth_url=ep)
|
||||
else:
|
||||
return nova_client.Client(NOVA_CLIENT_VERSION,
|
||||
username=user, api_key=password,
|
||||
project_id=tenant, auth_url=ep)
|
||||
|
||||
def authenticate_swift_user(self, keystone, user, password, tenant):
|
||||
"""Authenticates a regular user with swift api."""
|
||||
|
@ -1249,7 +1256,7 @@ class OpenStackAmuletUtils(AmuletUtils):
|
|||
contents = self.file_contents_safe(sentry_unit, '/etc/memcached.conf',
|
||||
fatal=True)
|
||||
ubuntu_release, _ = self.run_cmd_unit(sentry_unit, 'lsb_release -cs')
|
||||
if ubuntu_release <= 'trusty':
|
||||
if CompareHostReleases(ubuntu_release) <= 'trusty':
|
||||
memcache_listen_addr = 'ip6-localhost'
|
||||
else:
|
||||
memcache_listen_addr = '::1'
|
||||
|
|
|
@ -45,6 +45,7 @@ if __platform__ == "ubuntu":
|
|||
add_new_group,
|
||||
lsb_release,
|
||||
cmp_pkgrevno,
|
||||
CompareHostReleases,
|
||||
) # flake8: noqa -- ignore F401 for this import
|
||||
elif __platform__ == "centos":
|
||||
from charmhelpers.core.host_factory.centos import (
|
||||
|
@ -52,6 +53,7 @@ elif __platform__ == "centos":
|
|||
add_new_group,
|
||||
lsb_release,
|
||||
cmp_pkgrevno,
|
||||
CompareHostReleases,
|
||||
) # flake8: noqa -- ignore F401 for this import
|
||||
|
||||
UPDATEDB_PATH = '/etc/updatedb.conf'
|
||||
|
@ -306,6 +308,8 @@ SYSTEMD_SYSTEM = '/run/systemd/system'
|
|||
|
||||
def init_is_systemd():
|
||||
"""Return True if the host system uses systemd, False otherwise."""
|
||||
if lsb_release()['DISTRIB_CODENAME'] == 'trusty':
|
||||
return False
|
||||
return os.path.isdir(SYSTEMD_SYSTEM)
|
||||
|
||||
|
||||
|
|
|
@ -2,6 +2,22 @@ import subprocess
|
|||
import yum
|
||||
import os
|
||||
|
||||
from charmhelpers.core.strutils import BasicStringComparator
|
||||
|
||||
|
||||
class CompareHostReleases(BasicStringComparator):
|
||||
"""Provide comparisons of Host releases.
|
||||
|
||||
Use in the form of
|
||||
|
||||
if CompareHostReleases(release) > 'trusty':
|
||||
# do something with mitaka
|
||||
"""
|
||||
|
||||
def __init__(self, item):
|
||||
raise NotImplementedError(
|
||||
"CompareHostReleases() is not implemented for CentOS")
|
||||
|
||||
|
||||
def service_available(service_name):
|
||||
# """Determine whether a system service is available."""
|
||||
|
|
|
@ -1,5 +1,37 @@
|
|||
import subprocess
|
||||
|
||||
from charmhelpers.core.strutils import BasicStringComparator
|
||||
|
||||
|
||||
UBUNTU_RELEASES = (
|
||||
'lucid',
|
||||
'maverick',
|
||||
'natty',
|
||||
'oneiric',
|
||||
'precise',
|
||||
'quantal',
|
||||
'raring',
|
||||
'saucy',
|
||||
'trusty',
|
||||
'utopic',
|
||||
'vivid',
|
||||
'wily',
|
||||
'xenial',
|
||||
'yakkety',
|
||||
'zesty',
|
||||
)
|
||||
|
||||
|
||||
class CompareHostReleases(BasicStringComparator):
|
||||
"""Provide comparisons of Ubuntu releases.
|
||||
|
||||
Use in the form of
|
||||
|
||||
if CompareHostReleases(release) > 'trusty':
|
||||
# do something with mitaka
|
||||
"""
|
||||
_list = UBUNTU_RELEASES
|
||||
|
||||
|
||||
def service_available(service_name):
|
||||
"""Determine whether a system service is available"""
|
||||
|
|
|
@ -68,3 +68,56 @@ def bytes_from_string(value):
|
|||
msg = "Unable to interpret string value '%s' as bytes" % (value)
|
||||
raise ValueError(msg)
|
||||
return int(matches.group(1)) * (1024 ** BYTE_POWER[matches.group(2)])
|
||||
|
||||
|
||||
class BasicStringComparator(object):
|
||||
"""Provides a class that will compare strings from an iterator type object.
|
||||
Used to provide > and < comparisons on strings that may not necessarily be
|
||||
alphanumerically ordered. e.g. OpenStack or Ubuntu releases AFTER the
|
||||
z-wrap.
|
||||
"""
|
||||
|
||||
_list = None
|
||||
|
||||
def __init__(self, item):
|
||||
if self._list is None:
|
||||
raise Exception("Must define the _list in the class definition!")
|
||||
try:
|
||||
self.index = self._list.index(item)
|
||||
except Exception:
|
||||
raise KeyError("Item '{}' is not in list '{}'"
|
||||
.format(item, self._list))
|
||||
|
||||
def __eq__(self, other):
|
||||
assert isinstance(other, str) or isinstance(other, self.__class__)
|
||||
return self.index == self._list.index(other)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __lt__(self, other):
|
||||
assert isinstance(other, str) or isinstance(other, self.__class__)
|
||||
return self.index < self._list.index(other)
|
||||
|
||||
def __ge__(self, other):
|
||||
return not self.__lt__(other)
|
||||
|
||||
def __gt__(self, other):
|
||||
assert isinstance(other, str) or isinstance(other, self.__class__)
|
||||
return self.index > self._list.index(other)
|
||||
|
||||
def __le__(self, other):
|
||||
return not self.__gt__(other)
|
||||
|
||||
def __str__(self):
|
||||
"""Always give back the item at the index so it can be used in
|
||||
comparisons like:
|
||||
|
||||
s_mitaka = CompareOpenStack('mitaka')
|
||||
s_newton = CompareOpenstack('newton')
|
||||
|
||||
assert s_newton > s_mitaka
|
||||
|
||||
@returns: <string>
|
||||
"""
|
||||
return self._list[self.index]
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import platform
|
||||
|
||||
|
||||
def get_platform():
|
||||
"""Return the current OS platform.
|
||||
|
||||
For example: if current os platform is Ubuntu then a string "ubuntu"
|
||||
will be returned (which is the name of the module).
|
||||
This string is used to decide which platform module should be imported.
|
||||
"""
|
||||
# linux_distribution is deprecated and will be removed in Python 3.7
|
||||
# Warings *not* disabled, as we certainly need to fix this.
|
||||
tuple_platform = platform.linux_distribution()
|
||||
current_platform = tuple_platform[0]
|
||||
if "Ubuntu" in current_platform:
|
||||
return "ubuntu"
|
||||
elif "CentOS" in current_platform:
|
||||
return "centos"
|
||||
elif "debian" in current_platform:
|
||||
# Stock Python does not detect Ubuntu and instead returns debian.
|
||||
# Or at least it does in some build environments like Travis CI
|
||||
return "ubuntu"
|
||||
else:
|
||||
raise RuntimeError("This module is not supported on {}."
|
||||
.format(current_platform))
|
Loading…
Reference in New Issue