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: If253c8a18886630fcc7de4cfc2a91a15c2563eb5
Related-Bug: #1659575
This commit is contained in:
Alex Kavanagh 2017-04-05 12:38:15 +01:00
parent 8b876a4f1a
commit d8199f07bb
33 changed files with 547 additions and 140 deletions

View File

@ -25,6 +25,7 @@ from charmhelpers.core.unitdata import HookData, kv
from charmhelpers.contrib.openstack.utils import ( from charmhelpers.contrib.openstack.utils import (
get_os_codename_package, get_os_codename_package,
set_os_workload_status, set_os_workload_status,
CompareOpenStackReleases,
) )
from lib.swift_storage_utils import ( from lib.swift_storage_utils import (
assess_status, assess_status,
@ -40,7 +41,8 @@ def _get_services():
"""Return a list of services that need to be (un)paused.""" """Return a list of services that need to be (un)paused."""
services = SWIFT_SVCS[:] services = SWIFT_SVCS[:]
# Before Icehouse there was no swift-container-sync # Before Icehouse there was no swift-container-sync
if get_os_codename_package("swift-container") < "icehouse": _os_release = get_os_codename_package("swift-container")
if CompareOpenStackReleases(_os_release) < "icehouse":
services.remove("swift-container-sync") services.remove("swift-container-sync")
return services return services

View File

@ -4,3 +4,4 @@ include:
- contrib.amulet - contrib.amulet
- contrib.openstack.amulet - contrib.openstack.amulet
- core - core
- osplatform

View File

@ -373,7 +373,7 @@ def add_init_service_checks(nrpe, services, unit_name, immediate_check=True):
checkpath = '%s/service-check-%s.txt' % (nrpe.homedir, svc) checkpath = '%s/service-check-%s.txt' % (nrpe.homedir, svc)
croncmd = ( croncmd = (
'/usr/local/lib/nagios/plugins/check_exit_status.pl ' '/usr/local/lib/nagios/plugins/check_exit_status.pl '
'-s /etc/init.d/%s status' % svc '-e -s /etc/init.d/%s status' % svc
) )
cron_file = '*/5 * * * * root %s > %s\n' % (croncmd, checkpath) cron_file = '*/5 * * * * root %s > %s\n' % (croncmd, checkpath)
f = open(cronpath, 'w') f = open(cronpath, 'w')

View File

@ -26,6 +26,7 @@ from charmhelpers.contrib.hardening.audits.file import (
DirectoryPermissionAudit, DirectoryPermissionAudit,
NoReadWriteForOther, NoReadWriteForOther,
TemplatedFile, TemplatedFile,
DeletedFile
) )
from charmhelpers.contrib.hardening.audits.apache import DisabledModuleAudit from charmhelpers.contrib.hardening.audits.apache import DisabledModuleAudit
from charmhelpers.contrib.hardening.apache import TEMPLATES_DIR from charmhelpers.contrib.hardening.apache import TEMPLATES_DIR
@ -52,13 +53,13 @@ def get_audits():
'mods-available/alias.conf'), 'mods-available/alias.conf'),
context, context,
TEMPLATES_DIR, TEMPLATES_DIR,
mode=0o0755, mode=0o0640,
user='root', user='root',
service_actions=[{'service': 'apache2', service_actions=[{'service': 'apache2',
'actions': ['restart']}]), 'actions': ['restart']}]),
TemplatedFile(os.path.join(settings['common']['apache_dir'], TemplatedFile(os.path.join(settings['common']['apache_dir'],
'conf-enabled/hardening.conf'), 'conf-enabled/99-hardening.conf'),
context, context,
TEMPLATES_DIR, TEMPLATES_DIR,
mode=0o0640, mode=0o0640,
@ -69,11 +70,13 @@ def get_audits():
DirectoryPermissionAudit(settings['common']['apache_dir'], DirectoryPermissionAudit(settings['common']['apache_dir'],
user='root', user='root',
group='root', group='root',
mode=0o640), mode=0o0750),
DisabledModuleAudit(settings['hardening']['modules_to_disable']), DisabledModuleAudit(settings['hardening']['modules_to_disable']),
NoReadWriteForOther(settings['common']['apache_dir']), NoReadWriteForOther(settings['common']['apache_dir']),
DeletedFile(['/var/www/html/index.html'])
] ]
return audits return audits
@ -94,5 +97,4 @@ class ApacheConfContext(object):
ctxt['apache_version'] = re.search(r'.+version: Apache/(.+?)\s.+', ctxt['apache_version'] = re.search(r'.+version: Apache/(.+?)\s.+',
out).group(1) out).group(1)
ctxt['apache_icondir'] = '/usr/share/apache2/icons/' ctxt['apache_icondir'] = '/usr/share/apache2/icons/'
ctxt['traceenable'] = settings['hardening']['traceenable']
return ctxt return ctxt

View File

@ -15,4 +15,18 @@
</LimitExcept> </LimitExcept>
</Location> </Location>
<Directory />
Options -Indexes -FollowSymLinks
AllowOverride None
</Directory>
<Directory /var/www/>
Options -Indexes -FollowSymLinks
AllowOverride None
</Directory>
TraceEnable {{ traceenable }} TraceEnable {{ traceenable }}
ServerTokens {{ servertokens }}
SSLHonorCipherOrder {{ honor_cipher_order }}
SSLCipherSuite {{ cipher_suite }}

View File

@ -49,13 +49,6 @@ class BaseAudit(object): # NO-QA
# Invoke the callback if there is one. # Invoke the callback if there is one.
if hasattr(self.unless, '__call__'): if hasattr(self.unless, '__call__'):
results = self.unless() return not self.unless()
if results:
return False
else:
return True
if self.unless: return not self.unless
return False
else:
return True

View File

@ -11,3 +11,6 @@ hardening:
traceenable: 'off' traceenable: 'off'
allowed_http_methods: "GET POST" allowed_http_methods: "GET POST"
modules_to_disable: [ cgi, cgid ] modules_to_disable: [ cgi, cgid ]
servertokens: 'Prod'
honor_cipher_order: 'on'
cipher_suite: 'ALL:+MEDIUM:+HIGH:!LOW:!MD5:!RC4:!eNULL:!aNULL:!3DES'

View File

@ -7,3 +7,6 @@ common:
hardening: hardening:
allowed_http_methods: allowed_http_methods:
modules_to_disable: modules_to_disable:
servertokens:
honor_cipher_order:
cipher_suite:

View File

@ -58,6 +58,7 @@ security:
rsync rsync
kernel_enable_module_loading: True # (type:boolean) kernel_enable_module_loading: True # (type:boolean)
kernel_enable_core_dump: False # (type:boolean) kernel_enable_core_dump: False # (type:boolean)
ssh_tmout: 300
sysctl: sysctl:
kernel_secure_sysrq: 244 # 4 + 16 + 32 + 64 + 128 kernel_secure_sysrq: 244 # 4 + 16 + 32 + 64 + 128

View File

@ -34,6 +34,7 @@ security:
packages_list: packages_list:
kernel_enable_module_loading: kernel_enable_module_loading:
kernel_enable_core_dump: kernel_enable_core_dump:
ssh_tmout:
sysctl: sysctl:
kernel_secure_sysrq: kernel_secure_sysrq:
kernel_enable_sysrq: kernel_enable_sysrq:

View File

@ -25,7 +25,6 @@ def get_audits():
audits = [] audits = []
settings = utils.get_settings('os') settings = utils.get_settings('os')
# If core dumps are not enabled, then don't allow core dumps to be # If core dumps are not enabled, then don't allow core dumps to be
# created as they may contain sensitive information. # created as they may contain sensitive information.
if not settings['security']['kernel_enable_core_dump']: if not settings['security']['kernel_enable_core_dump']:
@ -33,11 +32,18 @@ def get_audits():
ProfileContext(), ProfileContext(),
template_dir=TEMPLATES_DIR, template_dir=TEMPLATES_DIR,
mode=0o0755, user='root', group='root')) mode=0o0755, user='root', group='root'))
if settings['security']['ssh_tmout']:
audits.append(TemplatedFile('/etc/profile.d/99-hardening.sh',
ProfileContext(),
template_dir=TEMPLATES_DIR,
mode=0o0644, user='root', group='root'))
return audits return audits
class ProfileContext(object): class ProfileContext(object):
def __call__(self): def __call__(self):
ctxt = {} settings = utils.get_settings('os')
ctxt = {'ssh_tmout':
settings['security']['ssh_tmout']}
return ctxt return ctxt

View File

@ -0,0 +1,5 @@
TMOUT={{ tmout }}
readonly TMOUT
export TMOUT
readonly HISTFILE

View File

@ -27,7 +27,10 @@ from charmhelpers.fetch import (
apt_install, apt_install,
apt_update, apt_update,
) )
from charmhelpers.core.host import lsb_release from charmhelpers.core.host import (
lsb_release,
CompareHostReleases,
)
from charmhelpers.contrib.hardening.audits.file import ( from charmhelpers.contrib.hardening.audits.file import (
TemplatedFile, TemplatedFile,
FileContentAudit, FileContentAudit,
@ -68,7 +71,8 @@ class SSHConfigContext(object):
'weak': default + ',hmac-sha1'} 'weak': default + ',hmac-sha1'}
# Use newer ciphers on Ubuntu Trusty and above # Use newer ciphers on Ubuntu Trusty and above
if lsb_release()['DISTRIB_CODENAME'].lower() >= 'trusty': _release = lsb_release()['DISTRIB_CODENAME'].lower()
if CompareHostReleases(_release) >= 'trusty':
log("Detected Ubuntu 14.04 or newer, using new macs", level=DEBUG) log("Detected Ubuntu 14.04 or newer, using new macs", level=DEBUG)
macs = macs_66 macs = macs_66
@ -96,7 +100,8 @@ class SSHConfigContext(object):
'weak': weak} 'weak': weak}
# Use newer kex on Ubuntu Trusty and above # Use newer kex on Ubuntu Trusty and above
if lsb_release()['DISTRIB_CODENAME'].lower() >= 'trusty': _release = lsb_release()['DISTRIB_CODENAME'].lower()
if CompareHostReleases(_release) >= 'trusty':
log('Detected Ubuntu 14.04 or newer, using new key exchange ' log('Detected Ubuntu 14.04 or newer, using new key exchange '
'algorithms', level=DEBUG) 'algorithms', level=DEBUG)
kex = kex_66 kex = kex_66
@ -119,7 +124,8 @@ class SSHConfigContext(object):
'weak': default + ',aes256-cbc,aes192-cbc,aes128-cbc'} 'weak': default + ',aes256-cbc,aes192-cbc,aes128-cbc'}
# Use newer ciphers on ubuntu Trusty and above # Use newer ciphers on ubuntu Trusty and above
if lsb_release()['DISTRIB_CODENAME'].lower() >= 'trusty': _release = lsb_release()['DISTRIB_CODENAME'].lower()
if CompareHostReleases(_release) >= 'trusty':
log('Detected Ubuntu 14.04 or newer, using new ciphers', log('Detected Ubuntu 14.04 or newer, using new ciphers',
level=DEBUG) level=DEBUG)
cipher = ciphers_66 cipher = ciphers_66
@ -291,7 +297,8 @@ class SSHConfigFileContentAudit(FileContentAudit):
self.fail_cases = [] self.fail_cases = []
settings = utils.get_settings('ssh') settings = utils.get_settings('ssh')
if lsb_release()['DISTRIB_CODENAME'].lower() >= 'trusty': _release = lsb_release()['DISTRIB_CODENAME'].lower()
if CompareHostReleases(_release) >= 'trusty':
if not settings['server']['weak_hmac']: if not settings['server']['weak_hmac']:
self.pass_cases.append(r'^MACs.+,hmac-ripemd160$') self.pass_cases.append(r'^MACs.+,hmac-ripemd160$')
else: else:
@ -364,7 +371,8 @@ class SSHDConfigFileContentAudit(FileContentAudit):
self.fail_cases = [] self.fail_cases = []
settings = utils.get_settings('ssh') settings = utils.get_settings('ssh')
if lsb_release()['DISTRIB_CODENAME'].lower() >= 'trusty': _release = lsb_release()['DISTRIB_CODENAME'].lower()
if CompareHostReleases(_release) >= 'trusty':
if not settings['server']['weak_hmac']: if not settings['server']['weak_hmac']:
self.pass_cases.append(r'^MACs.+,hmac-ripemd160$') self.pass_cases.append(r'^MACs.+,hmac-ripemd160$')
else: else:

View File

@ -31,6 +31,7 @@ from charmhelpers.core.hookenv import (
from charmhelpers.core.host import ( from charmhelpers.core.host import (
lsb_release, lsb_release,
CompareHostReleases,
) )
try: try:
@ -67,6 +68,24 @@ def no_ip_found_error_out(network):
raise ValueError(errmsg) 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): def get_address_in_network(network, fallback=None, fatal=False):
"""Get an IPv4 or IPv6 address within the network from the host. """Get an IPv4 or IPv6 address within the network from the host.
@ -92,18 +111,16 @@ def get_address_in_network(network, fallback=None, fatal=False):
for iface in netifaces.interfaces(): for iface in netifaces.interfaces():
addresses = netifaces.ifaddresses(iface) addresses = netifaces.ifaddresses(iface)
if network.version == 4 and netifaces.AF_INET in addresses: if network.version == 4 and netifaces.AF_INET in addresses:
addr = addresses[netifaces.AF_INET][0]['addr'] for addr in addresses[netifaces.AF_INET]:
netmask = addresses[netifaces.AF_INET][0]['netmask'] cidr = netaddr.IPNetwork("%s/%s" % (addr['addr'],
cidr = netaddr.IPNetwork("%s/%s" % (addr, netmask)) addr['netmask']))
if cidr in network: if cidr in network:
return str(cidr.ip) return str(cidr.ip)
if network.version == 6 and netifaces.AF_INET6 in addresses: if network.version == 6 and netifaces.AF_INET6 in addresses:
for addr in addresses[netifaces.AF_INET6]: for addr in addresses[netifaces.AF_INET6]:
if not addr['addr'].startswith('fe80'): cidr = _get_ipv6_network_from_address(addr)
cidr = netaddr.IPNetwork("%s/%s" % (addr['addr'], if cidr and cidr in network:
addr['netmask']))
if cidr in network:
return str(cidr.ip) return str(cidr.ip)
if fallback is not None: if fallback is not None:
@ -180,9 +197,10 @@ def _get_for_address(address, key):
if address.version == 6 and netifaces.AF_INET6 in addresses: if address.version == 6 and netifaces.AF_INET6 in addresses:
for addr in addresses[netifaces.AF_INET6]: for addr in addresses[netifaces.AF_INET6]:
if not addr['addr'].startswith('fe80'): network = _get_ipv6_network_from_address(addr)
network = netaddr.IPNetwork("%s/%s" % (addr['addr'], if not network:
addr['netmask'])) continue
cidr = network.cidr cidr = network.cidr
if address in cidr: if address in cidr:
if key == 'iface': if key == 'iface':
@ -191,7 +209,6 @@ def _get_for_address(address, key):
return str(cidr).split('/')[1] return str(cidr).split('/')[1]
else: else:
return addr[key] return addr[key]
return None return None
@ -222,6 +239,16 @@ def format_ipv6_addr(address):
return None return None
def is_ipv6_disabled():
try:
result = subprocess.check_output(
['sysctl', 'net.ipv6.conf.all.disable_ipv6'],
stderr=subprocess.STDOUT)
return "net.ipv6.conf.all.disable_ipv6 = 1" in result
except subprocess.CalledProcessError:
return True
def get_iface_addr(iface='eth0', inet_type='AF_INET', inc_aliases=False, def get_iface_addr(iface='eth0', inet_type='AF_INET', inc_aliases=False,
fatal=True, exc_list=None): fatal=True, exc_list=None):
"""Return the assigned IP address for a given interface, if any. """Return the assigned IP address for a given interface, if any.
@ -521,36 +548,44 @@ def port_has_listener(address, port):
def assert_charm_supports_ipv6(): def assert_charm_supports_ipv6():
"""Check whether we are able to support charms ipv6.""" """Check whether we are able to support charms ipv6."""
if lsb_release()['DISTRIB_CODENAME'].lower() < "trusty": release = lsb_release()['DISTRIB_CODENAME'].lower()
if CompareHostReleases(release) < "trusty":
raise Exception("IPv6 is not supported in the charms for Ubuntu " raise Exception("IPv6 is not supported in the charms for Ubuntu "
"versions less than Trusty 14.04") "versions less than Trusty 14.04")
def get_relation_ip(interface, config_override=None): def get_relation_ip(interface, cidr_network=None):
"""Return this unit's IP for the given relation. """Return this unit's IP for the given interface.
Allow for an arbitrary interface to use with network-get to select an IP. Allow for an arbitrary interface to use with network-get to select an IP.
Handle all address selection options including configuration parameter Handle all address selection options including passed cidr network and
override and IPv6. IPv6.
Usage: get_relation_ip('amqp', config_override='access-network') Usage: get_relation_ip('amqp', cidr_network='10.0.0.0/8')
@param interface: string name of the relation. @param interface: string name of the relation.
@param config_override: string name of the config option for network @param cidr_network: string CIDR Network to select an address from.
override. Supports legacy network override configuration parameters.
@raises Exception if prefer-ipv6 is configured but IPv6 unsupported. @raises Exception if prefer-ipv6 is configured but IPv6 unsupported.
@returns IPv6 or IPv4 address @returns IPv6 or IPv4 address
""" """
# Select the interface address first
# For possible use as a fallback bellow with get_address_in_network
try:
# Get the interface specific IP
address = network_get_primary_address(interface)
except NotImplementedError:
# If network-get is not available
address = get_host_ip(unit_get('private-address'))
fallback = get_host_ip(unit_get('private-address'))
if config('prefer-ipv6'): if config('prefer-ipv6'):
# Currently IPv6 has priority, eventually we want IPv6 to just be
# another network space.
assert_charm_supports_ipv6() assert_charm_supports_ipv6()
return get_ipv6_addr()[0] return get_ipv6_addr()[0]
elif config_override and config(config_override): elif cidr_network:
return get_address_in_network(config(config_override), # If a specific CIDR network is passed get the address from that
fallback) # network.
else: return get_address_in_network(cidr_network, address)
try:
return network_get_primary_address(interface) # Return the interface address
except NotImplementedError: return address
return fallback

View File

@ -40,6 +40,7 @@ from charmhelpers.contrib.amulet.utils import (
AmuletUtils AmuletUtils
) )
from charmhelpers.core.decorators import retry_on_exception from charmhelpers.core.decorators import retry_on_exception
from charmhelpers.core.host import CompareHostReleases
DEBUG = logging.DEBUG DEBUG = logging.DEBUG
ERROR = logging.ERROR ERROR = logging.ERROR
@ -546,7 +547,7 @@ class OpenStackAmuletUtils(AmuletUtils):
"""Create the specified instance.""" """Create the specified instance."""
self.log.debug('Creating instance ' self.log.debug('Creating instance '
'({}|{}|{})'.format(instance_name, image_name, flavor)) '({}|{}|{})'.format(instance_name, image_name, flavor))
image = nova.images.find(name=image_name) image = nova.glance.find_image(image_name)
flavor = nova.flavors.find(name=flavor) flavor = nova.flavors.find(name=flavor)
instance = nova.servers.create(name=instance_name, image=image, instance = nova.servers.create(name=instance_name, image=image,
flavor=flavor) flavor=flavor)
@ -1255,7 +1256,7 @@ class OpenStackAmuletUtils(AmuletUtils):
contents = self.file_contents_safe(sentry_unit, '/etc/memcached.conf', contents = self.file_contents_safe(sentry_unit, '/etc/memcached.conf',
fatal=True) fatal=True)
ubuntu_release, _ = self.run_cmd_unit(sentry_unit, 'lsb_release -cs') 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' memcache_listen_addr = 'ip6-localhost'
else: else:
memcache_listen_addr = '::1' memcache_listen_addr = '::1'

View File

@ -59,6 +59,8 @@ from charmhelpers.core.host import (
write_file, write_file,
pwgen, pwgen,
lsb_release, lsb_release,
CompareHostReleases,
is_container,
) )
from charmhelpers.contrib.hahelpers.cluster import ( from charmhelpers.contrib.hahelpers.cluster import (
determine_apache_port, determine_apache_port,
@ -87,6 +89,7 @@ from charmhelpers.contrib.network.ip import (
format_ipv6_addr, format_ipv6_addr,
is_address_in_network, is_address_in_network,
is_bridge_member, is_bridge_member,
is_ipv6_disabled,
) )
from charmhelpers.contrib.openstack.utils import ( from charmhelpers.contrib.openstack.utils import (
config_flags_parser, config_flags_parser,
@ -108,6 +111,7 @@ except ImportError:
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt' CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
ADDRESS_TYPES = ['admin', 'internal', 'public'] ADDRESS_TYPES = ['admin', 'internal', 'public']
HAPROXY_RUN_DIR = '/var/run/haproxy/'
def ensure_packages(packages): def ensure_packages(packages):
@ -155,7 +159,8 @@ class OSContextGenerator(object):
if self.missing_data: if self.missing_data:
self.complete = False 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: else:
self.complete = True self.complete = True
return self.complete return self.complete
@ -213,7 +218,8 @@ class SharedDBContext(OSContextGenerator):
hostname_key = "{}_hostname".format(self.relation_prefix) hostname_key = "{}_hostname".format(self.relation_prefix)
else: else:
hostname_key = "hostname" hostname_key = "hostname"
access_hostname = get_address_in_network(access_network, access_hostname = get_address_in_network(
access_network,
unit_get('private-address')) unit_get('private-address'))
set_hostname = relation_get(attribute=hostname_key, set_hostname = relation_get(attribute=hostname_key,
unit=local_unit()) unit=local_unit())
@ -308,7 +314,10 @@ def db_ssl(rdata, ctxt, ssl_dir):
class IdentityServiceContext(OSContextGenerator): 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 = service
self.service_user = service_user self.service_user = service_user
self.rel_name = rel_name self.rel_name = rel_name
@ -457,19 +466,17 @@ class AMQPContext(OSContextGenerator):
host = format_ipv6_addr(host) or host host = format_ipv6_addr(host) or host
rabbitmq_hosts.append(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 transport_hosts = rabbitmq_hosts
if transport_hosts: if transport_hosts:
transport_url_hosts = '' transport_url_hosts = ','.join([
for host in transport_hosts: "{}:{}@{}:{}".format(ctxt['rabbitmq_user'],
if transport_url_hosts: ctxt['rabbitmq_password'],
format_string = ",{}:{}@{}:{}" host_,
else: rabbitmq_port)
format_string = "{}:{}@{}:{}" for host_ in transport_hosts])
transport_url_hosts += format_string.format(
ctxt['rabbitmq_user'], ctxt['rabbitmq_password'],
host, rabbitmq_port)
ctxt['transport_url'] = "rabbit://{}/{}".format( ctxt['transport_url'] = "rabbit://{}/{}".format(
transport_url_hosts, vhost) transport_url_hosts, vhost)
@ -530,6 +537,8 @@ class HAProxyContext(OSContextGenerator):
"""Provides half a context for the haproxy template, which describes """Provides half a context for the haproxy template, which describes
all peers to be included in the cluster. Each charm needs to include all peers to be included in the cluster. Each charm needs to include
its own context generator that describes the port mapping. its own context generator that describes the port mapping.
:side effect: mkdir is called on HAPROXY_RUN_DIR
""" """
interfaces = ['cluster'] interfaces = ['cluster']
@ -537,6 +546,8 @@ class HAProxyContext(OSContextGenerator):
self.singlenode_mode = singlenode_mode self.singlenode_mode = singlenode_mode
def __call__(self): def __call__(self):
if not os.path.isdir(HAPROXY_RUN_DIR):
mkdir(path=HAPROXY_RUN_DIR)
if not relation_ids('cluster') and not self.singlenode_mode: if not relation_ids('cluster') and not self.singlenode_mode:
return {} return {}
@ -1217,22 +1228,54 @@ class BindHostContext(OSContextGenerator):
return {'bind_host': '0.0.0.0'} return {'bind_host': '0.0.0.0'}
class WorkerConfigContext(OSContextGenerator): MAX_DEFAULT_WORKERS = 4
DEFAULT_MULTIPLIER = 2
@property
def num_cpus(self):
# NOTE: use cpu_count if present (16.04 support)
if hasattr(psutil, 'cpu_count'):
return psutil.cpu_count()
else:
return psutil.NUM_CPUS
def __call__(self): def _calculate_workers():
multiplier = config('worker-multiplier') or 0 '''
count = int(self.num_cpus * multiplier) Determine the number of worker processes based on the CPU
count of the unit containing the application.
Workers will be limited to MAX_DEFAULT_WORKERS in
container environments where no worker-multipler configuration
option been set.
@returns int: number of worker processes to use
'''
multiplier = config('worker-multiplier') or DEFAULT_MULTIPLIER
count = int(_num_cpus() * multiplier)
if multiplier > 0 and count == 0: if multiplier > 0 and count == 0:
count = 1 count = 1
ctxt = {"workers": count}
if config('worker-multiplier') is None and is_container():
# NOTE(jamespage): Limit unconfigured worker-multiplier
# to MAX_DEFAULT_WORKERS to avoid insane
# worker configuration in LXD containers
# on large servers
# Reference: https://pad.lv/1665270
count = min(count, MAX_DEFAULT_WORKERS)
return count
def _num_cpus():
'''
Compatibility wrapper for calculating the number of CPU's
a unit has.
@returns: int: number of CPU cores detected
'''
try:
return psutil.cpu_count()
except AttributeError:
return psutil.NUM_CPUS
class WorkerConfigContext(OSContextGenerator):
def __call__(self):
ctxt = {"workers": _calculate_workers()}
return ctxt return ctxt
@ -1240,7 +1283,7 @@ class WSGIWorkerConfigContext(WorkerConfigContext):
def __init__(self, name=None, script=None, admin_script=None, def __init__(self, name=None, script=None, admin_script=None,
public_script=None, process_weight=1.00, public_script=None, process_weight=1.00,
admin_process_weight=0.75, public_process_weight=0.25): admin_process_weight=0.25, public_process_weight=0.75):
self.service_name = name self.service_name = name
self.user = name self.user = name
self.group = name self.group = name
@ -1252,8 +1295,7 @@ class WSGIWorkerConfigContext(WorkerConfigContext):
self.public_process_weight = public_process_weight self.public_process_weight = public_process_weight
def __call__(self): def __call__(self):
multiplier = config('worker-multiplier') or 1 total_processes = _calculate_workers()
total_processes = self.num_cpus * multiplier
ctxt = { ctxt = {
"service_name": self.service_name, "service_name": self.service_name,
"user": self.user, "user": self.user,
@ -1584,7 +1626,7 @@ class MemcacheContext(OSContextGenerator):
"""Memcache context """Memcache context
This context provides options for configuring a local memcache client and This context provides options for configuring a local memcache client and
server server for both IPv4 and IPv6
""" """
def __init__(self, package=None): def __init__(self, package=None):
@ -1601,7 +1643,19 @@ class MemcacheContext(OSContextGenerator):
if ctxt['use_memcache']: if ctxt['use_memcache']:
# Trusty version of memcached does not support ::1 as a listen # Trusty version of memcached does not support ::1 as a listen
# address so use host file entry instead # address so use host file entry instead
if lsb_release()['DISTRIB_CODENAME'].lower() > 'trusty': release = lsb_release()['DISTRIB_CODENAME'].lower()
if is_ipv6_disabled():
if CompareHostReleases(release) > 'trusty':
ctxt['memcache_server'] = '127.0.0.1'
else:
ctxt['memcache_server'] = 'localhost'
ctxt['memcache_server_formatted'] = '127.0.0.1'
ctxt['memcache_port'] = '11211'
ctxt['memcache_url'] = '{}:{}'.format(
ctxt['memcache_server_formatted'],
ctxt['memcache_port'])
else:
if CompareHostReleases(release) > 'trusty':
ctxt['memcache_server'] = '::1' ctxt['memcache_server'] = '::1'
else: else:
ctxt['memcache_server'] = 'ip6-localhost' ctxt['memcache_server'] = 'ip6-localhost'

View File

@ -23,7 +23,10 @@ from charmhelpers.core.hookenv import (
ERROR, ERROR,
) )
from charmhelpers.contrib.openstack.utils import os_release from charmhelpers.contrib.openstack.utils import (
os_release,
CompareOpenStackReleases,
)
def headers_package(): def headers_package():
@ -198,7 +201,8 @@ def neutron_plugins():
}, },
'plumgrid': { 'plumgrid': {
'config': '/etc/neutron/plugins/plumgrid/plumgrid.ini', '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': [ 'contexts': [
context.SharedDBContext(user=config('database-user'), context.SharedDBContext(user=config('database-user'),
database=config('database'), database=config('database'),
@ -225,7 +229,7 @@ def neutron_plugins():
'server_services': ['neutron-server'] 'server_services': ['neutron-server']
} }
} }
if release >= 'icehouse': if CompareOpenStackReleases(release) >= 'icehouse':
# NOTE: patch in ml2 plugin for icehouse onwards # NOTE: patch in ml2 plugin for icehouse onwards
plugins['ovs']['config'] = '/etc/neutron/plugins/ml2/ml2_conf.ini' plugins['ovs']['config'] = '/etc/neutron/plugins/ml2/ml2_conf.ini'
plugins['ovs']['driver'] = 'neutron.plugins.ml2.plugin.Ml2Plugin' plugins['ovs']['driver'] = 'neutron.plugins.ml2.plugin.Ml2Plugin'
@ -233,10 +237,10 @@ def neutron_plugins():
'neutron-plugin-ml2'] 'neutron-plugin-ml2']
# NOTE: patch in vmware renames nvp->nsx for icehouse onwards # NOTE: patch in vmware renames nvp->nsx for icehouse onwards
plugins['nvp'] = plugins['nsx'] plugins['nvp'] = plugins['nsx']
if release >= 'kilo': if CompareOpenStackReleases(release) >= 'kilo':
plugins['midonet']['driver'] = ( plugins['midonet']['driver'] = (
'neutron.plugins.midonet.plugin.MidonetPluginV2') 'neutron.plugins.midonet.plugin.MidonetPluginV2')
if release >= 'liberty': if CompareOpenStackReleases(release) >= 'liberty':
plugins['midonet']['driver'] = ( plugins['midonet']['driver'] = (
'midonet.neutron.plugin_v1.MidonetPluginV2') 'midonet.neutron.plugin_v1.MidonetPluginV2')
plugins['midonet']['server_packages'].remove( plugins['midonet']['server_packages'].remove(
@ -244,10 +248,11 @@ def neutron_plugins():
plugins['midonet']['server_packages'].append( plugins['midonet']['server_packages'].append(
'python-networking-midonet') 'python-networking-midonet')
plugins['plumgrid']['driver'] = ( plugins['plumgrid']['driver'] = (
'networking_plumgrid.neutron.plugins.plugin.NeutronPluginPLUMgridV2') 'networking_plumgrid.neutron.plugins'
'.plugin.NeutronPluginPLUMgridV2')
plugins['plumgrid']['server_packages'].remove( plugins['plumgrid']['server_packages'].remove(
'neutron-plugin-plumgrid') 'neutron-plugin-plumgrid')
if release >= 'mitaka': if CompareOpenStackReleases(release) >= 'mitaka':
plugins['nsx']['server_packages'].remove('neutron-plugin-vmware') plugins['nsx']['server_packages'].remove('neutron-plugin-vmware')
plugins['nsx']['server_packages'].append('python-vmware-nsx') plugins['nsx']['server_packages'].append('python-vmware-nsx')
plugins['nsx']['config'] = '/etc/neutron/nsx.ini' plugins['nsx']['config'] = '/etc/neutron/nsx.ini'

View File

@ -5,6 +5,8 @@ global
user haproxy user haproxy
group haproxy group haproxy
spread-checks 0 spread-checks 0
stats socket /var/run/haproxy/admin.sock mode 600 level admin
stats timeout 2m
defaults defaults
log global log global
@ -58,6 +60,15 @@ frontend tcp-in_{{ service }}
{% for frontend in frontends -%} {% for frontend in frontends -%}
backend {{ service }}_{{ frontend }} backend {{ service }}_{{ frontend }}
balance leastconn balance leastconn
{% if backend_options -%}
{% if backend_options[service] -%}
{% for option in backend_options[service] -%}
{% for key, value in option.items() -%}
{{ key }} {{ value }}
{% endfor -%}
{% endfor -%}
{% endif -%}
{% endif -%}
{% for unit, address in frontends[frontend]['backends'].items() -%} {% for unit, address in frontends[frontend]['backends'].items() -%}
server {{ unit }} {{ address }}:{{ ports[1] }} check server {{ unit }} {{ address }}:{{ ports[1] }} check
{% endfor %} {% endfor %}

View File

@ -33,9 +33,7 @@ import yaml
from charmhelpers.contrib.network import ip from charmhelpers.contrib.network import ip
from charmhelpers.core import ( from charmhelpers.core import unitdata
unitdata,
)
from charmhelpers.core.hookenv import ( from charmhelpers.core.hookenv import (
action_fail, action_fail,
@ -55,6 +53,8 @@ from charmhelpers.core.hookenv import (
application_version_set, application_version_set,
) )
from charmhelpers.core.strutils import BasicStringComparator
from charmhelpers.contrib.storage.linux.lvm import ( from charmhelpers.contrib.storage.linux.lvm import (
deactivate_lvm_volume_group, deactivate_lvm_volume_group,
is_lvm_physical_volume, is_lvm_physical_volume,
@ -97,6 +97,22 @@ CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA'
DISTRO_PROPOSED = ('deb http://archive.ubuntu.com/ubuntu/ %s-proposed ' DISTRO_PROPOSED = ('deb http://archive.ubuntu.com/ubuntu/ %s-proposed '
'restricted main multiverse universe') 'restricted main multiverse universe')
OPENSTACK_RELEASES = (
'diablo',
'essex',
'folsom',
'grizzly',
'havana',
'icehouse',
'juno',
'kilo',
'liberty',
'mitaka',
'newton',
'ocata',
'pike',
)
UBUNTU_OPENSTACK_RELEASE = OrderedDict([ UBUNTU_OPENSTACK_RELEASE = OrderedDict([
('oneiric', 'diablo'), ('oneiric', 'diablo'),
('precise', 'essex'), ('precise', 'essex'),
@ -238,6 +254,17 @@ GIT_DEFAULT_BRANCHES = {
DEFAULT_LOOPBACK_SIZE = '5G' 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): def error_out(msg):
juju_log("FATAL ERROR: %s" % msg, level='ERROR') juju_log("FATAL ERROR: %s" % msg, level='ERROR')
sys.exit(1) sys.exit(1)
@ -1066,7 +1093,8 @@ def git_generate_systemd_init_files(templates_dir):
shutil.copyfile(init_in_source, init_source) shutil.copyfile(init_in_source, init_source)
with open(init_source, 'a') as outfile: 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: with open(template) as infile:
outfile.write('\n\n{}'.format(infile.read())) outfile.write('\n\n{}'.format(infile.read()))
@ -1971,9 +1999,7 @@ def enable_memcache(source=None, release=None, package=None):
if not _release: if not _release:
_release = get_os_codename_install_source(source) _release = get_os_codename_install_source(source)
# TODO: this should be changed to a numeric comparison using a known list return CompareOpenStackReleases(_release) >= 'mitaka'
# of releases and comparing by index.
return _release >= 'mitaka'
def token_cache_pkgs(source=None, release=None): def token_cache_pkgs(source=None, release=None):

View File

@ -987,13 +987,15 @@ def ensure_ceph_storage(service, pool, rbd_img, sizemb, mount_point,
service_start(svc) service_start(svc)
def ensure_ceph_keyring(service, user=None, group=None, relation='ceph'): def ensure_ceph_keyring(service, user=None, group=None,
relation='ceph', key=None):
"""Ensures a ceph keyring is created for a named service and optionally """Ensures a ceph keyring is created for a named service and optionally
ensures user and group ownership. ensures user and group ownership.
Returns False if no ceph key is available in relation state. @returns boolean: Flag to indicate whether a key was successfully written
to disk based on either relation data or a supplied key
""" """
key = None if not key:
for rid in relation_ids(relation): for rid in relation_ids(relation):
for unit in related_units(rid): for unit in related_units(rid):
key = relation_get('key', rid=rid, unit=unit) key = relation_get('key', rid=rid, unit=unit)

View File

@ -45,6 +45,7 @@ if __platform__ == "ubuntu":
add_new_group, add_new_group,
lsb_release, lsb_release,
cmp_pkgrevno, cmp_pkgrevno,
CompareHostReleases,
) # flake8: noqa -- ignore F401 for this import ) # flake8: noqa -- ignore F401 for this import
elif __platform__ == "centos": elif __platform__ == "centos":
from charmhelpers.core.host_factory.centos import ( from charmhelpers.core.host_factory.centos import (
@ -52,6 +53,7 @@ elif __platform__ == "centos":
add_new_group, add_new_group,
lsb_release, lsb_release,
cmp_pkgrevno, cmp_pkgrevno,
CompareHostReleases,
) # flake8: noqa -- ignore F401 for this import ) # flake8: noqa -- ignore F401 for this import
UPDATEDB_PATH = '/etc/updatedb.conf' UPDATEDB_PATH = '/etc/updatedb.conf'
@ -189,7 +191,7 @@ def service_pause(service_name, init_dir="/etc/init", initd_dir="/etc/init.d",
upstart_file = os.path.join(init_dir, "{}.conf".format(service_name)) upstart_file = os.path.join(init_dir, "{}.conf".format(service_name))
sysv_file = os.path.join(initd_dir, service_name) sysv_file = os.path.join(initd_dir, service_name)
if init_is_systemd(): if init_is_systemd():
service('disable', service_name) service('mask', service_name)
elif os.path.exists(upstart_file): elif os.path.exists(upstart_file):
override_path = os.path.join( override_path = os.path.join(
init_dir, '{}.override'.format(service_name)) init_dir, '{}.override'.format(service_name))
@ -222,7 +224,7 @@ def service_resume(service_name, init_dir="/etc/init",
upstart_file = os.path.join(init_dir, "{}.conf".format(service_name)) upstart_file = os.path.join(init_dir, "{}.conf".format(service_name))
sysv_file = os.path.join(initd_dir, service_name) sysv_file = os.path.join(initd_dir, service_name)
if init_is_systemd(): if init_is_systemd():
service('enable', service_name) service('unmask', service_name)
elif os.path.exists(upstart_file): elif os.path.exists(upstart_file):
override_path = os.path.join( override_path = os.path.join(
init_dir, '{}.override'.format(service_name)) init_dir, '{}.override'.format(service_name))

View File

@ -2,6 +2,22 @@ import subprocess
import yum import yum
import os 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): def service_available(service_name):
# """Determine whether a system service is available.""" # """Determine whether a system service is available."""

View File

@ -1,5 +1,37 @@
import subprocess 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): def service_available(service_name):
"""Determine whether a system service is available""" """Determine whether a system service is available"""

View File

@ -68,3 +68,56 @@ def bytes_from_string(value):
msg = "Unable to interpret string value '%s' as bytes" % (value) msg = "Unable to interpret string value '%s' as bytes" % (value)
raise ValueError(msg) raise ValueError(msg)
return int(matches.group(1)) * (1024 ** BYTE_POWER[matches.group(2)]) 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]

View File

@ -35,7 +35,8 @@ from charmhelpers.core.host import (
mount, mount,
fstab_add, fstab_add,
service_restart, service_restart,
lsb_release lsb_release,
CompareHostReleases,
) )
from charmhelpers.core.hookenv import ( from charmhelpers.core.hookenv import (
@ -513,7 +514,8 @@ def save_script_rc():
def assert_charm_supports_ipv6(): def assert_charm_supports_ipv6():
"""Check whether we are able to support charms ipv6.""" """Check whether we are able to support charms ipv6."""
if lsb_release()['DISTRIB_CODENAME'].lower() < "trusty": _release = lsb_release()['DISTRIB_CODENAME'].lower()
if CompareHostReleases(_release) < "trusty":
raise Exception("IPv6 is not supported in the charms for Ubuntu " raise Exception("IPv6 is not supported in the charms for Ubuntu "
"versions less than Trusty 14.04") "versions less than Trusty 14.04")

View File

@ -324,7 +324,7 @@ class SwiftStorageBasicDeployment(OpenStackAmuletDeployment):
'DEFAULT': { 'DEFAULT': {
'bind_ip': '0.0.0.0', 'bind_ip': '0.0.0.0',
'bind_port': '6002', 'bind_port': '6002',
'workers': '1' 'workers': u.not_null,
}, },
'pipeline:main': { 'pipeline:main': {
'pipeline': 'recon account-server' 'pipeline': 'recon account-server'
@ -353,7 +353,7 @@ class SwiftStorageBasicDeployment(OpenStackAmuletDeployment):
'DEFAULT': { 'DEFAULT': {
'bind_ip': '0.0.0.0', 'bind_ip': '0.0.0.0',
'bind_port': '6001', 'bind_port': '6001',
'workers': '1' 'workers': u.not_null,
}, },
'pipeline:main': { 'pipeline:main': {
'pipeline': 'recon container-server' 'pipeline': 'recon container-server'
@ -383,7 +383,7 @@ class SwiftStorageBasicDeployment(OpenStackAmuletDeployment):
'DEFAULT': { 'DEFAULT': {
'bind_ip': '0.0.0.0', 'bind_ip': '0.0.0.0',
'bind_port': '6000', 'bind_port': '6000',
'workers': '1' 'workers': u.not_null,
}, },
'pipeline:main': { 'pipeline:main': {
'pipeline': 'recon object-server' 'pipeline': 'recon object-server'

View File

@ -40,6 +40,7 @@ from charmhelpers.contrib.amulet.utils import (
AmuletUtils AmuletUtils
) )
from charmhelpers.core.decorators import retry_on_exception from charmhelpers.core.decorators import retry_on_exception
from charmhelpers.core.host import CompareHostReleases
DEBUG = logging.DEBUG DEBUG = logging.DEBUG
ERROR = logging.ERROR ERROR = logging.ERROR
@ -546,7 +547,7 @@ class OpenStackAmuletUtils(AmuletUtils):
"""Create the specified instance.""" """Create the specified instance."""
self.log.debug('Creating instance ' self.log.debug('Creating instance '
'({}|{}|{})'.format(instance_name, image_name, flavor)) '({}|{}|{})'.format(instance_name, image_name, flavor))
image = nova.images.find(name=image_name) image = nova.glance.find_image(image_name)
flavor = nova.flavors.find(name=flavor) flavor = nova.flavors.find(name=flavor)
instance = nova.servers.create(name=instance_name, image=image, instance = nova.servers.create(name=instance_name, image=image,
flavor=flavor) flavor=flavor)
@ -1255,7 +1256,7 @@ class OpenStackAmuletUtils(AmuletUtils):
contents = self.file_contents_safe(sentry_unit, '/etc/memcached.conf', contents = self.file_contents_safe(sentry_unit, '/etc/memcached.conf',
fatal=True) fatal=True)
ubuntu_release, _ = self.run_cmd_unit(sentry_unit, 'lsb_release -cs') 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' memcache_listen_addr = 'ip6-localhost'
else: else:
memcache_listen_addr = '::1' memcache_listen_addr = '::1'

View File

@ -45,6 +45,7 @@ if __platform__ == "ubuntu":
add_new_group, add_new_group,
lsb_release, lsb_release,
cmp_pkgrevno, cmp_pkgrevno,
CompareHostReleases,
) # flake8: noqa -- ignore F401 for this import ) # flake8: noqa -- ignore F401 for this import
elif __platform__ == "centos": elif __platform__ == "centos":
from charmhelpers.core.host_factory.centos import ( from charmhelpers.core.host_factory.centos import (
@ -52,6 +53,7 @@ elif __platform__ == "centos":
add_new_group, add_new_group,
lsb_release, lsb_release,
cmp_pkgrevno, cmp_pkgrevno,
CompareHostReleases,
) # flake8: noqa -- ignore F401 for this import ) # flake8: noqa -- ignore F401 for this import
UPDATEDB_PATH = '/etc/updatedb.conf' UPDATEDB_PATH = '/etc/updatedb.conf'
@ -189,7 +191,7 @@ def service_pause(service_name, init_dir="/etc/init", initd_dir="/etc/init.d",
upstart_file = os.path.join(init_dir, "{}.conf".format(service_name)) upstart_file = os.path.join(init_dir, "{}.conf".format(service_name))
sysv_file = os.path.join(initd_dir, service_name) sysv_file = os.path.join(initd_dir, service_name)
if init_is_systemd(): if init_is_systemd():
service('disable', service_name) service('mask', service_name)
elif os.path.exists(upstart_file): elif os.path.exists(upstart_file):
override_path = os.path.join( override_path = os.path.join(
init_dir, '{}.override'.format(service_name)) init_dir, '{}.override'.format(service_name))
@ -222,7 +224,7 @@ def service_resume(service_name, init_dir="/etc/init",
upstart_file = os.path.join(init_dir, "{}.conf".format(service_name)) upstart_file = os.path.join(init_dir, "{}.conf".format(service_name))
sysv_file = os.path.join(initd_dir, service_name) sysv_file = os.path.join(initd_dir, service_name)
if init_is_systemd(): if init_is_systemd():
service('enable', service_name) service('unmask', service_name)
elif os.path.exists(upstart_file): elif os.path.exists(upstart_file):
override_path = os.path.join( override_path = os.path.join(
init_dir, '{}.override'.format(service_name)) init_dir, '{}.override'.format(service_name))

View File

@ -2,6 +2,22 @@ import subprocess
import yum import yum
import os 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): def service_available(service_name):
# """Determine whether a system service is available.""" # """Determine whether a system service is available."""

View File

@ -1,5 +1,37 @@
import subprocess 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): def service_available(service_name):
"""Determine whether a system service is available""" """Determine whether a system service is available"""

View File

@ -68,3 +68,56 @@ def bytes_from_string(value):
msg = "Unable to interpret string value '%s' as bytes" % (value) msg = "Unable to interpret string value '%s' as bytes" % (value)
raise ValueError(msg) raise ValueError(msg)
return int(matches.group(1)) * (1024 ** BYTE_POWER[matches.group(2)]) 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]

View File

@ -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))

View File

@ -14,7 +14,7 @@ install_command =
pip install --allow-unverified python-apt {opts} {packages} pip install --allow-unverified python-apt {opts} {packages}
commands = ostestr {posargs} commands = ostestr {posargs}
whitelist_externals = juju whitelist_externals = juju
passenv = HOME TERM AMULET_* passenv = HOME TERM AMULET_* CS_API_*
[testenv:py27] [testenv:py27]
basepython = python2.7 basepython = python2.7