Merge lp:~corey.bryant/charms/trusty/swift-proxy/amulet-updates into lp:~openstack-charmers-archive/charms/trusty/swift-proxy/next

Proposed by Corey Bryant
Status: Merged
Merged at revision: 63
Proposed branch: lp:~corey.bryant/charms/trusty/swift-proxy/amulet-updates
Merge into: lp:~openstack-charmers-archive/charms/trusty/swift-proxy/next
Diff against target: 1215 lines (+584/-137)
21 files modified
Makefile (+2/-1)
hooks/charmhelpers/contrib/hahelpers/apache.py (+10/-3)
hooks/charmhelpers/contrib/hahelpers/cluster.py (+1/-2)
hooks/charmhelpers/contrib/network/ip.py (+100/-16)
hooks/charmhelpers/contrib/openstack/amulet/deployment.py (+38/-8)
hooks/charmhelpers/contrib/openstack/amulet/utils.py (+5/-4)
hooks/charmhelpers/contrib/openstack/context.py (+75/-24)
hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg (+9/-0)
hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend (+9/-8)
hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend.conf (+9/-8)
hooks/charmhelpers/core/hookenv.py (+17/-4)
hooks/charmhelpers/core/host.py (+30/-5)
hooks/charmhelpers/core/services/helpers.py (+119/-5)
hooks/charmhelpers/fetch/__init__.py (+19/-5)
hooks/charmhelpers/fetch/archiveurl.py (+49/-4)
tests/00-setup (+5/-5)
tests/README (+6/-0)
tests/basic_deployment.py (+19/-10)
tests/charmhelpers/contrib/amulet/deployment.py (+19/-13)
tests/charmhelpers/contrib/openstack/amulet/deployment.py (+38/-8)
tests/charmhelpers/contrib/openstack/amulet/utils.py (+5/-4)
To merge this branch: bzr merge lp:~corey.bryant/charms/trusty/swift-proxy/amulet-updates
Reviewer Review Type Date Requested Status
OpenStack Charmers Pending
Review via email: mp+236516@code.launchpad.net
To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'Makefile'
--- Makefile 2014-08-13 13:13:06 +0000
+++ Makefile 2014-09-30 13:38:11 +0000
@@ -15,7 +15,8 @@
15 # coreycb note: The -v should only be temporary until Amulet sends15 # coreycb note: The -v should only be temporary until Amulet sends
16 # raise_status() messages to stderr:16 # raise_status() messages to stderr:
17 # https://bugs.launchpad.net/amulet/+bug/132035717 # https://bugs.launchpad.net/amulet/+bug/1320357
18 @juju test -v -p AMULET_HTTP_PROXY18 @juju test -v -p AMULET_HTTP_PROXY --timeout 900 \
19 00-setup 14-basic-precise-icehouse 15-basic-trusty-icehouse
1920
20bin/charm_helpers_sync.py:21bin/charm_helpers_sync.py:
21 @mkdir -p bin22 @mkdir -p bin
2223
=== modified file 'hooks/charmhelpers/contrib/hahelpers/apache.py'
--- hooks/charmhelpers/contrib/hahelpers/apache.py 2014-03-27 11:23:24 +0000
+++ hooks/charmhelpers/contrib/hahelpers/apache.py 2014-09-30 13:38:11 +0000
@@ -20,20 +20,27 @@
20)20)
2121
2222
23def get_cert():23def get_cert(cn=None):
24 # TODO: deal with multiple https endpoints via charm config
24 cert = config_get('ssl_cert')25 cert = config_get('ssl_cert')
25 key = config_get('ssl_key')26 key = config_get('ssl_key')
26 if not (cert and key):27 if not (cert and key):
27 log("Inspecting identity-service relations for SSL certificate.",28 log("Inspecting identity-service relations for SSL certificate.",
28 level=INFO)29 level=INFO)
29 cert = key = None30 cert = key = None
31 if cn:
32 ssl_cert_attr = 'ssl_cert_{}'.format(cn)
33 ssl_key_attr = 'ssl_key_{}'.format(cn)
34 else:
35 ssl_cert_attr = 'ssl_cert'
36 ssl_key_attr = 'ssl_key'
30 for r_id in relation_ids('identity-service'):37 for r_id in relation_ids('identity-service'):
31 for unit in relation_list(r_id):38 for unit in relation_list(r_id):
32 if not cert:39 if not cert:
33 cert = relation_get('ssl_cert',40 cert = relation_get(ssl_cert_attr,
34 rid=r_id, unit=unit)41 rid=r_id, unit=unit)
35 if not key:42 if not key:
36 key = relation_get('ssl_key',43 key = relation_get(ssl_key_attr,
37 rid=r_id, unit=unit)44 rid=r_id, unit=unit)
38 return (cert, key)45 return (cert, key)
3946
4047
=== modified file 'hooks/charmhelpers/contrib/hahelpers/cluster.py'
--- hooks/charmhelpers/contrib/hahelpers/cluster.py 2014-08-13 13:13:06 +0000
+++ hooks/charmhelpers/contrib/hahelpers/cluster.py 2014-09-30 13:38:11 +0000
@@ -139,10 +139,9 @@
139 return True139 return True
140 for r_id in relation_ids('identity-service'):140 for r_id in relation_ids('identity-service'):
141 for unit in relation_list(r_id):141 for unit in relation_list(r_id):
142 # TODO - needs fixing for new helper as ssl_cert/key suffixes with CN
142 rel_state = [143 rel_state = [
143 relation_get('https_keystone', rid=r_id, unit=unit),144 relation_get('https_keystone', rid=r_id, unit=unit),
144 relation_get('ssl_cert', rid=r_id, unit=unit),
145 relation_get('ssl_key', rid=r_id, unit=unit),
146 relation_get('ca_cert', rid=r_id, unit=unit),145 relation_get('ca_cert', rid=r_id, unit=unit),
147 ]146 ]
148 # NOTE: works around (LP: #1203241)147 # NOTE: works around (LP: #1203241)
149148
=== modified file 'hooks/charmhelpers/contrib/network/ip.py'
--- hooks/charmhelpers/contrib/network/ip.py 2014-08-13 13:13:06 +0000
+++ hooks/charmhelpers/contrib/network/ip.py 2014-09-30 13:38:11 +0000
@@ -1,10 +1,11 @@
1import glob
1import sys2import sys
23
3from functools import partial4from functools import partial
45
5from charmhelpers.fetch import apt_install6from charmhelpers.fetch import apt_install
6from charmhelpers.core.hookenv import (7from charmhelpers.core.hookenv import (
7 ERROR, log, config,8 ERROR, log,
8)9)
910
10try:11try:
@@ -156,19 +157,102 @@
156get_netmask_for_address = partial(_get_for_address, key='netmask')157get_netmask_for_address = partial(_get_for_address, key='netmask')
157158
158159
159def get_ipv6_addr(iface="eth0"):160def format_ipv6_addr(address):
161 """
162 IPv6 needs to be wrapped with [] in url link to parse correctly.
163 """
164 if is_ipv6(address):
165 address = "[%s]" % address
166 else:
167 log("Not an valid ipv6 address: %s" % address,
168 level=ERROR)
169 address = None
170 return address
171
172
173def get_iface_addr(iface='eth0', inet_type='AF_INET', inc_aliases=False, fatal=True, exc_list=None):
174 """
175 Return the assigned IP address for a given interface, if any, or [].
176 """
177 # Extract nic if passed /dev/ethX
178 if '/' in iface:
179 iface = iface.split('/')[-1]
180 if not exc_list:
181 exc_list = []
160 try:182 try:
161 iface_addrs = netifaces.ifaddresses(iface)183 inet_num = getattr(netifaces, inet_type)
162 if netifaces.AF_INET6 not in iface_addrs:184 except AttributeError:
163 raise Exception("Interface '%s' doesn't have an ipv6 address." % iface)185 raise Exception('Unknown inet type ' + str(inet_type))
164186
165 addresses = netifaces.ifaddresses(iface)[netifaces.AF_INET6]187 interfaces = netifaces.interfaces()
166 ipv6_addr = [a['addr'] for a in addresses if not a['addr'].startswith('fe80')188 if inc_aliases:
167 and config('vip') != a['addr']]189 ifaces = []
168 if not ipv6_addr:190 for _iface in interfaces:
169 raise Exception("Interface '%s' doesn't have global ipv6 address." % iface)191 if iface == _iface or _iface.split(':')[0] == iface:
170192 ifaces.append(_iface)
171 return ipv6_addr[0]193 if fatal and not ifaces:
172194 raise Exception("Invalid interface '%s'" % iface)
173 except ValueError:195 ifaces.sort()
174 raise ValueError("Invalid interface '%s'" % iface)196 else:
197 if iface not in interfaces:
198 if fatal:
199 raise Exception("%s not found " % (iface))
200 else:
201 return []
202 else:
203 ifaces = [iface]
204
205 addresses = []
206 for netiface in ifaces:
207 net_info = netifaces.ifaddresses(netiface)
208 if inet_num in net_info:
209 for entry in net_info[inet_num]:
210 if 'addr' in entry and entry['addr'] not in exc_list:
211 addresses.append(entry['addr'])
212 if fatal and not addresses:
213 raise Exception("Interface '%s' doesn't have any %s addresses." % (iface, inet_type))
214 return addresses
215
216get_ipv4_addr = partial(get_iface_addr, inet_type='AF_INET')
217
218
219def get_ipv6_addr(iface='eth0', inc_aliases=False, fatal=True, exc_list=None):
220 """
221 Return the assigned IPv6 address for a given interface, if any, or [].
222 """
223 addresses = get_iface_addr(iface=iface, inet_type='AF_INET6',
224 inc_aliases=inc_aliases, fatal=fatal,
225 exc_list=exc_list)
226 remotly_addressable = []
227 for address in addresses:
228 if not address.startswith('fe80'):
229 remotly_addressable.append(address)
230 if fatal and not remotly_addressable:
231 raise Exception("Interface '%s' doesn't have global ipv6 address." % iface)
232 return remotly_addressable
233
234
235def get_bridges(vnic_dir='/sys/devices/virtual/net'):
236 """
237 Return a list of bridges on the system or []
238 """
239 b_rgex = vnic_dir + '/*/bridge'
240 return [x.replace(vnic_dir, '').split('/')[1] for x in glob.glob(b_rgex)]
241
242
243def get_bridge_nics(bridge, vnic_dir='/sys/devices/virtual/net'):
244 """
245 Return a list of nics comprising a given bridge on the system or []
246 """
247 brif_rgex = "%s/%s/brif/*" % (vnic_dir, bridge)
248 return [x.split('/')[-1] for x in glob.glob(brif_rgex)]
249
250
251def is_bridge_member(nic):
252 """
253 Check if a given nic is a member of a bridge
254 """
255 for bridge in get_bridges():
256 if nic in get_bridge_nics(bridge):
257 return True
258 return False
175259
=== modified file 'hooks/charmhelpers/contrib/openstack/amulet/deployment.py'
--- hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2014-07-30 10:06:23 +0000
+++ hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2014-09-30 13:38:11 +0000
@@ -10,32 +10,62 @@
10 that is specifically for use by OpenStack charms.10 that is specifically for use by OpenStack charms.
11 """11 """
1212
13 def __init__(self, series=None, openstack=None, source=None):13 def __init__(self, series=None, openstack=None, source=None, stable=True):
14 """Initialize the deployment environment."""14 """Initialize the deployment environment."""
15 super(OpenStackAmuletDeployment, self).__init__(series)15 super(OpenStackAmuletDeployment, self).__init__(series)
16 self.openstack = openstack16 self.openstack = openstack
17 self.source = source17 self.source = source
18 self.stable = stable
19 # Note(coreycb): this needs to be changed when new next branches come
20 # out.
21 self.current_next = "trusty"
22
23 def _determine_branch_locations(self, other_services):
24 """Determine the branch locations for the other services.
25
26 Determine if the local branch being tested is derived from its
27 stable or next (dev) branch, and based on this, use the corresonding
28 stable or next branches for the other_services."""
29 base_charms = ['mysql', 'mongodb', 'rabbitmq-server']
30
31 if self.stable:
32 for svc in other_services:
33 temp = 'lp:charms/{}'
34 svc['location'] = temp.format(svc['name'])
35 else:
36 for svc in other_services:
37 if svc['name'] in base_charms:
38 temp = 'lp:charms/{}'
39 svc['location'] = temp.format(svc['name'])
40 else:
41 temp = 'lp:~openstack-charmers/charms/{}/{}/next'
42 svc['location'] = temp.format(self.current_next,
43 svc['name'])
44 return other_services
1845
19 def _add_services(self, this_service, other_services):46 def _add_services(self, this_service, other_services):
20 """Add services to the deployment and set openstack-origin."""47 """Add services to the deployment and set openstack-origin/source."""
48 other_services = self._determine_branch_locations(other_services)
49
21 super(OpenStackAmuletDeployment, self)._add_services(this_service,50 super(OpenStackAmuletDeployment, self)._add_services(this_service,
22 other_services)51 other_services)
23 name = 052
24 services = other_services53 services = other_services
25 services.append(this_service)54 services.append(this_service)
26 use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph']55 use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph',
56 'ceph-osd', 'ceph-radosgw']
2757
28 if self.openstack:58 if self.openstack:
29 for svc in services:59 for svc in services:
30 if svc[name] not in use_source:60 if svc['name'] not in use_source:
31 config = {'openstack-origin': self.openstack}61 config = {'openstack-origin': self.openstack}
32 self.d.configure(svc[name], config)62 self.d.configure(svc['name'], config)
3363
34 if self.source:64 if self.source:
35 for svc in services:65 for svc in services:
36 if svc[name] in use_source:66 if svc['name'] in use_source:
37 config = {'source': self.source}67 config = {'source': self.source}
38 self.d.configure(svc[name], config)68 self.d.configure(svc['name'], config)
3969
40 def _configure_services(self, configs):70 def _configure_services(self, configs):
41 """Configure all of the services."""71 """Configure all of the services."""
4272
=== modified file 'hooks/charmhelpers/contrib/openstack/amulet/utils.py'
--- hooks/charmhelpers/contrib/openstack/amulet/utils.py 2014-07-30 10:06:23 +0000
+++ hooks/charmhelpers/contrib/openstack/amulet/utils.py 2014-09-30 13:38:11 +0000
@@ -187,15 +187,16 @@
187187
188 f = opener.open("http://download.cirros-cloud.net/version/released")188 f = opener.open("http://download.cirros-cloud.net/version/released")
189 version = f.read().strip()189 version = f.read().strip()
190 cirros_img = "tests/cirros-{}-x86_64-disk.img".format(version)190 cirros_img = "cirros-{}-x86_64-disk.img".format(version)
191 local_path = os.path.join('tests', cirros_img)
191192
192 if not os.path.exists(cirros_img):193 if not os.path.exists(local_path):
193 cirros_url = "http://{}/{}/{}".format("download.cirros-cloud.net",194 cirros_url = "http://{}/{}/{}".format("download.cirros-cloud.net",
194 version, cirros_img)195 version, cirros_img)
195 opener.retrieve(cirros_url, cirros_img)196 opener.retrieve(cirros_url, local_path)
196 f.close()197 f.close()
197198
198 with open(cirros_img) as f:199 with open(local_path) as f:
199 image = glance.images.create(name=image_name, is_public=True,200 image = glance.images.create(name=image_name, is_public=True,
200 disk_format='qcow2',201 disk_format='qcow2',
201 container_format='bare', data=f)202 container_format='bare', data=f)
202203
=== modified file 'hooks/charmhelpers/contrib/openstack/context.py'
--- hooks/charmhelpers/contrib/openstack/context.py 2014-08-13 13:13:06 +0000
+++ hooks/charmhelpers/contrib/openstack/context.py 2014-09-30 13:38:11 +0000
@@ -8,7 +8,6 @@
8 check_call8 check_call
9)9)
1010
11
12from charmhelpers.fetch import (11from charmhelpers.fetch import (
13 apt_install,12 apt_install,
14 filter_installed_packages,13 filter_installed_packages,
@@ -28,6 +27,11 @@
28 INFO27 INFO
29)28)
3029
30from charmhelpers.core.host import (
31 mkdir,
32 write_file
33)
34
31from charmhelpers.contrib.hahelpers.cluster import (35from charmhelpers.contrib.hahelpers.cluster import (
32 determine_apache_port,36 determine_apache_port,
33 determine_api_port,37 determine_api_port,
@@ -38,6 +42,7 @@
38from charmhelpers.contrib.hahelpers.apache import (42from charmhelpers.contrib.hahelpers.apache import (
39 get_cert,43 get_cert,
40 get_ca_cert,44 get_ca_cert,
45 install_ca_cert,
41)46)
4247
43from charmhelpers.contrib.openstack.neutron import (48from charmhelpers.contrib.openstack.neutron import (
@@ -47,6 +52,7 @@
47from charmhelpers.contrib.network.ip import (52from charmhelpers.contrib.network.ip import (
48 get_address_in_network,53 get_address_in_network,
49 get_ipv6_addr,54 get_ipv6_addr,
55 is_address_in_network
50)56)
5157
52CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'58CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
@@ -421,6 +427,11 @@
421 'units': cluster_hosts,427 'units': cluster_hosts,
422 }428 }
423429
430 if config('haproxy-server-timeout'):
431 ctxt['haproxy_server_timeout'] = config('haproxy-server-timeout')
432 if config('haproxy-client-timeout'):
433 ctxt['haproxy_client_timeout'] = config('haproxy-client-timeout')
434
424 if config('prefer-ipv6'):435 if config('prefer-ipv6'):
425 ctxt['local_host'] = 'ip6-localhost'436 ctxt['local_host'] = 'ip6-localhost'
426 ctxt['haproxy_host'] = '::'437 ctxt['haproxy_host'] = '::'
@@ -490,22 +501,36 @@
490 cmd = ['a2enmod', 'ssl', 'proxy', 'proxy_http']501 cmd = ['a2enmod', 'ssl', 'proxy', 'proxy_http']
491 check_call(cmd)502 check_call(cmd)
492503
493 def configure_cert(self):504 def configure_cert(self, cn=None):
494 if not os.path.isdir('/etc/apache2/ssl'):
495 os.mkdir('/etc/apache2/ssl')
496 ssl_dir = os.path.join('/etc/apache2/ssl/', self.service_namespace)505 ssl_dir = os.path.join('/etc/apache2/ssl/', self.service_namespace)
497 if not os.path.isdir(ssl_dir):506 mkdir(path=ssl_dir)
498 os.mkdir(ssl_dir)507 cert, key = get_cert(cn)
499 cert, key = get_cert()508 if cn:
500 with open(os.path.join(ssl_dir, 'cert'), 'w') as cert_out:509 cert_filename = 'cert_{}'.format(cn)
501 cert_out.write(b64decode(cert))510 key_filename = 'key_{}'.format(cn)
502 with open(os.path.join(ssl_dir, 'key'), 'w') as key_out:511 else:
503 key_out.write(b64decode(key))512 cert_filename = 'cert'
513 key_filename = 'key'
514 write_file(path=os.path.join(ssl_dir, cert_filename),
515 content=b64decode(cert))
516 write_file(path=os.path.join(ssl_dir, key_filename),
517 content=b64decode(key))
518
519 def configure_ca(self):
504 ca_cert = get_ca_cert()520 ca_cert = get_ca_cert()
505 if ca_cert:521 if ca_cert:
506 with open(CA_CERT_PATH, 'w') as ca_out:522 install_ca_cert(b64decode(ca_cert))
507 ca_out.write(b64decode(ca_cert))523
508 check_call(['update-ca-certificates'])524 def canonical_names(self):
525 '''Figure out which canonical names clients will access this service'''
526 cns = []
527 for r_id in relation_ids('identity-service'):
528 for unit in related_units(r_id):
529 rdata = relation_get(rid=r_id, unit=unit)
530 for k in rdata:
531 if k.startswith('ssl_key_'):
532 cns.append(k.lstrip('ssl_key_'))
533 return list(set(cns))
509534
510 def __call__(self):535 def __call__(self):
511 if isinstance(self.external_ports, basestring):536 if isinstance(self.external_ports, basestring):
@@ -513,21 +538,47 @@
513 if (not self.external_ports or not https()):538 if (not self.external_ports or not https()):
514 return {}539 return {}
515540
516 self.configure_cert()541 self.configure_ca()
517 self.enable_modules()542 self.enable_modules()
518543
519 ctxt = {544 ctxt = {
520 'namespace': self.service_namespace,545 'namespace': self.service_namespace,
521 'private_address': unit_get('private-address'),546 'endpoints': [],
522 'endpoints': []547 'ext_ports': []
523 }548 }
524 if is_clustered():549
525 ctxt['private_address'] = config('vip')550 for cn in self.canonical_names():
526 for api_port in self.external_ports:551 self.configure_cert(cn)
527 ext_port = determine_apache_port(api_port)552
528 int_port = determine_api_port(api_port)553 addresses = []
529 portmap = (int(ext_port), int(int_port))554 vips = []
530 ctxt['endpoints'].append(portmap)555 if config('vip'):
556 vips = config('vip').split()
557
558 for network_type in ['os-internal-network',
559 'os-admin-network',
560 'os-public-network']:
561 address = get_address_in_network(config(network_type),
562 unit_get('private-address'))
563 if len(vips) > 0 and is_clustered():
564 for vip in vips:
565 if is_address_in_network(config(network_type),
566 vip):
567 addresses.append((address, vip))
568 break
569 elif is_clustered():
570 addresses.append((address, config('vip')))
571 else:
572 addresses.append((address, address))
573
574 for address, endpoint in set(addresses):
575 for api_port in self.external_ports:
576 ext_port = determine_apache_port(api_port)
577 int_port = determine_api_port(api_port)
578 portmap = (address, endpoint, int(ext_port), int(int_port))
579 ctxt['endpoints'].append(portmap)
580 ctxt['ext_ports'].append(int(ext_port))
581 ctxt['ext_ports'] = list(set(ctxt['ext_ports']))
531 return ctxt582 return ctxt
532583
533584
534585
=== modified file 'hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg'
--- hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg 2014-08-13 13:13:06 +0000
+++ hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg 2014-09-30 13:38:11 +0000
@@ -14,8 +14,17 @@
14 retries 314 retries 3
15 timeout queue 100015 timeout queue 1000
16 timeout connect 100016 timeout connect 1000
17{% if haproxy_client_timeout -%}
18 timeout client {{ haproxy_client_timeout }}
19{% else -%}
17 timeout client 3000020 timeout client 30000
21{% endif -%}
22
23{% if haproxy_server_timeout -%}
24 timeout server {{ haproxy_server_timeout }}
25{% else -%}
18 timeout server 3000026 timeout server 30000
27{% endif -%}
1928
20listen stats {{ stat_port }}29listen stats {{ stat_port }}
21 mode http30 mode http
2231
=== modified file 'hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend'
--- hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend 2013-09-27 12:02:37 +0000
+++ hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend 2014-09-30 13:38:11 +0000
@@ -1,16 +1,18 @@
1{% if endpoints -%}1{% if endpoints -%}
2{% for ext, int in endpoints -%}2{% for ext_port in ext_ports -%}
3Listen {{ ext }}3Listen {{ ext_port }}
4NameVirtualHost *:{{ ext }}4{% endfor -%}
5<VirtualHost *:{{ ext }}>5{% for address, endpoint, ext, int in endpoints -%}
6 ServerName {{ private_address }}6<VirtualHost {{ address }}:{{ ext }}>
7 ServerName {{ endpoint }}
7 SSLEngine on8 SSLEngine on
8 SSLCertificateFile /etc/apache2/ssl/{{ namespace }}/cert9 SSLCertificateFile /etc/apache2/ssl/{{ namespace }}/cert_{{ endpoint }}
9 SSLCertificateKeyFile /etc/apache2/ssl/{{ namespace }}/key10 SSLCertificateKeyFile /etc/apache2/ssl/{{ namespace }}/key_{{ endpoint }}
10 ProxyPass / http://localhost:{{ int }}/11 ProxyPass / http://localhost:{{ int }}/
11 ProxyPassReverse / http://localhost:{{ int }}/12 ProxyPassReverse / http://localhost:{{ int }}/
12 ProxyPreserveHost on13 ProxyPreserveHost on
13</VirtualHost>14</VirtualHost>
15{% endfor -%}
14<Proxy *>16<Proxy *>
15 Order deny,allow17 Order deny,allow
16 Allow from all18 Allow from all
@@ -19,5 +21,4 @@
19 Order allow,deny21 Order allow,deny
20 Allow from all22 Allow from all
21</Location>23</Location>
22{% endfor -%}
23{% endif -%}24{% endif -%}
2425
=== modified file 'hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend.conf'
--- hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend.conf 2013-09-27 12:02:37 +0000
+++ hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend.conf 2014-09-30 13:38:11 +0000
@@ -1,16 +1,18 @@
1{% if endpoints -%}1{% if endpoints -%}
2{% for ext, int in endpoints -%}2{% for ext_port in ext_ports -%}
3Listen {{ ext }}3Listen {{ ext_port }}
4NameVirtualHost *:{{ ext }}4{% endfor -%}
5<VirtualHost *:{{ ext }}>5{% for address, endpoint, ext, int in endpoints -%}
6 ServerName {{ private_address }}6<VirtualHost {{ address }}:{{ ext }}>
7 ServerName {{ endpoint }}
7 SSLEngine on8 SSLEngine on
8 SSLCertificateFile /etc/apache2/ssl/{{ namespace }}/cert9 SSLCertificateFile /etc/apache2/ssl/{{ namespace }}/cert_{{ endpoint }}
9 SSLCertificateKeyFile /etc/apache2/ssl/{{ namespace }}/key10 SSLCertificateKeyFile /etc/apache2/ssl/{{ namespace }}/key_{{ endpoint }}
10 ProxyPass / http://localhost:{{ int }}/11 ProxyPass / http://localhost:{{ int }}/
11 ProxyPassReverse / http://localhost:{{ int }}/12 ProxyPassReverse / http://localhost:{{ int }}/
12 ProxyPreserveHost on13 ProxyPreserveHost on
13</VirtualHost>14</VirtualHost>
15{% endfor -%}
14<Proxy *>16<Proxy *>
15 Order deny,allow17 Order deny,allow
16 Allow from all18 Allow from all
@@ -19,5 +21,4 @@
19 Order allow,deny21 Order allow,deny
20 Allow from all22 Allow from all
21</Location>23</Location>
22{% endfor -%}
23{% endif -%}24{% endif -%}
2425
=== modified file 'hooks/charmhelpers/core/hookenv.py'
--- hooks/charmhelpers/core/hookenv.py 2014-09-02 13:19:56 +0000
+++ hooks/charmhelpers/core/hookenv.py 2014-09-30 13:38:11 +0000
@@ -203,6 +203,17 @@
203 if os.path.exists(self.path):203 if os.path.exists(self.path):
204 self.load_previous()204 self.load_previous()
205205
206 def __getitem__(self, key):
207 """For regular dict lookups, check the current juju config first,
208 then the previous (saved) copy. This ensures that user-saved values
209 will be returned by a dict lookup.
210
211 """
212 try:
213 return dict.__getitem__(self, key)
214 except KeyError:
215 return (self._prev_dict or {})[key]
216
206 def load_previous(self, path=None):217 def load_previous(self, path=None):
207 """Load previous copy of config from disk.218 """Load previous copy of config from disk.
208219
@@ -475,9 +486,10 @@
475 hooks.execute(sys.argv)486 hooks.execute(sys.argv)
476 """487 """
477488
478 def __init__(self):489 def __init__(self, config_save=True):
479 super(Hooks, self).__init__()490 super(Hooks, self).__init__()
480 self._hooks = {}491 self._hooks = {}
492 self._config_save = config_save
481493
482 def register(self, name, function):494 def register(self, name, function):
483 """Register a hook"""495 """Register a hook"""
@@ -488,9 +500,10 @@
488 hook_name = os.path.basename(args[0])500 hook_name = os.path.basename(args[0])
489 if hook_name in self._hooks:501 if hook_name in self._hooks:
490 self._hooks[hook_name]()502 self._hooks[hook_name]()
491 cfg = config()503 if self._config_save:
492 if cfg.implicit_save:504 cfg = config()
493 cfg.save()505 if cfg.implicit_save:
506 cfg.save()
494 else:507 else:
495 raise UnregisteredHookError(hook_name)508 raise UnregisteredHookError(hook_name)
496509
497510
=== modified file 'hooks/charmhelpers/core/host.py'
--- hooks/charmhelpers/core/host.py 2014-08-26 13:30:43 +0000
+++ hooks/charmhelpers/core/host.py 2014-09-30 13:38:11 +0000
@@ -68,8 +68,8 @@
68 """Determine whether a system service is available"""68 """Determine whether a system service is available"""
69 try:69 try:
70 subprocess.check_output(['service', service_name, 'status'], stderr=subprocess.STDOUT)70 subprocess.check_output(['service', service_name, 'status'], stderr=subprocess.STDOUT)
71 except subprocess.CalledProcessError:71 except subprocess.CalledProcessError as e:
72 return False72 return 'unrecognized service' not in e.output
73 else:73 else:
74 return True74 return True
7575
@@ -209,10 +209,15 @@
209 return system_mounts209 return system_mounts
210210
211211
212def file_hash(path):212def file_hash(path, hash_type='md5'):
213 """Generate a md5 hash of the contents of 'path' or None if not found """213 """
214 Generate a hash checksum of the contents of 'path' or None if not found.
215
216 :param str hash_type: Any hash alrgorithm supported by :mod:`hashlib`,
217 such as md5, sha1, sha256, sha512, etc.
218 """
214 if os.path.exists(path):219 if os.path.exists(path):
215 h = hashlib.md5()220 h = getattr(hashlib, hash_type)()
216 with open(path, 'r') as source:221 with open(path, 'r') as source:
217 h.update(source.read()) # IGNORE:E1101 - it does have update222 h.update(source.read()) # IGNORE:E1101 - it does have update
218 return h.hexdigest()223 return h.hexdigest()
@@ -220,6 +225,26 @@
220 return None225 return None
221226
222227
228def check_hash(path, checksum, hash_type='md5'):
229 """
230 Validate a file using a cryptographic checksum.
231
232 :param str checksum: Value of the checksum used to validate the file.
233 :param str hash_type: Hash algorithm used to generate `checksum`.
234 Can be any hash alrgorithm supported by :mod:`hashlib`,
235 such as md5, sha1, sha256, sha512, etc.
236 :raises ChecksumError: If the file fails the checksum
237
238 """
239 actual_checksum = file_hash(path, hash_type)
240 if checksum != actual_checksum:
241 raise ChecksumError("'%s' != '%s'" % (checksum, actual_checksum))
242
243
244class ChecksumError(ValueError):
245 pass
246
247
223def restart_on_change(restart_map, stopstart=False):248def restart_on_change(restart_map, stopstart=False):
224 """Restart services based on configuration files changing249 """Restart services based on configuration files changing
225250
226251
=== modified file 'hooks/charmhelpers/core/services/helpers.py'
--- hooks/charmhelpers/core/services/helpers.py 2014-08-13 13:13:06 +0000
+++ hooks/charmhelpers/core/services/helpers.py 2014-09-30 13:38:11 +0000
@@ -1,3 +1,5 @@
1import os
2import yaml
1from charmhelpers.core import hookenv3from charmhelpers.core import hookenv
2from charmhelpers.core import templating4from charmhelpers.core import templating
35
@@ -19,15 +21,21 @@
19 the `name` attribute that are complete will used to populate the dictionary21 the `name` attribute that are complete will used to populate the dictionary
20 values (see `get_data`, below).22 values (see `get_data`, below).
2123
22 The generated context will be namespaced under the interface type, to prevent24 The generated context will be namespaced under the relation :attr:`name`,
23 potential naming conflicts.25 to prevent potential naming conflicts.
26
27 :param str name: Override the relation :attr:`name`, since it can vary from charm to charm
28 :param list additional_required_keys: Extend the list of :attr:`required_keys`
24 """29 """
25 name = None30 name = None
26 interface = None31 interface = None
27 required_keys = []32 required_keys = []
2833
29 def __init__(self, *args, **kwargs):34 def __init__(self, name=None, additional_required_keys=None):
30 super(RelationContext, self).__init__(*args, **kwargs)35 if name is not None:
36 self.name = name
37 if additional_required_keys is not None:
38 self.required_keys.extend(additional_required_keys)
31 self.get_data()39 self.get_data()
3240
33 def __bool__(self):41 def __bool__(self):
@@ -101,9 +109,115 @@
101 return {}109 return {}
102110
103111
112class MysqlRelation(RelationContext):
113 """
114 Relation context for the `mysql` interface.
115
116 :param str name: Override the relation :attr:`name`, since it can vary from charm to charm
117 :param list additional_required_keys: Extend the list of :attr:`required_keys`
118 """
119 name = 'db'
120 interface = 'mysql'
121 required_keys = ['host', 'user', 'password', 'database']
122
123
124class HttpRelation(RelationContext):
125 """
126 Relation context for the `http` interface.
127
128 :param str name: Override the relation :attr:`name`, since it can vary from charm to charm
129 :param list additional_required_keys: Extend the list of :attr:`required_keys`
130 """
131 name = 'website'
132 interface = 'http'
133 required_keys = ['host', 'port']
134
135 def provide_data(self):
136 return {
137 'host': hookenv.unit_get('private-address'),
138 'port': 80,
139 }
140
141
142class RequiredConfig(dict):
143 """
144 Data context that loads config options with one or more mandatory options.
145
146 Once the required options have been changed from their default values, all
147 config options will be available, namespaced under `config` to prevent
148 potential naming conflicts (for example, between a config option and a
149 relation property).
150
151 :param list *args: List of options that must be changed from their default values.
152 """
153
154 def __init__(self, *args):
155 self.required_options = args
156 self['config'] = hookenv.config()
157 with open(os.path.join(hookenv.charm_dir(), 'config.yaml')) as fp:
158 self.config = yaml.load(fp).get('options', {})
159
160 def __bool__(self):
161 for option in self.required_options:
162 if option not in self['config']:
163 return False
164 current_value = self['config'][option]
165 default_value = self.config[option].get('default')
166 if current_value == default_value:
167 return False
168 if current_value in (None, '') and default_value in (None, ''):
169 return False
170 return True
171
172 def __nonzero__(self):
173 return self.__bool__()
174
175
176class StoredContext(dict):
177 """
178 A data context that always returns the data that it was first created with.
179
180 This is useful to do a one-time generation of things like passwords, that
181 will thereafter use the same value that was originally generated, instead
182 of generating a new value each time it is run.
183 """
184 def __init__(self, file_name, config_data):
185 """
186 If the file exists, populate `self` with the data from the file.
187 Otherwise, populate with the given data and persist it to the file.
188 """
189 if os.path.exists(file_name):
190 self.update(self.read_context(file_name))
191 else:
192 self.store_context(file_name, config_data)
193 self.update(config_data)
194
195 def store_context(self, file_name, config_data):
196 if not os.path.isabs(file_name):
197 file_name = os.path.join(hookenv.charm_dir(), file_name)
198 with open(file_name, 'w') as file_stream:
199 os.fchmod(file_stream.fileno(), 0600)
200 yaml.dump(config_data, file_stream)
201
202 def read_context(self, file_name):
203 if not os.path.isabs(file_name):
204 file_name = os.path.join(hookenv.charm_dir(), file_name)
205 with open(file_name, 'r') as file_stream:
206 data = yaml.load(file_stream)
207 if not data:
208 raise OSError("%s is empty" % file_name)
209 return data
210
211
104class TemplateCallback(ManagerCallback):212class TemplateCallback(ManagerCallback):
105 """213 """
106 Callback class that will render a template, for use as a ready action.214 Callback class that will render a Jinja2 template, for use as a ready action.
215
216 :param str source: The template source file, relative to `$CHARM_DIR/templates`
217 :param str target: The target to write the rendered template to
218 :param str owner: The owner of the rendered file
219 :param str group: The group of the rendered file
220 :param int perms: The permissions of the rendered file
107 """221 """
108 def __init__(self, source, target, owner='root', group='root', perms=0444):222 def __init__(self, source, target, owner='root', group='root', perms=0444):
109 self.source = source223 self.source = source
110224
=== modified file 'hooks/charmhelpers/fetch/__init__.py'
--- hooks/charmhelpers/fetch/__init__.py 2014-08-26 13:30:43 +0000
+++ hooks/charmhelpers/fetch/__init__.py 2014-09-30 13:38:11 +0000
@@ -208,7 +208,8 @@
208 """Add a package source to this system.208 """Add a package source to this system.
209209
210 @param source: a URL or sources.list entry, as supported by210 @param source: a URL or sources.list entry, as supported by
211 add-apt-repository(1). Examples:211 add-apt-repository(1). Examples::
212
212 ppa:charmers/example213 ppa:charmers/example
213 deb https://stub:key@private.example.com/ubuntu trusty main214 deb https://stub:key@private.example.com/ubuntu trusty main
214215
@@ -311,22 +312,35 @@
311 apt_update(fatal=True)312 apt_update(fatal=True)
312313
313314
314def install_remote(source):315def install_remote(source, *args, **kwargs):
315 """316 """
316 Install a file tree from a remote source317 Install a file tree from a remote source
317318
318 The specified source should be a url of the form:319 The specified source should be a url of the form:
319 scheme://[host]/path[#[option=value][&...]]320 scheme://[host]/path[#[option=value][&...]]
320321
321 Schemes supported are based on this modules submodules322 Schemes supported are based on this modules submodules.
322 Options supported are submodule-specific"""323 Options supported are submodule-specific.
324 Additional arguments are passed through to the submodule.
325
326 For example::
327
328 dest = install_remote('http://example.com/archive.tgz',
329 checksum='deadbeef',
330 hash_type='sha1')
331
332 This will download `archive.tgz`, validate it using SHA1 and, if
333 the file is ok, extract it and return the directory in which it
334 was extracted. If the checksum fails, it will raise
335 :class:`charmhelpers.core.host.ChecksumError`.
336 """
323 # We ONLY check for True here because can_handle may return a string337 # We ONLY check for True here because can_handle may return a string
324 # explaining why it can't handle a given source.338 # explaining why it can't handle a given source.
325 handlers = [h for h in plugins() if h.can_handle(source) is True]339 handlers = [h for h in plugins() if h.can_handle(source) is True]
326 installed_to = None340 installed_to = None
327 for handler in handlers:341 for handler in handlers:
328 try:342 try:
329 installed_to = handler.install(source)343 installed_to = handler.install(source, *args, **kwargs)
330 except UnhandledSource:344 except UnhandledSource:
331 pass345 pass
332 if not installed_to:346 if not installed_to:
333347
=== modified file 'hooks/charmhelpers/fetch/archiveurl.py'
--- hooks/charmhelpers/fetch/archiveurl.py 2014-03-20 13:47:46 +0000
+++ hooks/charmhelpers/fetch/archiveurl.py 2014-09-30 13:38:11 +0000
@@ -1,6 +1,8 @@
1import os1import os
2import urllib22import urllib2
3from urllib import urlretrieve
3import urlparse4import urlparse
5import hashlib
46
5from charmhelpers.fetch import (7from charmhelpers.fetch import (
6 BaseFetchHandler,8 BaseFetchHandler,
@@ -10,11 +12,19 @@
10 get_archive_handler,12 get_archive_handler,
11 extract,13 extract,
12)14)
13from charmhelpers.core.host import mkdir15from charmhelpers.core.host import mkdir, check_hash
1416
1517
16class ArchiveUrlFetchHandler(BaseFetchHandler):18class ArchiveUrlFetchHandler(BaseFetchHandler):
17 """Handler for archives via generic URLs"""19 """
20 Handler to download archive files from arbitrary URLs.
21
22 Can fetch from http, https, ftp, and file URLs.
23
24 Can install either tarballs (.tar, .tgz, .tbz2, etc) or zip files.
25
26 Installs the contents of the archive in $CHARM_DIR/fetched/.
27 """
18 def can_handle(self, source):28 def can_handle(self, source):
19 url_parts = self.parse_url(source)29 url_parts = self.parse_url(source)
20 if url_parts.scheme not in ('http', 'https', 'ftp', 'file'):30 if url_parts.scheme not in ('http', 'https', 'ftp', 'file'):
@@ -24,6 +34,12 @@
24 return False34 return False
2535
26 def download(self, source, dest):36 def download(self, source, dest):
37 """
38 Download an archive file.
39
40 :param str source: URL pointing to an archive file.
41 :param str dest: Local path location to download archive file to.
42 """
27 # propogate all exceptions43 # propogate all exceptions
28 # URLError, OSError, etc44 # URLError, OSError, etc
29 proto, netloc, path, params, query, fragment = urlparse.urlparse(source)45 proto, netloc, path, params, query, fragment = urlparse.urlparse(source)
@@ -48,7 +64,30 @@
48 os.unlink(dest)64 os.unlink(dest)
49 raise e65 raise e
5066
51 def install(self, source):67 # Mandatory file validation via Sha1 or MD5 hashing.
68 def download_and_validate(self, url, hashsum, validate="sha1"):
69 tempfile, headers = urlretrieve(url)
70 check_hash(tempfile, hashsum, validate)
71 return tempfile
72
73 def install(self, source, dest=None, checksum=None, hash_type='sha1'):
74 """
75 Download and install an archive file, with optional checksum validation.
76
77 The checksum can also be given on the `source` URL's fragment.
78 For example::
79
80 handler.install('http://example.com/file.tgz#sha1=deadbeef')
81
82 :param str source: URL pointing to an archive file.
83 :param str dest: Local destination path to install to. If not given,
84 installs to `$CHARM_DIR/archives/archive_file_name`.
85 :param str checksum: If given, validate the archive file after download.
86 :param str hash_type: Algorithm used to generate `checksum`.
87 Can be any hash alrgorithm supported by :mod:`hashlib`,
88 such as md5, sha1, sha256, sha512, etc.
89
90 """
52 url_parts = self.parse_url(source)91 url_parts = self.parse_url(source)
53 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), 'fetched')92 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), 'fetched')
54 if not os.path.exists(dest_dir):93 if not os.path.exists(dest_dir):
@@ -60,4 +99,10 @@
60 raise UnhandledSource(e.reason)99 raise UnhandledSource(e.reason)
61 except OSError as e:100 except OSError as e:
62 raise UnhandledSource(e.strerror)101 raise UnhandledSource(e.strerror)
63 return extract(dld_file)102 options = urlparse.parse_qs(url_parts.fragment)
103 for key, value in options.items():
104 if key in hashlib.algorithms:
105 check_hash(dld_file, value, key)
106 if checksum:
107 check_hash(dld_file, checksum, hash_type)
108 return extract(dld_file, dest)
64109
=== modified file 'tests/00-setup'
--- tests/00-setup 2014-07-11 16:57:37 +0000
+++ tests/00-setup 2014-09-30 13:38:11 +0000
@@ -4,8 +4,8 @@
44
5sudo add-apt-repository --yes ppa:juju/stable5sudo add-apt-repository --yes ppa:juju/stable
6sudo apt-get update --yes6sudo apt-get update --yes
7sudo apt-get install --yes python-amulet7sudo apt-get install --yes python-amulet \
8sudo apt-get install --yes python-swiftclient8 python-swiftclient \
9sudo apt-get install --yes python-glanceclient9 python-glanceclient \
10sudo apt-get install --yes python-keystoneclient10 python-keystoneclient \
11sudo apt-get install --yes python-novaclient11 python-novaclient
1212
=== modified file 'tests/README'
--- tests/README 2014-07-11 16:57:37 +0000
+++ tests/README 2014-09-30 13:38:11 +0000
@@ -1,6 +1,12 @@
1This directory provides Amulet tests that focus on verification of swift-proxy1This directory provides Amulet tests that focus on verification of swift-proxy
2deployments.2deployments.
33
4In order to run tests, you'll need charm-tools installed (in addition to
5juju, of course):
6 sudo add-apt-repository ppa:juju/stable
7 sudo apt-get update
8 sudo apt-get install charm-tools
9
4If you use a web proxy server to access the web, you'll need to set the10If you use a web proxy server to access the web, you'll need to set the
5AMULET_HTTP_PROXY environment variable to the http URL of the proxy server.11AMULET_HTTP_PROXY environment variable to the http URL of the proxy server.
612
713
=== modified file 'tests/basic_deployment.py'
--- tests/basic_deployment.py 2014-07-11 16:57:37 +0000
+++ tests/basic_deployment.py 2014-09-30 13:38:11 +0000
@@ -20,10 +20,10 @@
20class SwiftProxyBasicDeployment(OpenStackAmuletDeployment):20class SwiftProxyBasicDeployment(OpenStackAmuletDeployment):
21 """Amulet tests on a basic swift-proxy deployment."""21 """Amulet tests on a basic swift-proxy deployment."""
2222
23 def __init__(self, series, openstack=None, source=None):23 def __init__(self, series, openstack=None, source=None, stable=False):
24 """Deploy the entire test environment."""24 """Deploy the entire test environment."""
25 super(SwiftProxyBasicDeployment, self).__init__(series, openstack,25 super(SwiftProxyBasicDeployment, self).__init__(series, openstack,
26 source)26 source, stable)
27 self._add_services()27 self._add_services()
28 self._add_relations()28 self._add_relations()
29 self._configure_services()29 self._configure_services()
@@ -31,12 +31,15 @@
31 self._initialize_tests()31 self._initialize_tests()
3232
33 def _add_services(self):33 def _add_services(self):
34 """Add the service that we're testing, including the number of units,34 """Add services
35 where swift-proxy is local, and the other charms are from35
36 the charm store."""36 Add the services that we're testing, where swift-proxy is local,
37 this_service = ('swift-proxy', 1)37 and the rest of the service are from lp branches that are
38 other_services = [('mysql', 1),38 compatible with the local charm (e.g. stable or next).
39 ('keystone', 1), ('glance', 1), ('swift-storage', 1)]39 """
40 this_service = {'name': 'swift-proxy'}
41 other_services = [{'name': 'mysql'}, {'name': 'keystone'},
42 {'name': 'glance'}, {'name': 'swift-storage'}]
40 super(SwiftProxyBasicDeployment, self)._add_services(this_service,43 super(SwiftProxyBasicDeployment, self)._add_services(this_service,
41 other_services)44 other_services)
4245
@@ -315,14 +318,20 @@
315 message = u.relation_error('swift-proxy object-store', ret)318 message = u.relation_error('swift-proxy object-store', ret)
316 amulet.raise_status(amulet.FAIL, msg=message)319 amulet.raise_status(amulet.FAIL, msg=message)
317320
318 def test_restart_on_config_change(self):321 def test_z_restart_on_config_change(self):
319 """Verify that the specified services are restarted when the config322 """Verify that the specified services are restarted when the config
320 is changed."""323 is changed.
324
325 Note(coreycb): The method name with the _z_ is a little odd
326 but it forces the test to run last. It just makes things
327 easier because restarting services requires re-authorization.
328 """
321 svc = 'swift-proxy'329 svc = 'swift-proxy'
322 self.d.configure('swift-proxy', {'node-timeout': '90'})330 self.d.configure('swift-proxy', {'node-timeout': '90'})
323331
324 if not u.service_restarted(self.swift_proxy_sentry, svc,332 if not u.service_restarted(self.swift_proxy_sentry, svc,
325 '/etc/swift/proxy-server.conf'):333 '/etc/swift/proxy-server.conf'):
334 self.d.configure('swift-proxy', {'node-timeout': '60'})
326 msg = "service {} didn't restart after config change".format(svc)335 msg = "service {} didn't restart after config change".format(svc)
327 amulet.raise_status(amulet.FAIL, msg=msg)336 amulet.raise_status(amulet.FAIL, msg=msg)
328337
329338
=== modified file 'tests/charmhelpers/contrib/amulet/deployment.py'
--- tests/charmhelpers/contrib/amulet/deployment.py 2014-07-30 10:06:23 +0000
+++ tests/charmhelpers/contrib/amulet/deployment.py 2014-09-30 13:38:11 +0000
@@ -24,25 +24,31 @@
24 """Add services.24 """Add services.
2525
26 Add services to the deployment where this_service is the local charm26 Add services to the deployment where this_service is the local charm
27 that we're focused on testing and other_services are the other27 that we're testing and other_services are the other services that
28 charms that come from the charm store.28 are being used in the local amulet tests.
29 """29 """
30 name, units = range(2)30 if this_service['name'] != os.path.basename(os.getcwd()):
3131 s = this_service['name']
32 if this_service[name] != os.path.basename(os.getcwd()):
33 s = this_service[name]
34 msg = "The charm's root directory name needs to be {}".format(s)32 msg = "The charm's root directory name needs to be {}".format(s)
35 amulet.raise_status(amulet.FAIL, msg=msg)33 amulet.raise_status(amulet.FAIL, msg=msg)
3634
37 self.d.add(this_service[name], units=this_service[units])35 if 'units' not in this_service:
36 this_service['units'] = 1
37
38 self.d.add(this_service['name'], units=this_service['units'])
3839
39 for svc in other_services:40 for svc in other_services:
40 if self.series:41 if 'location' in svc:
41 self.d.add(svc[name],42 branch_location = svc['location']
42 charm='cs:{}/{}'.format(self.series, svc[name]),43 elif self.series:
43 units=svc[units])44 branch_location = 'cs:{}/{}'.format(self.series, svc['name']),
44 else:45 else:
45 self.d.add(svc[name], units=svc[units])46 branch_location = None
47
48 if 'units' not in svc:
49 svc['units'] = 1
50
51 self.d.add(svc['name'], charm=branch_location, units=svc['units'])
4652
47 def _add_relations(self, relations):53 def _add_relations(self, relations):
48 """Add all of the relations for the services."""54 """Add all of the relations for the services."""
@@ -57,7 +63,7 @@
57 def _deploy(self):63 def _deploy(self):
58 """Deploy environment and wait for all hooks to finish executing."""64 """Deploy environment and wait for all hooks to finish executing."""
59 try:65 try:
60 self.d.setup()66 self.d.setup(timeout=900)
61 self.d.sentry.wait(timeout=900)67 self.d.sentry.wait(timeout=900)
62 except amulet.helpers.TimeoutError:68 except amulet.helpers.TimeoutError:
63 amulet.raise_status(amulet.FAIL, msg="Deployment timed out")69 amulet.raise_status(amulet.FAIL, msg="Deployment timed out")
6470
=== modified file 'tests/charmhelpers/contrib/openstack/amulet/deployment.py'
--- tests/charmhelpers/contrib/openstack/amulet/deployment.py 2014-07-30 10:06:23 +0000
+++ tests/charmhelpers/contrib/openstack/amulet/deployment.py 2014-09-30 13:38:11 +0000
@@ -10,32 +10,62 @@
10 that is specifically for use by OpenStack charms.10 that is specifically for use by OpenStack charms.
11 """11 """
1212
13 def __init__(self, series=None, openstack=None, source=None):13 def __init__(self, series=None, openstack=None, source=None, stable=True):
14 """Initialize the deployment environment."""14 """Initialize the deployment environment."""
15 super(OpenStackAmuletDeployment, self).__init__(series)15 super(OpenStackAmuletDeployment, self).__init__(series)
16 self.openstack = openstack16 self.openstack = openstack
17 self.source = source17 self.source = source
18 self.stable = stable
19 # Note(coreycb): this needs to be changed when new next branches come
20 # out.
21 self.current_next = "trusty"
22
23 def _determine_branch_locations(self, other_services):
24 """Determine the branch locations for the other services.
25
26 Determine if the local branch being tested is derived from its
27 stable or next (dev) branch, and based on this, use the corresonding
28 stable or next branches for the other_services."""
29 base_charms = ['mysql', 'mongodb', 'rabbitmq-server']
30
31 if self.stable:
32 for svc in other_services:
33 temp = 'lp:charms/{}'
34 svc['location'] = temp.format(svc['name'])
35 else:
36 for svc in other_services:
37 if svc['name'] in base_charms:
38 temp = 'lp:charms/{}'
39 svc['location'] = temp.format(svc['name'])
40 else:
41 temp = 'lp:~openstack-charmers/charms/{}/{}/next'
42 svc['location'] = temp.format(self.current_next,
43 svc['name'])
44 return other_services
1845
19 def _add_services(self, this_service, other_services):46 def _add_services(self, this_service, other_services):
20 """Add services to the deployment and set openstack-origin."""47 """Add services to the deployment and set openstack-origin/source."""
48 other_services = self._determine_branch_locations(other_services)
49
21 super(OpenStackAmuletDeployment, self)._add_services(this_service,50 super(OpenStackAmuletDeployment, self)._add_services(this_service,
22 other_services)51 other_services)
23 name = 052
24 services = other_services53 services = other_services
25 services.append(this_service)54 services.append(this_service)
26 use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph']55 use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph',
56 'ceph-osd', 'ceph-radosgw']
2757
28 if self.openstack:58 if self.openstack:
29 for svc in services:59 for svc in services:
30 if svc[name] not in use_source:60 if svc['name'] not in use_source:
31 config = {'openstack-origin': self.openstack}61 config = {'openstack-origin': self.openstack}
32 self.d.configure(svc[name], config)62 self.d.configure(svc['name'], config)
3363
34 if self.source:64 if self.source:
35 for svc in services:65 for svc in services:
36 if svc[name] in use_source:66 if svc['name'] in use_source:
37 config = {'source': self.source}67 config = {'source': self.source}
38 self.d.configure(svc[name], config)68 self.d.configure(svc['name'], config)
3969
40 def _configure_services(self, configs):70 def _configure_services(self, configs):
41 """Configure all of the services."""71 """Configure all of the services."""
4272
=== modified file 'tests/charmhelpers/contrib/openstack/amulet/utils.py'
--- tests/charmhelpers/contrib/openstack/amulet/utils.py 2014-07-30 10:06:23 +0000
+++ tests/charmhelpers/contrib/openstack/amulet/utils.py 2014-09-30 13:38:11 +0000
@@ -187,15 +187,16 @@
187187
188 f = opener.open("http://download.cirros-cloud.net/version/released")188 f = opener.open("http://download.cirros-cloud.net/version/released")
189 version = f.read().strip()189 version = f.read().strip()
190 cirros_img = "tests/cirros-{}-x86_64-disk.img".format(version)190 cirros_img = "cirros-{}-x86_64-disk.img".format(version)
191 local_path = os.path.join('tests', cirros_img)
191192
192 if not os.path.exists(cirros_img):193 if not os.path.exists(local_path):
193 cirros_url = "http://{}/{}/{}".format("download.cirros-cloud.net",194 cirros_url = "http://{}/{}/{}".format("download.cirros-cloud.net",
194 version, cirros_img)195 version, cirros_img)
195 opener.retrieve(cirros_url, cirros_img)196 opener.retrieve(cirros_url, local_path)
196 f.close()197 f.close()
197198
198 with open(cirros_img) as f:199 with open(local_path) as f:
199 image = glance.images.create(name=image_name, is_public=True,200 image = glance.images.create(name=image_name, is_public=True,
200 disk_format='qcow2',201 disk_format='qcow2',
201 container_format='bare', data=f)202 container_format='bare', data=f)

Subscribers

People subscribed via source and target branches