Merge lp:~openstack-charmers/charms/precise/glance/ceilometer-support into lp:~charmers/charms/precise/glance/trunk
- Precise Pangolin (12.04)
- ceilometer-support
- Merge into trunk
Proposed by
James Page
Status: | Merged |
---|---|
Merged at revision: | 41 |
Proposed branch: | lp:~openstack-charmers/charms/precise/glance/ceilometer-support |
Merge into: | lp:~charmers/charms/precise/glance/trunk |
Diff against target: |
1065 lines (+403/-99) 15 files modified
config.yaml (+8/-10) hooks/charmhelpers/contrib/openstack/context.py (+20/-1) hooks/charmhelpers/contrib/openstack/neutron.py (+20/-0) hooks/charmhelpers/contrib/openstack/utils.py (+81/-10) hooks/charmhelpers/contrib/storage/linux/ceph.py (+27/-3) hooks/charmhelpers/core/hookenv.py (+78/-23) hooks/charmhelpers/core/host.py (+15/-9) hooks/charmhelpers/fetch/__init__.py (+53/-5) hooks/charmhelpers/fetch/bzrurl.py (+1/-1) hooks/glance_contexts.py (+1/-10) hooks/glance_relations.py (+15/-1) hooks/glance_utils.py (+1/-0) metadata.yaml (+2/-0) templates/folsom/glance-api.conf (+9/-25) unit_tests/test_glance_relations.py (+72/-1) |
To merge this branch: | bzr merge lp:~openstack-charmers/charms/precise/glance/ceilometer-support |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
charmers | Pending | ||
Review via email: mp+194040@code.launchpad.net |
Commit message
Description of the change
Support for use with ceilometer
To post a comment you must log in.
- 43. By Adam Gandelman
-
Sync helpers, use is_relation_made() from helpers.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'config.yaml' | |||
2 | --- config.yaml 2013-09-26 09:49:19 +0000 | |||
3 | +++ config.yaml 2013-11-06 04:26:29 +0000 | |||
4 | @@ -70,14 +70,12 @@ | |||
5 | 70 | ssl_key: | 70 | ssl_key: |
6 | 71 | type: string | 71 | type: string |
7 | 72 | description: SSL key to use with certificate specified as ssl_cert. | 72 | description: SSL key to use with certificate specified as ssl_cert. |
18 | 73 | ceph-osd-replication-count: | 73 | rabbit-user: |
19 | 74 | default: 2 | 74 | default: glance |
20 | 75 | type: int | 75 | type: string |
21 | 76 | description: | | 76 | description: Username to request access on rabbitmq-server. |
22 | 77 | This value dictates the number of replicas ceph must make of any | 77 | rabbit-vhost: |
23 | 78 | object it stores within the images rbd pool. Of course, this only | 78 | default: openstack |
24 | 79 | applies if using Ceph as a backend store. Note that once the images | 79 | type: string |
25 | 80 | rbd pool has been created, changing this value will not have any | 80 | description: RabbitMQ virtual host to request access on rabbitmq-server. |
16 | 81 | effect (although it can be changed in ceph by manually configuring | ||
17 | 82 | your ceph cluster). | ||
26 | 83 | 81 | ||
27 | 84 | 82 | ||
28 | === added symlink 'hooks/amqp-relation-changed' | |||
29 | === target is u'glance_relations.py' | |||
30 | === added symlink 'hooks/amqp-relation-joined' | |||
31 | === target is u'glance_relations.py' | |||
32 | === modified file 'hooks/charmhelpers/contrib/openstack/context.py' | |||
33 | --- hooks/charmhelpers/contrib/openstack/context.py 2013-10-15 01:32:42 +0000 | |||
34 | +++ hooks/charmhelpers/contrib/openstack/context.py 2013-11-06 04:26:29 +0000 | |||
35 | @@ -385,16 +385,33 @@ | |||
36 | 385 | def ovs_ctxt(self): | 385 | def ovs_ctxt(self): |
37 | 386 | driver = neutron_plugin_attribute(self.plugin, 'driver', | 386 | driver = neutron_plugin_attribute(self.plugin, 'driver', |
38 | 387 | self.network_manager) | 387 | self.network_manager) |
40 | 388 | 388 | config = neutron_plugin_attribute(self.plugin, 'config', | |
41 | 389 | self.network_manager) | ||
42 | 389 | ovs_ctxt = { | 390 | ovs_ctxt = { |
43 | 390 | 'core_plugin': driver, | 391 | 'core_plugin': driver, |
44 | 391 | 'neutron_plugin': 'ovs', | 392 | 'neutron_plugin': 'ovs', |
45 | 392 | 'neutron_security_groups': self.neutron_security_groups, | 393 | 'neutron_security_groups': self.neutron_security_groups, |
46 | 393 | 'local_ip': unit_private_ip(), | 394 | 'local_ip': unit_private_ip(), |
47 | 395 | 'config': config | ||
48 | 394 | } | 396 | } |
49 | 395 | 397 | ||
50 | 396 | return ovs_ctxt | 398 | return ovs_ctxt |
51 | 397 | 399 | ||
52 | 400 | def nvp_ctxt(self): | ||
53 | 401 | driver = neutron_plugin_attribute(self.plugin, 'driver', | ||
54 | 402 | self.network_manager) | ||
55 | 403 | config = neutron_plugin_attribute(self.plugin, 'config', | ||
56 | 404 | self.network_manager) | ||
57 | 405 | nvp_ctxt = { | ||
58 | 406 | 'core_plugin': driver, | ||
59 | 407 | 'neutron_plugin': 'nvp', | ||
60 | 408 | 'neutron_security_groups': self.neutron_security_groups, | ||
61 | 409 | 'local_ip': unit_private_ip(), | ||
62 | 410 | 'config': config | ||
63 | 411 | } | ||
64 | 412 | |||
65 | 413 | return nvp_ctxt | ||
66 | 414 | |||
67 | 398 | def __call__(self): | 415 | def __call__(self): |
68 | 399 | self._ensure_packages() | 416 | self._ensure_packages() |
69 | 400 | 417 | ||
70 | @@ -408,6 +425,8 @@ | |||
71 | 408 | 425 | ||
72 | 409 | if self.plugin == 'ovs': | 426 | if self.plugin == 'ovs': |
73 | 410 | ctxt.update(self.ovs_ctxt()) | 427 | ctxt.update(self.ovs_ctxt()) |
74 | 428 | elif self.plugin == 'nvp': | ||
75 | 429 | ctxt.update(self.nvp_ctxt()) | ||
76 | 411 | 430 | ||
77 | 412 | self._save_flag_file() | 431 | self._save_flag_file() |
78 | 413 | return ctxt | 432 | return ctxt |
79 | 414 | 433 | ||
80 | === modified file 'hooks/charmhelpers/contrib/openstack/neutron.py' | |||
81 | --- hooks/charmhelpers/contrib/openstack/neutron.py 2013-10-15 01:32:42 +0000 | |||
82 | +++ hooks/charmhelpers/contrib/openstack/neutron.py 2013-11-06 04:26:29 +0000 | |||
83 | @@ -34,13 +34,23 @@ | |||
84 | 34 | 'services': ['quantum-plugin-openvswitch-agent'], | 34 | 'services': ['quantum-plugin-openvswitch-agent'], |
85 | 35 | 'packages': [[headers_package(), 'openvswitch-datapath-dkms'], | 35 | 'packages': [[headers_package(), 'openvswitch-datapath-dkms'], |
86 | 36 | ['quantum-plugin-openvswitch-agent']], | 36 | ['quantum-plugin-openvswitch-agent']], |
87 | 37 | 'server_packages': ['quantum-server', | ||
88 | 38 | 'quantum-plugin-openvswitch'], | ||
89 | 39 | 'server_services': ['quantum-server'] | ||
90 | 37 | }, | 40 | }, |
91 | 38 | 'nvp': { | 41 | 'nvp': { |
92 | 39 | 'config': '/etc/quantum/plugins/nicira/nvp.ini', | 42 | 'config': '/etc/quantum/plugins/nicira/nvp.ini', |
93 | 40 | 'driver': 'quantum.plugins.nicira.nicira_nvp_plugin.' | 43 | 'driver': 'quantum.plugins.nicira.nicira_nvp_plugin.' |
94 | 41 | 'QuantumPlugin.NvpPluginV2', | 44 | 'QuantumPlugin.NvpPluginV2', |
95 | 45 | 'contexts': [ | ||
96 | 46 | context.SharedDBContext(user=config('neutron-database-user'), | ||
97 | 47 | database=config('neutron-database'), | ||
98 | 48 | relation_prefix='neutron')], | ||
99 | 42 | 'services': [], | 49 | 'services': [], |
100 | 43 | 'packages': [], | 50 | 'packages': [], |
101 | 51 | 'server_packages': ['quantum-server', | ||
102 | 52 | 'quantum-plugin-nicira'], | ||
103 | 53 | 'server_services': ['quantum-server'] | ||
104 | 44 | } | 54 | } |
105 | 45 | } | 55 | } |
106 | 46 | 56 | ||
107 | @@ -60,13 +70,23 @@ | |||
108 | 60 | 'services': ['neutron-plugin-openvswitch-agent'], | 70 | 'services': ['neutron-plugin-openvswitch-agent'], |
109 | 61 | 'packages': [[headers_package(), 'openvswitch-datapath-dkms'], | 71 | 'packages': [[headers_package(), 'openvswitch-datapath-dkms'], |
110 | 62 | ['quantum-plugin-openvswitch-agent']], | 72 | ['quantum-plugin-openvswitch-agent']], |
111 | 73 | 'server_packages': ['neutron-server', | ||
112 | 74 | 'neutron-plugin-openvswitch'], | ||
113 | 75 | 'server_services': ['neutron-server'] | ||
114 | 63 | }, | 76 | }, |
115 | 64 | 'nvp': { | 77 | 'nvp': { |
116 | 65 | 'config': '/etc/neutron/plugins/nicira/nvp.ini', | 78 | 'config': '/etc/neutron/plugins/nicira/nvp.ini', |
117 | 66 | 'driver': 'neutron.plugins.nicira.nicira_nvp_plugin.' | 79 | 'driver': 'neutron.plugins.nicira.nicira_nvp_plugin.' |
118 | 67 | 'NeutronPlugin.NvpPluginV2', | 80 | 'NeutronPlugin.NvpPluginV2', |
119 | 81 | 'contexts': [ | ||
120 | 82 | context.SharedDBContext(user=config('neutron-database-user'), | ||
121 | 83 | database=config('neutron-database'), | ||
122 | 84 | relation_prefix='neutron')], | ||
123 | 68 | 'services': [], | 85 | 'services': [], |
124 | 69 | 'packages': [], | 86 | 'packages': [], |
125 | 87 | 'server_packages': ['neutron-server', | ||
126 | 88 | 'neutron-plugin-nicira'], | ||
127 | 89 | 'server_services': ['neutron-server'] | ||
128 | 70 | } | 90 | } |
129 | 71 | } | 91 | } |
130 | 72 | 92 | ||
131 | 73 | 93 | ||
132 | === modified file 'hooks/charmhelpers/contrib/openstack/utils.py' | |||
133 | --- hooks/charmhelpers/contrib/openstack/utils.py 2013-10-15 01:32:42 +0000 | |||
134 | +++ hooks/charmhelpers/contrib/openstack/utils.py 2013-11-06 04:26:29 +0000 | |||
135 | @@ -13,19 +13,28 @@ | |||
136 | 13 | config, | 13 | config, |
137 | 14 | log as juju_log, | 14 | log as juju_log, |
138 | 15 | charm_dir, | 15 | charm_dir, |
148 | 16 | ) | 16 | ERROR, |
149 | 17 | 17 | INFO | |
150 | 18 | from charmhelpers.core.host import ( | 18 | ) |
151 | 19 | lsb_release, | 19 | |
152 | 20 | ) | 20 | from charmhelpers.contrib.storage.linux.lvm import ( |
153 | 21 | 21 | deactivate_lvm_volume_group, | |
154 | 22 | from charmhelpers.fetch import ( | 22 | is_lvm_physical_volume, |
155 | 23 | apt_install, | 23 | remove_lvm_physical_volume, |
156 | 24 | ) | 24 | ) |
157 | 25 | |||
158 | 26 | from charmhelpers.core.host import lsb_release, mounts, umount | ||
159 | 27 | from charmhelpers.fetch import apt_install | ||
160 | 28 | from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk | ||
161 | 29 | from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device | ||
162 | 25 | 30 | ||
163 | 26 | CLOUD_ARCHIVE_URL = "http://ubuntu-cloud.archive.canonical.com/ubuntu" | 31 | CLOUD_ARCHIVE_URL = "http://ubuntu-cloud.archive.canonical.com/ubuntu" |
164 | 27 | CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA' | 32 | CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA' |
165 | 28 | 33 | ||
166 | 34 | DISTRO_PROPOSED = ('deb http://archive.ubuntu.com/ubuntu/ %s-proposed ' | ||
167 | 35 | 'restricted main multiverse universe') | ||
168 | 36 | |||
169 | 37 | |||
170 | 29 | UBUNTU_OPENSTACK_RELEASE = OrderedDict([ | 38 | UBUNTU_OPENSTACK_RELEASE = OrderedDict([ |
171 | 30 | ('oneiric', 'diablo'), | 39 | ('oneiric', 'diablo'), |
172 | 31 | ('precise', 'essex'), | 40 | ('precise', 'essex'), |
173 | @@ -57,6 +66,8 @@ | |||
174 | 57 | ('1.9.0', 'havana'), | 66 | ('1.9.0', 'havana'), |
175 | 58 | ]) | 67 | ]) |
176 | 59 | 68 | ||
177 | 69 | DEFAULT_LOOPBACK_SIZE = '5G' | ||
178 | 70 | |||
179 | 60 | 71 | ||
180 | 61 | def error_out(msg): | 72 | def error_out(msg): |
181 | 62 | juju_log("FATAL ERROR: %s" % msg, level='ERROR') | 73 | juju_log("FATAL ERROR: %s" % msg, level='ERROR') |
182 | @@ -67,7 +78,7 @@ | |||
183 | 67 | '''Derive OpenStack release codename from a given installation source.''' | 78 | '''Derive OpenStack release codename from a given installation source.''' |
184 | 68 | ubuntu_rel = lsb_release()['DISTRIB_CODENAME'] | 79 | ubuntu_rel = lsb_release()['DISTRIB_CODENAME'] |
185 | 69 | rel = '' | 80 | rel = '' |
187 | 70 | if src == 'distro': | 81 | if src in ['distro', 'distro-proposed']: |
188 | 71 | try: | 82 | try: |
189 | 72 | rel = UBUNTU_OPENSTACK_RELEASE[ubuntu_rel] | 83 | rel = UBUNTU_OPENSTACK_RELEASE[ubuntu_rel] |
190 | 73 | except KeyError: | 84 | except KeyError: |
191 | @@ -202,6 +213,10 @@ | |||
192 | 202 | '''Configure apt installation source.''' | 213 | '''Configure apt installation source.''' |
193 | 203 | if rel == 'distro': | 214 | if rel == 'distro': |
194 | 204 | return | 215 | return |
195 | 216 | elif rel == 'distro-proposed': | ||
196 | 217 | ubuntu_rel = lsb_release()['DISTRIB_CODENAME'] | ||
197 | 218 | with open('/etc/apt/sources.list.d/juju_deb.list', 'w') as f: | ||
198 | 219 | f.write(DISTRO_PROPOSED % ubuntu_rel) | ||
199 | 205 | elif rel[:4] == "ppa:": | 220 | elif rel[:4] == "ppa:": |
200 | 206 | src = rel | 221 | src = rel |
201 | 207 | subprocess.check_call(["add-apt-repository", "-y", src]) | 222 | subprocess.check_call(["add-apt-repository", "-y", src]) |
202 | @@ -299,6 +314,62 @@ | |||
203 | 299 | return apt.version_compare(available_vers, cur_vers) == 1 | 314 | return apt.version_compare(available_vers, cur_vers) == 1 |
204 | 300 | 315 | ||
205 | 301 | 316 | ||
206 | 317 | def ensure_block_device(block_device): | ||
207 | 318 | ''' | ||
208 | 319 | Confirm block_device, create as loopback if necessary. | ||
209 | 320 | |||
210 | 321 | :param block_device: str: Full path of block device to ensure. | ||
211 | 322 | |||
212 | 323 | :returns: str: Full path of ensured block device. | ||
213 | 324 | ''' | ||
214 | 325 | _none = ['None', 'none', None] | ||
215 | 326 | if (block_device in _none): | ||
216 | 327 | error_out('prepare_storage(): Missing required input: ' | ||
217 | 328 | 'block_device=%s.' % block_device, level=ERROR) | ||
218 | 329 | |||
219 | 330 | if block_device.startswith('/dev/'): | ||
220 | 331 | bdev = block_device | ||
221 | 332 | elif block_device.startswith('/'): | ||
222 | 333 | _bd = block_device.split('|') | ||
223 | 334 | if len(_bd) == 2: | ||
224 | 335 | bdev, size = _bd | ||
225 | 336 | else: | ||
226 | 337 | bdev = block_device | ||
227 | 338 | size = DEFAULT_LOOPBACK_SIZE | ||
228 | 339 | bdev = ensure_loopback_device(bdev, size) | ||
229 | 340 | else: | ||
230 | 341 | bdev = '/dev/%s' % block_device | ||
231 | 342 | |||
232 | 343 | if not is_block_device(bdev): | ||
233 | 344 | error_out('Failed to locate valid block device at %s' % bdev, | ||
234 | 345 | level=ERROR) | ||
235 | 346 | |||
236 | 347 | return bdev | ||
237 | 348 | |||
238 | 349 | |||
239 | 350 | def clean_storage(block_device): | ||
240 | 351 | ''' | ||
241 | 352 | Ensures a block device is clean. That is: | ||
242 | 353 | - unmounted | ||
243 | 354 | - any lvm volume groups are deactivated | ||
244 | 355 | - any lvm physical device signatures removed | ||
245 | 356 | - partition table wiped | ||
246 | 357 | |||
247 | 358 | :param block_device: str: Full path to block device to clean. | ||
248 | 359 | ''' | ||
249 | 360 | for mp, d in mounts(): | ||
250 | 361 | if d == block_device: | ||
251 | 362 | juju_log('clean_storage(): %s is mounted @ %s, unmounting.' % | ||
252 | 363 | (d, mp), level=INFO) | ||
253 | 364 | umount(mp, persist=True) | ||
254 | 365 | |||
255 | 366 | if is_lvm_physical_volume(block_device): | ||
256 | 367 | deactivate_lvm_volume_group(block_device) | ||
257 | 368 | remove_lvm_physical_volume(block_device) | ||
258 | 369 | else: | ||
259 | 370 | zap_disk(block_device) | ||
260 | 371 | |||
261 | 372 | |||
262 | 302 | def is_ip(address): | 373 | def is_ip(address): |
263 | 303 | """ | 374 | """ |
264 | 304 | Returns True if address is a valid IP address. | 375 | Returns True if address is a valid IP address. |
265 | 305 | 376 | ||
266 | === modified file 'hooks/charmhelpers/contrib/storage/linux/ceph.py' | |||
267 | --- hooks/charmhelpers/contrib/storage/linux/ceph.py 2013-09-24 14:54:12 +0000 | |||
268 | +++ hooks/charmhelpers/contrib/storage/linux/ceph.py 2013-11-06 04:26:29 +0000 | |||
269 | @@ -102,8 +102,12 @@ | |||
270 | 102 | Return a list of all Ceph Object Storage Daemons | 102 | Return a list of all Ceph Object Storage Daemons |
271 | 103 | currently in the cluster | 103 | currently in the cluster |
272 | 104 | ''' | 104 | ''' |
275 | 105 | return json.loads(check_output(['ceph', '--id', service, | 105 | version = ceph_version() |
276 | 106 | 'osd', 'ls', '--format=json'])) | 106 | if version and version >= '0.56': |
277 | 107 | return json.loads(check_output(['ceph', '--id', service, | ||
278 | 108 | 'osd', 'ls', '--format=json'])) | ||
279 | 109 | else: | ||
280 | 110 | return None | ||
281 | 107 | 111 | ||
282 | 108 | 112 | ||
283 | 109 | def create_pool(service, name, replicas=2): | 113 | def create_pool(service, name, replicas=2): |
284 | @@ -114,7 +118,13 @@ | |||
285 | 114 | return | 118 | return |
286 | 115 | # Calculate the number of placement groups based | 119 | # Calculate the number of placement groups based |
287 | 116 | # on upstream recommended best practices. | 120 | # on upstream recommended best practices. |
289 | 117 | pgnum = (len(get_osds(service)) * 100 / replicas) | 121 | osds = get_osds(service) |
290 | 122 | if osds: | ||
291 | 123 | pgnum = (len(osds) * 100 / replicas) | ||
292 | 124 | else: | ||
293 | 125 | # NOTE(james-page): Default to 200 for older ceph versions | ||
294 | 126 | # which don't support OSD query from cli | ||
295 | 127 | pgnum = 200 | ||
296 | 118 | cmd = [ | 128 | cmd = [ |
297 | 119 | 'ceph', '--id', service, | 129 | 'ceph', '--id', service, |
298 | 120 | 'osd', 'pool', 'create', | 130 | 'osd', 'pool', 'create', |
299 | @@ -357,3 +367,17 @@ | |||
300 | 357 | if user and group: | 367 | if user and group: |
301 | 358 | check_call(['chown', '%s.%s' % (user, group), keyring]) | 368 | check_call(['chown', '%s.%s' % (user, group), keyring]) |
302 | 359 | return True | 369 | return True |
303 | 370 | |||
304 | 371 | |||
305 | 372 | def ceph_version(): | ||
306 | 373 | ''' Retrieve the local version of ceph ''' | ||
307 | 374 | if os.path.exists('/usr/bin/ceph'): | ||
308 | 375 | cmd = ['ceph', '-v'] | ||
309 | 376 | output = check_output(cmd) | ||
310 | 377 | output = output.split() | ||
311 | 378 | if len(output) > 3: | ||
312 | 379 | return output[2] | ||
313 | 380 | else: | ||
314 | 381 | return None | ||
315 | 382 | else: | ||
316 | 383 | return None | ||
317 | 360 | 384 | ||
318 | === modified file 'hooks/charmhelpers/core/hookenv.py' | |||
319 | --- hooks/charmhelpers/core/hookenv.py 2013-08-14 22:31:44 +0000 | |||
320 | +++ hooks/charmhelpers/core/hookenv.py 2013-11-06 04:26:29 +0000 | |||
321 | @@ -9,6 +9,7 @@ | |||
322 | 9 | import yaml | 9 | import yaml |
323 | 10 | import subprocess | 10 | import subprocess |
324 | 11 | import UserDict | 11 | import UserDict |
325 | 12 | from subprocess import CalledProcessError | ||
326 | 12 | 13 | ||
327 | 13 | CRITICAL = "CRITICAL" | 14 | CRITICAL = "CRITICAL" |
328 | 14 | ERROR = "ERROR" | 15 | ERROR = "ERROR" |
329 | @@ -21,7 +22,7 @@ | |||
330 | 21 | 22 | ||
331 | 22 | 23 | ||
332 | 23 | def cached(func): | 24 | def cached(func): |
334 | 24 | ''' Cache return values for multiple executions of func + args | 25 | """Cache return values for multiple executions of func + args |
335 | 25 | 26 | ||
336 | 26 | For example: | 27 | For example: |
337 | 27 | 28 | ||
338 | @@ -32,7 +33,7 @@ | |||
339 | 32 | unit_get('test') | 33 | unit_get('test') |
340 | 33 | 34 | ||
341 | 34 | will cache the result of unit_get + 'test' for future calls. | 35 | will cache the result of unit_get + 'test' for future calls. |
343 | 35 | ''' | 36 | """ |
344 | 36 | def wrapper(*args, **kwargs): | 37 | def wrapper(*args, **kwargs): |
345 | 37 | global cache | 38 | global cache |
346 | 38 | key = str((func, args, kwargs)) | 39 | key = str((func, args, kwargs)) |
347 | @@ -46,8 +47,8 @@ | |||
348 | 46 | 47 | ||
349 | 47 | 48 | ||
350 | 48 | def flush(key): | 49 | def flush(key): |
353 | 49 | ''' Flushes any entries from function cache where the | 50 | """Flushes any entries from function cache where the |
354 | 50 | key is found in the function+args ''' | 51 | key is found in the function+args """ |
355 | 51 | flush_list = [] | 52 | flush_list = [] |
356 | 52 | for item in cache: | 53 | for item in cache: |
357 | 53 | if key in item: | 54 | if key in item: |
358 | @@ -57,7 +58,7 @@ | |||
359 | 57 | 58 | ||
360 | 58 | 59 | ||
361 | 59 | def log(message, level=None): | 60 | def log(message, level=None): |
363 | 60 | "Write a message to the juju log" | 61 | """Write a message to the juju log""" |
364 | 61 | command = ['juju-log'] | 62 | command = ['juju-log'] |
365 | 62 | if level: | 63 | if level: |
366 | 63 | command += ['-l', level] | 64 | command += ['-l', level] |
367 | @@ -66,7 +67,7 @@ | |||
368 | 66 | 67 | ||
369 | 67 | 68 | ||
370 | 68 | class Serializable(UserDict.IterableUserDict): | 69 | class Serializable(UserDict.IterableUserDict): |
372 | 69 | "Wrapper, an object that can be serialized to yaml or json" | 70 | """Wrapper, an object that can be serialized to yaml or json""" |
373 | 70 | 71 | ||
374 | 71 | def __init__(self, obj): | 72 | def __init__(self, obj): |
375 | 72 | # wrap the object | 73 | # wrap the object |
376 | @@ -96,11 +97,11 @@ | |||
377 | 96 | self.data = state | 97 | self.data = state |
378 | 97 | 98 | ||
379 | 98 | def json(self): | 99 | def json(self): |
381 | 99 | "Serialize the object to json" | 100 | """Serialize the object to json""" |
382 | 100 | return json.dumps(self.data) | 101 | return json.dumps(self.data) |
383 | 101 | 102 | ||
384 | 102 | def yaml(self): | 103 | def yaml(self): |
386 | 103 | "Serialize the object to yaml" | 104 | """Serialize the object to yaml""" |
387 | 104 | return yaml.dump(self.data) | 105 | return yaml.dump(self.data) |
388 | 105 | 106 | ||
389 | 106 | 107 | ||
390 | @@ -119,38 +120,38 @@ | |||
391 | 119 | 120 | ||
392 | 120 | 121 | ||
393 | 121 | def in_relation_hook(): | 122 | def in_relation_hook(): |
395 | 122 | "Determine whether we're running in a relation hook" | 123 | """Determine whether we're running in a relation hook""" |
396 | 123 | return 'JUJU_RELATION' in os.environ | 124 | return 'JUJU_RELATION' in os.environ |
397 | 124 | 125 | ||
398 | 125 | 126 | ||
399 | 126 | def relation_type(): | 127 | def relation_type(): |
401 | 127 | "The scope for the current relation hook" | 128 | """The scope for the current relation hook""" |
402 | 128 | return os.environ.get('JUJU_RELATION', None) | 129 | return os.environ.get('JUJU_RELATION', None) |
403 | 129 | 130 | ||
404 | 130 | 131 | ||
405 | 131 | def relation_id(): | 132 | def relation_id(): |
407 | 132 | "The relation ID for the current relation hook" | 133 | """The relation ID for the current relation hook""" |
408 | 133 | return os.environ.get('JUJU_RELATION_ID', None) | 134 | return os.environ.get('JUJU_RELATION_ID', None) |
409 | 134 | 135 | ||
410 | 135 | 136 | ||
411 | 136 | def local_unit(): | 137 | def local_unit(): |
413 | 137 | "Local unit ID" | 138 | """Local unit ID""" |
414 | 138 | return os.environ['JUJU_UNIT_NAME'] | 139 | return os.environ['JUJU_UNIT_NAME'] |
415 | 139 | 140 | ||
416 | 140 | 141 | ||
417 | 141 | def remote_unit(): | 142 | def remote_unit(): |
419 | 142 | "The remote unit for the current relation hook" | 143 | """The remote unit for the current relation hook""" |
420 | 143 | return os.environ['JUJU_REMOTE_UNIT'] | 144 | return os.environ['JUJU_REMOTE_UNIT'] |
421 | 144 | 145 | ||
422 | 145 | 146 | ||
423 | 146 | def service_name(): | 147 | def service_name(): |
425 | 147 | "The name service group this unit belongs to" | 148 | """The name service group this unit belongs to""" |
426 | 148 | return local_unit().split('/')[0] | 149 | return local_unit().split('/')[0] |
427 | 149 | 150 | ||
428 | 150 | 151 | ||
429 | 151 | @cached | 152 | @cached |
430 | 152 | def config(scope=None): | 153 | def config(scope=None): |
432 | 153 | "Juju charm configuration" | 154 | """Juju charm configuration""" |
433 | 154 | config_cmd_line = ['config-get'] | 155 | config_cmd_line = ['config-get'] |
434 | 155 | if scope is not None: | 156 | if scope is not None: |
435 | 156 | config_cmd_line.append(scope) | 157 | config_cmd_line.append(scope) |
436 | @@ -163,6 +164,7 @@ | |||
437 | 163 | 164 | ||
438 | 164 | @cached | 165 | @cached |
439 | 165 | def relation_get(attribute=None, unit=None, rid=None): | 166 | def relation_get(attribute=None, unit=None, rid=None): |
440 | 167 | """Get relation information""" | ||
441 | 166 | _args = ['relation-get', '--format=json'] | 168 | _args = ['relation-get', '--format=json'] |
442 | 167 | if rid: | 169 | if rid: |
443 | 168 | _args.append('-r') | 170 | _args.append('-r') |
444 | @@ -174,9 +176,14 @@ | |||
445 | 174 | return json.loads(subprocess.check_output(_args)) | 176 | return json.loads(subprocess.check_output(_args)) |
446 | 175 | except ValueError: | 177 | except ValueError: |
447 | 176 | return None | 178 | return None |
448 | 179 | except CalledProcessError, e: | ||
449 | 180 | if e.returncode == 2: | ||
450 | 181 | return None | ||
451 | 182 | raise | ||
452 | 177 | 183 | ||
453 | 178 | 184 | ||
454 | 179 | def relation_set(relation_id=None, relation_settings={}, **kwargs): | 185 | def relation_set(relation_id=None, relation_settings={}, **kwargs): |
455 | 186 | """Set relation information for the current unit""" | ||
456 | 180 | relation_cmd_line = ['relation-set'] | 187 | relation_cmd_line = ['relation-set'] |
457 | 181 | if relation_id is not None: | 188 | if relation_id is not None: |
458 | 182 | relation_cmd_line.extend(('-r', relation_id)) | 189 | relation_cmd_line.extend(('-r', relation_id)) |
459 | @@ -192,7 +199,7 @@ | |||
460 | 192 | 199 | ||
461 | 193 | @cached | 200 | @cached |
462 | 194 | def relation_ids(reltype=None): | 201 | def relation_ids(reltype=None): |
464 | 195 | "A list of relation_ids" | 202 | """A list of relation_ids""" |
465 | 196 | reltype = reltype or relation_type() | 203 | reltype = reltype or relation_type() |
466 | 197 | relid_cmd_line = ['relation-ids', '--format=json'] | 204 | relid_cmd_line = ['relation-ids', '--format=json'] |
467 | 198 | if reltype is not None: | 205 | if reltype is not None: |
468 | @@ -203,7 +210,7 @@ | |||
469 | 203 | 210 | ||
470 | 204 | @cached | 211 | @cached |
471 | 205 | def related_units(relid=None): | 212 | def related_units(relid=None): |
473 | 206 | "A list of related units" | 213 | """A list of related units""" |
474 | 207 | relid = relid or relation_id() | 214 | relid = relid or relation_id() |
475 | 208 | units_cmd_line = ['relation-list', '--format=json'] | 215 | units_cmd_line = ['relation-list', '--format=json'] |
476 | 209 | if relid is not None: | 216 | if relid is not None: |
477 | @@ -213,7 +220,7 @@ | |||
478 | 213 | 220 | ||
479 | 214 | @cached | 221 | @cached |
480 | 215 | def relation_for_unit(unit=None, rid=None): | 222 | def relation_for_unit(unit=None, rid=None): |
482 | 216 | "Get the json represenation of a unit's relation" | 223 | """Get the json represenation of a unit's relation""" |
483 | 217 | unit = unit or remote_unit() | 224 | unit = unit or remote_unit() |
484 | 218 | relation = relation_get(unit=unit, rid=rid) | 225 | relation = relation_get(unit=unit, rid=rid) |
485 | 219 | for key in relation: | 226 | for key in relation: |
486 | @@ -225,7 +232,7 @@ | |||
487 | 225 | 232 | ||
488 | 226 | @cached | 233 | @cached |
489 | 227 | def relations_for_id(relid=None): | 234 | def relations_for_id(relid=None): |
491 | 228 | "Get relations of a specific relation ID" | 235 | """Get relations of a specific relation ID""" |
492 | 229 | relation_data = [] | 236 | relation_data = [] |
493 | 230 | relid = relid or relation_ids() | 237 | relid = relid or relation_ids() |
494 | 231 | for unit in related_units(relid): | 238 | for unit in related_units(relid): |
495 | @@ -237,7 +244,7 @@ | |||
496 | 237 | 244 | ||
497 | 238 | @cached | 245 | @cached |
498 | 239 | def relations_of_type(reltype=None): | 246 | def relations_of_type(reltype=None): |
500 | 240 | "Get relations of a specific type" | 247 | """Get relations of a specific type""" |
501 | 241 | relation_data = [] | 248 | relation_data = [] |
502 | 242 | reltype = reltype or relation_type() | 249 | reltype = reltype or relation_type() |
503 | 243 | for relid in relation_ids(reltype): | 250 | for relid in relation_ids(reltype): |
504 | @@ -249,7 +256,7 @@ | |||
505 | 249 | 256 | ||
506 | 250 | @cached | 257 | @cached |
507 | 251 | def relation_types(): | 258 | def relation_types(): |
509 | 252 | "Get a list of relation types supported by this charm" | 259 | """Get a list of relation types supported by this charm""" |
510 | 253 | charmdir = os.environ.get('CHARM_DIR', '') | 260 | charmdir = os.environ.get('CHARM_DIR', '') |
511 | 254 | mdf = open(os.path.join(charmdir, 'metadata.yaml')) | 261 | mdf = open(os.path.join(charmdir, 'metadata.yaml')) |
512 | 255 | md = yaml.safe_load(mdf) | 262 | md = yaml.safe_load(mdf) |
513 | @@ -264,6 +271,7 @@ | |||
514 | 264 | 271 | ||
515 | 265 | @cached | 272 | @cached |
516 | 266 | def relations(): | 273 | def relations(): |
517 | 274 | """Get a nested dictionary of relation data for all related units""" | ||
518 | 267 | rels = {} | 275 | rels = {} |
519 | 268 | for reltype in relation_types(): | 276 | for reltype in relation_types(): |
520 | 269 | relids = {} | 277 | relids = {} |
521 | @@ -277,15 +285,35 @@ | |||
522 | 277 | return rels | 285 | return rels |
523 | 278 | 286 | ||
524 | 279 | 287 | ||
525 | 288 | @cached | ||
526 | 289 | def is_relation_made(relation, keys='private-address'): | ||
527 | 290 | ''' | ||
528 | 291 | Determine whether a relation is established by checking for | ||
529 | 292 | presence of key(s). If a list of keys is provided, they | ||
530 | 293 | must all be present for the relation to be identified as made | ||
531 | 294 | ''' | ||
532 | 295 | if isinstance(keys, str): | ||
533 | 296 | keys = [keys] | ||
534 | 297 | for r_id in relation_ids(relation): | ||
535 | 298 | for unit in related_units(r_id): | ||
536 | 299 | context = {} | ||
537 | 300 | for k in keys: | ||
538 | 301 | context[k] = relation_get(k, rid=r_id, | ||
539 | 302 | unit=unit) | ||
540 | 303 | if None not in context.values(): | ||
541 | 304 | return True | ||
542 | 305 | return False | ||
543 | 306 | |||
544 | 307 | |||
545 | 280 | def open_port(port, protocol="TCP"): | 308 | def open_port(port, protocol="TCP"): |
547 | 281 | "Open a service network port" | 309 | """Open a service network port""" |
548 | 282 | _args = ['open-port'] | 310 | _args = ['open-port'] |
549 | 283 | _args.append('{}/{}'.format(port, protocol)) | 311 | _args.append('{}/{}'.format(port, protocol)) |
550 | 284 | subprocess.check_call(_args) | 312 | subprocess.check_call(_args) |
551 | 285 | 313 | ||
552 | 286 | 314 | ||
553 | 287 | def close_port(port, protocol="TCP"): | 315 | def close_port(port, protocol="TCP"): |
555 | 288 | "Close a service network port" | 316 | """Close a service network port""" |
556 | 289 | _args = ['close-port'] | 317 | _args = ['close-port'] |
557 | 290 | _args.append('{}/{}'.format(port, protocol)) | 318 | _args.append('{}/{}'.format(port, protocol)) |
558 | 291 | subprocess.check_call(_args) | 319 | subprocess.check_call(_args) |
559 | @@ -293,6 +321,7 @@ | |||
560 | 293 | 321 | ||
561 | 294 | @cached | 322 | @cached |
562 | 295 | def unit_get(attribute): | 323 | def unit_get(attribute): |
563 | 324 | """Get the unit ID for the remote unit""" | ||
564 | 296 | _args = ['unit-get', '--format=json', attribute] | 325 | _args = ['unit-get', '--format=json', attribute] |
565 | 297 | try: | 326 | try: |
566 | 298 | return json.loads(subprocess.check_output(_args)) | 327 | return json.loads(subprocess.check_output(_args)) |
567 | @@ -301,22 +330,46 @@ | |||
568 | 301 | 330 | ||
569 | 302 | 331 | ||
570 | 303 | def unit_private_ip(): | 332 | def unit_private_ip(): |
571 | 333 | """Get this unit's private IP address""" | ||
572 | 304 | return unit_get('private-address') | 334 | return unit_get('private-address') |
573 | 305 | 335 | ||
574 | 306 | 336 | ||
575 | 307 | class UnregisteredHookError(Exception): | 337 | class UnregisteredHookError(Exception): |
576 | 338 | """Raised when an undefined hook is called""" | ||
577 | 308 | pass | 339 | pass |
578 | 309 | 340 | ||
579 | 310 | 341 | ||
580 | 311 | class Hooks(object): | 342 | class Hooks(object): |
581 | 343 | """A convenient handler for hook functions. | ||
582 | 344 | |||
583 | 345 | Example: | ||
584 | 346 | hooks = Hooks() | ||
585 | 347 | |||
586 | 348 | # register a hook, taking its name from the function name | ||
587 | 349 | @hooks.hook() | ||
588 | 350 | def install(): | ||
589 | 351 | ... | ||
590 | 352 | |||
591 | 353 | # register a hook, providing a custom hook name | ||
592 | 354 | @hooks.hook("config-changed") | ||
593 | 355 | def config_changed(): | ||
594 | 356 | ... | ||
595 | 357 | |||
596 | 358 | if __name__ == "__main__": | ||
597 | 359 | # execute a hook based on the name the program is called by | ||
598 | 360 | hooks.execute(sys.argv) | ||
599 | 361 | """ | ||
600 | 362 | |||
601 | 312 | def __init__(self): | 363 | def __init__(self): |
602 | 313 | super(Hooks, self).__init__() | 364 | super(Hooks, self).__init__() |
603 | 314 | self._hooks = {} | 365 | self._hooks = {} |
604 | 315 | 366 | ||
605 | 316 | def register(self, name, function): | 367 | def register(self, name, function): |
606 | 368 | """Register a hook""" | ||
607 | 317 | self._hooks[name] = function | 369 | self._hooks[name] = function |
608 | 318 | 370 | ||
609 | 319 | def execute(self, args): | 371 | def execute(self, args): |
610 | 372 | """Execute a registered hook based on args[0]""" | ||
611 | 320 | hook_name = os.path.basename(args[0]) | 373 | hook_name = os.path.basename(args[0]) |
612 | 321 | if hook_name in self._hooks: | 374 | if hook_name in self._hooks: |
613 | 322 | self._hooks[hook_name]() | 375 | self._hooks[hook_name]() |
614 | @@ -324,6 +377,7 @@ | |||
615 | 324 | raise UnregisteredHookError(hook_name) | 377 | raise UnregisteredHookError(hook_name) |
616 | 325 | 378 | ||
617 | 326 | def hook(self, *hook_names): | 379 | def hook(self, *hook_names): |
618 | 380 | """Decorator, registering them as hooks""" | ||
619 | 327 | def wrapper(decorated): | 381 | def wrapper(decorated): |
620 | 328 | for hook_name in hook_names: | 382 | for hook_name in hook_names: |
621 | 329 | self.register(hook_name, decorated) | 383 | self.register(hook_name, decorated) |
622 | @@ -337,4 +391,5 @@ | |||
623 | 337 | 391 | ||
624 | 338 | 392 | ||
625 | 339 | def charm_dir(): | 393 | def charm_dir(): |
626 | 394 | """Return the root directory of the current charm""" | ||
627 | 340 | return os.environ.get('CHARM_DIR') | 395 | return os.environ.get('CHARM_DIR') |
628 | 341 | 396 | ||
629 | === modified file 'hooks/charmhelpers/core/host.py' | |||
630 | --- hooks/charmhelpers/core/host.py 2013-09-20 15:52:45 +0000 | |||
631 | +++ hooks/charmhelpers/core/host.py 2013-11-06 04:26:29 +0000 | |||
632 | @@ -19,18 +19,22 @@ | |||
633 | 19 | 19 | ||
634 | 20 | 20 | ||
635 | 21 | def service_start(service_name): | 21 | def service_start(service_name): |
636 | 22 | """Start a system service""" | ||
637 | 22 | return service('start', service_name) | 23 | return service('start', service_name) |
638 | 23 | 24 | ||
639 | 24 | 25 | ||
640 | 25 | def service_stop(service_name): | 26 | def service_stop(service_name): |
641 | 27 | """Stop a system service""" | ||
642 | 26 | return service('stop', service_name) | 28 | return service('stop', service_name) |
643 | 27 | 29 | ||
644 | 28 | 30 | ||
645 | 29 | def service_restart(service_name): | 31 | def service_restart(service_name): |
646 | 32 | """Restart a system service""" | ||
647 | 30 | return service('restart', service_name) | 33 | return service('restart', service_name) |
648 | 31 | 34 | ||
649 | 32 | 35 | ||
650 | 33 | def service_reload(service_name, restart_on_failure=False): | 36 | def service_reload(service_name, restart_on_failure=False): |
651 | 37 | """Reload a system service, optionally falling back to restart if reload fails""" | ||
652 | 34 | service_result = service('reload', service_name) | 38 | service_result = service('reload', service_name) |
653 | 35 | if not service_result and restart_on_failure: | 39 | if not service_result and restart_on_failure: |
654 | 36 | service_result = service('restart', service_name) | 40 | service_result = service('restart', service_name) |
655 | @@ -38,11 +42,13 @@ | |||
656 | 38 | 42 | ||
657 | 39 | 43 | ||
658 | 40 | def service(action, service_name): | 44 | def service(action, service_name): |
659 | 45 | """Control a system service""" | ||
660 | 41 | cmd = ['service', service_name, action] | 46 | cmd = ['service', service_name, action] |
661 | 42 | return subprocess.call(cmd) == 0 | 47 | return subprocess.call(cmd) == 0 |
662 | 43 | 48 | ||
663 | 44 | 49 | ||
664 | 45 | def service_running(service): | 50 | def service_running(service): |
665 | 51 | """Determine whether a system service is running""" | ||
666 | 46 | try: | 52 | try: |
667 | 47 | output = subprocess.check_output(['service', service, 'status']) | 53 | output = subprocess.check_output(['service', service, 'status']) |
668 | 48 | except subprocess.CalledProcessError: | 54 | except subprocess.CalledProcessError: |
669 | @@ -55,7 +61,7 @@ | |||
670 | 55 | 61 | ||
671 | 56 | 62 | ||
672 | 57 | def adduser(username, password=None, shell='/bin/bash', system_user=False): | 63 | def adduser(username, password=None, shell='/bin/bash', system_user=False): |
674 | 58 | """Add a user""" | 64 | """Add a user to the system""" |
675 | 59 | try: | 65 | try: |
676 | 60 | user_info = pwd.getpwnam(username) | 66 | user_info = pwd.getpwnam(username) |
677 | 61 | log('user {0} already exists!'.format(username)) | 67 | log('user {0} already exists!'.format(username)) |
678 | @@ -138,7 +144,7 @@ | |||
679 | 138 | 144 | ||
680 | 139 | 145 | ||
681 | 140 | def mount(device, mountpoint, options=None, persist=False): | 146 | def mount(device, mountpoint, options=None, persist=False): |
683 | 141 | '''Mount a filesystem''' | 147 | """Mount a filesystem at a particular mountpoint""" |
684 | 142 | cmd_args = ['mount'] | 148 | cmd_args = ['mount'] |
685 | 143 | if options is not None: | 149 | if options is not None: |
686 | 144 | cmd_args.extend(['-o', options]) | 150 | cmd_args.extend(['-o', options]) |
687 | @@ -155,7 +161,7 @@ | |||
688 | 155 | 161 | ||
689 | 156 | 162 | ||
690 | 157 | def umount(mountpoint, persist=False): | 163 | def umount(mountpoint, persist=False): |
692 | 158 | '''Unmount a filesystem''' | 164 | """Unmount a filesystem""" |
693 | 159 | cmd_args = ['umount', mountpoint] | 165 | cmd_args = ['umount', mountpoint] |
694 | 160 | try: | 166 | try: |
695 | 161 | subprocess.check_output(cmd_args) | 167 | subprocess.check_output(cmd_args) |
696 | @@ -169,7 +175,7 @@ | |||
697 | 169 | 175 | ||
698 | 170 | 176 | ||
699 | 171 | def mounts(): | 177 | def mounts(): |
701 | 172 | '''List of all mounted volumes as [[mountpoint,device],[...]]''' | 178 | """Get a list of all mounted volumes as [[mountpoint,device],[...]]""" |
702 | 173 | with open('/proc/mounts') as f: | 179 | with open('/proc/mounts') as f: |
703 | 174 | # [['/mount/point','/dev/path'],[...]] | 180 | # [['/mount/point','/dev/path'],[...]] |
704 | 175 | system_mounts = [m[1::-1] for m in [l.strip().split() | 181 | system_mounts = [m[1::-1] for m in [l.strip().split() |
705 | @@ -178,7 +184,7 @@ | |||
706 | 178 | 184 | ||
707 | 179 | 185 | ||
708 | 180 | def file_hash(path): | 186 | def file_hash(path): |
710 | 181 | ''' Generate a md5 hash of the contents of 'path' or None if not found ''' | 187 | """Generate a md5 hash of the contents of 'path' or None if not found """ |
711 | 182 | if os.path.exists(path): | 188 | if os.path.exists(path): |
712 | 183 | h = hashlib.md5() | 189 | h = hashlib.md5() |
713 | 184 | with open(path, 'r') as source: | 190 | with open(path, 'r') as source: |
714 | @@ -189,7 +195,7 @@ | |||
715 | 189 | 195 | ||
716 | 190 | 196 | ||
717 | 191 | def restart_on_change(restart_map): | 197 | def restart_on_change(restart_map): |
719 | 192 | ''' Restart services based on configuration files changing | 198 | """Restart services based on configuration files changing |
720 | 193 | 199 | ||
721 | 194 | This function is used a decorator, for example | 200 | This function is used a decorator, for example |
722 | 195 | 201 | ||
723 | @@ -202,7 +208,7 @@ | |||
724 | 202 | In this example, the cinder-api and cinder-volume services | 208 | In this example, the cinder-api and cinder-volume services |
725 | 203 | would be restarted if /etc/ceph/ceph.conf is changed by the | 209 | would be restarted if /etc/ceph/ceph.conf is changed by the |
726 | 204 | ceph_client_changed function. | 210 | ceph_client_changed function. |
728 | 205 | ''' | 211 | """ |
729 | 206 | def wrap(f): | 212 | def wrap(f): |
730 | 207 | def wrapped_f(*args): | 213 | def wrapped_f(*args): |
731 | 208 | checksums = {} | 214 | checksums = {} |
732 | @@ -220,7 +226,7 @@ | |||
733 | 220 | 226 | ||
734 | 221 | 227 | ||
735 | 222 | def lsb_release(): | 228 | def lsb_release(): |
737 | 223 | '''Return /etc/lsb-release in a dict''' | 229 | """Return /etc/lsb-release in a dict""" |
738 | 224 | d = {} | 230 | d = {} |
739 | 225 | with open('/etc/lsb-release', 'r') as lsb: | 231 | with open('/etc/lsb-release', 'r') as lsb: |
740 | 226 | for l in lsb: | 232 | for l in lsb: |
741 | @@ -230,7 +236,7 @@ | |||
742 | 230 | 236 | ||
743 | 231 | 237 | ||
744 | 232 | def pwgen(length=None): | 238 | def pwgen(length=None): |
746 | 233 | '''Generate a random pasword.''' | 239 | """Generate a random pasword.""" |
747 | 234 | if length is None: | 240 | if length is None: |
748 | 235 | length = random.choice(range(35, 45)) | 241 | length = random.choice(range(35, 45)) |
749 | 236 | alphanumeric_chars = [ | 242 | alphanumeric_chars = [ |
750 | 237 | 243 | ||
751 | === modified file 'hooks/charmhelpers/fetch/__init__.py' | |||
752 | --- hooks/charmhelpers/fetch/__init__.py 2013-09-23 13:22:13 +0000 | |||
753 | +++ hooks/charmhelpers/fetch/__init__.py 2013-11-06 04:26:29 +0000 | |||
754 | @@ -20,6 +20,32 @@ | |||
755 | 20 | PROPOSED_POCKET = """# Proposed | 20 | PROPOSED_POCKET = """# Proposed |
756 | 21 | deb http://archive.ubuntu.com/ubuntu {}-proposed main universe multiverse restricted | 21 | deb http://archive.ubuntu.com/ubuntu {}-proposed main universe multiverse restricted |
757 | 22 | """ | 22 | """ |
758 | 23 | CLOUD_ARCHIVE_POCKETS = { | ||
759 | 24 | # Folsom | ||
760 | 25 | 'folsom': 'precise-updates/folsom', | ||
761 | 26 | 'precise-folsom': 'precise-updates/folsom', | ||
762 | 27 | 'precise-folsom/updates': 'precise-updates/folsom', | ||
763 | 28 | 'precise-updates/folsom': 'precise-updates/folsom', | ||
764 | 29 | 'folsom/proposed': 'precise-proposed/folsom', | ||
765 | 30 | 'precise-folsom/proposed': 'precise-proposed/folsom', | ||
766 | 31 | 'precise-proposed/folsom': 'precise-proposed/folsom', | ||
767 | 32 | # Grizzly | ||
768 | 33 | 'grizzly': 'precise-updates/grizzly', | ||
769 | 34 | 'precise-grizzly': 'precise-updates/grizzly', | ||
770 | 35 | 'precise-grizzly/updates': 'precise-updates/grizzly', | ||
771 | 36 | 'precise-updates/grizzly': 'precise-updates/grizzly', | ||
772 | 37 | 'grizzly/proposed': 'precise-proposed/grizzly', | ||
773 | 38 | 'precise-grizzly/proposed': 'precise-proposed/grizzly', | ||
774 | 39 | 'precise-proposed/grizzly': 'precise-proposed/grizzly', | ||
775 | 40 | # Havana | ||
776 | 41 | 'havana': 'precise-updates/havana', | ||
777 | 42 | 'precise-havana': 'precise-updates/havana', | ||
778 | 43 | 'precise-havana/updates': 'precise-updates/havana', | ||
779 | 44 | 'precise-updates/havana': 'precise-updates/havana', | ||
780 | 45 | 'havana/proposed': 'precise-proposed/havana', | ||
781 | 46 | 'precies-havana/proposed': 'precise-proposed/havana', | ||
782 | 47 | 'precise-proposed/havana': 'precise-proposed/havana', | ||
783 | 48 | } | ||
784 | 23 | 49 | ||
785 | 24 | 50 | ||
786 | 25 | def filter_installed_packages(packages): | 51 | def filter_installed_packages(packages): |
787 | @@ -79,16 +105,35 @@ | |||
788 | 79 | subprocess.call(cmd) | 105 | subprocess.call(cmd) |
789 | 80 | 106 | ||
790 | 81 | 107 | ||
791 | 108 | def apt_hold(packages, fatal=False): | ||
792 | 109 | """Hold one or more packages""" | ||
793 | 110 | cmd = ['apt-mark', 'hold'] | ||
794 | 111 | if isinstance(packages, basestring): | ||
795 | 112 | cmd.append(packages) | ||
796 | 113 | else: | ||
797 | 114 | cmd.extend(packages) | ||
798 | 115 | log("Holding {}".format(packages)) | ||
799 | 116 | if fatal: | ||
800 | 117 | subprocess.check_call(cmd) | ||
801 | 118 | else: | ||
802 | 119 | subprocess.call(cmd) | ||
803 | 120 | |||
804 | 121 | |||
805 | 82 | def add_source(source, key=None): | 122 | def add_source(source, key=None): |
808 | 83 | if ((source.startswith('ppa:') or | 123 | if (source.startswith('ppa:') or |
809 | 84 | source.startswith('http:'))): | 124 | source.startswith('http:') or |
810 | 125 | source.startswith('deb ') or | ||
811 | 126 | source.startswith('cloud-archive:')): | ||
812 | 85 | subprocess.check_call(['add-apt-repository', '--yes', source]) | 127 | subprocess.check_call(['add-apt-repository', '--yes', source]) |
813 | 86 | elif source.startswith('cloud:'): | 128 | elif source.startswith('cloud:'): |
814 | 87 | apt_install(filter_installed_packages(['ubuntu-cloud-keyring']), | 129 | apt_install(filter_installed_packages(['ubuntu-cloud-keyring']), |
815 | 88 | fatal=True) | 130 | fatal=True) |
816 | 89 | pocket = source.split(':')[-1] | 131 | pocket = source.split(':')[-1] |
817 | 132 | if pocket not in CLOUD_ARCHIVE_POCKETS: | ||
818 | 133 | raise SourceConfigError('Unsupported cloud: source option %s' % pocket) | ||
819 | 134 | actual_pocket = CLOUD_ARCHIVE_POCKETS[pocket] | ||
820 | 90 | with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt: | 135 | with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt: |
822 | 91 | apt.write(CLOUD_ARCHIVE.format(pocket)) | 136 | apt.write(CLOUD_ARCHIVE.format(actual_pocket)) |
823 | 92 | elif source == 'proposed': | 137 | elif source == 'proposed': |
824 | 93 | release = lsb_release()['DISTRIB_CODENAME'] | 138 | release = lsb_release()['DISTRIB_CODENAME'] |
825 | 94 | with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt: | 139 | with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt: |
826 | @@ -118,8 +163,11 @@ | |||
827 | 118 | Note that 'null' (a.k.a. None) should not be quoted. | 163 | Note that 'null' (a.k.a. None) should not be quoted. |
828 | 119 | """ | 164 | """ |
829 | 120 | sources = safe_load(config(sources_var)) | 165 | sources = safe_load(config(sources_var)) |
832 | 121 | keys = safe_load(config(keys_var)) | 166 | keys = config(keys_var) |
833 | 122 | if isinstance(sources, basestring) and isinstance(keys, basestring): | 167 | if keys is not None: |
834 | 168 | keys = safe_load(keys) | ||
835 | 169 | if isinstance(sources, basestring) and ( | ||
836 | 170 | keys is None or isinstance(keys, basestring)): | ||
837 | 123 | add_source(sources, keys) | 171 | add_source(sources, keys) |
838 | 124 | else: | 172 | else: |
839 | 125 | if not len(sources) == len(keys): | 173 | if not len(sources) == len(keys): |
840 | 126 | 174 | ||
841 | === modified file 'hooks/charmhelpers/fetch/bzrurl.py' | |||
842 | --- hooks/charmhelpers/fetch/bzrurl.py 2013-09-23 13:22:13 +0000 | |||
843 | +++ hooks/charmhelpers/fetch/bzrurl.py 2013-11-06 04:26:29 +0000 | |||
844 | @@ -12,6 +12,7 @@ | |||
845 | 12 | apt_install("python-bzrlib") | 12 | apt_install("python-bzrlib") |
846 | 13 | from bzrlib.branch import Branch | 13 | from bzrlib.branch import Branch |
847 | 14 | 14 | ||
848 | 15 | |||
849 | 15 | class BzrUrlFetchHandler(BaseFetchHandler): | 16 | class BzrUrlFetchHandler(BaseFetchHandler): |
850 | 16 | """Handler for bazaar branches via generic and lp URLs""" | 17 | """Handler for bazaar branches via generic and lp URLs""" |
851 | 17 | def can_handle(self, source): | 18 | def can_handle(self, source): |
852 | @@ -46,4 +47,3 @@ | |||
853 | 46 | except OSError as e: | 47 | except OSError as e: |
854 | 47 | raise UnhandledSource(e.strerror) | 48 | raise UnhandledSource(e.strerror) |
855 | 48 | return dest_dir | 49 | return dest_dir |
856 | 49 | |||
857 | 50 | 50 | ||
858 | === modified file 'hooks/glance_contexts.py' | |||
859 | --- hooks/glance_contexts.py 2013-10-19 21:01:07 +0000 | |||
860 | +++ hooks/glance_contexts.py 2013-11-06 04:26:29 +0000 | |||
861 | @@ -1,4 +1,5 @@ | |||
862 | 1 | from charmhelpers.core.hookenv import ( | 1 | from charmhelpers.core.hookenv import ( |
863 | 2 | is_relation_made, | ||
864 | 2 | relation_ids, | 3 | relation_ids, |
865 | 3 | related_units, | 4 | related_units, |
866 | 4 | relation_get, | 5 | relation_get, |
867 | @@ -16,16 +17,6 @@ | |||
868 | 16 | ) | 17 | ) |
869 | 17 | 18 | ||
870 | 18 | 19 | ||
871 | 19 | # TODO: switch to charmhelpers once landed | ||
872 | 20 | # NOTE: zero units tests - done in charmhelpers | ||
873 | 21 | def is_relation_made(relation, key='private-address'): | ||
874 | 22 | for r_id in relation_ids(relation): | ||
875 | 23 | for unit in related_units(r_id): | ||
876 | 24 | if relation_get(key, rid=r_id, unit=unit): | ||
877 | 25 | return True | ||
878 | 26 | return False | ||
879 | 27 | |||
880 | 28 | |||
881 | 29 | class CephGlanceContext(OSContextGenerator): | 20 | class CephGlanceContext(OSContextGenerator): |
882 | 30 | interfaces = ['ceph-glance'] | 21 | interfaces = ['ceph-glance'] |
883 | 31 | 22 | ||
884 | 32 | 23 | ||
885 | === modified file 'hooks/glance_relations.py' | |||
886 | --- hooks/glance_relations.py 2013-10-19 21:01:07 +0000 | |||
887 | +++ hooks/glance_relations.py 2013-11-06 04:26:29 +0000 | |||
888 | @@ -313,8 +313,22 @@ | |||
889 | 313 | image_service_joined(relation_id=r_id) | 313 | image_service_joined(relation_id=r_id) |
890 | 314 | 314 | ||
891 | 315 | 315 | ||
892 | 316 | @hooks.hook('amqp-relation-joined') | ||
893 | 317 | def amqp_joined(): | ||
894 | 318 | conf = config() | ||
895 | 319 | relation_set(username=conf['rabbit-user'], vhost=conf['rabbit-vhost']) | ||
896 | 320 | |||
897 | 321 | |||
898 | 322 | @hooks.hook('amqp-relation-changed') | ||
899 | 323 | @restart_on_change(restart_map()) | ||
900 | 324 | def amqp_changed(): | ||
901 | 325 | if 'amqp' not in CONFIGS.complete_contexts(): | ||
902 | 326 | juju_log('amqp relation incomplete. Peer not ready?') | ||
903 | 327 | return | ||
904 | 328 | CONFIGS.write(GLANCE_API_CONF) | ||
905 | 329 | |||
906 | 316 | if __name__ == '__main__': | 330 | if __name__ == '__main__': |
907 | 317 | try: | 331 | try: |
908 | 318 | hooks.execute(sys.argv) | 332 | hooks.execute(sys.argv) |
909 | 319 | except UnregisteredHookError as e: | 333 | except UnregisteredHookError as e: |
911 | 320 | juju_log('Unknown hook {} - skiping.'.format(e)) | 334 | juju_log('Unknown hook {} - skipping.'.format(e)) |
912 | 321 | 335 | ||
913 | === modified file 'hooks/glance_utils.py' | |||
914 | --- hooks/glance_utils.py 2013-10-19 21:01:07 +0000 | |||
915 | +++ hooks/glance_utils.py 2013-11-06 04:26:29 +0000 | |||
916 | @@ -68,6 +68,7 @@ | |||
917 | 68 | }), | 68 | }), |
918 | 69 | (GLANCE_API_CONF, { | 69 | (GLANCE_API_CONF, { |
919 | 70 | 'hook_contexts': [context.SharedDBContext(), | 70 | 'hook_contexts': [context.SharedDBContext(), |
920 | 71 | context.AMQPContext(), | ||
921 | 71 | context.IdentityServiceContext(), | 72 | context.IdentityServiceContext(), |
922 | 72 | glance_contexts.CephGlanceContext(), | 73 | glance_contexts.CephGlanceContext(), |
923 | 73 | glance_contexts.ObjectStoreContext(), | 74 | glance_contexts.ObjectStoreContext(), |
924 | 74 | 75 | ||
925 | === modified file 'metadata.yaml' | |||
926 | --- metadata.yaml 2013-09-26 09:47:58 +0000 | |||
927 | +++ metadata.yaml 2013-11-06 04:26:29 +0000 | |||
928 | @@ -14,6 +14,8 @@ | |||
929 | 14 | requires: | 14 | requires: |
930 | 15 | shared-db: | 15 | shared-db: |
931 | 16 | interface: mysql-shared | 16 | interface: mysql-shared |
932 | 17 | amqp: | ||
933 | 18 | interface: rabbitmq | ||
934 | 17 | object-store: | 19 | object-store: |
935 | 18 | interface: swift-proxy | 20 | interface: swift-proxy |
936 | 19 | identity-service: | 21 | identity-service: |
937 | 20 | 22 | ||
938 | === modified file 'templates/folsom/glance-api.conf' | |||
939 | --- templates/folsom/glance-api.conf 2013-10-07 14:48:52 +0000 | |||
940 | +++ templates/folsom/glance-api.conf 2013-11-06 04:26:29 +0000 | |||
941 | @@ -29,31 +29,15 @@ | |||
942 | 29 | registry_host = 0.0.0.0 | 29 | registry_host = 0.0.0.0 |
943 | 30 | registry_port = 9191 | 30 | registry_port = 9191 |
944 | 31 | registry_client_protocol = http | 31 | registry_client_protocol = http |
970 | 32 | notifier_strategy = noop | 32 | |
971 | 33 | rabbit_host = localhost | 33 | {% if rabbitmq_host -%} |
972 | 34 | rabbit_port = 5672 | 34 | notifier_strategy = rabbit |
973 | 35 | rabbit_use_ssl = false | 35 | rabbit_host = {{ rabbitmq_host }} |
974 | 36 | rabbit_userid = guest | 36 | rabbit_userid = {{ rabbitmq_user }} |
975 | 37 | rabbit_password = guest | 37 | rabbit_password = {{ rabbitmq_password }} |
976 | 38 | rabbit_virtual_host = / | 38 | rabbit_virtual_host = {{ rabbitmq_virtual_host }} |
977 | 39 | rabbit_notification_exchange = glance | 39 | {% endif -%} |
978 | 40 | rabbit_notification_topic = glance_notifications | 40 | |
954 | 41 | rabbit_durable_queues = False | ||
955 | 42 | qpid_notification_exchange = glance | ||
956 | 43 | qpid_notification_topic = glance_notifications | ||
957 | 44 | qpid_host = localhost | ||
958 | 45 | qpid_port = 5672 | ||
959 | 46 | qpid_username = | ||
960 | 47 | qpid_password = | ||
961 | 48 | qpid_sasl_mechanisms = | ||
962 | 49 | qpid_reconnect_timeout = 0 | ||
963 | 50 | qpid_reconnect_limit = 0 | ||
964 | 51 | qpid_reconnect_interval_min = 0 | ||
965 | 52 | qpid_reconnect_interval_max = 0 | ||
966 | 53 | qpid_reconnect_interval = 0 | ||
967 | 54 | qpid_heartbeat = 5 | ||
968 | 55 | qpid_protocol = tcp | ||
969 | 56 | qpid_tcp_nodelay = True | ||
979 | 57 | filesystem_store_datadir = /var/lib/glance/images/ | 41 | filesystem_store_datadir = /var/lib/glance/images/ |
980 | 58 | 42 | ||
981 | 59 | {% if swift_store %} | 43 | {% if swift_store %} |
982 | 60 | 44 | ||
983 | === modified file 'unit_tests/test_glance_relations.py' | |||
984 | --- unit_tests/test_glance_relations.py 2013-10-20 18:35:42 +0000 | |||
985 | +++ unit_tests/test_glance_relations.py 2013-11-06 04:26:29 +0000 | |||
986 | @@ -370,7 +370,78 @@ | |||
987 | 370 | def test_ha_relation_changed_not_clustered(self): | 370 | def test_ha_relation_changed_not_clustered(self): |
988 | 371 | self.relation_get.return_value = False | 371 | self.relation_get.return_value = False |
989 | 372 | relations.ha_relation_changed() | 372 | relations.ha_relation_changed() |
991 | 373 | self.assertTrue(self.juju_log.called) | 373 | self.juju_log.assert_called_with( |
992 | 374 | 'ha_changed: hacluster subordinate is not fully clustered.' | ||
993 | 375 | ) | ||
994 | 376 | |||
995 | 377 | @patch.object(relations, 'keystone_joined') | ||
996 | 378 | @patch.object(relations, 'CONFIGS') | ||
997 | 379 | def test_configure_https_enable_with_identity_service(self, configs, keystone_joined): | ||
998 | 380 | configs.complete_contexts = MagicMock() | ||
999 | 381 | configs.complete_contexts.return_value = ['https'] | ||
1000 | 382 | configs.write = MagicMock() | ||
1001 | 383 | self.relation_ids.return_value = ['identity-service:0'] | ||
1002 | 384 | relations.configure_https() | ||
1003 | 385 | cmd = ['a2ensite', 'openstack_https_frontend'] | ||
1004 | 386 | self.check_call.assert_called_with(cmd) | ||
1005 | 387 | keystone_joined.assert_called_with(relation_id='identity-service:0') | ||
1006 | 388 | |||
1007 | 389 | @patch.object(relations, 'keystone_joined') | ||
1008 | 390 | @patch.object(relations, 'CONFIGS') | ||
1009 | 391 | def test_configure_https_disable_with_keystone_joined(self, configs, keystone_joined): | ||
1010 | 392 | configs.complete_contexts = MagicMock() | ||
1011 | 393 | configs.complete_contexts.return_value = [''] | ||
1012 | 394 | configs.write = MagicMock() | ||
1013 | 395 | self.relation_ids.return_value = ['identity-service:0'] | ||
1014 | 396 | relations.configure_https() | ||
1015 | 397 | cmd = ['a2dissite', 'openstack_https_frontend'] | ||
1016 | 398 | self.check_call.assert_called_with(cmd) | ||
1017 | 399 | keystone_joined.assert_called_with(relation_id='identity-service:0') | ||
1018 | 400 | |||
1019 | 401 | @patch.object(relations, 'image_service_joined') | ||
1020 | 402 | @patch.object(relations, 'CONFIGS') | ||
1021 | 403 | def test_configure_https_enable_with_image_service(self, configs, image_service_joined): | ||
1022 | 404 | configs.complete_contexts = MagicMock() | ||
1023 | 405 | configs.complete_contexts.return_value = ['https'] | ||
1024 | 406 | configs.write = MagicMock() | ||
1025 | 407 | self.relation_ids.return_value = ['image-service:0'] | ||
1026 | 408 | relations.configure_https() | ||
1027 | 409 | cmd = ['a2ensite', 'openstack_https_frontend'] | ||
1028 | 410 | self.check_call.assert_called_with(cmd) | ||
1029 | 411 | image_service_joined.assert_called_with(relation_id='image-service:0') | ||
1030 | 412 | |||
1031 | 413 | @patch.object(relations, 'image_service_joined') | ||
1032 | 414 | @patch.object(relations, 'CONFIGS') | ||
1033 | 415 | def test_configure_https_disable_with_image_service(self, configs, image_service_joined): | ||
1034 | 416 | configs.complete_contexts = MagicMock() | ||
1035 | 417 | configs.complete_contexts.return_value = [''] | ||
1036 | 418 | configs.write = MagicMock() | ||
1037 | 419 | self.relation_ids.return_value = ['image-service:0'] | ||
1038 | 420 | relations.configure_https() | ||
1039 | 421 | cmd = ['a2dissite', 'openstack_https_frontend'] | ||
1040 | 422 | self.check_call.assert_called_with(cmd) | ||
1041 | 423 | image_service_joined.assert_called_with(relation_id='image-service:0') | ||
1042 | 424 | |||
1043 | 425 | def test_amqp_joined(self): | ||
1044 | 426 | relations.amqp_joined() | ||
1045 | 427 | self.relation_set.assert_called_with(username='glance', vhost='openstack') | ||
1046 | 428 | |||
1047 | 429 | @patch.object(relations, 'CONFIGS') | ||
1048 | 430 | def test_amqp_changed_missing_relation_data(self, configs): | ||
1049 | 431 | configs.complete_contexts = MagicMock() | ||
1050 | 432 | configs.complete_contexts.return_value = [] | ||
1051 | 433 | relations.amqp_changed() | ||
1052 | 434 | self.juju_log.assert_called() | ||
1053 | 435 | |||
1054 | 436 | @patch.object(relations, 'CONFIGS') | ||
1055 | 437 | def test_amqp_changed_relation_data(self, configs): | ||
1056 | 438 | configs.complete_contexts = MagicMock() | ||
1057 | 439 | configs.complete_contexts.return_value = ['amqp'] | ||
1058 | 440 | configs.write = MagicMock() | ||
1059 | 441 | relations.amqp_changed() | ||
1060 | 442 | self.assertEquals([call('/etc/glance/glance-api.conf')], | ||
1061 | 443 | configs.write.call_args_list) | ||
1062 | 444 | self.assertFalse(self.juju_log.called) | ||
1063 | 374 | 445 | ||
1064 | 375 | @patch.object(relations, 'keystone_joined') | 446 | @patch.object(relations, 'keystone_joined') |
1065 | 376 | def test_ha_relation_changed_not_leader(self, joined): | 447 | def test_ha_relation_changed_not_leader(self, joined): |