Merge lp:~corey.bryant/charms/trusty/swift-proxy/amulet-updates into lp:~openstack-charmers-archive/charms/trusty/swift-proxy/next
- Trusty Tahr (14.04)
- amulet-updates
- Merge into 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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
OpenStack Charmers | Pending | ||
Review via email:
|
Commit message
Description of the change
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
1 | === modified file 'Makefile' | |||
2 | --- Makefile 2014-08-13 13:13:06 +0000 | |||
3 | +++ Makefile 2014-09-30 13:38:11 +0000 | |||
4 | @@ -15,7 +15,8 @@ | |||
5 | 15 | # coreycb note: The -v should only be temporary until Amulet sends | 15 | # coreycb note: The -v should only be temporary until Amulet sends |
6 | 16 | # raise_status() messages to stderr: | 16 | # raise_status() messages to stderr: |
7 | 17 | # https://bugs.launchpad.net/amulet/+bug/1320357 | 17 | # https://bugs.launchpad.net/amulet/+bug/1320357 |
9 | 18 | @juju test -v -p AMULET_HTTP_PROXY | 18 | @juju test -v -p AMULET_HTTP_PROXY --timeout 900 \ |
10 | 19 | 00-setup 14-basic-precise-icehouse 15-basic-trusty-icehouse | ||
11 | 19 | 20 | ||
12 | 20 | bin/charm_helpers_sync.py: | 21 | bin/charm_helpers_sync.py: |
13 | 21 | @mkdir -p bin | 22 | @mkdir -p bin |
14 | 22 | 23 | ||
15 | === modified file 'hooks/charmhelpers/contrib/hahelpers/apache.py' | |||
16 | --- hooks/charmhelpers/contrib/hahelpers/apache.py 2014-03-27 11:23:24 +0000 | |||
17 | +++ hooks/charmhelpers/contrib/hahelpers/apache.py 2014-09-30 13:38:11 +0000 | |||
18 | @@ -20,20 +20,27 @@ | |||
19 | 20 | ) | 20 | ) |
20 | 21 | 21 | ||
21 | 22 | 22 | ||
23 | 23 | def get_cert(): | 23 | def get_cert(cn=None): |
24 | 24 | # TODO: deal with multiple https endpoints via charm config | ||
25 | 24 | cert = config_get('ssl_cert') | 25 | cert = config_get('ssl_cert') |
26 | 25 | key = config_get('ssl_key') | 26 | key = config_get('ssl_key') |
27 | 26 | if not (cert and key): | 27 | if not (cert and key): |
28 | 27 | log("Inspecting identity-service relations for SSL certificate.", | 28 | log("Inspecting identity-service relations for SSL certificate.", |
29 | 28 | level=INFO) | 29 | level=INFO) |
30 | 29 | cert = key = None | 30 | cert = key = None |
31 | 31 | if cn: | ||
32 | 32 | ssl_cert_attr = 'ssl_cert_{}'.format(cn) | ||
33 | 33 | ssl_key_attr = 'ssl_key_{}'.format(cn) | ||
34 | 34 | else: | ||
35 | 35 | ssl_cert_attr = 'ssl_cert' | ||
36 | 36 | ssl_key_attr = 'ssl_key' | ||
37 | 30 | for r_id in relation_ids('identity-service'): | 37 | for r_id in relation_ids('identity-service'): |
38 | 31 | for unit in relation_list(r_id): | 38 | for unit in relation_list(r_id): |
39 | 32 | if not cert: | 39 | if not cert: |
41 | 33 | cert = relation_get('ssl_cert', | 40 | cert = relation_get(ssl_cert_attr, |
42 | 34 | rid=r_id, unit=unit) | 41 | rid=r_id, unit=unit) |
43 | 35 | if not key: | 42 | if not key: |
45 | 36 | key = relation_get('ssl_key', | 43 | key = relation_get(ssl_key_attr, |
46 | 37 | rid=r_id, unit=unit) | 44 | rid=r_id, unit=unit) |
47 | 38 | return (cert, key) | 45 | return (cert, key) |
48 | 39 | 46 | ||
49 | 40 | 47 | ||
50 | === modified file 'hooks/charmhelpers/contrib/hahelpers/cluster.py' | |||
51 | --- hooks/charmhelpers/contrib/hahelpers/cluster.py 2014-08-13 13:13:06 +0000 | |||
52 | +++ hooks/charmhelpers/contrib/hahelpers/cluster.py 2014-09-30 13:38:11 +0000 | |||
53 | @@ -139,10 +139,9 @@ | |||
54 | 139 | return True | 139 | return True |
55 | 140 | for r_id in relation_ids('identity-service'): | 140 | for r_id in relation_ids('identity-service'): |
56 | 141 | for unit in relation_list(r_id): | 141 | for unit in relation_list(r_id): |
57 | 142 | # TODO - needs fixing for new helper as ssl_cert/key suffixes with CN | ||
58 | 142 | rel_state = [ | 143 | rel_state = [ |
59 | 143 | relation_get('https_keystone', rid=r_id, unit=unit), | 144 | relation_get('https_keystone', rid=r_id, unit=unit), |
60 | 144 | relation_get('ssl_cert', rid=r_id, unit=unit), | ||
61 | 145 | relation_get('ssl_key', rid=r_id, unit=unit), | ||
62 | 146 | relation_get('ca_cert', rid=r_id, unit=unit), | 145 | relation_get('ca_cert', rid=r_id, unit=unit), |
63 | 147 | ] | 146 | ] |
64 | 148 | # NOTE: works around (LP: #1203241) | 147 | # NOTE: works around (LP: #1203241) |
65 | 149 | 148 | ||
66 | === modified file 'hooks/charmhelpers/contrib/network/ip.py' | |||
67 | --- hooks/charmhelpers/contrib/network/ip.py 2014-08-13 13:13:06 +0000 | |||
68 | +++ hooks/charmhelpers/contrib/network/ip.py 2014-09-30 13:38:11 +0000 | |||
69 | @@ -1,10 +1,11 @@ | |||
70 | 1 | import glob | ||
71 | 1 | import sys | 2 | import sys |
72 | 2 | 3 | ||
73 | 3 | from functools import partial | 4 | from functools import partial |
74 | 4 | 5 | ||
75 | 5 | from charmhelpers.fetch import apt_install | 6 | from charmhelpers.fetch import apt_install |
76 | 6 | from charmhelpers.core.hookenv import ( | 7 | from charmhelpers.core.hookenv import ( |
78 | 7 | ERROR, log, config, | 8 | ERROR, log, |
79 | 8 | ) | 9 | ) |
80 | 9 | 10 | ||
81 | 10 | try: | 11 | try: |
82 | @@ -156,19 +157,102 @@ | |||
83 | 156 | get_netmask_for_address = partial(_get_for_address, key='netmask') | 157 | get_netmask_for_address = partial(_get_for_address, key='netmask') |
84 | 157 | 158 | ||
85 | 158 | 159 | ||
87 | 159 | def get_ipv6_addr(iface="eth0"): | 160 | def format_ipv6_addr(address): |
88 | 161 | """ | ||
89 | 162 | IPv6 needs to be wrapped with [] in url link to parse correctly. | ||
90 | 163 | """ | ||
91 | 164 | if is_ipv6(address): | ||
92 | 165 | address = "[%s]" % address | ||
93 | 166 | else: | ||
94 | 167 | log("Not an valid ipv6 address: %s" % address, | ||
95 | 168 | level=ERROR) | ||
96 | 169 | address = None | ||
97 | 170 | return address | ||
98 | 171 | |||
99 | 172 | |||
100 | 173 | def get_iface_addr(iface='eth0', inet_type='AF_INET', inc_aliases=False, fatal=True, exc_list=None): | ||
101 | 174 | """ | ||
102 | 175 | Return the assigned IP address for a given interface, if any, or []. | ||
103 | 176 | """ | ||
104 | 177 | # Extract nic if passed /dev/ethX | ||
105 | 178 | if '/' in iface: | ||
106 | 179 | iface = iface.split('/')[-1] | ||
107 | 180 | if not exc_list: | ||
108 | 181 | exc_list = [] | ||
109 | 160 | try: | 182 | try: |
124 | 161 | iface_addrs = netifaces.ifaddresses(iface) | 183 | inet_num = getattr(netifaces, inet_type) |
125 | 162 | if netifaces.AF_INET6 not in iface_addrs: | 184 | except AttributeError: |
126 | 163 | raise Exception("Interface '%s' doesn't have an ipv6 address." % iface) | 185 | raise Exception('Unknown inet type ' + str(inet_type)) |
127 | 164 | 186 | ||
128 | 165 | addresses = netifaces.ifaddresses(iface)[netifaces.AF_INET6] | 187 | interfaces = netifaces.interfaces() |
129 | 166 | ipv6_addr = [a['addr'] for a in addresses if not a['addr'].startswith('fe80') | 188 | if inc_aliases: |
130 | 167 | and config('vip') != a['addr']] | 189 | ifaces = [] |
131 | 168 | if not ipv6_addr: | 190 | for _iface in interfaces: |
132 | 169 | raise Exception("Interface '%s' doesn't have global ipv6 address." % iface) | 191 | if iface == _iface or _iface.split(':')[0] == iface: |
133 | 170 | 192 | ifaces.append(_iface) | |
134 | 171 | return ipv6_addr[0] | 193 | if fatal and not ifaces: |
135 | 172 | 194 | raise Exception("Invalid interface '%s'" % iface) | |
136 | 173 | except ValueError: | 195 | ifaces.sort() |
137 | 174 | raise ValueError("Invalid interface '%s'" % iface) | 196 | else: |
138 | 197 | if iface not in interfaces: | ||
139 | 198 | if fatal: | ||
140 | 199 | raise Exception("%s not found " % (iface)) | ||
141 | 200 | else: | ||
142 | 201 | return [] | ||
143 | 202 | else: | ||
144 | 203 | ifaces = [iface] | ||
145 | 204 | |||
146 | 205 | addresses = [] | ||
147 | 206 | for netiface in ifaces: | ||
148 | 207 | net_info = netifaces.ifaddresses(netiface) | ||
149 | 208 | if inet_num in net_info: | ||
150 | 209 | for entry in net_info[inet_num]: | ||
151 | 210 | if 'addr' in entry and entry['addr'] not in exc_list: | ||
152 | 211 | addresses.append(entry['addr']) | ||
153 | 212 | if fatal and not addresses: | ||
154 | 213 | raise Exception("Interface '%s' doesn't have any %s addresses." % (iface, inet_type)) | ||
155 | 214 | return addresses | ||
156 | 215 | |||
157 | 216 | get_ipv4_addr = partial(get_iface_addr, inet_type='AF_INET') | ||
158 | 217 | |||
159 | 218 | |||
160 | 219 | def get_ipv6_addr(iface='eth0', inc_aliases=False, fatal=True, exc_list=None): | ||
161 | 220 | """ | ||
162 | 221 | Return the assigned IPv6 address for a given interface, if any, or []. | ||
163 | 222 | """ | ||
164 | 223 | addresses = get_iface_addr(iface=iface, inet_type='AF_INET6', | ||
165 | 224 | inc_aliases=inc_aliases, fatal=fatal, | ||
166 | 225 | exc_list=exc_list) | ||
167 | 226 | remotly_addressable = [] | ||
168 | 227 | for address in addresses: | ||
169 | 228 | if not address.startswith('fe80'): | ||
170 | 229 | remotly_addressable.append(address) | ||
171 | 230 | if fatal and not remotly_addressable: | ||
172 | 231 | raise Exception("Interface '%s' doesn't have global ipv6 address." % iface) | ||
173 | 232 | return remotly_addressable | ||
174 | 233 | |||
175 | 234 | |||
176 | 235 | def get_bridges(vnic_dir='/sys/devices/virtual/net'): | ||
177 | 236 | """ | ||
178 | 237 | Return a list of bridges on the system or [] | ||
179 | 238 | """ | ||
180 | 239 | b_rgex = vnic_dir + '/*/bridge' | ||
181 | 240 | return [x.replace(vnic_dir, '').split('/')[1] for x in glob.glob(b_rgex)] | ||
182 | 241 | |||
183 | 242 | |||
184 | 243 | def get_bridge_nics(bridge, vnic_dir='/sys/devices/virtual/net'): | ||
185 | 244 | """ | ||
186 | 245 | Return a list of nics comprising a given bridge on the system or [] | ||
187 | 246 | """ | ||
188 | 247 | brif_rgex = "%s/%s/brif/*" % (vnic_dir, bridge) | ||
189 | 248 | return [x.split('/')[-1] for x in glob.glob(brif_rgex)] | ||
190 | 249 | |||
191 | 250 | |||
192 | 251 | def is_bridge_member(nic): | ||
193 | 252 | """ | ||
194 | 253 | Check if a given nic is a member of a bridge | ||
195 | 254 | """ | ||
196 | 255 | for bridge in get_bridges(): | ||
197 | 256 | if nic in get_bridge_nics(bridge): | ||
198 | 257 | return True | ||
199 | 258 | return False | ||
200 | 175 | 259 | ||
201 | === modified file 'hooks/charmhelpers/contrib/openstack/amulet/deployment.py' | |||
202 | --- hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2014-07-30 10:06:23 +0000 | |||
203 | +++ hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2014-09-30 13:38:11 +0000 | |||
204 | @@ -10,32 +10,62 @@ | |||
205 | 10 | that is specifically for use by OpenStack charms. | 10 | that is specifically for use by OpenStack charms. |
206 | 11 | """ | 11 | """ |
207 | 12 | 12 | ||
209 | 13 | def __init__(self, series=None, openstack=None, source=None): | 13 | def __init__(self, series=None, openstack=None, source=None, stable=True): |
210 | 14 | """Initialize the deployment environment.""" | 14 | """Initialize the deployment environment.""" |
211 | 15 | super(OpenStackAmuletDeployment, self).__init__(series) | 15 | super(OpenStackAmuletDeployment, self).__init__(series) |
212 | 16 | self.openstack = openstack | 16 | self.openstack = openstack |
213 | 17 | self.source = source | 17 | self.source = source |
214 | 18 | self.stable = stable | ||
215 | 19 | # Note(coreycb): this needs to be changed when new next branches come | ||
216 | 20 | # out. | ||
217 | 21 | self.current_next = "trusty" | ||
218 | 22 | |||
219 | 23 | def _determine_branch_locations(self, other_services): | ||
220 | 24 | """Determine the branch locations for the other services. | ||
221 | 25 | |||
222 | 26 | Determine if the local branch being tested is derived from its | ||
223 | 27 | stable or next (dev) branch, and based on this, use the corresonding | ||
224 | 28 | stable or next branches for the other_services.""" | ||
225 | 29 | base_charms = ['mysql', 'mongodb', 'rabbitmq-server'] | ||
226 | 30 | |||
227 | 31 | if self.stable: | ||
228 | 32 | for svc in other_services: | ||
229 | 33 | temp = 'lp:charms/{}' | ||
230 | 34 | svc['location'] = temp.format(svc['name']) | ||
231 | 35 | else: | ||
232 | 36 | for svc in other_services: | ||
233 | 37 | if svc['name'] in base_charms: | ||
234 | 38 | temp = 'lp:charms/{}' | ||
235 | 39 | svc['location'] = temp.format(svc['name']) | ||
236 | 40 | else: | ||
237 | 41 | temp = 'lp:~openstack-charmers/charms/{}/{}/next' | ||
238 | 42 | svc['location'] = temp.format(self.current_next, | ||
239 | 43 | svc['name']) | ||
240 | 44 | return other_services | ||
241 | 18 | 45 | ||
242 | 19 | def _add_services(self, this_service, other_services): | 46 | def _add_services(self, this_service, other_services): |
244 | 20 | """Add services to the deployment and set openstack-origin.""" | 47 | """Add services to the deployment and set openstack-origin/source.""" |
245 | 48 | other_services = self._determine_branch_locations(other_services) | ||
246 | 49 | |||
247 | 21 | super(OpenStackAmuletDeployment, self)._add_services(this_service, | 50 | super(OpenStackAmuletDeployment, self)._add_services(this_service, |
248 | 22 | other_services) | 51 | other_services) |
250 | 23 | name = 0 | 52 | |
251 | 24 | services = other_services | 53 | services = other_services |
252 | 25 | services.append(this_service) | 54 | services.append(this_service) |
254 | 26 | use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph'] | 55 | use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph', |
255 | 56 | 'ceph-osd', 'ceph-radosgw'] | ||
256 | 27 | 57 | ||
257 | 28 | if self.openstack: | 58 | if self.openstack: |
258 | 29 | for svc in services: | 59 | for svc in services: |
260 | 30 | if svc[name] not in use_source: | 60 | if svc['name'] not in use_source: |
261 | 31 | config = {'openstack-origin': self.openstack} | 61 | config = {'openstack-origin': self.openstack} |
263 | 32 | self.d.configure(svc[name], config) | 62 | self.d.configure(svc['name'], config) |
264 | 33 | 63 | ||
265 | 34 | if self.source: | 64 | if self.source: |
266 | 35 | for svc in services: | 65 | for svc in services: |
268 | 36 | if svc[name] in use_source: | 66 | if svc['name'] in use_source: |
269 | 37 | config = {'source': self.source} | 67 | config = {'source': self.source} |
271 | 38 | self.d.configure(svc[name], config) | 68 | self.d.configure(svc['name'], config) |
272 | 39 | 69 | ||
273 | 40 | def _configure_services(self, configs): | 70 | def _configure_services(self, configs): |
274 | 41 | """Configure all of the services.""" | 71 | """Configure all of the services.""" |
275 | 42 | 72 | ||
276 | === modified file 'hooks/charmhelpers/contrib/openstack/amulet/utils.py' | |||
277 | --- hooks/charmhelpers/contrib/openstack/amulet/utils.py 2014-07-30 10:06:23 +0000 | |||
278 | +++ hooks/charmhelpers/contrib/openstack/amulet/utils.py 2014-09-30 13:38:11 +0000 | |||
279 | @@ -187,15 +187,16 @@ | |||
280 | 187 | 187 | ||
281 | 188 | f = opener.open("http://download.cirros-cloud.net/version/released") | 188 | f = opener.open("http://download.cirros-cloud.net/version/released") |
282 | 189 | version = f.read().strip() | 189 | version = f.read().strip() |
284 | 190 | cirros_img = "tests/cirros-{}-x86_64-disk.img".format(version) | 190 | cirros_img = "cirros-{}-x86_64-disk.img".format(version) |
285 | 191 | local_path = os.path.join('tests', cirros_img) | ||
286 | 191 | 192 | ||
288 | 192 | if not os.path.exists(cirros_img): | 193 | if not os.path.exists(local_path): |
289 | 193 | cirros_url = "http://{}/{}/{}".format("download.cirros-cloud.net", | 194 | cirros_url = "http://{}/{}/{}".format("download.cirros-cloud.net", |
290 | 194 | version, cirros_img) | 195 | version, cirros_img) |
292 | 195 | opener.retrieve(cirros_url, cirros_img) | 196 | opener.retrieve(cirros_url, local_path) |
293 | 196 | f.close() | 197 | f.close() |
294 | 197 | 198 | ||
296 | 198 | with open(cirros_img) as f: | 199 | with open(local_path) as f: |
297 | 199 | image = glance.images.create(name=image_name, is_public=True, | 200 | image = glance.images.create(name=image_name, is_public=True, |
298 | 200 | disk_format='qcow2', | 201 | disk_format='qcow2', |
299 | 201 | container_format='bare', data=f) | 202 | container_format='bare', data=f) |
300 | 202 | 203 | ||
301 | === modified file 'hooks/charmhelpers/contrib/openstack/context.py' | |||
302 | --- hooks/charmhelpers/contrib/openstack/context.py 2014-08-13 13:13:06 +0000 | |||
303 | +++ hooks/charmhelpers/contrib/openstack/context.py 2014-09-30 13:38:11 +0000 | |||
304 | @@ -8,7 +8,6 @@ | |||
305 | 8 | check_call | 8 | check_call |
306 | 9 | ) | 9 | ) |
307 | 10 | 10 | ||
308 | 11 | |||
309 | 12 | from charmhelpers.fetch import ( | 11 | from charmhelpers.fetch import ( |
310 | 13 | apt_install, | 12 | apt_install, |
311 | 14 | filter_installed_packages, | 13 | filter_installed_packages, |
312 | @@ -28,6 +27,11 @@ | |||
313 | 28 | INFO | 27 | INFO |
314 | 29 | ) | 28 | ) |
315 | 30 | 29 | ||
316 | 30 | from charmhelpers.core.host import ( | ||
317 | 31 | mkdir, | ||
318 | 32 | write_file | ||
319 | 33 | ) | ||
320 | 34 | |||
321 | 31 | from charmhelpers.contrib.hahelpers.cluster import ( | 35 | from charmhelpers.contrib.hahelpers.cluster import ( |
322 | 32 | determine_apache_port, | 36 | determine_apache_port, |
323 | 33 | determine_api_port, | 37 | determine_api_port, |
324 | @@ -38,6 +42,7 @@ | |||
325 | 38 | from charmhelpers.contrib.hahelpers.apache import ( | 42 | from charmhelpers.contrib.hahelpers.apache import ( |
326 | 39 | get_cert, | 43 | get_cert, |
327 | 40 | get_ca_cert, | 44 | get_ca_cert, |
328 | 45 | install_ca_cert, | ||
329 | 41 | ) | 46 | ) |
330 | 42 | 47 | ||
331 | 43 | from charmhelpers.contrib.openstack.neutron import ( | 48 | from charmhelpers.contrib.openstack.neutron import ( |
332 | @@ -47,6 +52,7 @@ | |||
333 | 47 | from charmhelpers.contrib.network.ip import ( | 52 | from charmhelpers.contrib.network.ip import ( |
334 | 48 | get_address_in_network, | 53 | get_address_in_network, |
335 | 49 | get_ipv6_addr, | 54 | get_ipv6_addr, |
336 | 55 | is_address_in_network | ||
337 | 50 | ) | 56 | ) |
338 | 51 | 57 | ||
339 | 52 | CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt' | 58 | CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt' |
340 | @@ -421,6 +427,11 @@ | |||
341 | 421 | 'units': cluster_hosts, | 427 | 'units': cluster_hosts, |
342 | 422 | } | 428 | } |
343 | 423 | 429 | ||
344 | 430 | if config('haproxy-server-timeout'): | ||
345 | 431 | ctxt['haproxy_server_timeout'] = config('haproxy-server-timeout') | ||
346 | 432 | if config('haproxy-client-timeout'): | ||
347 | 433 | ctxt['haproxy_client_timeout'] = config('haproxy-client-timeout') | ||
348 | 434 | |||
349 | 424 | if config('prefer-ipv6'): | 435 | if config('prefer-ipv6'): |
350 | 425 | ctxt['local_host'] = 'ip6-localhost' | 436 | ctxt['local_host'] = 'ip6-localhost' |
351 | 426 | ctxt['haproxy_host'] = '::' | 437 | ctxt['haproxy_host'] = '::' |
352 | @@ -490,22 +501,36 @@ | |||
353 | 490 | cmd = ['a2enmod', 'ssl', 'proxy', 'proxy_http'] | 501 | cmd = ['a2enmod', 'ssl', 'proxy', 'proxy_http'] |
354 | 491 | check_call(cmd) | 502 | check_call(cmd) |
355 | 492 | 503 | ||
359 | 493 | def configure_cert(self): | 504 | def configure_cert(self, cn=None): |
357 | 494 | if not os.path.isdir('/etc/apache2/ssl'): | ||
358 | 495 | os.mkdir('/etc/apache2/ssl') | ||
360 | 496 | ssl_dir = os.path.join('/etc/apache2/ssl/', self.service_namespace) | 505 | ssl_dir = os.path.join('/etc/apache2/ssl/', self.service_namespace) |
368 | 497 | if not os.path.isdir(ssl_dir): | 506 | mkdir(path=ssl_dir) |
369 | 498 | os.mkdir(ssl_dir) | 507 | cert, key = get_cert(cn) |
370 | 499 | cert, key = get_cert() | 508 | if cn: |
371 | 500 | with open(os.path.join(ssl_dir, 'cert'), 'w') as cert_out: | 509 | cert_filename = 'cert_{}'.format(cn) |
372 | 501 | cert_out.write(b64decode(cert)) | 510 | key_filename = 'key_{}'.format(cn) |
373 | 502 | with open(os.path.join(ssl_dir, 'key'), 'w') as key_out: | 511 | else: |
374 | 503 | key_out.write(b64decode(key)) | 512 | cert_filename = 'cert' |
375 | 513 | key_filename = 'key' | ||
376 | 514 | write_file(path=os.path.join(ssl_dir, cert_filename), | ||
377 | 515 | content=b64decode(cert)) | ||
378 | 516 | write_file(path=os.path.join(ssl_dir, key_filename), | ||
379 | 517 | content=b64decode(key)) | ||
380 | 518 | |||
381 | 519 | def configure_ca(self): | ||
382 | 504 | ca_cert = get_ca_cert() | 520 | ca_cert = get_ca_cert() |
383 | 505 | if ca_cert: | 521 | if ca_cert: |
387 | 506 | with open(CA_CERT_PATH, 'w') as ca_out: | 522 | install_ca_cert(b64decode(ca_cert)) |
388 | 507 | ca_out.write(b64decode(ca_cert)) | 523 | |
389 | 508 | check_call(['update-ca-certificates']) | 524 | def canonical_names(self): |
390 | 525 | '''Figure out which canonical names clients will access this service''' | ||
391 | 526 | cns = [] | ||
392 | 527 | for r_id in relation_ids('identity-service'): | ||
393 | 528 | for unit in related_units(r_id): | ||
394 | 529 | rdata = relation_get(rid=r_id, unit=unit) | ||
395 | 530 | for k in rdata: | ||
396 | 531 | if k.startswith('ssl_key_'): | ||
397 | 532 | cns.append(k.lstrip('ssl_key_')) | ||
398 | 533 | return list(set(cns)) | ||
399 | 509 | 534 | ||
400 | 510 | def __call__(self): | 535 | def __call__(self): |
401 | 511 | if isinstance(self.external_ports, basestring): | 536 | if isinstance(self.external_ports, basestring): |
402 | @@ -513,21 +538,47 @@ | |||
403 | 513 | if (not self.external_ports or not https()): | 538 | if (not self.external_ports or not https()): |
404 | 514 | return {} | 539 | return {} |
405 | 515 | 540 | ||
407 | 516 | self.configure_cert() | 541 | self.configure_ca() |
408 | 517 | self.enable_modules() | 542 | self.enable_modules() |
409 | 518 | 543 | ||
410 | 519 | ctxt = { | 544 | ctxt = { |
411 | 520 | 'namespace': self.service_namespace, | 545 | 'namespace': self.service_namespace, |
414 | 521 | 'private_address': unit_get('private-address'), | 546 | 'endpoints': [], |
415 | 522 | 'endpoints': [] | 547 | 'ext_ports': [] |
416 | 523 | } | 548 | } |
424 | 524 | if is_clustered(): | 549 | |
425 | 525 | ctxt['private_address'] = config('vip') | 550 | for cn in self.canonical_names(): |
426 | 526 | for api_port in self.external_ports: | 551 | self.configure_cert(cn) |
427 | 527 | ext_port = determine_apache_port(api_port) | 552 | |
428 | 528 | int_port = determine_api_port(api_port) | 553 | addresses = [] |
429 | 529 | portmap = (int(ext_port), int(int_port)) | 554 | vips = [] |
430 | 530 | ctxt['endpoints'].append(portmap) | 555 | if config('vip'): |
431 | 556 | vips = config('vip').split() | ||
432 | 557 | |||
433 | 558 | for network_type in ['os-internal-network', | ||
434 | 559 | 'os-admin-network', | ||
435 | 560 | 'os-public-network']: | ||
436 | 561 | address = get_address_in_network(config(network_type), | ||
437 | 562 | unit_get('private-address')) | ||
438 | 563 | if len(vips) > 0 and is_clustered(): | ||
439 | 564 | for vip in vips: | ||
440 | 565 | if is_address_in_network(config(network_type), | ||
441 | 566 | vip): | ||
442 | 567 | addresses.append((address, vip)) | ||
443 | 568 | break | ||
444 | 569 | elif is_clustered(): | ||
445 | 570 | addresses.append((address, config('vip'))) | ||
446 | 571 | else: | ||
447 | 572 | addresses.append((address, address)) | ||
448 | 573 | |||
449 | 574 | for address, endpoint in set(addresses): | ||
450 | 575 | for api_port in self.external_ports: | ||
451 | 576 | ext_port = determine_apache_port(api_port) | ||
452 | 577 | int_port = determine_api_port(api_port) | ||
453 | 578 | portmap = (address, endpoint, int(ext_port), int(int_port)) | ||
454 | 579 | ctxt['endpoints'].append(portmap) | ||
455 | 580 | ctxt['ext_ports'].append(int(ext_port)) | ||
456 | 581 | ctxt['ext_ports'] = list(set(ctxt['ext_ports'])) | ||
457 | 531 | return ctxt | 582 | return ctxt |
458 | 532 | 583 | ||
459 | 533 | 584 | ||
460 | 534 | 585 | ||
461 | === modified file 'hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg' | |||
462 | --- hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg 2014-08-13 13:13:06 +0000 | |||
463 | +++ hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg 2014-09-30 13:38:11 +0000 | |||
464 | @@ -14,8 +14,17 @@ | |||
465 | 14 | retries 3 | 14 | retries 3 |
466 | 15 | timeout queue 1000 | 15 | timeout queue 1000 |
467 | 16 | timeout connect 1000 | 16 | timeout connect 1000 |
468 | 17 | {% if haproxy_client_timeout -%} | ||
469 | 18 | timeout client {{ haproxy_client_timeout }} | ||
470 | 19 | {% else -%} | ||
471 | 17 | timeout client 30000 | 20 | timeout client 30000 |
472 | 21 | {% endif -%} | ||
473 | 22 | |||
474 | 23 | {% if haproxy_server_timeout -%} | ||
475 | 24 | timeout server {{ haproxy_server_timeout }} | ||
476 | 25 | {% else -%} | ||
477 | 18 | timeout server 30000 | 26 | timeout server 30000 |
478 | 27 | {% endif -%} | ||
479 | 19 | 28 | ||
480 | 20 | listen stats {{ stat_port }} | 29 | listen stats {{ stat_port }} |
481 | 21 | mode http | 30 | mode http |
482 | 22 | 31 | ||
483 | === modified file 'hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend' | |||
484 | --- hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend 2013-09-27 12:02:37 +0000 | |||
485 | +++ hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend 2014-09-30 13:38:11 +0000 | |||
486 | @@ -1,16 +1,18 @@ | |||
487 | 1 | {% if endpoints -%} | 1 | {% if endpoints -%} |
493 | 2 | {% for ext, int in endpoints -%} | 2 | {% for ext_port in ext_ports -%} |
494 | 3 | Listen {{ ext }} | 3 | Listen {{ ext_port }} |
495 | 4 | NameVirtualHost *:{{ ext }} | 4 | {% endfor -%} |
496 | 5 | <VirtualHost *:{{ ext }}> | 5 | {% for address, endpoint, ext, int in endpoints -%} |
497 | 6 | ServerName {{ private_address }} | 6 | <VirtualHost {{ address }}:{{ ext }}> |
498 | 7 | ServerName {{ endpoint }} | ||
499 | 7 | SSLEngine on | 8 | SSLEngine on |
502 | 8 | SSLCertificateFile /etc/apache2/ssl/{{ namespace }}/cert | 9 | SSLCertificateFile /etc/apache2/ssl/{{ namespace }}/cert_{{ endpoint }} |
503 | 9 | SSLCertificateKeyFile /etc/apache2/ssl/{{ namespace }}/key | 10 | SSLCertificateKeyFile /etc/apache2/ssl/{{ namespace }}/key_{{ endpoint }} |
504 | 10 | ProxyPass / http://localhost:{{ int }}/ | 11 | ProxyPass / http://localhost:{{ int }}/ |
505 | 11 | ProxyPassReverse / http://localhost:{{ int }}/ | 12 | ProxyPassReverse / http://localhost:{{ int }}/ |
506 | 12 | ProxyPreserveHost on | 13 | ProxyPreserveHost on |
507 | 13 | </VirtualHost> | 14 | </VirtualHost> |
508 | 15 | {% endfor -%} | ||
509 | 14 | <Proxy *> | 16 | <Proxy *> |
510 | 15 | Order deny,allow | 17 | Order deny,allow |
511 | 16 | Allow from all | 18 | Allow from all |
512 | @@ -19,5 +21,4 @@ | |||
513 | 19 | Order allow,deny | 21 | Order allow,deny |
514 | 20 | Allow from all | 22 | Allow from all |
515 | 21 | </Location> | 23 | </Location> |
516 | 22 | {% endfor -%} | ||
517 | 23 | {% endif -%} | 24 | {% endif -%} |
518 | 24 | 25 | ||
519 | === modified file 'hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend.conf' | |||
520 | --- hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend.conf 2013-09-27 12:02:37 +0000 | |||
521 | +++ hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend.conf 2014-09-30 13:38:11 +0000 | |||
522 | @@ -1,16 +1,18 @@ | |||
523 | 1 | {% if endpoints -%} | 1 | {% if endpoints -%} |
529 | 2 | {% for ext, int in endpoints -%} | 2 | {% for ext_port in ext_ports -%} |
530 | 3 | Listen {{ ext }} | 3 | Listen {{ ext_port }} |
531 | 4 | NameVirtualHost *:{{ ext }} | 4 | {% endfor -%} |
532 | 5 | <VirtualHost *:{{ ext }}> | 5 | {% for address, endpoint, ext, int in endpoints -%} |
533 | 6 | ServerName {{ private_address }} | 6 | <VirtualHost {{ address }}:{{ ext }}> |
534 | 7 | ServerName {{ endpoint }} | ||
535 | 7 | SSLEngine on | 8 | SSLEngine on |
538 | 8 | SSLCertificateFile /etc/apache2/ssl/{{ namespace }}/cert | 9 | SSLCertificateFile /etc/apache2/ssl/{{ namespace }}/cert_{{ endpoint }} |
539 | 9 | SSLCertificateKeyFile /etc/apache2/ssl/{{ namespace }}/key | 10 | SSLCertificateKeyFile /etc/apache2/ssl/{{ namespace }}/key_{{ endpoint }} |
540 | 10 | ProxyPass / http://localhost:{{ int }}/ | 11 | ProxyPass / http://localhost:{{ int }}/ |
541 | 11 | ProxyPassReverse / http://localhost:{{ int }}/ | 12 | ProxyPassReverse / http://localhost:{{ int }}/ |
542 | 12 | ProxyPreserveHost on | 13 | ProxyPreserveHost on |
543 | 13 | </VirtualHost> | 14 | </VirtualHost> |
544 | 15 | {% endfor -%} | ||
545 | 14 | <Proxy *> | 16 | <Proxy *> |
546 | 15 | Order deny,allow | 17 | Order deny,allow |
547 | 16 | Allow from all | 18 | Allow from all |
548 | @@ -19,5 +21,4 @@ | |||
549 | 19 | Order allow,deny | 21 | Order allow,deny |
550 | 20 | Allow from all | 22 | Allow from all |
551 | 21 | </Location> | 23 | </Location> |
552 | 22 | {% endfor -%} | ||
553 | 23 | {% endif -%} | 24 | {% endif -%} |
554 | 24 | 25 | ||
555 | === modified file 'hooks/charmhelpers/core/hookenv.py' | |||
556 | --- hooks/charmhelpers/core/hookenv.py 2014-09-02 13:19:56 +0000 | |||
557 | +++ hooks/charmhelpers/core/hookenv.py 2014-09-30 13:38:11 +0000 | |||
558 | @@ -203,6 +203,17 @@ | |||
559 | 203 | if os.path.exists(self.path): | 203 | if os.path.exists(self.path): |
560 | 204 | self.load_previous() | 204 | self.load_previous() |
561 | 205 | 205 | ||
562 | 206 | def __getitem__(self, key): | ||
563 | 207 | """For regular dict lookups, check the current juju config first, | ||
564 | 208 | then the previous (saved) copy. This ensures that user-saved values | ||
565 | 209 | will be returned by a dict lookup. | ||
566 | 210 | |||
567 | 211 | """ | ||
568 | 212 | try: | ||
569 | 213 | return dict.__getitem__(self, key) | ||
570 | 214 | except KeyError: | ||
571 | 215 | return (self._prev_dict or {})[key] | ||
572 | 216 | |||
573 | 206 | def load_previous(self, path=None): | 217 | def load_previous(self, path=None): |
574 | 207 | """Load previous copy of config from disk. | 218 | """Load previous copy of config from disk. |
575 | 208 | 219 | ||
576 | @@ -475,9 +486,10 @@ | |||
577 | 475 | hooks.execute(sys.argv) | 486 | hooks.execute(sys.argv) |
578 | 476 | """ | 487 | """ |
579 | 477 | 488 | ||
581 | 478 | def __init__(self): | 489 | def __init__(self, config_save=True): |
582 | 479 | super(Hooks, self).__init__() | 490 | super(Hooks, self).__init__() |
583 | 480 | self._hooks = {} | 491 | self._hooks = {} |
584 | 492 | self._config_save = config_save | ||
585 | 481 | 493 | ||
586 | 482 | def register(self, name, function): | 494 | def register(self, name, function): |
587 | 483 | """Register a hook""" | 495 | """Register a hook""" |
588 | @@ -488,9 +500,10 @@ | |||
589 | 488 | hook_name = os.path.basename(args[0]) | 500 | hook_name = os.path.basename(args[0]) |
590 | 489 | if hook_name in self._hooks: | 501 | if hook_name in self._hooks: |
591 | 490 | self._hooks[hook_name]() | 502 | self._hooks[hook_name]() |
595 | 491 | cfg = config() | 503 | if self._config_save: |
596 | 492 | if cfg.implicit_save: | 504 | cfg = config() |
597 | 493 | cfg.save() | 505 | if cfg.implicit_save: |
598 | 506 | cfg.save() | ||
599 | 494 | else: | 507 | else: |
600 | 495 | raise UnregisteredHookError(hook_name) | 508 | raise UnregisteredHookError(hook_name) |
601 | 496 | 509 | ||
602 | 497 | 510 | ||
603 | === modified file 'hooks/charmhelpers/core/host.py' | |||
604 | --- hooks/charmhelpers/core/host.py 2014-08-26 13:30:43 +0000 | |||
605 | +++ hooks/charmhelpers/core/host.py 2014-09-30 13:38:11 +0000 | |||
606 | @@ -68,8 +68,8 @@ | |||
607 | 68 | """Determine whether a system service is available""" | 68 | """Determine whether a system service is available""" |
608 | 69 | try: | 69 | try: |
609 | 70 | subprocess.check_output(['service', service_name, 'status'], stderr=subprocess.STDOUT) | 70 | subprocess.check_output(['service', service_name, 'status'], stderr=subprocess.STDOUT) |
612 | 71 | except subprocess.CalledProcessError: | 71 | except subprocess.CalledProcessError as e: |
613 | 72 | return False | 72 | return 'unrecognized service' not in e.output |
614 | 73 | else: | 73 | else: |
615 | 74 | return True | 74 | return True |
616 | 75 | 75 | ||
617 | @@ -209,10 +209,15 @@ | |||
618 | 209 | return system_mounts | 209 | return system_mounts |
619 | 210 | 210 | ||
620 | 211 | 211 | ||
623 | 212 | def file_hash(path): | 212 | def file_hash(path, hash_type='md5'): |
624 | 213 | """Generate a md5 hash of the contents of 'path' or None if not found """ | 213 | """ |
625 | 214 | Generate a hash checksum of the contents of 'path' or None if not found. | ||
626 | 215 | |||
627 | 216 | :param str hash_type: Any hash alrgorithm supported by :mod:`hashlib`, | ||
628 | 217 | such as md5, sha1, sha256, sha512, etc. | ||
629 | 218 | """ | ||
630 | 214 | if os.path.exists(path): | 219 | if os.path.exists(path): |
632 | 215 | h = hashlib.md5() | 220 | h = getattr(hashlib, hash_type)() |
633 | 216 | with open(path, 'r') as source: | 221 | with open(path, 'r') as source: |
634 | 217 | h.update(source.read()) # IGNORE:E1101 - it does have update | 222 | h.update(source.read()) # IGNORE:E1101 - it does have update |
635 | 218 | return h.hexdigest() | 223 | return h.hexdigest() |
636 | @@ -220,6 +225,26 @@ | |||
637 | 220 | return None | 225 | return None |
638 | 221 | 226 | ||
639 | 222 | 227 | ||
640 | 228 | def check_hash(path, checksum, hash_type='md5'): | ||
641 | 229 | """ | ||
642 | 230 | Validate a file using a cryptographic checksum. | ||
643 | 231 | |||
644 | 232 | :param str checksum: Value of the checksum used to validate the file. | ||
645 | 233 | :param str hash_type: Hash algorithm used to generate `checksum`. | ||
646 | 234 | Can be any hash alrgorithm supported by :mod:`hashlib`, | ||
647 | 235 | such as md5, sha1, sha256, sha512, etc. | ||
648 | 236 | :raises ChecksumError: If the file fails the checksum | ||
649 | 237 | |||
650 | 238 | """ | ||
651 | 239 | actual_checksum = file_hash(path, hash_type) | ||
652 | 240 | if checksum != actual_checksum: | ||
653 | 241 | raise ChecksumError("'%s' != '%s'" % (checksum, actual_checksum)) | ||
654 | 242 | |||
655 | 243 | |||
656 | 244 | class ChecksumError(ValueError): | ||
657 | 245 | pass | ||
658 | 246 | |||
659 | 247 | |||
660 | 223 | def restart_on_change(restart_map, stopstart=False): | 248 | def restart_on_change(restart_map, stopstart=False): |
661 | 224 | """Restart services based on configuration files changing | 249 | """Restart services based on configuration files changing |
662 | 225 | 250 | ||
663 | 226 | 251 | ||
664 | === modified file 'hooks/charmhelpers/core/services/helpers.py' | |||
665 | --- hooks/charmhelpers/core/services/helpers.py 2014-08-13 13:13:06 +0000 | |||
666 | +++ hooks/charmhelpers/core/services/helpers.py 2014-09-30 13:38:11 +0000 | |||
667 | @@ -1,3 +1,5 @@ | |||
668 | 1 | import os | ||
669 | 2 | import yaml | ||
670 | 1 | from charmhelpers.core import hookenv | 3 | from charmhelpers.core import hookenv |
671 | 2 | from charmhelpers.core import templating | 4 | from charmhelpers.core import templating |
672 | 3 | 5 | ||
673 | @@ -19,15 +21,21 @@ | |||
674 | 19 | the `name` attribute that are complete will used to populate the dictionary | 21 | the `name` attribute that are complete will used to populate the dictionary |
675 | 20 | values (see `get_data`, below). | 22 | values (see `get_data`, below). |
676 | 21 | 23 | ||
679 | 22 | The generated context will be namespaced under the interface type, to prevent | 24 | The generated context will be namespaced under the relation :attr:`name`, |
680 | 23 | potential naming conflicts. | 25 | to prevent potential naming conflicts. |
681 | 26 | |||
682 | 27 | :param str name: Override the relation :attr:`name`, since it can vary from charm to charm | ||
683 | 28 | :param list additional_required_keys: Extend the list of :attr:`required_keys` | ||
684 | 24 | """ | 29 | """ |
685 | 25 | name = None | 30 | name = None |
686 | 26 | interface = None | 31 | interface = None |
687 | 27 | required_keys = [] | 32 | required_keys = [] |
688 | 28 | 33 | ||
691 | 29 | def __init__(self, *args, **kwargs): | 34 | def __init__(self, name=None, additional_required_keys=None): |
692 | 30 | super(RelationContext, self).__init__(*args, **kwargs) | 35 | if name is not None: |
693 | 36 | self.name = name | ||
694 | 37 | if additional_required_keys is not None: | ||
695 | 38 | self.required_keys.extend(additional_required_keys) | ||
696 | 31 | self.get_data() | 39 | self.get_data() |
697 | 32 | 40 | ||
698 | 33 | def __bool__(self): | 41 | def __bool__(self): |
699 | @@ -101,9 +109,115 @@ | |||
700 | 101 | return {} | 109 | return {} |
701 | 102 | 110 | ||
702 | 103 | 111 | ||
703 | 112 | class MysqlRelation(RelationContext): | ||
704 | 113 | """ | ||
705 | 114 | Relation context for the `mysql` interface. | ||
706 | 115 | |||
707 | 116 | :param str name: Override the relation :attr:`name`, since it can vary from charm to charm | ||
708 | 117 | :param list additional_required_keys: Extend the list of :attr:`required_keys` | ||
709 | 118 | """ | ||
710 | 119 | name = 'db' | ||
711 | 120 | interface = 'mysql' | ||
712 | 121 | required_keys = ['host', 'user', 'password', 'database'] | ||
713 | 122 | |||
714 | 123 | |||
715 | 124 | class HttpRelation(RelationContext): | ||
716 | 125 | """ | ||
717 | 126 | Relation context for the `http` interface. | ||
718 | 127 | |||
719 | 128 | :param str name: Override the relation :attr:`name`, since it can vary from charm to charm | ||
720 | 129 | :param list additional_required_keys: Extend the list of :attr:`required_keys` | ||
721 | 130 | """ | ||
722 | 131 | name = 'website' | ||
723 | 132 | interface = 'http' | ||
724 | 133 | required_keys = ['host', 'port'] | ||
725 | 134 | |||
726 | 135 | def provide_data(self): | ||
727 | 136 | return { | ||
728 | 137 | 'host': hookenv.unit_get('private-address'), | ||
729 | 138 | 'port': 80, | ||
730 | 139 | } | ||
731 | 140 | |||
732 | 141 | |||
733 | 142 | class RequiredConfig(dict): | ||
734 | 143 | """ | ||
735 | 144 | Data context that loads config options with one or more mandatory options. | ||
736 | 145 | |||
737 | 146 | Once the required options have been changed from their default values, all | ||
738 | 147 | config options will be available, namespaced under `config` to prevent | ||
739 | 148 | potential naming conflicts (for example, between a config option and a | ||
740 | 149 | relation property). | ||
741 | 150 | |||
742 | 151 | :param list *args: List of options that must be changed from their default values. | ||
743 | 152 | """ | ||
744 | 153 | |||
745 | 154 | def __init__(self, *args): | ||
746 | 155 | self.required_options = args | ||
747 | 156 | self['config'] = hookenv.config() | ||
748 | 157 | with open(os.path.join(hookenv.charm_dir(), 'config.yaml')) as fp: | ||
749 | 158 | self.config = yaml.load(fp).get('options', {}) | ||
750 | 159 | |||
751 | 160 | def __bool__(self): | ||
752 | 161 | for option in self.required_options: | ||
753 | 162 | if option not in self['config']: | ||
754 | 163 | return False | ||
755 | 164 | current_value = self['config'][option] | ||
756 | 165 | default_value = self.config[option].get('default') | ||
757 | 166 | if current_value == default_value: | ||
758 | 167 | return False | ||
759 | 168 | if current_value in (None, '') and default_value in (None, ''): | ||
760 | 169 | return False | ||
761 | 170 | return True | ||
762 | 171 | |||
763 | 172 | def __nonzero__(self): | ||
764 | 173 | return self.__bool__() | ||
765 | 174 | |||
766 | 175 | |||
767 | 176 | class StoredContext(dict): | ||
768 | 177 | """ | ||
769 | 178 | A data context that always returns the data that it was first created with. | ||
770 | 179 | |||
771 | 180 | This is useful to do a one-time generation of things like passwords, that | ||
772 | 181 | will thereafter use the same value that was originally generated, instead | ||
773 | 182 | of generating a new value each time it is run. | ||
774 | 183 | """ | ||
775 | 184 | def __init__(self, file_name, config_data): | ||
776 | 185 | """ | ||
777 | 186 | If the file exists, populate `self` with the data from the file. | ||
778 | 187 | Otherwise, populate with the given data and persist it to the file. | ||
779 | 188 | """ | ||
780 | 189 | if os.path.exists(file_name): | ||
781 | 190 | self.update(self.read_context(file_name)) | ||
782 | 191 | else: | ||
783 | 192 | self.store_context(file_name, config_data) | ||
784 | 193 | self.update(config_data) | ||
785 | 194 | |||
786 | 195 | def store_context(self, file_name, config_data): | ||
787 | 196 | if not os.path.isabs(file_name): | ||
788 | 197 | file_name = os.path.join(hookenv.charm_dir(), file_name) | ||
789 | 198 | with open(file_name, 'w') as file_stream: | ||
790 | 199 | os.fchmod(file_stream.fileno(), 0600) | ||
791 | 200 | yaml.dump(config_data, file_stream) | ||
792 | 201 | |||
793 | 202 | def read_context(self, file_name): | ||
794 | 203 | if not os.path.isabs(file_name): | ||
795 | 204 | file_name = os.path.join(hookenv.charm_dir(), file_name) | ||
796 | 205 | with open(file_name, 'r') as file_stream: | ||
797 | 206 | data = yaml.load(file_stream) | ||
798 | 207 | if not data: | ||
799 | 208 | raise OSError("%s is empty" % file_name) | ||
800 | 209 | return data | ||
801 | 210 | |||
802 | 211 | |||
803 | 104 | class TemplateCallback(ManagerCallback): | 212 | class TemplateCallback(ManagerCallback): |
804 | 105 | """ | 213 | """ |
806 | 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. |
807 | 215 | |||
808 | 216 | :param str source: The template source file, relative to `$CHARM_DIR/templates` | ||
809 | 217 | :param str target: The target to write the rendered template to | ||
810 | 218 | :param str owner: The owner of the rendered file | ||
811 | 219 | :param str group: The group of the rendered file | ||
812 | 220 | :param int perms: The permissions of the rendered file | ||
813 | 107 | """ | 221 | """ |
814 | 108 | def __init__(self, source, target, owner='root', group='root', perms=0444): | 222 | def __init__(self, source, target, owner='root', group='root', perms=0444): |
815 | 109 | self.source = source | 223 | self.source = source |
816 | 110 | 224 | ||
817 | === modified file 'hooks/charmhelpers/fetch/__init__.py' | |||
818 | --- hooks/charmhelpers/fetch/__init__.py 2014-08-26 13:30:43 +0000 | |||
819 | +++ hooks/charmhelpers/fetch/__init__.py 2014-09-30 13:38:11 +0000 | |||
820 | @@ -208,7 +208,8 @@ | |||
821 | 208 | """Add a package source to this system. | 208 | """Add a package source to this system. |
822 | 209 | 209 | ||
823 | 210 | @param source: a URL or sources.list entry, as supported by | 210 | @param source: a URL or sources.list entry, as supported by |
825 | 211 | add-apt-repository(1). Examples: | 211 | add-apt-repository(1). Examples:: |
826 | 212 | |||
827 | 212 | ppa:charmers/example | 213 | ppa:charmers/example |
828 | 213 | deb https://stub:key@private.example.com/ubuntu trusty main | 214 | deb https://stub:key@private.example.com/ubuntu trusty main |
829 | 214 | 215 | ||
830 | @@ -311,22 +312,35 @@ | |||
831 | 311 | apt_update(fatal=True) | 312 | apt_update(fatal=True) |
832 | 312 | 313 | ||
833 | 313 | 314 | ||
835 | 314 | def install_remote(source): | 315 | def install_remote(source, *args, **kwargs): |
836 | 315 | """ | 316 | """ |
837 | 316 | Install a file tree from a remote source | 317 | Install a file tree from a remote source |
838 | 317 | 318 | ||
839 | 318 | The specified source should be a url of the form: | 319 | The specified source should be a url of the form: |
840 | 319 | scheme://[host]/path[#[option=value][&...]] | 320 | scheme://[host]/path[#[option=value][&...]] |
841 | 320 | 321 | ||
844 | 321 | Schemes supported are based on this modules submodules | 322 | Schemes supported are based on this modules submodules. |
845 | 322 | Options supported are submodule-specific""" | 323 | Options supported are submodule-specific. |
846 | 324 | Additional arguments are passed through to the submodule. | ||
847 | 325 | |||
848 | 326 | For example:: | ||
849 | 327 | |||
850 | 328 | dest = install_remote('http://example.com/archive.tgz', | ||
851 | 329 | checksum='deadbeef', | ||
852 | 330 | hash_type='sha1') | ||
853 | 331 | |||
854 | 332 | This will download `archive.tgz`, validate it using SHA1 and, if | ||
855 | 333 | the file is ok, extract it and return the directory in which it | ||
856 | 334 | was extracted. If the checksum fails, it will raise | ||
857 | 335 | :class:`charmhelpers.core.host.ChecksumError`. | ||
858 | 336 | """ | ||
859 | 323 | # We ONLY check for True here because can_handle may return a string | 337 | # We ONLY check for True here because can_handle may return a string |
860 | 324 | # explaining why it can't handle a given source. | 338 | # explaining why it can't handle a given source. |
861 | 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] |
862 | 326 | installed_to = None | 340 | installed_to = None |
863 | 327 | for handler in handlers: | 341 | for handler in handlers: |
864 | 328 | try: | 342 | try: |
866 | 329 | installed_to = handler.install(source) | 343 | installed_to = handler.install(source, *args, **kwargs) |
867 | 330 | except UnhandledSource: | 344 | except UnhandledSource: |
868 | 331 | pass | 345 | pass |
869 | 332 | if not installed_to: | 346 | if not installed_to: |
870 | 333 | 347 | ||
871 | === modified file 'hooks/charmhelpers/fetch/archiveurl.py' | |||
872 | --- hooks/charmhelpers/fetch/archiveurl.py 2014-03-20 13:47:46 +0000 | |||
873 | +++ hooks/charmhelpers/fetch/archiveurl.py 2014-09-30 13:38:11 +0000 | |||
874 | @@ -1,6 +1,8 @@ | |||
875 | 1 | import os | 1 | import os |
876 | 2 | import urllib2 | 2 | import urllib2 |
877 | 3 | from urllib import urlretrieve | ||
878 | 3 | import urlparse | 4 | import urlparse |
879 | 5 | import hashlib | ||
880 | 4 | 6 | ||
881 | 5 | from charmhelpers.fetch import ( | 7 | from charmhelpers.fetch import ( |
882 | 6 | BaseFetchHandler, | 8 | BaseFetchHandler, |
883 | @@ -10,11 +12,19 @@ | |||
884 | 10 | get_archive_handler, | 12 | get_archive_handler, |
885 | 11 | extract, | 13 | extract, |
886 | 12 | ) | 14 | ) |
888 | 13 | from charmhelpers.core.host import mkdir | 15 | from charmhelpers.core.host import mkdir, check_hash |
889 | 14 | 16 | ||
890 | 15 | 17 | ||
891 | 16 | class ArchiveUrlFetchHandler(BaseFetchHandler): | 18 | class ArchiveUrlFetchHandler(BaseFetchHandler): |
893 | 17 | """Handler for archives via generic URLs""" | 19 | """ |
894 | 20 | Handler to download archive files from arbitrary URLs. | ||
895 | 21 | |||
896 | 22 | Can fetch from http, https, ftp, and file URLs. | ||
897 | 23 | |||
898 | 24 | Can install either tarballs (.tar, .tgz, .tbz2, etc) or zip files. | ||
899 | 25 | |||
900 | 26 | Installs the contents of the archive in $CHARM_DIR/fetched/. | ||
901 | 27 | """ | ||
902 | 18 | def can_handle(self, source): | 28 | def can_handle(self, source): |
903 | 19 | url_parts = self.parse_url(source) | 29 | url_parts = self.parse_url(source) |
904 | 20 | if url_parts.scheme not in ('http', 'https', 'ftp', 'file'): | 30 | if url_parts.scheme not in ('http', 'https', 'ftp', 'file'): |
905 | @@ -24,6 +34,12 @@ | |||
906 | 24 | return False | 34 | return False |
907 | 25 | 35 | ||
908 | 26 | def download(self, source, dest): | 36 | def download(self, source, dest): |
909 | 37 | """ | ||
910 | 38 | Download an archive file. | ||
911 | 39 | |||
912 | 40 | :param str source: URL pointing to an archive file. | ||
913 | 41 | :param str dest: Local path location to download archive file to. | ||
914 | 42 | """ | ||
915 | 27 | # propogate all exceptions | 43 | # propogate all exceptions |
916 | 28 | # URLError, OSError, etc | 44 | # URLError, OSError, etc |
917 | 29 | proto, netloc, path, params, query, fragment = urlparse.urlparse(source) | 45 | proto, netloc, path, params, query, fragment = urlparse.urlparse(source) |
918 | @@ -48,7 +64,30 @@ | |||
919 | 48 | os.unlink(dest) | 64 | os.unlink(dest) |
920 | 49 | raise e | 65 | raise e |
921 | 50 | 66 | ||
923 | 51 | def install(self, source): | 67 | # Mandatory file validation via Sha1 or MD5 hashing. |
924 | 68 | def download_and_validate(self, url, hashsum, validate="sha1"): | ||
925 | 69 | tempfile, headers = urlretrieve(url) | ||
926 | 70 | check_hash(tempfile, hashsum, validate) | ||
927 | 71 | return tempfile | ||
928 | 72 | |||
929 | 73 | def install(self, source, dest=None, checksum=None, hash_type='sha1'): | ||
930 | 74 | """ | ||
931 | 75 | Download and install an archive file, with optional checksum validation. | ||
932 | 76 | |||
933 | 77 | The checksum can also be given on the `source` URL's fragment. | ||
934 | 78 | For example:: | ||
935 | 79 | |||
936 | 80 | handler.install('http://example.com/file.tgz#sha1=deadbeef') | ||
937 | 81 | |||
938 | 82 | :param str source: URL pointing to an archive file. | ||
939 | 83 | :param str dest: Local destination path to install to. If not given, | ||
940 | 84 | installs to `$CHARM_DIR/archives/archive_file_name`. | ||
941 | 85 | :param str checksum: If given, validate the archive file after download. | ||
942 | 86 | :param str hash_type: Algorithm used to generate `checksum`. | ||
943 | 87 | Can be any hash alrgorithm supported by :mod:`hashlib`, | ||
944 | 88 | such as md5, sha1, sha256, sha512, etc. | ||
945 | 89 | |||
946 | 90 | """ | ||
947 | 52 | url_parts = self.parse_url(source) | 91 | url_parts = self.parse_url(source) |
948 | 53 | dest_dir = os.path.join(os.environ.get('CHARM_DIR'), 'fetched') | 92 | dest_dir = os.path.join(os.environ.get('CHARM_DIR'), 'fetched') |
949 | 54 | if not os.path.exists(dest_dir): | 93 | if not os.path.exists(dest_dir): |
950 | @@ -60,4 +99,10 @@ | |||
951 | 60 | raise UnhandledSource(e.reason) | 99 | raise UnhandledSource(e.reason) |
952 | 61 | except OSError as e: | 100 | except OSError as e: |
953 | 62 | raise UnhandledSource(e.strerror) | 101 | raise UnhandledSource(e.strerror) |
955 | 63 | return extract(dld_file) | 102 | options = urlparse.parse_qs(url_parts.fragment) |
956 | 103 | for key, value in options.items(): | ||
957 | 104 | if key in hashlib.algorithms: | ||
958 | 105 | check_hash(dld_file, value, key) | ||
959 | 106 | if checksum: | ||
960 | 107 | check_hash(dld_file, checksum, hash_type) | ||
961 | 108 | return extract(dld_file, dest) | ||
962 | 64 | 109 | ||
963 | === modified file 'tests/00-setup' | |||
964 | --- tests/00-setup 2014-07-11 16:57:37 +0000 | |||
965 | +++ tests/00-setup 2014-09-30 13:38:11 +0000 | |||
966 | @@ -4,8 +4,8 @@ | |||
967 | 4 | 4 | ||
968 | 5 | sudo add-apt-repository --yes ppa:juju/stable | 5 | sudo add-apt-repository --yes ppa:juju/stable |
969 | 6 | sudo apt-get update --yes | 6 | sudo apt-get update --yes |
975 | 7 | sudo apt-get install --yes python-amulet | 7 | sudo apt-get install --yes python-amulet \ |
976 | 8 | sudo apt-get install --yes python-swiftclient | 8 | python-swiftclient \ |
977 | 9 | sudo apt-get install --yes python-glanceclient | 9 | python-glanceclient \ |
978 | 10 | sudo apt-get install --yes python-keystoneclient | 10 | python-keystoneclient \ |
979 | 11 | sudo apt-get install --yes python-novaclient | 11 | python-novaclient |
980 | 12 | 12 | ||
981 | === modified file 'tests/README' | |||
982 | --- tests/README 2014-07-11 16:57:37 +0000 | |||
983 | +++ tests/README 2014-09-30 13:38:11 +0000 | |||
984 | @@ -1,6 +1,12 @@ | |||
985 | 1 | This directory provides Amulet tests that focus on verification of swift-proxy | 1 | This directory provides Amulet tests that focus on verification of swift-proxy |
986 | 2 | deployments. | 2 | deployments. |
987 | 3 | 3 | ||
988 | 4 | In order to run tests, you'll need charm-tools installed (in addition to | ||
989 | 5 | juju, of course): | ||
990 | 6 | sudo add-apt-repository ppa:juju/stable | ||
991 | 7 | sudo apt-get update | ||
992 | 8 | sudo apt-get install charm-tools | ||
993 | 9 | |||
994 | 4 | If you use a web proxy server to access the web, you'll need to set the | 10 | If you use a web proxy server to access the web, you'll need to set the |
995 | 5 | AMULET_HTTP_PROXY environment variable to the http URL of the proxy server. | 11 | AMULET_HTTP_PROXY environment variable to the http URL of the proxy server. |
996 | 6 | 12 | ||
997 | 7 | 13 | ||
998 | === modified file 'tests/basic_deployment.py' | |||
999 | --- tests/basic_deployment.py 2014-07-11 16:57:37 +0000 | |||
1000 | +++ tests/basic_deployment.py 2014-09-30 13:38:11 +0000 | |||
1001 | @@ -20,10 +20,10 @@ | |||
1002 | 20 | class SwiftProxyBasicDeployment(OpenStackAmuletDeployment): | 20 | class SwiftProxyBasicDeployment(OpenStackAmuletDeployment): |
1003 | 21 | """Amulet tests on a basic swift-proxy deployment.""" | 21 | """Amulet tests on a basic swift-proxy deployment.""" |
1004 | 22 | 22 | ||
1006 | 23 | def __init__(self, series, openstack=None, source=None): | 23 | def __init__(self, series, openstack=None, source=None, stable=False): |
1007 | 24 | """Deploy the entire test environment.""" | 24 | """Deploy the entire test environment.""" |
1008 | 25 | super(SwiftProxyBasicDeployment, self).__init__(series, openstack, | 25 | super(SwiftProxyBasicDeployment, self).__init__(series, openstack, |
1010 | 26 | source) | 26 | source, stable) |
1011 | 27 | self._add_services() | 27 | self._add_services() |
1012 | 28 | self._add_relations() | 28 | self._add_relations() |
1013 | 29 | self._configure_services() | 29 | self._configure_services() |
1014 | @@ -31,12 +31,15 @@ | |||
1015 | 31 | self._initialize_tests() | 31 | self._initialize_tests() |
1016 | 32 | 32 | ||
1017 | 33 | def _add_services(self): | 33 | def _add_services(self): |
1024 | 34 | """Add the service that we're testing, including the number of units, | 34 | """Add services |
1025 | 35 | where swift-proxy is local, and the other charms are from | 35 | |
1026 | 36 | the charm store.""" | 36 | Add the services that we're testing, where swift-proxy is local, |
1027 | 37 | this_service = ('swift-proxy', 1) | 37 | and the rest of the service are from lp branches that are |
1028 | 38 | other_services = [('mysql', 1), | 38 | compatible with the local charm (e.g. stable or next). |
1029 | 39 | ('keystone', 1), ('glance', 1), ('swift-storage', 1)] | 39 | """ |
1030 | 40 | this_service = {'name': 'swift-proxy'} | ||
1031 | 41 | other_services = [{'name': 'mysql'}, {'name': 'keystone'}, | ||
1032 | 42 | {'name': 'glance'}, {'name': 'swift-storage'}] | ||
1033 | 40 | super(SwiftProxyBasicDeployment, self)._add_services(this_service, | 43 | super(SwiftProxyBasicDeployment, self)._add_services(this_service, |
1034 | 41 | other_services) | 44 | other_services) |
1035 | 42 | 45 | ||
1036 | @@ -315,14 +318,20 @@ | |||
1037 | 315 | message = u.relation_error('swift-proxy object-store', ret) | 318 | message = u.relation_error('swift-proxy object-store', ret) |
1038 | 316 | amulet.raise_status(amulet.FAIL, msg=message) | 319 | amulet.raise_status(amulet.FAIL, msg=message) |
1039 | 317 | 320 | ||
1041 | 318 | def test_restart_on_config_change(self): | 321 | def test_z_restart_on_config_change(self): |
1042 | 319 | """Verify that the specified services are restarted when the config | 322 | """Verify that the specified services are restarted when the config |
1044 | 320 | is changed.""" | 323 | is changed. |
1045 | 324 | |||
1046 | 325 | Note(coreycb): The method name with the _z_ is a little odd | ||
1047 | 326 | but it forces the test to run last. It just makes things | ||
1048 | 327 | easier because restarting services requires re-authorization. | ||
1049 | 328 | """ | ||
1050 | 321 | svc = 'swift-proxy' | 329 | svc = 'swift-proxy' |
1051 | 322 | self.d.configure('swift-proxy', {'node-timeout': '90'}) | 330 | self.d.configure('swift-proxy', {'node-timeout': '90'}) |
1052 | 323 | 331 | ||
1053 | 324 | if not u.service_restarted(self.swift_proxy_sentry, svc, | 332 | if not u.service_restarted(self.swift_proxy_sentry, svc, |
1054 | 325 | '/etc/swift/proxy-server.conf'): | 333 | '/etc/swift/proxy-server.conf'): |
1055 | 334 | self.d.configure('swift-proxy', {'node-timeout': '60'}) | ||
1056 | 326 | msg = "service {} didn't restart after config change".format(svc) | 335 | msg = "service {} didn't restart after config change".format(svc) |
1057 | 327 | amulet.raise_status(amulet.FAIL, msg=msg) | 336 | amulet.raise_status(amulet.FAIL, msg=msg) |
1058 | 328 | 337 | ||
1059 | 329 | 338 | ||
1060 | === modified file 'tests/charmhelpers/contrib/amulet/deployment.py' | |||
1061 | --- tests/charmhelpers/contrib/amulet/deployment.py 2014-07-30 10:06:23 +0000 | |||
1062 | +++ tests/charmhelpers/contrib/amulet/deployment.py 2014-09-30 13:38:11 +0000 | |||
1063 | @@ -24,25 +24,31 @@ | |||
1064 | 24 | """Add services. | 24 | """Add services. |
1065 | 25 | 25 | ||
1066 | 26 | Add services to the deployment where this_service is the local charm | 26 | Add services to the deployment where this_service is the local charm |
1069 | 27 | that we're focused on testing and other_services are the other | 27 | that we're testing and other_services are the other services that |
1070 | 28 | charms that come from the charm store. | 28 | are being used in the local amulet tests. |
1071 | 29 | """ | 29 | """ |
1076 | 30 | name, units = range(2) | 30 | if this_service['name'] != os.path.basename(os.getcwd()): |
1077 | 31 | 31 | s = this_service['name'] | |
1074 | 32 | if this_service[name] != os.path.basename(os.getcwd()): | ||
1075 | 33 | s = this_service[name] | ||
1078 | 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) |
1079 | 35 | amulet.raise_status(amulet.FAIL, msg=msg) | 33 | amulet.raise_status(amulet.FAIL, msg=msg) |
1080 | 36 | 34 | ||
1082 | 37 | self.d.add(this_service[name], units=this_service[units]) | 35 | if 'units' not in this_service: |
1083 | 36 | this_service['units'] = 1 | ||
1084 | 37 | |||
1085 | 38 | self.d.add(this_service['name'], units=this_service['units']) | ||
1086 | 38 | 39 | ||
1087 | 39 | for svc in other_services: | 40 | for svc in other_services: |
1092 | 40 | if self.series: | 41 | if 'location' in svc: |
1093 | 41 | self.d.add(svc[name], | 42 | branch_location = svc['location'] |
1094 | 42 | charm='cs:{}/{}'.format(self.series, svc[name]), | 43 | elif self.series: |
1095 | 43 | units=svc[units]) | 44 | branch_location = 'cs:{}/{}'.format(self.series, svc['name']), |
1096 | 44 | else: | 45 | else: |
1098 | 45 | self.d.add(svc[name], units=svc[units]) | 46 | branch_location = None |
1099 | 47 | |||
1100 | 48 | if 'units' not in svc: | ||
1101 | 49 | svc['units'] = 1 | ||
1102 | 50 | |||
1103 | 51 | self.d.add(svc['name'], charm=branch_location, units=svc['units']) | ||
1104 | 46 | 52 | ||
1105 | 47 | def _add_relations(self, relations): | 53 | def _add_relations(self, relations): |
1106 | 48 | """Add all of the relations for the services.""" | 54 | """Add all of the relations for the services.""" |
1107 | @@ -57,7 +63,7 @@ | |||
1108 | 57 | def _deploy(self): | 63 | def _deploy(self): |
1109 | 58 | """Deploy environment and wait for all hooks to finish executing.""" | 64 | """Deploy environment and wait for all hooks to finish executing.""" |
1110 | 59 | try: | 65 | try: |
1112 | 60 | self.d.setup() | 66 | self.d.setup(timeout=900) |
1113 | 61 | self.d.sentry.wait(timeout=900) | 67 | self.d.sentry.wait(timeout=900) |
1114 | 62 | except amulet.helpers.TimeoutError: | 68 | except amulet.helpers.TimeoutError: |
1115 | 63 | amulet.raise_status(amulet.FAIL, msg="Deployment timed out") | 69 | amulet.raise_status(amulet.FAIL, msg="Deployment timed out") |
1116 | 64 | 70 | ||
1117 | === modified file 'tests/charmhelpers/contrib/openstack/amulet/deployment.py' | |||
1118 | --- tests/charmhelpers/contrib/openstack/amulet/deployment.py 2014-07-30 10:06:23 +0000 | |||
1119 | +++ tests/charmhelpers/contrib/openstack/amulet/deployment.py 2014-09-30 13:38:11 +0000 | |||
1120 | @@ -10,32 +10,62 @@ | |||
1121 | 10 | that is specifically for use by OpenStack charms. | 10 | that is specifically for use by OpenStack charms. |
1122 | 11 | """ | 11 | """ |
1123 | 12 | 12 | ||
1125 | 13 | def __init__(self, series=None, openstack=None, source=None): | 13 | def __init__(self, series=None, openstack=None, source=None, stable=True): |
1126 | 14 | """Initialize the deployment environment.""" | 14 | """Initialize the deployment environment.""" |
1127 | 15 | super(OpenStackAmuletDeployment, self).__init__(series) | 15 | super(OpenStackAmuletDeployment, self).__init__(series) |
1128 | 16 | self.openstack = openstack | 16 | self.openstack = openstack |
1129 | 17 | self.source = source | 17 | self.source = source |
1130 | 18 | self.stable = stable | ||
1131 | 19 | # Note(coreycb): this needs to be changed when new next branches come | ||
1132 | 20 | # out. | ||
1133 | 21 | self.current_next = "trusty" | ||
1134 | 22 | |||
1135 | 23 | def _determine_branch_locations(self, other_services): | ||
1136 | 24 | """Determine the branch locations for the other services. | ||
1137 | 25 | |||
1138 | 26 | Determine if the local branch being tested is derived from its | ||
1139 | 27 | stable or next (dev) branch, and based on this, use the corresonding | ||
1140 | 28 | stable or next branches for the other_services.""" | ||
1141 | 29 | base_charms = ['mysql', 'mongodb', 'rabbitmq-server'] | ||
1142 | 30 | |||
1143 | 31 | if self.stable: | ||
1144 | 32 | for svc in other_services: | ||
1145 | 33 | temp = 'lp:charms/{}' | ||
1146 | 34 | svc['location'] = temp.format(svc['name']) | ||
1147 | 35 | else: | ||
1148 | 36 | for svc in other_services: | ||
1149 | 37 | if svc['name'] in base_charms: | ||
1150 | 38 | temp = 'lp:charms/{}' | ||
1151 | 39 | svc['location'] = temp.format(svc['name']) | ||
1152 | 40 | else: | ||
1153 | 41 | temp = 'lp:~openstack-charmers/charms/{}/{}/next' | ||
1154 | 42 | svc['location'] = temp.format(self.current_next, | ||
1155 | 43 | svc['name']) | ||
1156 | 44 | return other_services | ||
1157 | 18 | 45 | ||
1158 | 19 | def _add_services(self, this_service, other_services): | 46 | def _add_services(self, this_service, other_services): |
1160 | 20 | """Add services to the deployment and set openstack-origin.""" | 47 | """Add services to the deployment and set openstack-origin/source.""" |
1161 | 48 | other_services = self._determine_branch_locations(other_services) | ||
1162 | 49 | |||
1163 | 21 | super(OpenStackAmuletDeployment, self)._add_services(this_service, | 50 | super(OpenStackAmuletDeployment, self)._add_services(this_service, |
1164 | 22 | other_services) | 51 | other_services) |
1166 | 23 | name = 0 | 52 | |
1167 | 24 | services = other_services | 53 | services = other_services |
1168 | 25 | services.append(this_service) | 54 | services.append(this_service) |
1170 | 26 | use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph'] | 55 | use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph', |
1171 | 56 | 'ceph-osd', 'ceph-radosgw'] | ||
1172 | 27 | 57 | ||
1173 | 28 | if self.openstack: | 58 | if self.openstack: |
1174 | 29 | for svc in services: | 59 | for svc in services: |
1176 | 30 | if svc[name] not in use_source: | 60 | if svc['name'] not in use_source: |
1177 | 31 | config = {'openstack-origin': self.openstack} | 61 | config = {'openstack-origin': self.openstack} |
1179 | 32 | self.d.configure(svc[name], config) | 62 | self.d.configure(svc['name'], config) |
1180 | 33 | 63 | ||
1181 | 34 | if self.source: | 64 | if self.source: |
1182 | 35 | for svc in services: | 65 | for svc in services: |
1184 | 36 | if svc[name] in use_source: | 66 | if svc['name'] in use_source: |
1185 | 37 | config = {'source': self.source} | 67 | config = {'source': self.source} |
1187 | 38 | self.d.configure(svc[name], config) | 68 | self.d.configure(svc['name'], config) |
1188 | 39 | 69 | ||
1189 | 40 | def _configure_services(self, configs): | 70 | def _configure_services(self, configs): |
1190 | 41 | """Configure all of the services.""" | 71 | """Configure all of the services.""" |
1191 | 42 | 72 | ||
1192 | === modified file 'tests/charmhelpers/contrib/openstack/amulet/utils.py' | |||
1193 | --- tests/charmhelpers/contrib/openstack/amulet/utils.py 2014-07-30 10:06:23 +0000 | |||
1194 | +++ tests/charmhelpers/contrib/openstack/amulet/utils.py 2014-09-30 13:38:11 +0000 | |||
1195 | @@ -187,15 +187,16 @@ | |||
1196 | 187 | 187 | ||
1197 | 188 | f = opener.open("http://download.cirros-cloud.net/version/released") | 188 | f = opener.open("http://download.cirros-cloud.net/version/released") |
1198 | 189 | version = f.read().strip() | 189 | version = f.read().strip() |
1200 | 190 | cirros_img = "tests/cirros-{}-x86_64-disk.img".format(version) | 190 | cirros_img = "cirros-{}-x86_64-disk.img".format(version) |
1201 | 191 | local_path = os.path.join('tests', cirros_img) | ||
1202 | 191 | 192 | ||
1204 | 192 | if not os.path.exists(cirros_img): | 193 | if not os.path.exists(local_path): |
1205 | 193 | cirros_url = "http://{}/{}/{}".format("download.cirros-cloud.net", | 194 | cirros_url = "http://{}/{}/{}".format("download.cirros-cloud.net", |
1206 | 194 | version, cirros_img) | 195 | version, cirros_img) |
1208 | 195 | opener.retrieve(cirros_url, cirros_img) | 196 | opener.retrieve(cirros_url, local_path) |
1209 | 196 | f.close() | 197 | f.close() |
1210 | 197 | 198 | ||
1212 | 198 | with open(cirros_img) as f: | 199 | with open(local_path) as f: |
1213 | 199 | image = glance.images.create(name=image_name, is_public=True, | 200 | image = glance.images.create(name=image_name, is_public=True, |
1214 | 200 | disk_format='qcow2', | 201 | disk_format='qcow2', |
1215 | 201 | container_format='bare', data=f) | 202 | container_format='bare', data=f) |