Merge lp:~1chb1n/charms/trusty/glance/next-amulet-debug-and-makefile into lp:~openstack-charmers-archive/charms/trusty/glance/trunk

Proposed by Ryan Beisner
Status: Superseded
Proposed branch: lp:~1chb1n/charms/trusty/glance/next-amulet-debug-and-makefile
Merge into: lp:~openstack-charmers-archive/charms/trusty/glance/trunk
Diff against target: 4234 lines (+2604/-318)
57 files modified
.bzrignore (+1/-0)
Makefile (+2/-3)
README.md (+82/-0)
actions.yaml (+2/-0)
actions/git_reinstall.py (+45/-0)
config.yaml (+22/-0)
hooks/charmhelpers/contrib/charmsupport/nrpe.py (+41/-7)
hooks/charmhelpers/contrib/hahelpers/cluster.py (+5/-1)
hooks/charmhelpers/contrib/network/ip.py (+84/-1)
hooks/charmhelpers/contrib/openstack/amulet/deployment.py (+34/-5)
hooks/charmhelpers/contrib/openstack/context.py (+280/-13)
hooks/charmhelpers/contrib/openstack/files/__init__.py (+18/-0)
hooks/charmhelpers/contrib/openstack/files/check_haproxy.sh (+32/-0)
hooks/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh (+30/-0)
hooks/charmhelpers/contrib/openstack/ip.py (+37/-0)
hooks/charmhelpers/contrib/openstack/neutron.py (+83/-0)
hooks/charmhelpers/contrib/openstack/templates/git.upstart (+17/-0)
hooks/charmhelpers/contrib/openstack/templates/section-keystone-authtoken (+9/-0)
hooks/charmhelpers/contrib/openstack/templates/section-rabbitmq-oslo (+22/-0)
hooks/charmhelpers/contrib/openstack/templates/section-zeromq (+14/-0)
hooks/charmhelpers/contrib/openstack/utils.py (+142/-141)
hooks/charmhelpers/contrib/python/packages.py (+2/-2)
hooks/charmhelpers/core/fstab.py (+4/-4)
hooks/charmhelpers/core/hookenv.py (+40/-1)
hooks/charmhelpers/core/host.py (+10/-6)
hooks/charmhelpers/core/services/helpers.py (+12/-4)
hooks/charmhelpers/core/strutils.py (+42/-0)
hooks/charmhelpers/core/sysctl.py (+2/-2)
hooks/charmhelpers/core/templating.py (+3/-3)
hooks/charmhelpers/core/unitdata.py (+477/-0)
hooks/charmhelpers/fetch/archiveurl.py (+10/-10)
hooks/charmhelpers/fetch/giturl.py (+1/-1)
hooks/glance_relations.py (+43/-17)
hooks/glance_utils.py (+139/-9)
templates/kilo/glance-api-paste.ini (+77/-0)
templates/kilo/glance-api.conf (+83/-0)
templates/kilo/glance-registry-paste.ini (+30/-0)
templates/kilo/glance-registry.conf (+27/-0)
templates/parts/keystone (+1/-0)
templates/parts/section-database (+1/-0)
tests/016-basic-trusty-juno (+11/-0)
tests/017-basic-trusty-kilo (+11/-0)
tests/018-basic-utopic-juno (+9/-0)
tests/019-basic-vivid-kilo (+9/-0)
tests/050-basic-trusty-icehouse-git (+9/-0)
tests/051-basic-trusty-juno-git (+12/-0)
tests/10-basic-precise-essex (+0/-9)
tests/11-basic-precise-folsom (+0/-11)
tests/12-basic-precise-grizzly (+0/-11)
tests/13-basic-precise-havana (+0/-11)
tests/basic_deployment.py (+27/-4)
tests/charmhelpers/contrib/amulet/utils.py (+125/-3)
tests/charmhelpers/contrib/openstack/amulet/deployment.py (+34/-5)
unit_tests/__init__.py (+1/-0)
unit_tests/test_actions_git_reinstall.py (+96/-0)
unit_tests/test_glance_relations.py (+113/-26)
unit_tests/test_glance_utils.py (+141/-8)
To merge this branch: bzr merge lp:~1chb1n/charms/trusty/glance/next-amulet-debug-and-makefile
Reviewer Review Type Date Requested Status
OpenStack Charmers Pending
Review via email: mp+256581@code.launchpad.net

This proposal has been superseded by a proposal from 2015-04-17.

Description of the change

auto normalize amulet test definitions and amulet make targets; charm-helper sync.

To post a comment you must log in.
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #3538 glance for 1chb1n mp256581
    LINT OK: passed

Build: http://10.245.162.77:8080/job/charm_lint_check/3538/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #3326 glance for 1chb1n mp256581
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/3326/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #3293 glance for 1chb1n mp256581
    AMULET FAIL: amulet-test failed

AMULET Results (max last 2 lines):
make: *** [test] Error 1
ERROR:root:Make target returned non-zero.

Full amulet test output: http://paste.ubuntu.com/10835861/
Build: http://10.245.162.77:8080/job/charm_amulet_test/3293/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #3577 glance for 1chb1n mp256581
    LINT OK: passed

Build: http://10.245.162.77:8080/job/charm_lint_check/3577/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #3365 glance for 1chb1n mp256581
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/3365/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #3334 glance for 1chb1n mp256581
    AMULET OK: passed

Build: http://10.245.162.77:8080/job/charm_amulet_test/3334/

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file '.bzrignore'
--- .bzrignore 2014-07-02 08:09:03 +0000
+++ .bzrignore 2015-04-16 21:50:07 +0000
@@ -1,2 +1,3 @@
1.coverage1.coverage
2bin2bin
3tags
34
=== modified file 'Makefile'
--- Makefile 2014-10-08 20:18:38 +0000
+++ Makefile 2015-04-16 21:50:07 +0000
@@ -3,7 +3,7 @@
33
4lint:4lint:
5 @echo "Running flake8 tests: "5 @echo "Running flake8 tests: "
6 @flake8 --exclude hooks/charmhelpers hooks unit_tests tests6 @flake8 --exclude hooks/charmhelpers actions hooks unit_tests tests
7 @echo "OK"7 @echo "OK"
8 @echo "Running charm proof: "8 @echo "Running charm proof: "
9 @charm proof9 @charm proof
@@ -26,8 +26,7 @@
26 # /!\ Note: The -v should only be temporary until Amulet sends26 # /!\ Note: The -v should only be temporary until Amulet sends
27 # raise_status() messages to stderr:27 # raise_status() messages to stderr:
28 # https://bugs.launchpad.net/amulet/+bug/132035728 # https://bugs.launchpad.net/amulet/+bug/1320357
29 @juju test -v -p AMULET_HTTP_PROXY --timeout 900 \29 @juju test -v -p AMULET_HTTP_PROXY,AMULET_OS_VIP --timeout 2700
30 00-setup 14-basic-precise-icehouse 15-basic-trusty-icehouse
3130
32publish: lint unit_test31publish: lint unit_test
33 bzr push lp:charms/glance32 bzr push lp:charms/glance
3433
=== modified file 'README.md'
--- README.md 2013-09-26 10:22:09 +0000
+++ README.md 2015-04-16 21:50:07 +0000
@@ -81,6 +81,88 @@
81Note that Glance in this configuration must be used with either Ceph or81Note that Glance in this configuration must be used with either Ceph or
82Swift providing backing image storage.82Swift providing backing image storage.
8383
84Deploying from source
85---------------------
86
87The minimum openstack-origin-git config required to deploy from source is:
88
89 openstack-origin-git:
90 "repositories:
91 - {name: requirements,
92 repository: 'git://git.openstack.org/openstack/requirements',
93 branch: stable/juno}
94 - {name: glance,
95 repository: 'git://git.openstack.org/openstack/glance',
96 branch: stable/juno}"
97
98Note that there are only two 'name' values the charm knows about: 'requirements'
99and 'glance'. These repositories must correspond to these 'name' values.
100Additionally, the requirements repository must be specified first and the
101glance repository must be specified last. All other repostories are installed
102in the order in which they are specified.
103
104The following is a full list of current tip repos (may not be up-to-date):
105
106 openstack-origin-git:
107 "repositories:
108 - {name: requirements,
109 repository: 'git://git.openstack.org/openstack/requirements',
110 branch: master}
111 - {name: oslo-concurrency,
112 repository: 'git://git.openstack.org/openstack/oslo.concurrency',
113 branch: master}
114 - {name: oslo-config,
115 repository: 'git://git.openstack.org/openstack/oslo.config',
116 branch: master}
117 - {name: oslo-db,
118 repository: 'git://git.openstack.org/openstack/oslo.db',
119 branch: master}
120 - {name: oslo-i18n,
121 repository: 'git://git.openstack.org/openstack/oslo.i18n',
122 branch: master}
123 - {name: oslo-messaging,
124 repository: 'git://git.openstack.org/openstack/oslo.messaging',
125 branch: master}
126 - {name: oslo-serialization,
127 repository: 'git://git.openstack.org/openstack/oslo.serialization',
128 branch: master}
129 - {name: oslo-utils,
130 repository: 'git://git.openstack.org/openstack/oslo.utils',
131 branch: master}
132 - {name: oslo-vmware,
133 repository: 'git://git.openstack.org/openstack/oslo.vmware',
134 branch: master}
135 - {name: osprofiler,
136 repository: 'git://git.openstack.org/stackforge/osprofiler',
137 branch: master}
138 - {name: pbr,
139 repository: 'git://git.openstack.org/openstack-dev/pbr',
140 branch: master}
141 - {name: python-keystoneclient,
142 repository: 'git://git.openstack.org/openstack/python-keystoneclient',
143 branch: master}
144 - {name: python-swiftclient,
145 repository: 'git://git.openstack.org/openstack/python-swiftclient',
146 branch: master}
147 - {name: sqlalchemy-migrate,
148 repository: 'git://git.openstack.org/stackforge/sqlalchemy-migrate',
149 branch: master}
150 - {name: stevedore,
151 repository: 'git://git.openstack.org/openstack/stevedore',
152 branch: master}
153 - {name: wsme,
154 repository: 'git://git.openstack.org/stackforge/wsme',
155 branch: master}
156 - {name: keystonemiddleware,
157 repository: 'git://git.openstack.org/openstack/keystonemiddleware',
158 branch: master}
159 - {name: glance-store,
160 repository: 'git://git.openstack.org/openstack/glance_store',
161 branch: master}
162 - {name: glance,
163 repository: 'git://git.openstack.org/openstack/glance',
164 branch: master}"
165
84Contact Information166Contact Information
85-------------------167-------------------
86168
87169
=== added directory 'actions'
=== added file 'actions.yaml'
--- actions.yaml 1970-01-01 00:00:00 +0000
+++ actions.yaml 2015-04-16 21:50:07 +0000
@@ -0,0 +1,2 @@
1git-reinstall:
2 description: Reinstall glance from the openstack-origin-git repositories.
03
=== added symlink 'actions/git-reinstall'
=== target is u'git_reinstall.py'
=== added file 'actions/git_reinstall.py'
--- actions/git_reinstall.py 1970-01-01 00:00:00 +0000
+++ actions/git_reinstall.py 2015-04-16 21:50:07 +0000
@@ -0,0 +1,45 @@
1#!/usr/bin/python
2import sys
3import traceback
4
5sys.path.append('hooks/')
6
7from charmhelpers.contrib.openstack.utils import (
8 git_install_requested,
9)
10
11from charmhelpers.core.hookenv import (
12 action_set,
13 action_fail,
14 config,
15)
16
17from glance_utils import (
18 git_install,
19)
20
21from glance_relations import (
22 config_changed,
23)
24
25
26def git_reinstall():
27 """Reinstall from source and restart services.
28
29 If the openstack-origin-git config option was used to install openstack
30 from source git repositories, then this action can be used to reinstall
31 from updated git repositories, followed by a restart of services."""
32 if not git_install_requested():
33 action_fail('openstack-origin-git is not configured')
34 return
35
36 try:
37 git_install(config('openstack-origin-git'))
38 config_changed()
39 except:
40 action_set({'traceback': traceback.format_exc()})
41 action_fail('git-reinstall resulted in an unexpected error')
42
43
44if __name__ == '__main__':
45 git_reinstall()
046
=== modified file 'config.yaml'
--- config.yaml 2015-01-21 14:38:50 +0000
+++ config.yaml 2015-04-16 21:50:07 +0000
@@ -14,6 +14,22 @@
14 Note that updating this setting to a source that is known to14 Note that updating this setting to a source that is known to
15 provide a later version of OpenStack will trigger a software15 provide a later version of OpenStack will trigger a software
16 upgrade.16 upgrade.
17
18 Note that when openstack-origin-git is specified, openstack
19 specific packages will be installed from source rather than
20 from the openstack-origin repository.
21 openstack-origin-git:
22 default:
23 type: string
24 description: |
25 Specifies a YAML-formatted dictionary listing the git
26 repositories and branches from which to install OpenStack and
27 its dependencies.
28
29 Note that the installed config files will be determined based on
30 the OpenStack release of the openstack-origin option.
31
32 For more details see README.md.
17 database-user:33 database-user:
18 default: glance34 default: glance
19 type: string35 type: string
@@ -189,4 +205,10 @@
189 juju-myservice-0205 juju-myservice-0
190 If you're running multiple environments with the same services in them206 If you're running multiple environments with the same services in them
191 this allows you to differentiate between them.207 this allows you to differentiate between them.
208 nagios_servicegroups:
209 default: ""
210 type: string
211 description: |
212 A comma-separated list of nagios servicegroups.
213 If left empty, the nagios_context will be used as the servicegroup
192214
193215
=== modified file 'hooks/charmhelpers/contrib/charmsupport/nrpe.py'
--- hooks/charmhelpers/contrib/charmsupport/nrpe.py 2015-01-26 09:45:23 +0000
+++ hooks/charmhelpers/contrib/charmsupport/nrpe.py 2015-04-16 21:50:07 +0000
@@ -24,6 +24,8 @@
24import pwd24import pwd
25import grp25import grp
26import os26import os
27import glob
28import shutil
27import re29import re
28import shlex30import shlex
29import yaml31import yaml
@@ -161,7 +163,7 @@
161 log('Check command not found: {}'.format(parts[0]))163 log('Check command not found: {}'.format(parts[0]))
162 return ''164 return ''
163165
164 def write(self, nagios_context, hostname, nagios_servicegroups=None):166 def write(self, nagios_context, hostname, nagios_servicegroups):
165 nrpe_check_file = '/etc/nagios/nrpe.d/{}.cfg'.format(167 nrpe_check_file = '/etc/nagios/nrpe.d/{}.cfg'.format(
166 self.command)168 self.command)
167 with open(nrpe_check_file, 'w') as nrpe_check_config:169 with open(nrpe_check_file, 'w') as nrpe_check_config:
@@ -177,14 +179,11 @@
177 nagios_servicegroups)179 nagios_servicegroups)
178180
179 def write_service_config(self, nagios_context, hostname,181 def write_service_config(self, nagios_context, hostname,
180 nagios_servicegroups=None):182 nagios_servicegroups):
181 for f in os.listdir(NRPE.nagios_exportdir):183 for f in os.listdir(NRPE.nagios_exportdir):
182 if re.search('.*{}.cfg'.format(self.command), f):184 if re.search('.*{}.cfg'.format(self.command), f):
183 os.remove(os.path.join(NRPE.nagios_exportdir, f))185 os.remove(os.path.join(NRPE.nagios_exportdir, f))
184186
185 if not nagios_servicegroups:
186 nagios_servicegroups = nagios_context
187
188 templ_vars = {187 templ_vars = {
189 'nagios_hostname': hostname,188 'nagios_hostname': hostname,
190 'nagios_servicegroup': nagios_servicegroups,189 'nagios_servicegroup': nagios_servicegroups,
@@ -211,10 +210,10 @@
211 super(NRPE, self).__init__()210 super(NRPE, self).__init__()
212 self.config = config()211 self.config = config()
213 self.nagios_context = self.config['nagios_context']212 self.nagios_context = self.config['nagios_context']
214 if 'nagios_servicegroups' in self.config:213 if 'nagios_servicegroups' in self.config and self.config['nagios_servicegroups']:
215 self.nagios_servicegroups = self.config['nagios_servicegroups']214 self.nagios_servicegroups = self.config['nagios_servicegroups']
216 else:215 else:
217 self.nagios_servicegroups = 'juju'216 self.nagios_servicegroups = self.nagios_context
218 self.unit_name = local_unit().replace('/', '-')217 self.unit_name = local_unit().replace('/', '-')
219 if hostname:218 if hostname:
220 self.hostname = hostname219 self.hostname = hostname
@@ -322,3 +321,38 @@
322 check_cmd='check_status_file.py -f '321 check_cmd='check_status_file.py -f '
323 '/var/lib/nagios/service-check-%s.txt' % svc,322 '/var/lib/nagios/service-check-%s.txt' % svc,
324 )323 )
324
325
326def copy_nrpe_checks():
327 """
328 Copy the nrpe checks into place
329
330 """
331 NAGIOS_PLUGINS = '/usr/local/lib/nagios/plugins'
332 nrpe_files_dir = os.path.join(os.getenv('CHARM_DIR'), 'hooks',
333 'charmhelpers', 'contrib', 'openstack',
334 'files')
335
336 if not os.path.exists(NAGIOS_PLUGINS):
337 os.makedirs(NAGIOS_PLUGINS)
338 for fname in glob.glob(os.path.join(nrpe_files_dir, "check_*")):
339 if os.path.isfile(fname):
340 shutil.copy2(fname,
341 os.path.join(NAGIOS_PLUGINS, os.path.basename(fname)))
342
343
344def add_haproxy_checks(nrpe, unit_name):
345 """
346 Add checks for each service in list
347
348 :param NRPE nrpe: NRPE object to add check to
349 :param str unit_name: Unit name to use in check description
350 """
351 nrpe.add_check(
352 shortname='haproxy_servers',
353 description='Check HAProxy {%s}' % unit_name,
354 check_cmd='check_haproxy.sh')
355 nrpe.add_check(
356 shortname='haproxy_queue',
357 description='Check HAProxy queue depth {%s}' % unit_name,
358 check_cmd='check_haproxy_queue_depth.sh')
325359
=== modified file 'hooks/charmhelpers/contrib/hahelpers/cluster.py'
--- hooks/charmhelpers/contrib/hahelpers/cluster.py 2015-01-26 09:45:23 +0000
+++ hooks/charmhelpers/contrib/hahelpers/cluster.py 2015-04-16 21:50:07 +0000
@@ -48,6 +48,9 @@
48from charmhelpers.core.decorators import (48from charmhelpers.core.decorators import (
49 retry_on_exception,49 retry_on_exception,
50)50)
51from charmhelpers.core.strutils import (
52 bool_from_string,
53)
5154
5255
53class HAIncompleteConfig(Exception):56class HAIncompleteConfig(Exception):
@@ -164,7 +167,8 @@
164 .167 .
165 returns: boolean168 returns: boolean
166 '''169 '''
167 if config_get('use-https') == "yes":170 use_https = config_get('use-https')
171 if use_https and bool_from_string(use_https):
168 return True172 return True
169 if config_get('ssl_cert') and config_get('ssl_key'):173 if config_get('ssl_cert') and config_get('ssl_key'):
170 return True174 return True
171175
=== modified file 'hooks/charmhelpers/contrib/network/ip.py'
--- hooks/charmhelpers/contrib/network/ip.py 2015-01-26 09:45:23 +0000
+++ hooks/charmhelpers/contrib/network/ip.py 2015-04-16 21:50:07 +0000
@@ -17,13 +17,16 @@
17import glob17import glob
18import re18import re
19import subprocess19import subprocess
20import six
21import socket
2022
21from functools import partial23from functools import partial
2224
23from charmhelpers.core.hookenv import unit_get25from charmhelpers.core.hookenv import unit_get
24from charmhelpers.fetch import apt_install26from charmhelpers.fetch import apt_install
25from charmhelpers.core.hookenv import (27from charmhelpers.core.hookenv import (
26 log28 log,
29 WARNING,
27)30)
2831
29try:32try:
@@ -365,3 +368,83 @@
365 return True368 return True
366369
367 return False370 return False
371
372
373def is_ip(address):
374 """
375 Returns True if address is a valid IP address.
376 """
377 try:
378 # Test to see if already an IPv4 address
379 socket.inet_aton(address)
380 return True
381 except socket.error:
382 return False
383
384
385def ns_query(address):
386 try:
387 import dns.resolver
388 except ImportError:
389 apt_install('python-dnspython')
390 import dns.resolver
391
392 if isinstance(address, dns.name.Name):
393 rtype = 'PTR'
394 elif isinstance(address, six.string_types):
395 rtype = 'A'
396 else:
397 return None
398
399 answers = dns.resolver.query(address, rtype)
400 if answers:
401 return str(answers[0])
402 return None
403
404
405def get_host_ip(hostname, fallback=None):
406 """
407 Resolves the IP for a given hostname, or returns
408 the input if it is already an IP.
409 """
410 if is_ip(hostname):
411 return hostname
412
413 ip_addr = ns_query(hostname)
414 if not ip_addr:
415 try:
416 ip_addr = socket.gethostbyname(hostname)
417 except:
418 log("Failed to resolve hostname '%s'" % (hostname),
419 level=WARNING)
420 return fallback
421 return ip_addr
422
423
424def get_hostname(address, fqdn=True):
425 """
426 Resolves hostname for given IP, or returns the input
427 if it is already a hostname.
428 """
429 if is_ip(address):
430 try:
431 import dns.reversename
432 except ImportError:
433 apt_install("python-dnspython")
434 import dns.reversename
435
436 rev = dns.reversename.from_address(address)
437 result = ns_query(rev)
438 if not result:
439 return None
440 else:
441 result = address
442
443 if fqdn:
444 # strip trailing .
445 if result.endswith('.'):
446 return result[:-1]
447 else:
448 return result
449 else:
450 return result.split('.')[0]
368451
=== modified file 'hooks/charmhelpers/contrib/openstack/amulet/deployment.py'
--- hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2015-01-26 09:45:23 +0000
+++ hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2015-04-16 21:50:07 +0000
@@ -15,6 +15,7 @@
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1616
17import six17import six
18from collections import OrderedDict
18from charmhelpers.contrib.amulet.deployment import (19from charmhelpers.contrib.amulet.deployment import (
19 AmuletDeployment20 AmuletDeployment
20)21)
@@ -43,7 +44,7 @@
43 Determine if the local branch being tested is derived from its44 Determine if the local branch being tested is derived from its
44 stable or next (dev) branch, and based on this, use the corresonding45 stable or next (dev) branch, and based on this, use the corresonding
45 stable or next branches for the other_services."""46 stable or next branches for the other_services."""
46 base_charms = ['mysql', 'mongodb', 'rabbitmq-server']47 base_charms = ['mysql', 'mongodb']
4748
48 if self.stable:49 if self.stable:
49 for svc in other_services:50 for svc in other_services:
@@ -71,16 +72,19 @@
71 services.append(this_service)72 services.append(this_service)
72 use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph',73 use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph',
73 'ceph-osd', 'ceph-radosgw']74 'ceph-osd', 'ceph-radosgw']
75 # Openstack subordinate charms do not expose an origin option as that
76 # is controlled by the principle
77 ignore = ['neutron-openvswitch']
7478
75 if self.openstack:79 if self.openstack:
76 for svc in services:80 for svc in services:
77 if svc['name'] not in use_source:81 if svc['name'] not in use_source + ignore:
78 config = {'openstack-origin': self.openstack}82 config = {'openstack-origin': self.openstack}
79 self.d.configure(svc['name'], config)83 self.d.configure(svc['name'], config)
8084
81 if self.source:85 if self.source:
82 for svc in services:86 for svc in services:
83 if svc['name'] in use_source:87 if svc['name'] in use_source and svc['name'] not in ignore:
84 config = {'source': self.source}88 config = {'source': self.source}
85 self.d.configure(svc['name'], config)89 self.d.configure(svc['name'], config)
8690
@@ -97,12 +101,37 @@
97 """101 """
98 (self.precise_essex, self.precise_folsom, self.precise_grizzly,102 (self.precise_essex, self.precise_folsom, self.precise_grizzly,
99 self.precise_havana, self.precise_icehouse,103 self.precise_havana, self.precise_icehouse,
100 self.trusty_icehouse) = range(6)104 self.trusty_icehouse, self.trusty_juno, self.trusty_kilo,
105 self.utopic_juno, self.vivid_kilo) = range(10)
101 releases = {106 releases = {
102 ('precise', None): self.precise_essex,107 ('precise', None): self.precise_essex,
103 ('precise', 'cloud:precise-folsom'): self.precise_folsom,108 ('precise', 'cloud:precise-folsom'): self.precise_folsom,
104 ('precise', 'cloud:precise-grizzly'): self.precise_grizzly,109 ('precise', 'cloud:precise-grizzly'): self.precise_grizzly,
105 ('precise', 'cloud:precise-havana'): self.precise_havana,110 ('precise', 'cloud:precise-havana'): self.precise_havana,
106 ('precise', 'cloud:precise-icehouse'): self.precise_icehouse,111 ('precise', 'cloud:precise-icehouse'): self.precise_icehouse,
107 ('trusty', None): self.trusty_icehouse}112 ('trusty', None): self.trusty_icehouse,
113 ('trusty', 'cloud:trusty-juno'): self.trusty_juno,
114 ('trusty', 'cloud:trusty-kilo'): self.trusty_kilo,
115 ('utopic', None): self.utopic_juno,
116 ('vivid', None): self.vivid_kilo}
108 return releases[(self.series, self.openstack)]117 return releases[(self.series, self.openstack)]
118
119 def _get_openstack_release_string(self):
120 """Get openstack release string.
121
122 Return a string representing the openstack release.
123 """
124 releases = OrderedDict([
125 ('precise', 'essex'),
126 ('quantal', 'folsom'),
127 ('raring', 'grizzly'),
128 ('saucy', 'havana'),
129 ('trusty', 'icehouse'),
130 ('utopic', 'juno'),
131 ('vivid', 'kilo'),
132 ])
133 if self.openstack:
134 os_origin = self.openstack.split(':')[1]
135 return os_origin.split('%s-' % self.series)[1].split('/')[0]
136 else:
137 return releases[self.series]
109138
=== modified file 'hooks/charmhelpers/contrib/openstack/context.py'
--- hooks/charmhelpers/contrib/openstack/context.py 2015-03-19 21:56:40 +0000
+++ hooks/charmhelpers/contrib/openstack/context.py 2015-04-16 21:50:07 +0000
@@ -16,11 +16,13 @@
1616
17import json17import json
18import os18import os
19import re
19import time20import time
20from base64 import b64decode21from base64 import b64decode
21from subprocess import check_call22from subprocess import check_call
2223
23import six24import six
25import yaml
2426
25from charmhelpers.fetch import (27from charmhelpers.fetch import (
26 apt_install,28 apt_install,
@@ -45,8 +47,11 @@
45)47)
4648
47from charmhelpers.core.sysctl import create as sysctl_create49from charmhelpers.core.sysctl import create as sysctl_create
50from charmhelpers.core.strutils import bool_from_string
4851
49from charmhelpers.core.host import (52from charmhelpers.core.host import (
53 list_nics,
54 get_nic_hwaddr,
50 mkdir,55 mkdir,
51 write_file,56 write_file,
52)57)
@@ -63,6 +68,11 @@
63)68)
64from charmhelpers.contrib.openstack.neutron import (69from charmhelpers.contrib.openstack.neutron import (
65 neutron_plugin_attribute,70 neutron_plugin_attribute,
71 parse_data_port_mappings,
72)
73from charmhelpers.contrib.openstack.ip import (
74 resolve_address,
75 INTERNAL,
66)76)
67from charmhelpers.contrib.openstack.ip import (77from charmhelpers.contrib.openstack.ip import (
68 resolve_address,78 resolve_address,
@@ -70,13 +80,14 @@
70)80)
71from charmhelpers.contrib.network.ip import (81from charmhelpers.contrib.network.ip import (
72 get_address_in_network,82 get_address_in_network,
83 get_ipv4_addr,
73 get_ipv6_addr,84 get_ipv6_addr,
74 get_netmask_for_address,85 get_netmask_for_address,
75 format_ipv6_addr,86 format_ipv6_addr,
76 is_address_in_network,87 is_address_in_network,
88 is_bridge_member,
77)89)
78from charmhelpers.contrib.openstack.utils import get_host_ip90from charmhelpers.contrib.openstack.utils import get_host_ip
79
80CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'91CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
81ADDRESS_TYPES = ['admin', 'internal', 'public']92ADDRESS_TYPES = ['admin', 'internal', 'public']
8293
@@ -108,9 +119,41 @@
108def config_flags_parser(config_flags):119def config_flags_parser(config_flags):
109 """Parses config flags string into dict.120 """Parses config flags string into dict.
110121
122 This parsing method supports a few different formats for the config
123 flag values to be parsed:
124
125 1. A string in the simple format of key=value pairs, with the possibility
126 of specifying multiple key value pairs within the same string. For
127 example, a string in the format of 'key1=value1, key2=value2' will
128 return a dict of:
129 {'key1': 'value1',
130 'key2': 'value2'}.
131
132 2. A string in the above format, but supporting a comma-delimited list
133 of values for the same key. For example, a string in the format of
134 'key1=value1, key2=value3,value4,value5' will return a dict of:
135 {'key1', 'value1',
136 'key2', 'value2,value3,value4'}
137
138 3. A string containing a colon character (:) prior to an equal
139 character (=) will be treated as yaml and parsed as such. This can be
140 used to specify more complex key value pairs. For example,
141 a string in the format of 'key1: subkey1=value1, subkey2=value2' will
142 return a dict of:
143 {'key1', 'subkey1=value1, subkey2=value2'}
144
111 The provided config_flags string may be a list of comma-separated values145 The provided config_flags string may be a list of comma-separated values
112 which themselves may be comma-separated list of values.146 which themselves may be comma-separated list of values.
113 """147 """
148 # If we find a colon before an equals sign then treat it as yaml.
149 # Note: limit it to finding the colon first since this indicates assignment
150 # for inline yaml.
151 colon = config_flags.find(':')
152 equals = config_flags.find('=')
153 if colon > 0:
154 if colon < equals or equals < 0:
155 return yaml.safe_load(config_flags)
156
114 if config_flags.find('==') >= 0:157 if config_flags.find('==') >= 0:
115 log("config_flags is not in expected format (key=value)", level=ERROR)158 log("config_flags is not in expected format (key=value)", level=ERROR)
116 raise OSContextError159 raise OSContextError
@@ -281,12 +324,29 @@
281324
282325
283class IdentityServiceContext(OSContextGenerator):326class IdentityServiceContext(OSContextGenerator):
284 interfaces = ['identity-service']327
328 def __init__(self, service=None, service_user=None, rel_name='identity-service'):
329 self.service = service
330 self.service_user = service_user
331 self.rel_name = rel_name
332 self.interfaces = [self.rel_name]
285333
286 def __call__(self):334 def __call__(self):
287 log('Generating template context for identity-service', level=DEBUG)335 log('Generating template context for ' + self.rel_name, level=DEBUG)
288 ctxt = {}336 ctxt = {}
289 for rid in relation_ids('identity-service'):337
338 if self.service and self.service_user:
339 # This is required for pki token signing if we don't want /tmp to
340 # be used.
341 cachedir = '/var/cache/%s' % (self.service)
342 if not os.path.isdir(cachedir):
343 log("Creating service cache dir %s" % (cachedir), level=DEBUG)
344 mkdir(path=cachedir, owner=self.service_user,
345 group=self.service_user, perms=0o700)
346
347 ctxt['signing_dir'] = cachedir
348
349 for rid in relation_ids(self.rel_name):
290 for unit in related_units(rid):350 for unit in related_units(rid):
291 rdata = relation_get(rid=rid, unit=unit)351 rdata = relation_get(rid=rid, unit=unit)
292 serv_host = rdata.get('service_host')352 serv_host = rdata.get('service_host')
@@ -295,15 +355,16 @@
295 auth_host = format_ipv6_addr(auth_host) or auth_host355 auth_host = format_ipv6_addr(auth_host) or auth_host
296 svc_protocol = rdata.get('service_protocol') or 'http'356 svc_protocol = rdata.get('service_protocol') or 'http'
297 auth_protocol = rdata.get('auth_protocol') or 'http'357 auth_protocol = rdata.get('auth_protocol') or 'http'
298 ctxt = {'service_port': rdata.get('service_port'),358 ctxt.update({'service_port': rdata.get('service_port'),
299 'service_host': serv_host,359 'service_host': serv_host,
300 'auth_host': auth_host,360 'auth_host': auth_host,
301 'auth_port': rdata.get('auth_port'),361 'auth_port': rdata.get('auth_port'),
302 'admin_tenant_name': rdata.get('service_tenant'),362 'admin_tenant_name': rdata.get('service_tenant'),
303 'admin_user': rdata.get('service_username'),363 'admin_user': rdata.get('service_username'),
304 'admin_password': rdata.get('service_password'),364 'admin_password': rdata.get('service_password'),
305 'service_protocol': svc_protocol,365 'service_protocol': svc_protocol,
306 'auth_protocol': auth_protocol}366 'auth_protocol': auth_protocol})
367
307 if context_complete(ctxt):368 if context_complete(ctxt):
308 # NOTE(jamespage) this is required for >= icehouse369 # NOTE(jamespage) this is required for >= icehouse
309 # so a missing value just indicates keystone needs370 # so a missing value just indicates keystone needs
@@ -402,6 +463,11 @@
402463
403 ctxt['rabbitmq_hosts'] = ','.join(sorted(rabbitmq_hosts))464 ctxt['rabbitmq_hosts'] = ','.join(sorted(rabbitmq_hosts))
404465
466 oslo_messaging_flags = conf.get('oslo-messaging-flags', None)
467 if oslo_messaging_flags:
468 ctxt['oslo_messaging_flags'] = config_flags_parser(
469 oslo_messaging_flags)
470
405 if not context_complete(ctxt):471 if not context_complete(ctxt):
406 return {}472 return {}
407473
@@ -751,6 +817,19 @@
751817
752 return ovs_ctxt818 return ovs_ctxt
753819
820 def nuage_ctxt(self):
821 driver = neutron_plugin_attribute(self.plugin, 'driver',
822 self.network_manager)
823 config = neutron_plugin_attribute(self.plugin, 'config',
824 self.network_manager)
825 nuage_ctxt = {'core_plugin': driver,
826 'neutron_plugin': 'vsp',
827 'neutron_security_groups': self.neutron_security_groups,
828 'local_ip': unit_private_ip(),
829 'config': config}
830
831 return nuage_ctxt
832
754 def nvp_ctxt(self):833 def nvp_ctxt(self):
755 driver = neutron_plugin_attribute(self.plugin, 'driver',834 driver = neutron_plugin_attribute(self.plugin, 'driver',
756 self.network_manager)835 self.network_manager)
@@ -834,6 +913,8 @@
834 ctxt.update(self.n1kv_ctxt())913 ctxt.update(self.n1kv_ctxt())
835 elif self.plugin == 'Calico':914 elif self.plugin == 'Calico':
836 ctxt.update(self.calico_ctxt())915 ctxt.update(self.calico_ctxt())
916 elif self.plugin == 'vsp':
917 ctxt.update(self.nuage_ctxt())
837918
838 alchemy_flags = config('neutron-alchemy-flags')919 alchemy_flags = config('neutron-alchemy-flags')
839 if alchemy_flags:920 if alchemy_flags:
@@ -844,6 +925,48 @@
844 return ctxt925 return ctxt
845926
846927
928class NeutronPortContext(OSContextGenerator):
929 NIC_PREFIXES = ['eth', 'bond']
930
931 def resolve_ports(self, ports):
932 """Resolve NICs not yet bound to bridge(s)
933
934 If hwaddress provided then returns resolved hwaddress otherwise NIC.
935 """
936 if not ports:
937 return None
938
939 hwaddr_to_nic = {}
940 hwaddr_to_ip = {}
941 for nic in list_nics(self.NIC_PREFIXES):
942 hwaddr = get_nic_hwaddr(nic)
943 hwaddr_to_nic[hwaddr] = nic
944 addresses = get_ipv4_addr(nic, fatal=False)
945 addresses += get_ipv6_addr(iface=nic, fatal=False)
946 hwaddr_to_ip[hwaddr] = addresses
947
948 resolved = []
949 mac_regex = re.compile(r'([0-9A-F]{2}[:-]){5}([0-9A-F]{2})', re.I)
950 for entry in ports:
951 if re.match(mac_regex, entry):
952 # NIC is in known NICs and does NOT hace an IP address
953 if entry in hwaddr_to_nic and not hwaddr_to_ip[entry]:
954 # If the nic is part of a bridge then don't use it
955 if is_bridge_member(hwaddr_to_nic[entry]):
956 continue
957
958 # Entry is a MAC address for a valid interface that doesn't
959 # have an IP address assigned yet.
960 resolved.append(hwaddr_to_nic[entry])
961 else:
962 # If the passed entry is not a MAC address, assume it's a valid
963 # interface, and that the user put it there on purpose (we can
964 # trust it to be the real external network).
965 resolved.append(entry)
966
967 return resolved
968
969
847class OSConfigFlagContext(OSContextGenerator):970class OSConfigFlagContext(OSContextGenerator):
848 """Provides support for user-defined config flags.971 """Provides support for user-defined config flags.
849972
@@ -1032,6 +1155,8 @@
1032 for unit in related_units(rid):1155 for unit in related_units(rid):
1033 ctxt['zmq_nonce'] = relation_get('nonce', unit, rid)1156 ctxt['zmq_nonce'] = relation_get('nonce', unit, rid)
1034 ctxt['zmq_host'] = relation_get('host', unit, rid)1157 ctxt['zmq_host'] = relation_get('host', unit, rid)
1158 ctxt['zmq_redis_address'] = relation_get(
1159 'zmq_redis_address', unit, rid)
10351160
1036 return ctxt1161 return ctxt
10371162
@@ -1063,3 +1188,145 @@
1063 sysctl_create(sysctl_dict,1188 sysctl_create(sysctl_dict,
1064 '/etc/sysctl.d/50-{0}.conf'.format(charm_name()))1189 '/etc/sysctl.d/50-{0}.conf'.format(charm_name()))
1065 return {'sysctl': sysctl_dict}1190 return {'sysctl': sysctl_dict}
1191
1192
1193class NeutronAPIContext(OSContextGenerator):
1194 '''
1195 Inspects current neutron-plugin-api relation for neutron settings. Return
1196 defaults if it is not present.
1197 '''
1198 interfaces = ['neutron-plugin-api']
1199
1200 def __call__(self):
1201 self.neutron_defaults = {
1202 'l2_population': {
1203 'rel_key': 'l2-population',
1204 'default': False,
1205 },
1206 'overlay_network_type': {
1207 'rel_key': 'overlay-network-type',
1208 'default': 'gre',
1209 },
1210 'neutron_security_groups': {
1211 'rel_key': 'neutron-security-groups',
1212 'default': False,
1213 },
1214 'network_device_mtu': {
1215 'rel_key': 'network-device-mtu',
1216 'default': None,
1217 },
1218 'enable_dvr': {
1219 'rel_key': 'enable-dvr',
1220 'default': False,
1221 },
1222 'enable_l3ha': {
1223 'rel_key': 'enable-l3ha',
1224 'default': False,
1225 },
1226 }
1227 ctxt = self.get_neutron_options({})
1228 for rid in relation_ids('neutron-plugin-api'):
1229 for unit in related_units(rid):
1230 rdata = relation_get(rid=rid, unit=unit)
1231 if 'l2-population' in rdata:
1232 ctxt.update(self.get_neutron_options(rdata))
1233
1234 return ctxt
1235
1236 def get_neutron_options(self, rdata):
1237 settings = {}
1238 for nkey in self.neutron_defaults.keys():
1239 defv = self.neutron_defaults[nkey]['default']
1240 rkey = self.neutron_defaults[nkey]['rel_key']
1241 if rkey in rdata.keys():
1242 if type(defv) is bool:
1243 settings[nkey] = bool_from_string(rdata[rkey])
1244 else:
1245 settings[nkey] = rdata[rkey]
1246 else:
1247 settings[nkey] = defv
1248 return settings
1249
1250
1251class ExternalPortContext(NeutronPortContext):
1252
1253 def __call__(self):
1254 ctxt = {}
1255 ports = config('ext-port')
1256 if ports:
1257 ports = [p.strip() for p in ports.split()]
1258 ports = self.resolve_ports(ports)
1259 if ports:
1260 ctxt = {"ext_port": ports[0]}
1261 napi_settings = NeutronAPIContext()()
1262 mtu = napi_settings.get('network_device_mtu')
1263 if mtu:
1264 ctxt['ext_port_mtu'] = mtu
1265
1266 return ctxt
1267
1268
1269class DataPortContext(NeutronPortContext):
1270
1271 def __call__(self):
1272 ports = config('data-port')
1273 if ports:
1274 portmap = parse_data_port_mappings(ports)
1275 ports = portmap.values()
1276 resolved = self.resolve_ports(ports)
1277 normalized = {get_nic_hwaddr(port): port for port in resolved
1278 if port not in ports}
1279 normalized.update({port: port for port in resolved
1280 if port in ports})
1281 if resolved:
1282 return {bridge: normalized[port] for bridge, port in
1283 six.iteritems(portmap) if port in normalized.keys()}
1284
1285 return None
1286
1287
1288class PhyNICMTUContext(DataPortContext):
1289
1290 def __call__(self):
1291 ctxt = {}
1292 mappings = super(PhyNICMTUContext, self).__call__()
1293 if mappings and mappings.values():
1294 ports = mappings.values()
1295 napi_settings = NeutronAPIContext()()
1296 mtu = napi_settings.get('network_device_mtu')
1297 if mtu:
1298 ctxt["devs"] = '\\n'.join(ports)
1299 ctxt['mtu'] = mtu
1300
1301 return ctxt
1302
1303
1304class NetworkServiceContext(OSContextGenerator):
1305
1306 def __init__(self, rel_name='quantum-network-service'):
1307 self.rel_name = rel_name
1308 self.interfaces = [rel_name]
1309
1310 def __call__(self):
1311 for rid in relation_ids(self.rel_name):
1312 for unit in related_units(rid):
1313 rdata = relation_get(rid=rid, unit=unit)
1314 ctxt = {
1315 'keystone_host': rdata.get('keystone_host'),
1316 'service_port': rdata.get('service_port'),
1317 'auth_port': rdata.get('auth_port'),
1318 'service_tenant': rdata.get('service_tenant'),
1319 'service_username': rdata.get('service_username'),
1320 'service_password': rdata.get('service_password'),
1321 'quantum_host': rdata.get('quantum_host'),
1322 'quantum_port': rdata.get('quantum_port'),
1323 'quantum_url': rdata.get('quantum_url'),
1324 'region': rdata.get('region'),
1325 'service_protocol':
1326 rdata.get('service_protocol') or 'http',
1327 'auth_protocol':
1328 rdata.get('auth_protocol') or 'http',
1329 }
1330 if context_complete(ctxt):
1331 return ctxt
1332 return {}
10661333
=== added directory 'hooks/charmhelpers/contrib/openstack/files'
=== added file 'hooks/charmhelpers/contrib/openstack/files/__init__.py'
--- hooks/charmhelpers/contrib/openstack/files/__init__.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/contrib/openstack/files/__init__.py 2015-04-16 21:50:07 +0000
@@ -0,0 +1,18 @@
1# Copyright 2014-2015 Canonical Limited.
2#
3# This file is part of charm-helpers.
4#
5# charm-helpers is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License version 3 as
7# published by the Free Software Foundation.
8#
9# charm-helpers is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
16
17# dummy __init__.py to fool syncer into thinking this is a syncable python
18# module
019
=== added file 'hooks/charmhelpers/contrib/openstack/files/check_haproxy.sh'
--- hooks/charmhelpers/contrib/openstack/files/check_haproxy.sh 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/contrib/openstack/files/check_haproxy.sh 2015-04-16 21:50:07 +0000
@@ -0,0 +1,32 @@
1#!/bin/bash
2#--------------------------------------------
3# This file is managed by Juju
4#--------------------------------------------
5#
6# Copyright 2009,2012 Canonical Ltd.
7# Author: Tom Haddon
8
9CRITICAL=0
10NOTACTIVE=''
11LOGFILE=/var/log/nagios/check_haproxy.log
12AUTH=$(grep -r "stats auth" /etc/haproxy | head -1 | awk '{print $4}')
13
14for appserver in $(grep ' server' /etc/haproxy/haproxy.cfg | awk '{print $2'});
15do
16 output=$(/usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 --regex="class=\"(active|backup)(2|3).*${appserver}" -e ' 200 OK')
17 if [ $? != 0 ]; then
18 date >> $LOGFILE
19 echo $output >> $LOGFILE
20 /usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 -v | grep $appserver >> $LOGFILE 2>&1
21 CRITICAL=1
22 NOTACTIVE="${NOTACTIVE} $appserver"
23 fi
24done
25
26if [ $CRITICAL = 1 ]; then
27 echo "CRITICAL:${NOTACTIVE}"
28 exit 2
29fi
30
31echo "OK: All haproxy instances looking good"
32exit 0
033
=== added file 'hooks/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh'
--- hooks/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh 2015-04-16 21:50:07 +0000
@@ -0,0 +1,30 @@
1#!/bin/bash
2#--------------------------------------------
3# This file is managed by Juju
4#--------------------------------------------
5#
6# Copyright 2009,2012 Canonical Ltd.
7# Author: Tom Haddon
8
9# These should be config options at some stage
10CURRQthrsh=0
11MAXQthrsh=100
12
13AUTH=$(grep -r "stats auth" /etc/haproxy | head -1 | awk '{print $4}')
14
15HAPROXYSTATS=$(/usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 -u '/;csv' -v)
16
17for BACKEND in $(echo $HAPROXYSTATS| xargs -n1 | grep BACKEND | awk -F , '{print $1}')
18do
19 CURRQ=$(echo "$HAPROXYSTATS" | grep $BACKEND | grep BACKEND | cut -d , -f 3)
20 MAXQ=$(echo "$HAPROXYSTATS" | grep $BACKEND | grep BACKEND | cut -d , -f 4)
21
22 if [[ $CURRQ -gt $CURRQthrsh || $MAXQ -gt $MAXQthrsh ]] ; then
23 echo "CRITICAL: queue depth for $BACKEND - CURRENT:$CURRQ MAX:$MAXQ"
24 exit 2
25 fi
26done
27
28echo "OK: All haproxy queue depths looking good"
29exit 0
30
031
=== modified file 'hooks/charmhelpers/contrib/openstack/ip.py'
--- hooks/charmhelpers/contrib/openstack/ip.py 2015-01-26 09:45:23 +0000
+++ hooks/charmhelpers/contrib/openstack/ip.py 2015-04-16 21:50:07 +0000
@@ -26,6 +26,8 @@
26)26)
27from charmhelpers.contrib.hahelpers.cluster import is_clustered27from charmhelpers.contrib.hahelpers.cluster import is_clustered
2828
29from functools import partial
30
29PUBLIC = 'public'31PUBLIC = 'public'
30INTERNAL = 'int'32INTERNAL = 'int'
31ADMIN = 'admin'33ADMIN = 'admin'
@@ -107,3 +109,38 @@
107 "clustered=%s)" % (net_type, clustered))109 "clustered=%s)" % (net_type, clustered))
108110
109 return resolved_address111 return resolved_address
112
113
114def endpoint_url(configs, url_template, port, endpoint_type=PUBLIC,
115 override=None):
116 """Returns the correct endpoint URL to advertise to Keystone.
117
118 This method provides the correct endpoint URL which should be advertised to
119 the keystone charm for endpoint creation. This method allows for the url to
120 be overridden to force a keystone endpoint to have specific URL for any of
121 the defined scopes (admin, internal, public).
122
123 :param configs: OSTemplateRenderer config templating object to inspect
124 for a complete https context.
125 :param url_template: str format string for creating the url template. Only
126 two values will be passed - the scheme+hostname
127 returned by the canonical_url and the port.
128 :param endpoint_type: str endpoint type to resolve.
129 :param override: str the name of the config option which overrides the
130 endpoint URL defined by the charm itself. None will
131 disable any overrides (default).
132 """
133 if override:
134 # Return any user-defined overrides for the keystone endpoint URL.
135 user_value = config(override)
136 if user_value:
137 return user_value.strip()
138
139 return url_template % (canonical_url(configs, endpoint_type), port)
140
141
142public_endpoint = partial(endpoint_url, endpoint_type=PUBLIC)
143
144internal_endpoint = partial(endpoint_url, endpoint_type=INTERNAL)
145
146admin_endpoint = partial(endpoint_url, endpoint_type=ADMIN)
110147
=== modified file 'hooks/charmhelpers/contrib/openstack/neutron.py'
--- hooks/charmhelpers/contrib/openstack/neutron.py 2015-01-26 09:45:23 +0000
+++ hooks/charmhelpers/contrib/openstack/neutron.py 2015-04-16 21:50:07 +0000
@@ -16,6 +16,7 @@
1616
17# Various utilies for dealing with Neutron and the renaming from Quantum.17# Various utilies for dealing with Neutron and the renaming from Quantum.
1818
19import six
19from subprocess import check_output20from subprocess import check_output
2021
21from charmhelpers.core.hookenv import (22from charmhelpers.core.hookenv import (
@@ -179,6 +180,19 @@
179 'nova-api-metadata']],180 'nova-api-metadata']],
180 'server_packages': ['neutron-server', 'calico-control'],181 'server_packages': ['neutron-server', 'calico-control'],
181 'server_services': ['neutron-server']182 'server_services': ['neutron-server']
183 },
184 'vsp': {
185 'config': '/etc/neutron/plugins/nuage/nuage_plugin.ini',
186 'driver': 'neutron.plugins.nuage.plugin.NuagePlugin',
187 'contexts': [
188 context.SharedDBContext(user=config('neutron-database-user'),
189 database=config('neutron-database'),
190 relation_prefix='neutron',
191 ssl_dir=NEUTRON_CONF_DIR)],
192 'services': [],
193 'packages': [],
194 'server_packages': ['neutron-server', 'neutron-plugin-nuage'],
195 'server_services': ['neutron-server']
182 }196 }
183 }197 }
184 if release >= 'icehouse':198 if release >= 'icehouse':
@@ -237,3 +251,72 @@
237 else:251 else:
238 # ensure accurate naming for all releases post-H252 # ensure accurate naming for all releases post-H
239 return 'neutron'253 return 'neutron'
254
255
256def parse_mappings(mappings):
257 parsed = {}
258 if mappings:
259 mappings = mappings.split(' ')
260 for m in mappings:
261 p = m.partition(':')
262 if p[1] == ':':
263 parsed[p[0].strip()] = p[2].strip()
264
265 return parsed
266
267
268def parse_bridge_mappings(mappings):
269 """Parse bridge mappings.
270
271 Mappings must be a space-delimited list of provider:bridge mappings.
272
273 Returns dict of the form {provider:bridge}.
274 """
275 return parse_mappings(mappings)
276
277
278def parse_data_port_mappings(mappings, default_bridge='br-data'):
279 """Parse data port mappings.
280
281 Mappings must be a space-delimited list of bridge:port mappings.
282
283 Returns dict of the form {bridge:port}.
284 """
285 _mappings = parse_mappings(mappings)
286 if not _mappings:
287 if not mappings:
288 return {}
289
290 # For backwards-compatibility we need to support port-only provided in
291 # config.
292 _mappings = {default_bridge: mappings.split(' ')[0]}
293
294 bridges = _mappings.keys()
295 ports = _mappings.values()
296 if len(set(bridges)) != len(bridges):
297 raise Exception("It is not allowed to have more than one port "
298 "configured on the same bridge")
299
300 if len(set(ports)) != len(ports):
301 raise Exception("It is not allowed to have the same port configured "
302 "on more than one bridge")
303
304 return _mappings
305
306
307def parse_vlan_range_mappings(mappings):
308 """Parse vlan range mappings.
309
310 Mappings must be a space-delimited list of provider:start:end mappings.
311
312 Returns dict of the form {provider: (start, end)}.
313 """
314 _mappings = parse_mappings(mappings)
315 if not _mappings:
316 return {}
317
318 mappings = {}
319 for p, r in six.iteritems(_mappings):
320 mappings[p] = tuple(r.split(':'))
321
322 return mappings
240323
=== added file 'hooks/charmhelpers/contrib/openstack/templates/git.upstart'
--- hooks/charmhelpers/contrib/openstack/templates/git.upstart 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/contrib/openstack/templates/git.upstart 2015-04-16 21:50:07 +0000
@@ -0,0 +1,17 @@
1description "{{ service_description }}"
2author "Juju {{ service_name }} Charm <juju@localhost>"
3
4start on runlevel [2345]
5stop on runlevel [!2345]
6
7respawn
8
9exec start-stop-daemon --start --chuid {{ user_name }} \
10 --chdir {{ start_dir }} --name {{ process_name }} \
11 --exec {{ executable_name }} -- \
12 {% for config_file in config_files -%}
13 --config-file={{ config_file }} \
14 {% endfor -%}
15 {% if log_file -%}
16 --log-file={{ log_file }}
17 {% endif -%}
018
=== added file 'hooks/charmhelpers/contrib/openstack/templates/section-keystone-authtoken'
--- hooks/charmhelpers/contrib/openstack/templates/section-keystone-authtoken 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/contrib/openstack/templates/section-keystone-authtoken 2015-04-16 21:50:07 +0000
@@ -0,0 +1,9 @@
1{% if auth_host -%}
2[keystone_authtoken]
3identity_uri = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}/{{ auth_admin_prefix }}
4auth_uri = {{ service_protocol }}://{{ service_host }}:{{ service_port }}/{{ service_admin_prefix }}
5admin_tenant_name = {{ admin_tenant_name }}
6admin_user = {{ admin_user }}
7admin_password = {{ admin_password }}
8signing_dir = {{ signing_dir }}
9{% endif -%}
010
=== added file 'hooks/charmhelpers/contrib/openstack/templates/section-rabbitmq-oslo'
--- hooks/charmhelpers/contrib/openstack/templates/section-rabbitmq-oslo 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/contrib/openstack/templates/section-rabbitmq-oslo 2015-04-16 21:50:07 +0000
@@ -0,0 +1,22 @@
1{% if rabbitmq_host or rabbitmq_hosts -%}
2[oslo_messaging_rabbit]
3rabbit_userid = {{ rabbitmq_user }}
4rabbit_virtual_host = {{ rabbitmq_virtual_host }}
5rabbit_password = {{ rabbitmq_password }}
6{% if rabbitmq_hosts -%}
7rabbit_hosts = {{ rabbitmq_hosts }}
8{% if rabbitmq_ha_queues -%}
9rabbit_ha_queues = True
10rabbit_durable_queues = False
11{% endif -%}
12{% else -%}
13rabbit_host = {{ rabbitmq_host }}
14{% endif -%}
15{% if rabbit_ssl_port -%}
16rabbit_use_ssl = True
17rabbit_port = {{ rabbit_ssl_port }}
18{% if rabbit_ssl_ca -%}
19kombu_ssl_ca_certs = {{ rabbit_ssl_ca }}
20{% endif -%}
21{% endif -%}
22{% endif -%}
023
=== added file 'hooks/charmhelpers/contrib/openstack/templates/section-zeromq'
--- hooks/charmhelpers/contrib/openstack/templates/section-zeromq 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/contrib/openstack/templates/section-zeromq 2015-04-16 21:50:07 +0000
@@ -0,0 +1,14 @@
1{% if zmq_host -%}
2# ZeroMQ configuration (restart-nonce: {{ zmq_nonce }})
3rpc_backend = zmq
4rpc_zmq_host = {{ zmq_host }}
5{% if zmq_redis_address -%}
6rpc_zmq_matchmaker = redis
7matchmaker_heartbeat_freq = 15
8matchmaker_heartbeat_ttl = 30
9[matchmaker_redis]
10host = {{ zmq_redis_address }}
11{% else -%}
12rpc_zmq_matchmaker = ring
13{% endif -%}
14{% endif -%}
015
=== modified file 'hooks/charmhelpers/contrib/openstack/utils.py'
--- hooks/charmhelpers/contrib/openstack/utils.py 2015-01-26 09:45:23 +0000
+++ hooks/charmhelpers/contrib/openstack/utils.py 2015-04-16 21:50:07 +0000
@@ -23,12 +23,17 @@
23import subprocess23import subprocess
24import json24import json
25import os25import os
26import socket
27import sys26import sys
2827
29import six28import six
30import yaml29import yaml
3130
31from charmhelpers.contrib.network import ip
32
33from charmhelpers.core import (
34 unitdata,
35)
36
32from charmhelpers.core.hookenv import (37from charmhelpers.core.hookenv import (
33 config,38 config,
34 log as juju_log,39 log as juju_log,
@@ -103,6 +108,7 @@
103 ('2.1.0', 'juno'),108 ('2.1.0', 'juno'),
104 ('2.2.0', 'juno'),109 ('2.2.0', 'juno'),
105 ('2.2.1', 'kilo'),110 ('2.2.1', 'kilo'),
111 ('2.2.2', 'kilo'),
106])112])
107113
108DEFAULT_LOOPBACK_SIZE = '5G'114DEFAULT_LOOPBACK_SIZE = '5G'
@@ -328,6 +334,21 @@
328 error_out("Invalid openstack-release specified: %s" % rel)334 error_out("Invalid openstack-release specified: %s" % rel)
329335
330336
337def config_value_changed(option):
338 """
339 Determine if config value changed since last call to this function.
340 """
341 hook_data = unitdata.HookData()
342 with hook_data():
343 db = unitdata.kv()
344 current = config(option)
345 saved = db.get(option)
346 db.set(option, current)
347 if saved is None:
348 return False
349 return current != saved
350
351
331def save_script_rc(script_path="scripts/scriptrc", **env_vars):352def save_script_rc(script_path="scripts/scriptrc", **env_vars):
332 """353 """
333 Write an rc file in the charm-delivered directory containing354 Write an rc file in the charm-delivered directory containing
@@ -420,77 +441,10 @@
420 else:441 else:
421 zap_disk(block_device)442 zap_disk(block_device)
422443
423444is_ip = ip.is_ip
424def is_ip(address):445ns_query = ip.ns_query
425 """446get_host_ip = ip.get_host_ip
426 Returns True if address is a valid IP address.447get_hostname = ip.get_hostname
427 """
428 try:
429 # Test to see if already an IPv4 address
430 socket.inet_aton(address)
431 return True
432 except socket.error:
433 return False
434
435
436def ns_query(address):
437 try:
438 import dns.resolver
439 except ImportError:
440 apt_install('python-dnspython')
441 import dns.resolver
442
443 if isinstance(address, dns.name.Name):
444 rtype = 'PTR'
445 elif isinstance(address, six.string_types):
446 rtype = 'A'
447 else:
448 return None
449
450 answers = dns.resolver.query(address, rtype)
451 if answers:
452 return str(answers[0])
453 return None
454
455
456def get_host_ip(hostname):
457 """
458 Resolves the IP for a given hostname, or returns
459 the input if it is already an IP.
460 """
461 if is_ip(hostname):
462 return hostname
463
464 return ns_query(hostname)
465
466
467def get_hostname(address, fqdn=True):
468 """
469 Resolves hostname for given IP, or returns the input
470 if it is already a hostname.
471 """
472 if is_ip(address):
473 try:
474 import dns.reversename
475 except ImportError:
476 apt_install('python-dnspython')
477 import dns.reversename
478
479 rev = dns.reversename.from_address(address)
480 result = ns_query(rev)
481 if not result:
482 return None
483 else:
484 result = address
485
486 if fqdn:
487 # strip trailing .
488 if result.endswith('.'):
489 return result[:-1]
490 else:
491 return result
492 else:
493 return result.split('.')[0]
494448
495449
496def get_matchmaker_map(mm_file='/etc/oslo/matchmaker_ring.json'):450def get_matchmaker_map(mm_file='/etc/oslo/matchmaker_ring.json'):
@@ -534,82 +488,106 @@
534488
535489
536def git_install_requested():490def git_install_requested():
537 """Returns true if openstack-origin-git is specified."""491 """
538 return config('openstack-origin-git') != "None"492 Returns true if openstack-origin-git is specified.
493 """
494 return config('openstack-origin-git') is not None
539495
540496
541requirements_dir = None497requirements_dir = None
542498
543499
544def git_clone_and_install(file_name, core_project):500def git_clone_and_install(projects_yaml, core_project):
545 """Clone/install all OpenStack repos specified in yaml config file."""501 """
502 Clone/install all specified OpenStack repositories.
503
504 The expected format of projects_yaml is:
505 repositories:
506 - {name: keystone,
507 repository: 'git://git.openstack.org/openstack/keystone.git',
508 branch: 'stable/icehouse'}
509 - {name: requirements,
510 repository: 'git://git.openstack.org/openstack/requirements.git',
511 branch: 'stable/icehouse'}
512 directory: /mnt/openstack-git
513 http_proxy: http://squid.internal:3128
514 https_proxy: https://squid.internal:3128
515
516 The directory, http_proxy, and https_proxy keys are optional.
517 """
546 global requirements_dir518 global requirements_dir
519 parent_dir = '/mnt/openstack-git'
547520
548 if file_name == "None":521 if not projects_yaml:
549 return522 return
550523
551 yaml_file = os.path.join(charm_dir(), file_name)524 projects = yaml.load(projects_yaml)
552525 _git_validate_projects_yaml(projects, core_project)
553 # clone/install the requirements project first526
554 installed = _git_clone_and_install_subset(yaml_file,527 old_environ = dict(os.environ)
555 whitelist=['requirements'])528
556 if 'requirements' not in installed:529 if 'http_proxy' in projects.keys():
557 error_out('requirements git repository must be specified')530 os.environ['http_proxy'] = projects['http_proxy']
558531 if 'https_proxy' in projects.keys():
559 # clone/install all other projects except requirements and the core project532 os.environ['https_proxy'] = projects['https_proxy']
560 blacklist = ['requirements', core_project]533
561 _git_clone_and_install_subset(yaml_file, blacklist=blacklist,534 if 'directory' in projects.keys():
562 update_requirements=True)535 parent_dir = projects['directory']
563536
564 # clone/install the core project537 for p in projects['repositories']:
565 whitelist = [core_project]538 repo = p['repository']
566 installed = _git_clone_and_install_subset(yaml_file, whitelist=whitelist,539 branch = p['branch']
567 update_requirements=True)540 if p['name'] == 'requirements':
568 if core_project not in installed:541 repo_dir = _git_clone_and_install_single(repo, branch, parent_dir,
569 error_out('{} git repository must be specified'.format(core_project))542 update_requirements=False)
570543 requirements_dir = repo_dir
571544 else:
572def _git_clone_and_install_subset(yaml_file, whitelist=[], blacklist=[],545 repo_dir = _git_clone_and_install_single(repo, branch, parent_dir,
573 update_requirements=False):546 update_requirements=True)
574 """Clone/install subset of OpenStack repos specified in yaml config file."""547
575 global requirements_dir548 os.environ = old_environ
576 installed = []549
577550
578 with open(yaml_file, 'r') as fd:551def _git_validate_projects_yaml(projects, core_project):
579 projects = yaml.load(fd)552 """
580 for proj, val in projects.items():553 Validate the projects yaml.
581 # The project subset is chosen based on the following 3 rules:554 """
582 # 1) If project is in blacklist, we don't clone/install it, period.555 _git_ensure_key_exists('repositories', projects)
583 # 2) If whitelist is empty, we clone/install everything else.556
584 # 3) If whitelist is not empty, we clone/install everything in the557 for project in projects['repositories']:
585 # whitelist.558 _git_ensure_key_exists('name', project.keys())
586 if proj in blacklist:559 _git_ensure_key_exists('repository', project.keys())
587 continue560 _git_ensure_key_exists('branch', project.keys())
588 if whitelist and proj not in whitelist:561
589 continue562 if projects['repositories'][0]['name'] != 'requirements':
590 repo = val['repository']563 error_out('{} git repo must be specified first'.format('requirements'))
591 branch = val['branch']564
592 repo_dir = _git_clone_and_install_single(repo, branch,565 if projects['repositories'][-1]['name'] != core_project:
593 update_requirements)566 error_out('{} git repo must be specified last'.format(core_project))
594 if proj == 'requirements':567
595 requirements_dir = repo_dir568
596 installed.append(proj)569def _git_ensure_key_exists(key, keys):
597 return installed570 """
598571 Ensure that key exists in keys.
599572 """
600def _git_clone_and_install_single(repo, branch, update_requirements=False):573 if key not in keys:
601 """Clone and install a single git repository."""574 error_out('openstack-origin-git key \'{}\' is missing'.format(key))
602 dest_parent_dir = "/mnt/openstack-git/"575
603 dest_dir = os.path.join(dest_parent_dir, os.path.basename(repo))576
604577def _git_clone_and_install_single(repo, branch, parent_dir, update_requirements):
605 if not os.path.exists(dest_parent_dir):578 """
606 juju_log('Host dir not mounted at {}. '579 Clone and install a single git repository.
607 'Creating directory there instead.'.format(dest_parent_dir))580 """
608 os.mkdir(dest_parent_dir)581 dest_dir = os.path.join(parent_dir, os.path.basename(repo))
582
583 if not os.path.exists(parent_dir):
584 juju_log('Directory already exists at {}. '
585 'No need to create directory.'.format(parent_dir))
586 os.mkdir(parent_dir)
609587
610 if not os.path.exists(dest_dir):588 if not os.path.exists(dest_dir):
611 juju_log('Cloning git repo: {}, branch: {}'.format(repo, branch))589 juju_log('Cloning git repo: {}, branch: {}'.format(repo, branch))
612 repo_dir = install_remote(repo, dest=dest_parent_dir, branch=branch)590 repo_dir = install_remote(repo, dest=parent_dir, branch=branch)
613 else:591 else:
614 repo_dir = dest_dir592 repo_dir = dest_dir
615593
@@ -626,16 +604,39 @@
626604
627605
628def _git_update_requirements(package_dir, reqs_dir):606def _git_update_requirements(package_dir, reqs_dir):
629 """Update from global requirements.607 """
608 Update from global requirements.
630609
631 Update an OpenStack git directory's requirements.txt and610 Update an OpenStack git directory's requirements.txt and
632 test-requirements.txt from global-requirements.txt."""611 test-requirements.txt from global-requirements.txt.
612 """
633 orig_dir = os.getcwd()613 orig_dir = os.getcwd()
634 os.chdir(reqs_dir)614 os.chdir(reqs_dir)
635 cmd = "python update.py {}".format(package_dir)615 cmd = ['python', 'update.py', package_dir]
636 try:616 try:
637 subprocess.check_call(cmd.split(' '))617 subprocess.check_call(cmd)
638 except subprocess.CalledProcessError:618 except subprocess.CalledProcessError:
639 package = os.path.basename(package_dir)619 package = os.path.basename(package_dir)
640 error_out("Error updating {} from global-requirements.txt".format(package))620 error_out("Error updating {} from global-requirements.txt".format(package))
641 os.chdir(orig_dir)621 os.chdir(orig_dir)
622
623
624def git_src_dir(projects_yaml, project):
625 """
626 Return the directory where the specified project's source is located.
627 """
628 parent_dir = '/mnt/openstack-git'
629
630 if not projects_yaml:
631 return
632
633 projects = yaml.load(projects_yaml)
634
635 if 'directory' in projects.keys():
636 parent_dir = projects['directory']
637
638 for p in projects['repositories']:
639 if p['name'] == project:
640 return os.path.join(parent_dir, os.path.basename(p['repository']))
641
642 return None
642643
=== modified file 'hooks/charmhelpers/contrib/python/packages.py'
--- hooks/charmhelpers/contrib/python/packages.py 2015-01-26 09:45:23 +0000
+++ hooks/charmhelpers/contrib/python/packages.py 2015-04-16 21:50:07 +0000
@@ -17,8 +17,6 @@
17# You should have received a copy of the GNU Lesser General Public License17# You should have received a copy of the GNU Lesser General Public License
18# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.18# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1919
20__author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>"
21
22from charmhelpers.fetch import apt_install, apt_update20from charmhelpers.fetch import apt_install, apt_update
23from charmhelpers.core.hookenv import log21from charmhelpers.core.hookenv import log
2422
@@ -29,6 +27,8 @@
29 apt_install('python-pip')27 apt_install('python-pip')
30 from pip import main as pip_execute28 from pip import main as pip_execute
3129
30__author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>"
31
3232
33def parse_options(given, available):33def parse_options(given, available):
34 """Given a set of options, check if available"""34 """Given a set of options, check if available"""
3535
=== modified file 'hooks/charmhelpers/core/fstab.py'
--- hooks/charmhelpers/core/fstab.py 2015-01-26 09:45:23 +0000
+++ hooks/charmhelpers/core/fstab.py 2015-04-16 21:50:07 +0000
@@ -17,11 +17,11 @@
17# You should have received a copy of the GNU Lesser General Public License17# You should have received a copy of the GNU Lesser General Public License
18# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.18# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1919
20__author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
21
22import io20import io
23import os21import os
2422
23__author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
24
2525
26class Fstab(io.FileIO):26class Fstab(io.FileIO):
27 """This class extends file in order to implement a file reader/writer27 """This class extends file in order to implement a file reader/writer
@@ -77,7 +77,7 @@
77 for line in self.readlines():77 for line in self.readlines():
78 line = line.decode('us-ascii')78 line = line.decode('us-ascii')
79 try:79 try:
80 if line.strip() and not line.startswith("#"):80 if line.strip() and not line.strip().startswith("#"):
81 yield self._hydrate_entry(line)81 yield self._hydrate_entry(line)
82 except ValueError:82 except ValueError:
83 pass83 pass
@@ -104,7 +104,7 @@
104104
105 found = False105 found = False
106 for index, line in enumerate(lines):106 for index, line in enumerate(lines):
107 if not line.startswith("#"):107 if line.strip() and not line.strip().startswith("#"):
108 if self._hydrate_entry(line) == entry:108 if self._hydrate_entry(line) == entry:
109 found = True109 found = True
110 break110 break
111111
=== modified file 'hooks/charmhelpers/core/hookenv.py'
--- hooks/charmhelpers/core/hookenv.py 2015-01-26 09:45:23 +0000
+++ hooks/charmhelpers/core/hookenv.py 2015-04-16 21:50:07 +0000
@@ -20,11 +20,13 @@
20# Authors:20# Authors:
21# Charm Helpers Developers <juju@lists.ubuntu.com>21# Charm Helpers Developers <juju@lists.ubuntu.com>
2222
23from __future__ import print_function
23import os24import os
24import json25import json
25import yaml26import yaml
26import subprocess27import subprocess
27import sys28import sys
29import errno
28from subprocess import CalledProcessError30from subprocess import CalledProcessError
2931
30import six32import six
@@ -87,7 +89,18 @@
87 if not isinstance(message, six.string_types):89 if not isinstance(message, six.string_types):
88 message = repr(message)90 message = repr(message)
89 command += [message]91 command += [message]
90 subprocess.call(command)92 # Missing juju-log should not cause failures in unit tests
93 # Send log output to stderr
94 try:
95 subprocess.call(command)
96 except OSError as e:
97 if e.errno == errno.ENOENT:
98 if level:
99 message = "{}: {}".format(level, message)
100 message = "juju-log: {}".format(message)
101 print(message, file=sys.stderr)
102 else:
103 raise
91104
92105
93class Serializable(UserDict):106class Serializable(UserDict):
@@ -566,3 +579,29 @@
566def charm_dir():579def charm_dir():
567 """Return the root directory of the current charm"""580 """Return the root directory of the current charm"""
568 return os.environ.get('CHARM_DIR')581 return os.environ.get('CHARM_DIR')
582
583
584@cached
585def action_get(key=None):
586 """Gets the value of an action parameter, or all key/value param pairs"""
587 cmd = ['action-get']
588 if key is not None:
589 cmd.append(key)
590 cmd.append('--format=json')
591 action_data = json.loads(subprocess.check_output(cmd).decode('UTF-8'))
592 return action_data
593
594
595def action_set(values):
596 """Sets the values to be returned after the action finishes"""
597 cmd = ['action-set']
598 for k, v in list(values.items()):
599 cmd.append('{}={}'.format(k, v))
600 subprocess.check_call(cmd)
601
602
603def action_fail(message):
604 """Sets the action status to failed and sets the error message.
605
606 The results set by action_set are preserved."""
607 subprocess.check_call(['action-fail', message])
569608
=== modified file 'hooks/charmhelpers/core/host.py'
--- hooks/charmhelpers/core/host.py 2015-01-26 09:45:23 +0000
+++ hooks/charmhelpers/core/host.py 2015-04-16 21:50:07 +0000
@@ -191,11 +191,11 @@
191191
192192
193def write_file(path, content, owner='root', group='root', perms=0o444):193def write_file(path, content, owner='root', group='root', perms=0o444):
194 """Create or overwrite a file with the contents of a string"""194 """Create or overwrite a file with the contents of a byte string."""
195 log("Writing file {} {}:{} {:o}".format(path, owner, group, perms))195 log("Writing file {} {}:{} {:o}".format(path, owner, group, perms))
196 uid = pwd.getpwnam(owner).pw_uid196 uid = pwd.getpwnam(owner).pw_uid
197 gid = grp.getgrnam(group).gr_gid197 gid = grp.getgrnam(group).gr_gid
198 with open(path, 'w') as target:198 with open(path, 'wb') as target:
199 os.fchown(target.fileno(), uid, gid)199 os.fchown(target.fileno(), uid, gid)
200 os.fchmod(target.fileno(), perms)200 os.fchmod(target.fileno(), perms)
201 target.write(content)201 target.write(content)
@@ -305,11 +305,11 @@
305 ceph_client_changed function.305 ceph_client_changed function.
306 """306 """
307 def wrap(f):307 def wrap(f):
308 def wrapped_f(*args):308 def wrapped_f(*args, **kwargs):
309 checksums = {}309 checksums = {}
310 for path in restart_map:310 for path in restart_map:
311 checksums[path] = file_hash(path)311 checksums[path] = file_hash(path)
312 f(*args)312 f(*args, **kwargs)
313 restarts = []313 restarts = []
314 for path in restart_map:314 for path in restart_map:
315 if checksums[path] != file_hash(path):315 if checksums[path] != file_hash(path):
@@ -339,12 +339,16 @@
339def pwgen(length=None):339def pwgen(length=None):
340 """Generate a random pasword."""340 """Generate a random pasword."""
341 if length is None:341 if length is None:
342 # A random length is ok to use a weak PRNG
342 length = random.choice(range(35, 45))343 length = random.choice(range(35, 45))
343 alphanumeric_chars = [344 alphanumeric_chars = [
344 l for l in (string.ascii_letters + string.digits)345 l for l in (string.ascii_letters + string.digits)
345 if l not in 'l0QD1vAEIOUaeiou']346 if l not in 'l0QD1vAEIOUaeiou']
347 # Use a crypto-friendly PRNG (e.g. /dev/urandom) for making the
348 # actual password
349 random_generator = random.SystemRandom()
346 random_chars = [350 random_chars = [
347 random.choice(alphanumeric_chars) for _ in range(length)]351 random_generator.choice(alphanumeric_chars) for _ in range(length)]
348 return(''.join(random_chars))352 return(''.join(random_chars))
349353
350354
@@ -361,7 +365,7 @@
361 ip_output = (line for line in ip_output if line)365 ip_output = (line for line in ip_output if line)
362 for line in ip_output:366 for line in ip_output:
363 if line.split()[1].startswith(int_type):367 if line.split()[1].startswith(int_type):
364 matched = re.search('.*: (bond[0-9]+\.[0-9]+)@.*', line)368 matched = re.search('.*: (' + int_type + r'[0-9]+\.[0-9]+)@.*', line)
365 if matched:369 if matched:
366 interface = matched.groups()[0]370 interface = matched.groups()[0]
367 else:371 else:
368372
=== modified file 'hooks/charmhelpers/core/services/helpers.py'
--- hooks/charmhelpers/core/services/helpers.py 2015-01-26 09:45:23 +0000
+++ hooks/charmhelpers/core/services/helpers.py 2015-04-16 21:50:07 +0000
@@ -45,12 +45,14 @@
45 """45 """
46 name = None46 name = None
47 interface = None47 interface = None
48 required_keys = []
4948
50 def __init__(self, name=None, additional_required_keys=None):49 def __init__(self, name=None, additional_required_keys=None):
50 if not hasattr(self, 'required_keys'):
51 self.required_keys = []
52
51 if name is not None:53 if name is not None:
52 self.name = name54 self.name = name
53 if additional_required_keys is not None:55 if additional_required_keys:
54 self.required_keys.extend(additional_required_keys)56 self.required_keys.extend(additional_required_keys)
55 self.get_data()57 self.get_data()
5658
@@ -134,7 +136,10 @@
134 """136 """
135 name = 'db'137 name = 'db'
136 interface = 'mysql'138 interface = 'mysql'
137 required_keys = ['host', 'user', 'password', 'database']139
140 def __init__(self, *args, **kwargs):
141 self.required_keys = ['host', 'user', 'password', 'database']
142 RelationContext.__init__(self, *args, **kwargs)
138143
139144
140class HttpRelation(RelationContext):145class HttpRelation(RelationContext):
@@ -146,7 +151,10 @@
146 """151 """
147 name = 'website'152 name = 'website'
148 interface = 'http'153 interface = 'http'
149 required_keys = ['host', 'port']154
155 def __init__(self, *args, **kwargs):
156 self.required_keys = ['host', 'port']
157 RelationContext.__init__(self, *args, **kwargs)
150158
151 def provide_data(self):159 def provide_data(self):
152 return {160 return {
153161
=== added file 'hooks/charmhelpers/core/strutils.py'
--- hooks/charmhelpers/core/strutils.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/core/strutils.py 2015-04-16 21:50:07 +0000
@@ -0,0 +1,42 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4# Copyright 2014-2015 Canonical Limited.
5#
6# This file is part of charm-helpers.
7#
8# charm-helpers is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Lesser General Public License version 3 as
10# published by the Free Software Foundation.
11#
12# charm-helpers is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Lesser General Public License for more details.
16#
17# You should have received a copy of the GNU Lesser General Public License
18# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
19
20import six
21
22
23def bool_from_string(value):
24 """Interpret string value as boolean.
25
26 Returns True if value translates to True otherwise False.
27 """
28 if isinstance(value, six.string_types):
29 value = six.text_type(value)
30 else:
31 msg = "Unable to interpret non-string value '%s' as boolean" % (value)
32 raise ValueError(msg)
33
34 value = value.strip().lower()
35
36 if value in ['y', 'yes', 'true', 't', 'on']:
37 return True
38 elif value in ['n', 'no', 'false', 'f', 'off']:
39 return False
40
41 msg = "Unable to interpret string value '%s' as boolean" % (value)
42 raise ValueError(msg)
043
=== modified file 'hooks/charmhelpers/core/sysctl.py'
--- hooks/charmhelpers/core/sysctl.py 2015-03-05 10:50:47 +0000
+++ hooks/charmhelpers/core/sysctl.py 2015-04-16 21:50:07 +0000
@@ -17,8 +17,6 @@
17# You should have received a copy of the GNU Lesser General Public License17# You should have received a copy of the GNU Lesser General Public License
18# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.18# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1919
20__author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
21
22import yaml20import yaml
2321
24from subprocess import check_call22from subprocess import check_call
@@ -29,6 +27,8 @@
29 ERROR,27 ERROR,
30)28)
3129
30__author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
31
3232
33def create(sysctl_dict, sysctl_file):33def create(sysctl_dict, sysctl_file):
34 """Creates a sysctl.conf file from a YAML associative array34 """Creates a sysctl.conf file from a YAML associative array
3535
=== modified file 'hooks/charmhelpers/core/templating.py'
--- hooks/charmhelpers/core/templating.py 2015-01-26 09:45:23 +0000
+++ hooks/charmhelpers/core/templating.py 2015-04-16 21:50:07 +0000
@@ -21,7 +21,7 @@
2121
2222
23def render(source, target, context, owner='root', group='root',23def render(source, target, context, owner='root', group='root',
24 perms=0o444, templates_dir=None):24 perms=0o444, templates_dir=None, encoding='UTF-8'):
25 """25 """
26 Render a template.26 Render a template.
2727
@@ -64,5 +64,5 @@
64 level=hookenv.ERROR)64 level=hookenv.ERROR)
65 raise e65 raise e
66 content = template.render(context)66 content = template.render(context)
67 host.mkdir(os.path.dirname(target), owner, group)67 host.mkdir(os.path.dirname(target), owner, group, perms=0o755)
68 host.write_file(target, content, owner, group, perms)68 host.write_file(target, content.encode(encoding), owner, group, perms)
6969
=== added file 'hooks/charmhelpers/core/unitdata.py'
--- hooks/charmhelpers/core/unitdata.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/core/unitdata.py 2015-04-16 21:50:07 +0000
@@ -0,0 +1,477 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# Copyright 2014-2015 Canonical Limited.
5#
6# This file is part of charm-helpers.
7#
8# charm-helpers is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Lesser General Public License version 3 as
10# published by the Free Software Foundation.
11#
12# charm-helpers is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Lesser General Public License for more details.
16#
17# You should have received a copy of the GNU Lesser General Public License
18# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
19#
20#
21# Authors:
22# Kapil Thangavelu <kapil.foss@gmail.com>
23#
24"""
25Intro
26-----
27
28A simple way to store state in units. This provides a key value
29storage with support for versioned, transactional operation,
30and can calculate deltas from previous values to simplify unit logic
31when processing changes.
32
33
34Hook Integration
35----------------
36
37There are several extant frameworks for hook execution, including
38
39 - charmhelpers.core.hookenv.Hooks
40 - charmhelpers.core.services.ServiceManager
41
42The storage classes are framework agnostic, one simple integration is
43via the HookData contextmanager. It will record the current hook
44execution environment (including relation data, config data, etc.),
45setup a transaction and allow easy access to the changes from
46previously seen values. One consequence of the integration is the
47reservation of particular keys ('rels', 'unit', 'env', 'config',
48'charm_revisions') for their respective values.
49
50Here's a fully worked integration example using hookenv.Hooks::
51
52 from charmhelper.core import hookenv, unitdata
53
54 hook_data = unitdata.HookData()
55 db = unitdata.kv()
56 hooks = hookenv.Hooks()
57
58 @hooks.hook
59 def config_changed():
60 # Print all changes to configuration from previously seen
61 # values.
62 for changed, (prev, cur) in hook_data.conf.items():
63 print('config changed', changed,
64 'previous value', prev,
65 'current value', cur)
66
67 # Get some unit specific bookeeping
68 if not db.get('pkg_key'):
69 key = urllib.urlopen('https://example.com/pkg_key').read()
70 db.set('pkg_key', key)
71
72 # Directly access all charm config as a mapping.
73 conf = db.getrange('config', True)
74
75 # Directly access all relation data as a mapping
76 rels = db.getrange('rels', True)
77
78 if __name__ == '__main__':
79 with hook_data():
80 hook.execute()
81
82
83A more basic integration is via the hook_scope context manager which simply
84manages transaction scope (and records hook name, and timestamp)::
85
86 >>> from unitdata import kv
87 >>> db = kv()
88 >>> with db.hook_scope('install'):
89 ... # do work, in transactional scope.
90 ... db.set('x', 1)
91 >>> db.get('x')
92 1
93
94
95Usage
96-----
97
98Values are automatically json de/serialized to preserve basic typing
99and complex data struct capabilities (dicts, lists, ints, booleans, etc).
100
101Individual values can be manipulated via get/set::
102
103 >>> kv.set('y', True)
104 >>> kv.get('y')
105 True
106
107 # We can set complex values (dicts, lists) as a single key.
108 >>> kv.set('config', {'a': 1, 'b': True'})
109
110 # Also supports returning dictionaries as a record which
111 # provides attribute access.
112 >>> config = kv.get('config', record=True)
113 >>> config.b
114 True
115
116
117Groups of keys can be manipulated with update/getrange::
118
119 >>> kv.update({'z': 1, 'y': 2}, prefix="gui.")
120 >>> kv.getrange('gui.', strip=True)
121 {'z': 1, 'y': 2}
122
123When updating values, its very helpful to understand which values
124have actually changed and how have they changed. The storage
125provides a delta method to provide for this::
126
127 >>> data = {'debug': True, 'option': 2}
128 >>> delta = kv.delta(data, 'config.')
129 >>> delta.debug.previous
130 None
131 >>> delta.debug.current
132 True
133 >>> delta
134 {'debug': (None, True), 'option': (None, 2)}
135
136Note the delta method does not persist the actual change, it needs to
137be explicitly saved via 'update' method::
138
139 >>> kv.update(data, 'config.')
140
141Values modified in the context of a hook scope retain historical values
142associated to the hookname.
143
144 >>> with db.hook_scope('config-changed'):
145 ... db.set('x', 42)
146 >>> db.gethistory('x')
147 [(1, u'x', 1, u'install', u'2015-01-21T16:49:30.038372'),
148 (2, u'x', 42, u'config-changed', u'2015-01-21T16:49:30.038786')]
149
150"""
151
152import collections
153import contextlib
154import datetime
155import json
156import os
157import pprint
158import sqlite3
159import sys
160
161__author__ = 'Kapil Thangavelu <kapil.foss@gmail.com>'
162
163
164class Storage(object):
165 """Simple key value database for local unit state within charms.
166
167 Modifications are automatically committed at hook exit. That's
168 currently regardless of exit code.
169
170 To support dicts, lists, integer, floats, and booleans values
171 are automatically json encoded/decoded.
172 """
173 def __init__(self, path=None):
174 self.db_path = path
175 if path is None:
176 self.db_path = os.path.join(
177 os.environ.get('CHARM_DIR', ''), '.unit-state.db')
178 self.conn = sqlite3.connect('%s' % self.db_path)
179 self.cursor = self.conn.cursor()
180 self.revision = None
181 self._closed = False
182 self._init()
183
184 def close(self):
185 if self._closed:
186 return
187 self.flush(False)
188 self.cursor.close()
189 self.conn.close()
190 self._closed = True
191
192 def _scoped_query(self, stmt, params=None):
193 if params is None:
194 params = []
195 return stmt, params
196
197 def get(self, key, default=None, record=False):
198 self.cursor.execute(
199 *self._scoped_query(
200 'select data from kv where key=?', [key]))
201 result = self.cursor.fetchone()
202 if not result:
203 return default
204 if record:
205 return Record(json.loads(result[0]))
206 return json.loads(result[0])
207
208 def getrange(self, key_prefix, strip=False):
209 stmt = "select key, data from kv where key like '%s%%'" % key_prefix
210 self.cursor.execute(*self._scoped_query(stmt))
211 result = self.cursor.fetchall()
212
213 if not result:
214 return None
215 if not strip:
216 key_prefix = ''
217 return dict([
218 (k[len(key_prefix):], json.loads(v)) for k, v in result])
219
220 def update(self, mapping, prefix=""):
221 for k, v in mapping.items():
222 self.set("%s%s" % (prefix, k), v)
223
224 def unset(self, key):
225 self.cursor.execute('delete from kv where key=?', [key])
226 if self.revision and self.cursor.rowcount:
227 self.cursor.execute(
228 'insert into kv_revisions values (?, ?, ?)',
229 [key, self.revision, json.dumps('DELETED')])
230
231 def set(self, key, value):
232 serialized = json.dumps(value)
233
234 self.cursor.execute(
235 'select data from kv where key=?', [key])
236 exists = self.cursor.fetchone()
237
238 # Skip mutations to the same value
239 if exists:
240 if exists[0] == serialized:
241 return value
242
243 if not exists:
244 self.cursor.execute(
245 'insert into kv (key, data) values (?, ?)',
246 (key, serialized))
247 else:
248 self.cursor.execute('''
249 update kv
250 set data = ?
251 where key = ?''', [serialized, key])
252
253 # Save
254 if not self.revision:
255 return value
256
257 self.cursor.execute(
258 'select 1 from kv_revisions where key=? and revision=?',
259 [key, self.revision])
260 exists = self.cursor.fetchone()
261
262 if not exists:
263 self.cursor.execute(
264 '''insert into kv_revisions (
265 revision, key, data) values (?, ?, ?)''',
266 (self.revision, key, serialized))
267 else:
268 self.cursor.execute(
269 '''
270 update kv_revisions
271 set data = ?
272 where key = ?
273 and revision = ?''',
274 [serialized, key, self.revision])
275
276 return value
277
278 def delta(self, mapping, prefix):
279 """
280 return a delta containing values that have changed.
281 """
282 previous = self.getrange(prefix, strip=True)
283 if not previous:
284 pk = set()
285 else:
286 pk = set(previous.keys())
287 ck = set(mapping.keys())
288 delta = DeltaSet()
289
290 # added
291 for k in ck.difference(pk):
292 delta[k] = Delta(None, mapping[k])
293
294 # removed
295 for k in pk.difference(ck):
296 delta[k] = Delta(previous[k], None)
297
298 # changed
299 for k in pk.intersection(ck):
300 c = mapping[k]
301 p = previous[k]
302 if c != p:
303 delta[k] = Delta(p, c)
304
305 return delta
306
307 @contextlib.contextmanager
308 def hook_scope(self, name=""):
309 """Scope all future interactions to the current hook execution
310 revision."""
311 assert not self.revision
312 self.cursor.execute(
313 'insert into hooks (hook, date) values (?, ?)',
314 (name or sys.argv[0],
315 datetime.datetime.utcnow().isoformat()))
316 self.revision = self.cursor.lastrowid
317 try:
318 yield self.revision
319 self.revision = None
320 except:
321 self.flush(False)
322 self.revision = None
323 raise
324 else:
325 self.flush()
326
327 def flush(self, save=True):
328 if save:
329 self.conn.commit()
330 elif self._closed:
331 return
332 else:
333 self.conn.rollback()
334
335 def _init(self):
336 self.cursor.execute('''
337 create table if not exists kv (
338 key text,
339 data text,
340 primary key (key)
341 )''')
342 self.cursor.execute('''
343 create table if not exists kv_revisions (
344 key text,
345 revision integer,
346 data text,
347 primary key (key, revision)
348 )''')
349 self.cursor.execute('''
350 create table if not exists hooks (
351 version integer primary key autoincrement,
352 hook text,
353 date text
354 )''')
355 self.conn.commit()
356
357 def gethistory(self, key, deserialize=False):
358 self.cursor.execute(
359 '''
360 select kv.revision, kv.key, kv.data, h.hook, h.date
361 from kv_revisions kv,
362 hooks h
363 where kv.key=?
364 and kv.revision = h.version
365 ''', [key])
366 if deserialize is False:
367 return self.cursor.fetchall()
368 return map(_parse_history, self.cursor.fetchall())
369
370 def debug(self, fh=sys.stderr):
371 self.cursor.execute('select * from kv')
372 pprint.pprint(self.cursor.fetchall(), stream=fh)
373 self.cursor.execute('select * from kv_revisions')
374 pprint.pprint(self.cursor.fetchall(), stream=fh)
375
376
377def _parse_history(d):
378 return (d[0], d[1], json.loads(d[2]), d[3],
379 datetime.datetime.strptime(d[-1], "%Y-%m-%dT%H:%M:%S.%f"))
380
381
382class HookData(object):
383 """Simple integration for existing hook exec frameworks.
384
385 Records all unit information, and stores deltas for processing
386 by the hook.
387
388 Sample::
389
390 from charmhelper.core import hookenv, unitdata
391
392 changes = unitdata.HookData()
393 db = unitdata.kv()
394 hooks = hookenv.Hooks()
395
396 @hooks.hook
397 def config_changed():
398 # View all changes to configuration
399 for changed, (prev, cur) in changes.conf.items():
400 print('config changed', changed,
401 'previous value', prev,
402 'current value', cur)
403
404 # Get some unit specific bookeeping
405 if not db.get('pkg_key'):
406 key = urllib.urlopen('https://example.com/pkg_key').read()
407 db.set('pkg_key', key)
408
409 if __name__ == '__main__':
410 with changes():
411 hook.execute()
412
413 """
414 def __init__(self):
415 self.kv = kv()
416 self.conf = None
417 self.rels = None
418
419 @contextlib.contextmanager
420 def __call__(self):
421 from charmhelpers.core import hookenv
422 hook_name = hookenv.hook_name()
423
424 with self.kv.hook_scope(hook_name):
425 self._record_charm_version(hookenv.charm_dir())
426 delta_config, delta_relation = self._record_hook(hookenv)
427 yield self.kv, delta_config, delta_relation
428
429 def _record_charm_version(self, charm_dir):
430 # Record revisions.. charm revisions are meaningless
431 # to charm authors as they don't control the revision.
432 # so logic dependnent on revision is not particularly
433 # useful, however it is useful for debugging analysis.
434 charm_rev = open(
435 os.path.join(charm_dir, 'revision')).read().strip()
436 charm_rev = charm_rev or '0'
437 revs = self.kv.get('charm_revisions', [])
438 if charm_rev not in revs:
439 revs.append(charm_rev.strip() or '0')
440 self.kv.set('charm_revisions', revs)
441
442 def _record_hook(self, hookenv):
443 data = hookenv.execution_environment()
444 self.conf = conf_delta = self.kv.delta(data['conf'], 'config')
445 self.rels = rels_delta = self.kv.delta(data['rels'], 'rels')
446 self.kv.set('env', dict(data['env']))
447 self.kv.set('unit', data['unit'])
448 self.kv.set('relid', data.get('relid'))
449 return conf_delta, rels_delta
450
451
452class Record(dict):
453
454 __slots__ = ()
455
456 def __getattr__(self, k):
457 if k in self:
458 return self[k]
459 raise AttributeError(k)
460
461
462class DeltaSet(Record):
463
464 __slots__ = ()
465
466
467Delta = collections.namedtuple('Delta', ['previous', 'current'])
468
469
470_KV = None
471
472
473def kv():
474 global _KV
475 if _KV is None:
476 _KV = Storage()
477 return _KV
0478
=== modified file 'hooks/charmhelpers/fetch/archiveurl.py'
--- hooks/charmhelpers/fetch/archiveurl.py 2015-01-26 09:45:23 +0000
+++ hooks/charmhelpers/fetch/archiveurl.py 2015-04-16 21:50:07 +0000
@@ -18,6 +18,16 @@
18import hashlib18import hashlib
19import re19import re
2020
21from charmhelpers.fetch import (
22 BaseFetchHandler,
23 UnhandledSource
24)
25from charmhelpers.payload.archive import (
26 get_archive_handler,
27 extract,
28)
29from charmhelpers.core.host import mkdir, check_hash
30
21import six31import six
22if six.PY3:32if six.PY3:
23 from urllib.request import (33 from urllib.request import (
@@ -35,16 +45,6 @@
35 )45 )
36 from urlparse import urlparse, urlunparse, parse_qs46 from urlparse import urlparse, urlunparse, parse_qs
3747
38from charmhelpers.fetch import (
39 BaseFetchHandler,
40 UnhandledSource
41)
42from charmhelpers.payload.archive import (
43 get_archive_handler,
44 extract,
45)
46from charmhelpers.core.host import mkdir, check_hash
47
4848
49def splituser(host):49def splituser(host):
50 '''urllib.splituser(), but six's support of this seems broken'''50 '''urllib.splituser(), but six's support of this seems broken'''
5151
=== modified file 'hooks/charmhelpers/fetch/giturl.py'
--- hooks/charmhelpers/fetch/giturl.py 2015-01-26 09:45:23 +0000
+++ hooks/charmhelpers/fetch/giturl.py 2015-04-16 21:50:07 +0000
@@ -32,7 +32,7 @@
32 apt_install("python-git")32 apt_install("python-git")
33 from git import Repo33 from git import Repo
3434
35from git.exc import GitCommandError35from git.exc import GitCommandError # noqa E402
3636
3737
38class GitUrlFetchHandler(BaseFetchHandler):38class GitUrlFetchHandler(BaseFetchHandler):
3939
=== modified file 'hooks/glance_relations.py'
--- hooks/glance_relations.py 2015-01-22 16:26:28 +0000
+++ hooks/glance_relations.py 2015-04-16 21:50:07 +0000
@@ -1,18 +1,20 @@
1#!/usr/bin/python1#!/usr/bin/python
2import sys
3
2from subprocess import (4from subprocess import (
5 call,
3 check_call,6 check_call,
4 call
5)7)
6import sys
78
8from glance_utils import (9from glance_utils import (
9 do_openstack_upgrade,10 do_openstack_upgrade,
11 git_install,
10 migrate_database,12 migrate_database,
11 register_configs,13 register_configs,
12 restart_map,14 restart_map,
13 services,15 services,
14 CLUSTER_RES,16 CLUSTER_RES,
15 PACKAGES,17 determine_packages,
16 SERVICES,18 SERVICES,
17 CHARM,19 CHARM,
18 GLANCE_REGISTRY_CONF,20 GLANCE_REGISTRY_CONF,
@@ -41,6 +43,7 @@
41)43)
42from charmhelpers.core.host import (44from charmhelpers.core.host import (
43 restart_on_change,45 restart_on_change,
46 service_reload,
44 service_stop,47 service_stop,
45)48)
46from charmhelpers.fetch import (49from charmhelpers.fetch import (
@@ -53,16 +56,19 @@
53 get_hacluster_config56 get_hacluster_config
54)57)
55from charmhelpers.contrib.openstack.utils import (58from charmhelpers.contrib.openstack.utils import (
59 config_value_changed,
56 configure_installation_source,60 configure_installation_source,
57 get_os_codename_package,61 git_install_requested,
62 lsb_release,
58 openstack_upgrade_available,63 openstack_upgrade_available,
59 lsb_release,64 os_release,
60 sync_db_with_multi_ipv6_addresses65 sync_db_with_multi_ipv6_addresses,
61)66)
62from charmhelpers.contrib.storage.linux.ceph import (67from charmhelpers.contrib.storage.linux.ceph import (
63 ensure_ceph_keyring,68 ensure_ceph_keyring,
64 CephBrokerRq,69 CephBrokerRq,
65 CephBrokerRsp,70 CephBrokerRsp,
71 delete_keyring,
66)72)
67from charmhelpers.payload.execd import (73from charmhelpers.payload.execd import (
68 execd_preinstall74 execd_preinstall
@@ -100,7 +106,9 @@
100 configure_installation_source(src)106 configure_installation_source(src)
101107
102 apt_update(fatal=True)108 apt_update(fatal=True)
103 apt_install(PACKAGES, fatal=True)109 apt_install(determine_packages(), fatal=True)
110
111 git_install(config('openstack-origin-git'))
104112
105 for service in SERVICES:113 for service in SERVICES:
106 service_stop(service)114 service_stop(service)
@@ -140,7 +148,7 @@
140@hooks.hook('shared-db-relation-changed')148@hooks.hook('shared-db-relation-changed')
141@restart_on_change(restart_map())149@restart_on_change(restart_map())
142def db_changed():150def db_changed():
143 rel = get_os_codename_package("glance-common")151 rel = os_release('glance-common')
144152
145 if 'shared-db' not in CONFIGS.complete_contexts():153 if 'shared-db' not in CONFIGS.complete_contexts():
146 juju_log('shared-db relation incomplete. Peer not ready?')154 juju_log('shared-db relation incomplete. Peer not ready?')
@@ -163,7 +171,8 @@
163 status = call(['glance-manage', 'db_version'])171 status = call(['glance-manage', 'db_version'])
164 if status != 0:172 if status != 0:
165 juju_log('Setting version_control to 0')173 juju_log('Setting version_control to 0')
166 check_call(["glance-manage", "version_control", "0"])174 cmd = ["glance-manage", "version_control", "0"]
175 check_call(cmd)
167176
168 juju_log('Cluster leader, performing db sync')177 juju_log('Cluster leader, performing db sync')
169 migrate_database()178 migrate_database()
@@ -172,7 +181,7 @@
172@hooks.hook('pgsql-db-relation-changed')181@hooks.hook('pgsql-db-relation-changed')
173@restart_on_change(restart_map())182@restart_on_change(restart_map())
174def pgsql_db_changed():183def pgsql_db_changed():
175 rel = get_os_codename_package("glance-common")184 rel = os_release('glance-common')
176185
177 if 'pgsql-db' not in CONFIGS.complete_contexts():186 if 'pgsql-db' not in CONFIGS.complete_contexts():
178 juju_log('pgsql-db relation incomplete. Peer not ready?')187 juju_log('pgsql-db relation incomplete. Peer not ready?')
@@ -188,7 +197,8 @@
188 status = call(['glance-manage', 'db_version'])197 status = call(['glance-manage', 'db_version'])
189 if status != 0:198 if status != 0:
190 juju_log('Setting version_control to 0')199 juju_log('Setting version_control to 0')
191 check_call(["glance-manage", "version_control", "0"])200 cmd = ["glance-manage", "version_control", "0"]
201 check_call(cmd)
192202
193 juju_log('Cluster leader, performing db sync')203 juju_log('Cluster leader, performing db sync')
194 migrate_database()204 migrate_database()
@@ -263,6 +273,13 @@
263 juju_log("Request(s) sent to Ceph broker (rid=%s)" % (rid))273 juju_log("Request(s) sent to Ceph broker (rid=%s)" % (rid))
264274
265275
276@hooks.hook('ceph-relation-broken')
277def ceph_broken():
278 service = service_name()
279 delete_keyring(service=service)
280 CONFIGS.write_all()
281
282
266@hooks.hook('identity-service-relation-joined')283@hooks.hook('identity-service-relation-joined')
267def keystone_joined(relation_id=None):284def keystone_joined(relation_id=None):
268 public_url = '{}:9292'.format(canonical_url(CONFIGS, PUBLIC))285 public_url = '{}:9292'.format(canonical_url(CONFIGS, PUBLIC))
@@ -308,9 +325,13 @@
308 sync_db_with_multi_ipv6_addresses(config('database'),325 sync_db_with_multi_ipv6_addresses(config('database'),
309 config('database-user'))326 config('database-user'))
310327
311 if openstack_upgrade_available('glance-common'):328 if git_install_requested():
312 juju_log('Upgrading OpenStack release')329 if config_value_changed('openstack-origin-git'):
313 do_openstack_upgrade(CONFIGS)330 git_install(config('openstack-origin-git'))
331 else:
332 if openstack_upgrade_available('glance-common'):
333 juju_log('Upgrading OpenStack release')
334 do_openstack_upgrade(CONFIGS)
314335
315 open_port(9292)336 open_port(9292)
316 configure_https()337 configure_https()
@@ -354,7 +375,7 @@
354@hooks.hook('upgrade-charm')375@hooks.hook('upgrade-charm')
355@restart_on_change(restart_map(), stopstart=True)376@restart_on_change(restart_map(), stopstart=True)
356def upgrade_charm():377def upgrade_charm():
357 apt_install(filter_installed_packages(PACKAGES), fatal=True)378 apt_install(filter_installed_packages(determine_packages()), fatal=True)
358 configure_https()379 configure_https()
359 update_nrpe_config()380 update_nrpe_config()
360 CONFIGS.write_all()381 CONFIGS.write_all()
@@ -433,8 +454,7 @@
433 [image_service_joined(rid) for rid in relation_ids('image-service')]454 [image_service_joined(rid) for rid in relation_ids('image-service')]
434455
435456
436@hooks.hook('ceph-relation-broken',457@hooks.hook('identity-service-relation-broken',
437 'identity-service-relation-broken',
438 'object-store-relation-broken',458 'object-store-relation-broken',
439 'shared-db-relation-broken',459 'shared-db-relation-broken',
440 'pgsql-db-relation-broken')460 'pgsql-db-relation-broken')
@@ -455,6 +475,10 @@
455 cmd = ['a2dissite', 'openstack_https_frontend']475 cmd = ['a2dissite', 'openstack_https_frontend']
456 check_call(cmd)476 check_call(cmd)
457477
478 # TODO: improve this by checking if local CN certs are available
479 # first then checking reload status (see LP #1433114).
480 service_reload('apache2', restart_on_failure=True)
481
458 for r_id in relation_ids('identity-service'):482 for r_id in relation_ids('identity-service'):
459 keystone_joined(relation_id=r_id)483 keystone_joined(relation_id=r_id)
460 for r_id in relation_ids('image-service'):484 for r_id in relation_ids('image-service'):
@@ -484,7 +508,9 @@
484 hostname = nrpe.get_nagios_hostname()508 hostname = nrpe.get_nagios_hostname()
485 current_unit = nrpe.get_nagios_unit_name()509 current_unit = nrpe.get_nagios_unit_name()
486 nrpe_setup = nrpe.NRPE(hostname=hostname)510 nrpe_setup = nrpe.NRPE(hostname=hostname)
511 nrpe.copy_nrpe_checks()
487 nrpe.add_init_service_checks(nrpe_setup, services(), current_unit)512 nrpe.add_init_service_checks(nrpe_setup, services(), current_unit)
513 nrpe.add_haproxy_checks(nrpe_setup, current_unit)
488 nrpe_setup.write()514 nrpe_setup.write()
489515
490516
491517
=== modified file 'hooks/glance_utils.py'
--- hooks/glance_utils.py 2015-01-08 10:02:48 +0000
+++ hooks/glance_utils.py 2015-04-16 21:50:07 +0000
@@ -1,6 +1,7 @@
1#!/usr/bin/python1#!/usr/bin/python
22
3import os3import os
4import shutil
4import subprocess5import subprocess
56
6import glance_contexts7import glance_contexts
@@ -14,21 +15,27 @@
14 add_source)15 add_source)
1516
16from charmhelpers.core.hookenv import (17from charmhelpers.core.hookenv import (
18 charm_dir,
17 config,19 config,
18 log,20 log,
19 relation_ids,21 relation_ids,
20 service_name)22 service_name)
2123
22from charmhelpers.core.host import (24from charmhelpers.core.host import (
25 adduser,
26 add_group,
27 add_user_to_group,
23 mkdir,28 mkdir,
24 service_stop,29 service_stop,
25 service_start,30 service_start,
26 lsb_release31 service_restart,
32 lsb_release,
33 write_file,
27)34)
2835
29from charmhelpers.contrib.openstack import (36from charmhelpers.contrib.openstack import (
30 templating,37 templating,
31 context, )38 context,)
3239
33from charmhelpers.contrib.hahelpers.cluster import (40from charmhelpers.contrib.hahelpers.cluster import (
34 eligible_leader,41 eligible_leader,
@@ -37,8 +44,14 @@
37from charmhelpers.contrib.openstack.alternatives import install_alternative44from charmhelpers.contrib.openstack.alternatives import install_alternative
38from charmhelpers.contrib.openstack.utils import (45from charmhelpers.contrib.openstack.utils import (
39 get_os_codename_install_source,46 get_os_codename_install_source,
40 get_os_codename_package,47 git_install_requested,
41 configure_installation_source)48 git_clone_and_install,
49 git_src_dir,
50 configure_installation_source,
51 os_release,
52)
53
54from charmhelpers.core.templating import render
4255
43CLUSTER_RES = "grp_glance_vips"56CLUSTER_RES = "grp_glance_vips"
4457
@@ -46,8 +59,27 @@
46 "apache2", "glance", "python-mysqldb", "python-swiftclient",59 "apache2", "glance", "python-mysqldb", "python-swiftclient",
47 "python-psycopg2", "python-keystone", "python-six", "uuid", "haproxy", ]60 "python-psycopg2", "python-keystone", "python-six", "uuid", "haproxy", ]
4861
62BASE_GIT_PACKAGES = [
63 'libxml2-dev',
64 'libxslt1-dev',
65 'python-dev',
66 'python-pip',
67 'python-setuptools',
68 'zlib1g-dev',
69]
70
49SERVICES = [71SERVICES = [
50 "glance-api", "glance-registry", ]72 "glance-api",
73 "glance-registry",
74]
75
76# ubuntu packages that should not be installed when deploying from git
77GIT_PACKAGE_BLACKLIST = [
78 'glance',
79 'python-swiftclient',
80 'python-keystone',
81]
82
5183
52CHARM = "glance"84CHARM = "glance"
5385
@@ -76,7 +108,9 @@
76 (GLANCE_REGISTRY_CONF, {108 (GLANCE_REGISTRY_CONF, {
77 'hook_contexts': [context.SharedDBContext(ssl_dir=GLANCE_CONF_DIR),109 'hook_contexts': [context.SharedDBContext(ssl_dir=GLANCE_CONF_DIR),
78 context.PostgresqlDBContext(),110 context.PostgresqlDBContext(),
79 context.IdentityServiceContext(),111 context.IdentityServiceContext(
112 service='glance',
113 service_user='glance'),
80 context.SyslogContext(),114 context.SyslogContext(),
81 glance_contexts.LoggingConfigContext(),115 glance_contexts.LoggingConfigContext(),
82 glance_contexts.GlanceIPv6Context(),116 glance_contexts.GlanceIPv6Context(),
@@ -90,7 +124,9 @@
90 'hook_contexts': [context.SharedDBContext(ssl_dir=GLANCE_CONF_DIR),124 'hook_contexts': [context.SharedDBContext(ssl_dir=GLANCE_CONF_DIR),
91 context.AMQPContext(ssl_dir=GLANCE_CONF_DIR),125 context.AMQPContext(ssl_dir=GLANCE_CONF_DIR),
92 context.PostgresqlDBContext(),126 context.PostgresqlDBContext(),
93 context.IdentityServiceContext(),127 context.IdentityServiceContext(
128 service='glance',
129 service_user='glance'),
94 glance_contexts.CephGlanceContext(),130 glance_contexts.CephGlanceContext(),
95 glance_contexts.ObjectStoreContext(),131 glance_contexts.ObjectStoreContext(),
96 glance_contexts.HAProxyContext(),132 glance_contexts.HAProxyContext(),
@@ -136,7 +172,7 @@
136 # Register config files with their respective contexts.172 # Register config files with their respective contexts.
137 # Regstration of some configs may not be required depending on173 # Regstration of some configs may not be required depending on
138 # existing of certain relations.174 # existing of certain relations.
139 release = get_os_codename_package('glance-common', fatal=False) or 'essex'175 release = os_release('glance-common')
140 configs = templating.OSConfigRenderer(templates_dir=TEMPLATES,176 configs = templating.OSConfigRenderer(templates_dir=TEMPLATES,
141 openstack_release=release)177 openstack_release=release)
142178
@@ -173,6 +209,18 @@
173 return configs209 return configs
174210
175211
212def determine_packages():
213 packages = [] + PACKAGES
214
215 if git_install_requested():
216 packages.extend(BASE_GIT_PACKAGES)
217 # don't include packages that will be installed from git
218 for p in GIT_PACKAGE_BLACKLIST:
219 packages.remove(p)
220
221 return list(set(packages))
222
223
176def migrate_database():224def migrate_database():
177 '''Runs glance-manage to initialize a new database225 '''Runs glance-manage to initialize a new database
178 or migrate existing226 or migrate existing
@@ -201,7 +249,7 @@
201 ]249 ]
202 apt_update()250 apt_update()
203 apt_upgrade(options=dpkg_opts, fatal=True, dist=True)251 apt_upgrade(options=dpkg_opts, fatal=True, dist=True)
204 apt_install(PACKAGES, fatal=True)252 apt_install(determine_packages(), fatal=True)
205253
206 # set CONFIGS to load templates from new release and regenerate config254 # set CONFIGS to load templates from new release and regenerate config
207 configs.set_release(openstack_release=new_os_rel)255 configs.set_release(openstack_release=new_os_rel)
@@ -252,3 +300,85 @@
252 ' main')300 ' main')
253 apt_update()301 apt_update()
254 apt_install('haproxy/trusty-backports', fatal=True)302 apt_install('haproxy/trusty-backports', fatal=True)
303
304
305def git_install(projects_yaml):
306 """Perform setup, and install git repos specified in yaml parameter."""
307 if git_install_requested():
308 git_pre_install()
309 git_clone_and_install(projects_yaml, core_project='glance')
310 git_post_install(projects_yaml)
311
312
313def git_pre_install():
314 """Perform glance pre-install setup."""
315 dirs = [
316 '/var/lib/glance',
317 '/var/lib/glance/images',
318 '/var/lib/glance/image-cache',
319 '/var/lib/glance/image-cache/incomplete',
320 '/var/lib/glance/image-cache/invalid',
321 '/var/lib/glance/image-cache/queue',
322 '/var/log/glance',
323 ]
324
325 logs = [
326 '/var/log/glance/glance-api.log',
327 '/var/log/glance/glance-registry.log',
328 ]
329
330 adduser('glance', shell='/bin/bash', system_user=True)
331 add_group('glance', system_group=True)
332 add_user_to_group('glance', 'glance')
333
334 for d in dirs:
335 mkdir(d, owner='glance', group='glance', perms=0700, force=False)
336
337 for l in logs:
338 write_file(l, '', owner='glance', group='glance', perms=0600)
339
340
341def git_post_install(projects_yaml):
342 """Perform glance post-install setup."""
343 src_etc = os.path.join(git_src_dir(projects_yaml, 'glance'), 'etc')
344 configs = {
345 'src': src_etc,
346 'dest': '/etc/glance',
347 }
348
349 if os.path.exists(configs['dest']):
350 shutil.rmtree(configs['dest'])
351 shutil.copytree(configs['src'], configs['dest'])
352
353 glance_api_context = {
354 'service_description': 'Glance API server',
355 'service_name': 'Glance',
356 'user_name': 'glance',
357 'start_dir': '/var/lib/glance',
358 'process_name': 'glance-api',
359 'executable_name': '/usr/local/bin/glance-api',
360 'config_files': ['/etc/glance/glance-api.conf'],
361 'log_file': '/var/log/glance/api.log',
362 }
363
364 glance_registry_context = {
365 'service_description': 'Glance registry server',
366 'service_name': 'Glance',
367 'user_name': 'glance',
368 'start_dir': '/var/lib/glance',
369 'process_name': 'glance-registry',
370 'executable_name': '/usr/local/bin/glance-registry',
371 'config_files': ['/etc/glance/glance-registry.conf'],
372 'log_file': '/var/log/glance/registry.log',
373 }
374
375 # NOTE(coreycb): Needs systemd support
376 templates_dir = 'hooks/charmhelpers/contrib/openstack/templates'
377 templates_dir = os.path.join(charm_dir(), templates_dir)
378 render('git.upstart', '/etc/init/glance-api.conf',
379 glance_api_context, perms=0o644, templates_dir=templates_dir)
380 render('git.upstart', '/etc/init/glance-registry.conf',
381 glance_registry_context, perms=0o644, templates_dir=templates_dir)
382
383 service_restart('glance-api')
384 service_restart('glance-registry')
255385
=== added directory 'templates/kilo'
=== added file 'templates/kilo/glance-api-paste.ini'
--- templates/kilo/glance-api-paste.ini 1970-01-01 00:00:00 +0000
+++ templates/kilo/glance-api-paste.ini 2015-04-16 21:50:07 +0000
@@ -0,0 +1,77 @@
1# Use this pipeline for no auth or image caching - DEFAULT
2[pipeline:glance-api]
3pipeline = versionnegotiation osprofiler unauthenticated-context rootapp
4
5# Use this pipeline for image caching and no auth
6[pipeline:glance-api-caching]
7pipeline = versionnegotiation osprofiler unauthenticated-context cache rootapp
8
9# Use this pipeline for caching w/ management interface but no auth
10[pipeline:glance-api-cachemanagement]
11pipeline = versionnegotiation osprofiler unauthenticated-context cache cachemanage rootapp
12
13# Use this pipeline for keystone auth
14[pipeline:glance-api-keystone]
15pipeline = versionnegotiation osprofiler authtoken context rootapp
16
17# Use this pipeline for keystone auth with image caching
18[pipeline:glance-api-keystone+caching]
19pipeline = versionnegotiation osprofiler authtoken context cache rootapp
20
21# Use this pipeline for keystone auth with caching and cache management
22[pipeline:glance-api-keystone+cachemanagement]
23pipeline = versionnegotiation osprofiler authtoken context cache cachemanage rootapp
24
25# Use this pipeline for authZ only. This means that the registry will treat a
26# user as authenticated without making requests to keystone to reauthenticate
27# the user.
28[pipeline:glance-api-trusted-auth]
29pipeline = versionnegotiation osprofiler context rootapp
30
31# Use this pipeline for authZ only. This means that the registry will treat a
32# user as authenticated without making requests to keystone to reauthenticate
33# the user and uses cache management
34[pipeline:glance-api-trusted-auth+cachemanagement]
35pipeline = versionnegotiation osprofiler context cache cachemanage rootapp
36
37[composite:rootapp]
38paste.composite_factory = glance.api:root_app_factory
39/: apiversions
40/v1: apiv1app
41/v2: apiv2app
42
43[app:apiversions]
44paste.app_factory = glance.api.versions:create_resource
45
46[app:apiv1app]
47paste.app_factory = glance.api.v1.router:API.factory
48
49[app:apiv2app]
50paste.app_factory = glance.api.v2.router:API.factory
51
52[filter:versionnegotiation]
53paste.filter_factory = glance.api.middleware.version_negotiation:VersionNegotiationFilter.factory
54
55[filter:cache]
56paste.filter_factory = glance.api.middleware.cache:CacheFilter.factory
57
58[filter:cachemanage]
59paste.filter_factory = glance.api.middleware.cache_manage:CacheManageFilter.factory
60
61[filter:context]
62paste.filter_factory = glance.api.middleware.context:ContextMiddleware.factory
63
64[filter:unauthenticated-context]
65paste.filter_factory = glance.api.middleware.context:UnauthenticatedContextMiddleware.factory
66
67[filter:authtoken]
68paste.filter_factory = keystonemiddleware.auth_token:filter_factory
69delay_auth_decision = true
70
71[filter:gzip]
72paste.filter_factory = glance.api.middleware.gzip:GzipMiddleware.factory
73
74[filter:osprofiler]
75paste.filter_factory = osprofiler.web:WsgiMiddleware.factory
76hmac_keys = SECRET_KEY
77enabled = yes
078
=== added file 'templates/kilo/glance-api.conf'
--- templates/kilo/glance-api.conf 1970-01-01 00:00:00 +0000
+++ templates/kilo/glance-api.conf 2015-04-16 21:50:07 +0000
@@ -0,0 +1,83 @@
1[DEFAULT]
2verbose = {{ verbose }}
3use_syslog = {{ use_syslog }}
4debug = {{ debug }}
5workers = {{ workers }}
6
7known_stores = {{ known_stores }}
8{% if rbd_pool -%}
9default_store = rbd
10{% elif swift_store -%}
11default_store = swift
12{% else -%}
13default_store = file
14{% endif -%}
15
16bind_host = {{ bind_host }}
17
18{% if ext -%}
19bind_port = {{ ext }}
20{% elif bind_port -%}
21bind_port = {{ bind_port }}
22{% else -%}
23bind_port = 9292
24{% endif -%}
25
26log_file = /var/log/glance/api.log
27backlog = 4096
28
29registry_host = {{ registry_host }}
30registry_port = 9191
31registry_client_protocol = http
32
33{% if api_config_flags -%}
34{% for key, value in api_config_flags.iteritems() -%}
35{{ key }} = {{ value }}
36{% endfor -%}
37{% endif -%}
38
39{% if rabbitmq_host or rabbitmq_hosts -%}
40notification_driver = rabbit
41{% endif -%}
42
43{% if swift_store -%}
44swift_store_auth_version = 2
45swift_store_auth_address = {{ service_protocol }}://{{ service_host }}:{{ service_port }}/v2.0/
46swift_store_user = {{ admin_tenant_name }}:{{ admin_user }}
47swift_store_key = {{ admin_password }}
48swift_store_create_container_on_put = True
49swift_store_container = glance
50swift_store_large_object_size = 5120
51swift_store_large_object_chunk_size = 200
52swift_enable_snet = False
53{% endif -%}
54
55{% if rbd_pool -%}
56rbd_store_ceph_conf = /etc/ceph/ceph.conf
57rbd_store_user = {{ rbd_user }}
58rbd_store_pool = {{ rbd_pool }}
59rbd_store_chunk_size = 8
60{% endif -%}
61
62delayed_delete = False
63scrub_time = 43200
64scrubber_datadir = /var/lib/glance/scrubber
65image_cache_dir = /var/lib/glance/image-cache/
66db_enforce_mysql_charset = False
67
68[glance_store]
69filesystem_store_datadir = /var/lib/glance/images/
70
71[image_format]
72disk_formats=ami,ari,aki,vhd,vmdk,raw,qcow2,vdi,iso,root-tar
73
74{% include "section-keystone-authtoken" %}
75
76{% if auth_host -%}
77[paste_deploy]
78flavor = keystone
79{% endif %}
80
81{% include "parts/section-database" %}
82
83{% include "section-rabbitmq-oslo" %}
084
=== added file 'templates/kilo/glance-registry-paste.ini'
--- templates/kilo/glance-registry-paste.ini 1970-01-01 00:00:00 +0000
+++ templates/kilo/glance-registry-paste.ini 2015-04-16 21:50:07 +0000
@@ -0,0 +1,30 @@
1# Use this pipeline for no auth - DEFAULT
2[pipeline:glance-registry]
3pipeline = osprofiler unauthenticated-context registryapp
4
5# Use this pipeline for keystone auth
6[pipeline:glance-registry-keystone]
7pipeline = osprofiler authtoken context registryapp
8
9# Use this pipeline for authZ only. This means that the registry will treat a
10# user as authenticated without making requests to keystone to reauthenticate
11# the user.
12[pipeline:glance-registry-trusted-auth]
13pipeline = osprofiler context registryapp
14
15[app:registryapp]
16paste.app_factory = glance.registry.api:API.factory
17
18[filter:context]
19paste.filter_factory = glance.api.middleware.context:ContextMiddleware.factory
20
21[filter:unauthenticated-context]
22paste.filter_factory = glance.api.middleware.context:UnauthenticatedContextMiddleware.factory
23
24[filter:authtoken]
25paste.filter_factory = keystonemiddleware.auth_token:filter_factory
26
27[filter:osprofiler]
28paste.filter_factory = osprofiler.web:WsgiMiddleware.factory
29hmac_keys = SECRET_KEY
30enabled = yes
031
=== added file 'templates/kilo/glance-registry.conf'
--- templates/kilo/glance-registry.conf 1970-01-01 00:00:00 +0000
+++ templates/kilo/glance-registry.conf 2015-04-16 21:50:07 +0000
@@ -0,0 +1,27 @@
1[DEFAULT]
2verbose = {{ verbose }}
3use_syslog = {{ use_syslog }}
4debug = {{ debug }}
5workers = {{ workers }}
6
7bind_host = {{ bind_host }}
8bind_port = 9191
9log_file = /var/log/glance/registry.log
10backlog = 4096
11api_limit_max = 1000
12limit_param_default = 25
13
14{% if registry_config_flags -%}
15{% for key, value in registry_config_flags.iteritems() -%}
16{{ key }} = {{ value }}
17{% endfor -%}
18{% endif -%}
19
20{% include "section-keystone-authtoken" %}
21
22{% if auth_host -%}
23[paste_deploy]
24flavor = keystone
25{% endif %}
26
27{% include "parts/section-database" %}
028
=== modified file 'templates/parts/keystone'
--- templates/parts/keystone 2014-04-12 16:16:54 +0000
+++ templates/parts/keystone 2015-04-16 21:50:07 +0000
@@ -7,6 +7,7 @@
7admin_tenant_name = {{ admin_tenant_name }}7admin_tenant_name = {{ admin_tenant_name }}
8admin_user = {{ admin_user }}8admin_user = {{ admin_user }}
9admin_password = {{ admin_password }}9admin_password = {{ admin_password }}
10signing_dir = {{ signing_dir }}
1011
11[paste_deploy]12[paste_deploy]
12flavor = keystone13flavor = keystone
1314
=== modified file 'templates/parts/section-database'
--- templates/parts/section-database 2014-04-12 15:29:10 +0000
+++ templates/parts/section-database 2015-04-16 21:50:07 +0000
@@ -1,4 +1,5 @@
1{% if database_host -%}1{% if database_host -%}
2[database]2[database]
3connection = {{ database_type }}://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %}3connection = {{ database_type }}://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %}
4idle_timeout = 3600
4{% endif -%}5{% endif -%}
56
=== renamed file 'tests/14-basic-precise-icehouse' => 'tests/014-basic-precise-icehouse'
=== renamed file 'tests/15-basic-trusty-icehouse' => 'tests/015-basic-trusty-icehouse'
=== added file 'tests/016-basic-trusty-juno'
--- tests/016-basic-trusty-juno 1970-01-01 00:00:00 +0000
+++ tests/016-basic-trusty-juno 2015-04-16 21:50:07 +0000
@@ -0,0 +1,11 @@
1#!/usr/bin/python
2
3"""Amulet tests on a basic Glance deployment on trusty-juno."""
4
5from basic_deployment import GlanceBasicDeployment
6
7if __name__ == '__main__':
8 deployment = GlanceBasicDeployment(series='trusty',
9 openstack='cloud:trusty-juno',
10 source='cloud:trusty-updates/juno')
11 deployment.run_tests()
012
=== added file 'tests/017-basic-trusty-kilo'
--- tests/017-basic-trusty-kilo 1970-01-01 00:00:00 +0000
+++ tests/017-basic-trusty-kilo 2015-04-16 21:50:07 +0000
@@ -0,0 +1,11 @@
1#!/usr/bin/python
2
3"""Amulet tests on a basic glance deployment on trusty-kilo."""
4
5from basic_deployment import GlanceBasicDeployment
6
7if __name__ == '__main__':
8 deployment = GlanceBasicDeployment(series='trusty',
9 openstack='cloud:trusty-kilo',
10 source='cloud:trusty-updates/kilo')
11 deployment.run_tests()
012
=== added file 'tests/018-basic-utopic-juno'
--- tests/018-basic-utopic-juno 1970-01-01 00:00:00 +0000
+++ tests/018-basic-utopic-juno 2015-04-16 21:50:07 +0000
@@ -0,0 +1,9 @@
1#!/usr/bin/python
2
3"""Amulet tests on a basic Glance deployment on utopic-juno."""
4
5from basic_deployment import GlanceBasicDeployment
6
7if __name__ == '__main__':
8 deployment = GlanceBasicDeployment(series='utopic')
9 deployment.run_tests()
010
=== added file 'tests/019-basic-vivid-kilo'
--- tests/019-basic-vivid-kilo 1970-01-01 00:00:00 +0000
+++ tests/019-basic-vivid-kilo 2015-04-16 21:50:07 +0000
@@ -0,0 +1,9 @@
1#!/usr/bin/python
2
3"""Amulet tests on a basic Glance deployment on vivid-kilo."""
4
5from basic_deployment import GlanceBasicDeployment
6
7if __name__ == '__main__':
8 deployment = GlanceBasicDeployment(series='vivid')
9 deployment.run_tests()
010
=== added file 'tests/050-basic-trusty-icehouse-git'
--- tests/050-basic-trusty-icehouse-git 1970-01-01 00:00:00 +0000
+++ tests/050-basic-trusty-icehouse-git 2015-04-16 21:50:07 +0000
@@ -0,0 +1,9 @@
1#!/usr/bin/python
2
3"""Amulet tests on a basic Glance git deployment on trusty-icehouse."""
4
5from basic_deployment import GlanceBasicDeployment
6
7if __name__ == '__main__':
8 deployment = GlanceBasicDeployment(series='trusty', git=True)
9 deployment.run_tests()
010
=== added file 'tests/051-basic-trusty-juno-git'
--- tests/051-basic-trusty-juno-git 1970-01-01 00:00:00 +0000
+++ tests/051-basic-trusty-juno-git 2015-04-16 21:50:07 +0000
@@ -0,0 +1,12 @@
1#!/usr/bin/python
2
3"""Amulet tests on a basic Glance git deployment on trusty-juno."""
4
5from basic_deployment import GlanceBasicDeployment
6
7if __name__ == '__main__':
8 deployment = GlanceBasicDeployment(series='trusty',
9 openstack='cloud:trusty-juno',
10 source='cloud:trusty-updates/juno',
11 git=True)
12 deployment.run_tests()
013
=== removed file 'tests/10-basic-precise-essex'
--- tests/10-basic-precise-essex 2014-07-11 14:11:03 +0000
+++ tests/10-basic-precise-essex 1970-01-01 00:00:00 +0000
@@ -1,9 +0,0 @@
1#!/usr/bin/python
2
3"""Amulet tests on a basic glance deployment on precise-essex."""
4
5from basic_deployment import GlanceBasicDeployment
6
7if __name__ == '__main__':
8 deployment = GlanceBasicDeployment(series='precise')
9 deployment.run_tests()
100
=== removed file 'tests/11-basic-precise-folsom'
--- tests/11-basic-precise-folsom 2014-07-11 14:11:03 +0000
+++ tests/11-basic-precise-folsom 1970-01-01 00:00:00 +0000
@@ -1,11 +0,0 @@
1#!/usr/bin/python
2
3"""Amulet tests on a basic glance deployment on precise-folsom."""
4
5from basic_deployment import GlanceBasicDeployment
6
7if __name__ == '__main__':
8 deployment = GlanceBasicDeployment(series='precise',
9 openstack='cloud:precise-folsom',
10 source='cloud:precise-updates/folsom')
11 deployment.run_tests()
120
=== removed file 'tests/12-basic-precise-grizzly'
--- tests/12-basic-precise-grizzly 2014-07-11 14:11:03 +0000
+++ tests/12-basic-precise-grizzly 1970-01-01 00:00:00 +0000
@@ -1,11 +0,0 @@
1#!/usr/bin/python
2
3"""Amulet tests on a basic glance deployment on precise-grizzly."""
4
5from basic_deployment import GlanceBasicDeployment
6
7if __name__ == '__main__':
8 deployment = GlanceBasicDeployment(series='precise',
9 openstack='cloud:precise-grizzly',
10 source='cloud:precise-updates/grizzly')
11 deployment.run_tests()
120
=== removed file 'tests/13-basic-precise-havana'
--- tests/13-basic-precise-havana 2014-07-11 14:11:03 +0000
+++ tests/13-basic-precise-havana 1970-01-01 00:00:00 +0000
@@ -1,11 +0,0 @@
1#!/usr/bin/python
2
3"""Amulet tests on a basic glance deployment on precise-havana."""
4
5from basic_deployment import GlanceBasicDeployment
6
7if __name__ == '__main__':
8 deployment = GlanceBasicDeployment(series='precise',
9 openstack='cloud:precise-havana',
10 source='cloud:precise-updates/havana')
11 deployment.run_tests()
120
=== modified file 'tests/basic_deployment.py' (properties changed: +x to -x)
--- tests/basic_deployment.py 2015-04-09 00:37:53 +0000
+++ tests/basic_deployment.py 2015-04-16 21:50:07 +0000
@@ -1,6 +1,8 @@
1#!/usr/bin/python1#!/usr/bin/python
22
3import amulet3import amulet
4import os
5import yaml
46
5from charmhelpers.contrib.openstack.amulet.deployment import (7from charmhelpers.contrib.openstack.amulet.deployment import (
6 OpenStackAmuletDeployment8 OpenStackAmuletDeployment
@@ -13,7 +15,7 @@
13)15)
1416
15# Use DEBUG to turn on debug logging17# Use DEBUG to turn on debug logging
16u = OpenStackAmuletUtils(ERROR)18u = OpenStackAmuletUtils(DEBUG)
1719
18class GlanceBasicDeployment(OpenStackAmuletDeployment):20class GlanceBasicDeployment(OpenStackAmuletDeployment):
19 '''Amulet tests on a basic file-backed glance deployment. Verify relations,21 '''Amulet tests on a basic file-backed glance deployment. Verify relations,
@@ -23,9 +25,11 @@
23# * Add tests with different storage back ends25# * Add tests with different storage back ends
24# * Resolve Essex->Havana juju set charm bug26# * Resolve Essex->Havana juju set charm bug
2527
26 def __init__(self, series=None, openstack=None, source=None, stable=True):28 def __init__(self, series=None, openstack=None, source=None, git=False,
29 stable=False):
27 '''Deploy the entire test environment.'''30 '''Deploy the entire test environment.'''
28 super(GlanceBasicDeployment, self).__init__(series, openstack, source, stable)31 super(GlanceBasicDeployment, self).__init__(series, openstack, source, stable)
32 self.git = git
29 self._add_services()33 self._add_services()
30 self._add_relations()34 self._add_relations()
31 self._configure_services()35 self._configure_services()
@@ -55,11 +59,30 @@
5559
56 def _configure_services(self):60 def _configure_services(self):
57 '''Configure all of the services.'''61 '''Configure all of the services.'''
62 glance_config = {}
63 if self.git:
64 branch = 'stable/' + self._get_openstack_release_string()
65 amulet_http_proxy = os.environ.get('AMULET_HTTP_PROXY')
66 openstack_origin_git = {
67 'repositories': [
68 {'name': 'requirements',
69 'repository': 'git://git.openstack.org/openstack/requirements',
70 'branch': branch},
71 {'name': 'glance',
72 'repository': 'git://git.openstack.org/openstack/glance',
73 'branch': branch},
74 ],
75 'directory': '/mnt/openstack-git',
76 'http_proxy': amulet_http_proxy,
77 'https_proxy': amulet_http_proxy,
78 }
79 glance_config['openstack-origin-git'] = yaml.dump(openstack_origin_git)
80
58 keystone_config = {'admin-password': 'openstack',81 keystone_config = {'admin-password': 'openstack',
59 'admin-token': 'ubuntutesting'}82 'admin-token': 'ubuntutesting'}
60
61 mysql_config = {'dataset-size': '50%'}83 mysql_config = {'dataset-size': '50%'}
62 configs = {'keystone': keystone_config,84 configs = {'glance': glance_config,
85 'keystone': keystone_config,
63 'mysql': mysql_config}86 'mysql': mysql_config}
64 super(GlanceBasicDeployment, self)._configure_services(configs)87 super(GlanceBasicDeployment, self)._configure_services(configs)
6588
6689
=== modified file 'tests/charmhelpers/contrib/amulet/utils.py'
--- tests/charmhelpers/contrib/amulet/utils.py 2015-01-26 09:45:23 +0000
+++ tests/charmhelpers/contrib/amulet/utils.py 2015-04-16 21:50:07 +0000
@@ -118,6 +118,9 @@
118 longs, or can be a function that evaluate a variable and returns a118 longs, or can be a function that evaluate a variable and returns a
119 bool.119 bool.
120 """120 """
121 self.log.debug('actual: {}'.format(repr(actual)))
122 self.log.debug('expected: {}'.format(repr(expected)))
123
121 for k, v in six.iteritems(expected):124 for k, v in six.iteritems(expected):
122 if k in actual:125 if k in actual:
123 if (isinstance(v, six.string_types) or126 if (isinstance(v, six.string_types) or
@@ -134,7 +137,6 @@
134 def validate_relation_data(self, sentry_unit, relation, expected):137 def validate_relation_data(self, sentry_unit, relation, expected):
135 """Validate actual relation data based on expected relation data."""138 """Validate actual relation data based on expected relation data."""
136 actual = sentry_unit.relation(relation[0], relation[1])139 actual = sentry_unit.relation(relation[0], relation[1])
137 self.log.debug('actual: {}'.format(repr(actual)))
138 return self._validate_dict_data(expected, actual)140 return self._validate_dict_data(expected, actual)
139141
140 def _validate_list_data(self, expected, actual):142 def _validate_list_data(self, expected, actual):
@@ -169,8 +171,13 @@
169 cmd = 'pgrep -o -f {}'.format(service)171 cmd = 'pgrep -o -f {}'.format(service)
170 else:172 else:
171 cmd = 'pgrep -o {}'.format(service)173 cmd = 'pgrep -o {}'.format(service)
172 proc_dir = '/proc/{}'.format(sentry_unit.run(cmd)[0].strip())174 cmd = cmd + ' | grep -v pgrep || exit 0'
173 return self._get_dir_mtime(sentry_unit, proc_dir)175 cmd_out = sentry_unit.run(cmd)
176 self.log.debug('CMDout: ' + str(cmd_out))
177 if cmd_out[0]:
178 self.log.debug('Pid for %s %s' % (service, str(cmd_out[0])))
179 proc_dir = '/proc/{}'.format(cmd_out[0].strip())
180 return self._get_dir_mtime(sentry_unit, proc_dir)
174181
175 def service_restarted(self, sentry_unit, service, filename,182 def service_restarted(self, sentry_unit, service, filename,
176 pgrep_full=False, sleep_time=20):183 pgrep_full=False, sleep_time=20):
@@ -187,6 +194,121 @@
187 else:194 else:
188 return False195 return False
189196
197 def service_restarted_since(self, sentry_unit, mtime, service,
198 pgrep_full=False, sleep_time=20,
199 retry_count=2):
200 """Check if service was been started after a given time.
201
202 Args:
203 sentry_unit (sentry): The sentry unit to check for the service on
204 mtime (float): The epoch time to check against
205 service (string): service name to look for in process table
206 pgrep_full (boolean): Use full command line search mode with pgrep
207 sleep_time (int): Seconds to sleep before looking for process
208 retry_count (int): If service is not found, how many times to retry
209
210 Returns:
211 bool: True if service found and its start time it newer than mtime,
212 False if service is older than mtime or if service was
213 not found.
214 """
215 self.log.debug('Checking %s restarted since %s' % (service, mtime))
216 time.sleep(sleep_time)
217 proc_start_time = self._get_proc_start_time(sentry_unit, service,
218 pgrep_full)
219 while retry_count > 0 and not proc_start_time:
220 self.log.debug('No pid file found for service %s, will retry %i '
221 'more times' % (service, retry_count))
222 time.sleep(30)
223 proc_start_time = self._get_proc_start_time(sentry_unit, service,
224 pgrep_full)
225 retry_count = retry_count - 1
226
227 if not proc_start_time:
228 self.log.warn('No proc start time found, assuming service did '
229 'not start')
230 return False
231 if proc_start_time >= mtime:
232 self.log.debug('proc start time is newer than provided mtime'
233 '(%s >= %s)' % (proc_start_time, mtime))
234 return True
235 else:
236 self.log.warn('proc start time (%s) is older than provided mtime '
237 '(%s), service did not restart' % (proc_start_time,
238 mtime))
239 return False
240
241 def config_updated_since(self, sentry_unit, filename, mtime,
242 sleep_time=20):
243 """Check if file was modified after a given time.
244
245 Args:
246 sentry_unit (sentry): The sentry unit to check the file mtime on
247 filename (string): The file to check mtime of
248 mtime (float): The epoch time to check against
249 sleep_time (int): Seconds to sleep before looking for process
250
251 Returns:
252 bool: True if file was modified more recently than mtime, False if
253 file was modified before mtime,
254 """
255 self.log.debug('Checking %s updated since %s' % (filename, mtime))
256 time.sleep(sleep_time)
257 file_mtime = self._get_file_mtime(sentry_unit, filename)
258 if file_mtime >= mtime:
259 self.log.debug('File mtime is newer than provided mtime '
260 '(%s >= %s)' % (file_mtime, mtime))
261 return True
262 else:
263 self.log.warn('File mtime %s is older than provided mtime %s'
264 % (file_mtime, mtime))
265 return False
266
267 def validate_service_config_changed(self, sentry_unit, mtime, service,
268 filename, pgrep_full=False,
269 sleep_time=20, retry_count=2):
270 """Check service and file were updated after mtime
271
272 Args:
273 sentry_unit (sentry): The sentry unit to check for the service on
274 mtime (float): The epoch time to check against
275 service (string): service name to look for in process table
276 filename (string): The file to check mtime of
277 pgrep_full (boolean): Use full command line search mode with pgrep
278 sleep_time (int): Seconds to sleep before looking for process
279 retry_count (int): If service is not found, how many times to retry
280
281 Typical Usage:
282 u = OpenStackAmuletUtils(ERROR)
283 ...
284 mtime = u.get_sentry_time(self.cinder_sentry)
285 self.d.configure('cinder', {'verbose': 'True', 'debug': 'True'})
286 if not u.validate_service_config_changed(self.cinder_sentry,
287 mtime,
288 'cinder-api',
289 '/etc/cinder/cinder.conf')
290 amulet.raise_status(amulet.FAIL, msg='update failed')
291 Returns:
292 bool: True if both service and file where updated/restarted after
293 mtime, False if service is older than mtime or if service was
294 not found or if filename was modified before mtime.
295 """
296 self.log.debug('Checking %s restarted since %s' % (service, mtime))
297 time.sleep(sleep_time)
298 service_restart = self.service_restarted_since(sentry_unit, mtime,
299 service,
300 pgrep_full=pgrep_full,
301 sleep_time=0,
302 retry_count=retry_count)
303 config_update = self.config_updated_since(sentry_unit, filename, mtime,
304 sleep_time=0)
305 return service_restart and config_update
306
307 def get_sentry_time(self, sentry_unit):
308 """Return current epoch time on a sentry"""
309 cmd = "date +'%s'"
310 return float(sentry_unit.run(cmd)[0])
311
190 def relation_error(self, name, data):312 def relation_error(self, name, data):
191 return 'unexpected relation data in {} - {}'.format(name, data)313 return 'unexpected relation data in {} - {}'.format(name, data)
192314
193315
=== modified file 'tests/charmhelpers/contrib/openstack/amulet/deployment.py'
--- tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-01-26 09:45:23 +0000
+++ tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-04-16 21:50:07 +0000
@@ -15,6 +15,7 @@
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1616
17import six17import six
18from collections import OrderedDict
18from charmhelpers.contrib.amulet.deployment import (19from charmhelpers.contrib.amulet.deployment import (
19 AmuletDeployment20 AmuletDeployment
20)21)
@@ -43,7 +44,7 @@
43 Determine if the local branch being tested is derived from its44 Determine if the local branch being tested is derived from its
44 stable or next (dev) branch, and based on this, use the corresonding45 stable or next (dev) branch, and based on this, use the corresonding
45 stable or next branches for the other_services."""46 stable or next branches for the other_services."""
46 base_charms = ['mysql', 'mongodb', 'rabbitmq-server']47 base_charms = ['mysql', 'mongodb']
4748
48 if self.stable:49 if self.stable:
49 for svc in other_services:50 for svc in other_services:
@@ -71,16 +72,19 @@
71 services.append(this_service)72 services.append(this_service)
72 use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph',73 use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph',
73 'ceph-osd', 'ceph-radosgw']74 'ceph-osd', 'ceph-radosgw']
75 # Openstack subordinate charms do not expose an origin option as that
76 # is controlled by the principle
77 ignore = ['neutron-openvswitch']
7478
75 if self.openstack:79 if self.openstack:
76 for svc in services:80 for svc in services:
77 if svc['name'] not in use_source:81 if svc['name'] not in use_source + ignore:
78 config = {'openstack-origin': self.openstack}82 config = {'openstack-origin': self.openstack}
79 self.d.configure(svc['name'], config)83 self.d.configure(svc['name'], config)
8084
81 if self.source:85 if self.source:
82 for svc in services:86 for svc in services:
83 if svc['name'] in use_source:87 if svc['name'] in use_source and svc['name'] not in ignore:
84 config = {'source': self.source}88 config = {'source': self.source}
85 self.d.configure(svc['name'], config)89 self.d.configure(svc['name'], config)
8690
@@ -97,12 +101,37 @@
97 """101 """
98 (self.precise_essex, self.precise_folsom, self.precise_grizzly,102 (self.precise_essex, self.precise_folsom, self.precise_grizzly,
99 self.precise_havana, self.precise_icehouse,103 self.precise_havana, self.precise_icehouse,
100 self.trusty_icehouse) = range(6)104 self.trusty_icehouse, self.trusty_juno, self.trusty_kilo,
105 self.utopic_juno, self.vivid_kilo) = range(10)
101 releases = {106 releases = {
102 ('precise', None): self.precise_essex,107 ('precise', None): self.precise_essex,
103 ('precise', 'cloud:precise-folsom'): self.precise_folsom,108 ('precise', 'cloud:precise-folsom'): self.precise_folsom,
104 ('precise', 'cloud:precise-grizzly'): self.precise_grizzly,109 ('precise', 'cloud:precise-grizzly'): self.precise_grizzly,
105 ('precise', 'cloud:precise-havana'): self.precise_havana,110 ('precise', 'cloud:precise-havana'): self.precise_havana,
106 ('precise', 'cloud:precise-icehouse'): self.precise_icehouse,111 ('precise', 'cloud:precise-icehouse'): self.precise_icehouse,
107 ('trusty', None): self.trusty_icehouse}112 ('trusty', None): self.trusty_icehouse,
113 ('trusty', 'cloud:trusty-juno'): self.trusty_juno,
114 ('trusty', 'cloud:trusty-kilo'): self.trusty_kilo,
115 ('utopic', None): self.utopic_juno,
116 ('vivid', None): self.vivid_kilo}
108 return releases[(self.series, self.openstack)]117 return releases[(self.series, self.openstack)]
118
119 def _get_openstack_release_string(self):
120 """Get openstack release string.
121
122 Return a string representing the openstack release.
123 """
124 releases = OrderedDict([
125 ('precise', 'essex'),
126 ('quantal', 'folsom'),
127 ('raring', 'grizzly'),
128 ('saucy', 'havana'),
129 ('trusty', 'icehouse'),
130 ('utopic', 'juno'),
131 ('vivid', 'kilo'),
132 ])
133 if self.openstack:
134 os_origin = self.openstack.split(':')[1]
135 return os_origin.split('%s-' % self.series)[1].split('/')[0]
136 else:
137 return releases[self.series]
109138
=== added directory 'trusty'
=== modified file 'unit_tests/__init__.py'
--- unit_tests/__init__.py 2013-08-09 18:45:02 +0000
+++ unit_tests/__init__.py 2015-04-16 21:50:07 +0000
@@ -1,3 +1,4 @@
1import sys1import sys
22
3sys.path.append('actions/')
3sys.path.append('hooks/')4sys.path.append('hooks/')
45
=== added file 'unit_tests/test_actions_git_reinstall.py'
--- unit_tests/test_actions_git_reinstall.py 1970-01-01 00:00:00 +0000
+++ unit_tests/test_actions_git_reinstall.py 2015-04-16 21:50:07 +0000
@@ -0,0 +1,96 @@
1from mock import patch
2import os
3
4os.environ['JUJU_UNIT_NAME'] = 'glance'
5
6with patch('glance_utils.register_configs') as register_configs:
7 import git_reinstall
8
9from test_utils import (
10 CharmTestCase
11)
12
13TO_PATCH = [
14 'config',
15]
16
17
18openstack_origin_git = \
19 """repositories:
20 - {name: requirements,
21 repository: 'git://git.openstack.org/openstack/requirements',
22 branch: stable/juno}
23 - {name: glance,
24 repository: 'git://git.openstack.org/openstack/glance',
25 branch: stable/juno}"""
26
27
28class TestGlanceActions(CharmTestCase):
29
30 def setUp(self):
31 super(TestGlanceActions, self).setUp(git_reinstall, TO_PATCH)
32 self.config.side_effect = self.test_config.get
33
34 @patch.object(git_reinstall, 'action_set')
35 @patch.object(git_reinstall, 'action_fail')
36 @patch.object(git_reinstall, 'git_install')
37 @patch.object(git_reinstall, 'config_changed')
38 @patch('charmhelpers.contrib.openstack.utils.config')
39 def test_git_reinstall(self, _config, config_changed, git_install,
40 action_fail, action_set):
41 _config.return_value = openstack_origin_git
42 self.test_config.set('openstack-origin-git', openstack_origin_git)
43
44 git_reinstall.git_reinstall()
45
46 git_install.assert_called_with(openstack_origin_git)
47 self.assertTrue(git_install.called)
48 self.assertTrue(config_changed.called)
49 self.assertFalse(action_set.called)
50 self.assertFalse(action_fail.called)
51
52 @patch.object(git_reinstall, 'action_set')
53 @patch.object(git_reinstall, 'action_fail')
54 @patch.object(git_reinstall, 'git_install')
55 @patch.object(git_reinstall, 'config_changed')
56 @patch('charmhelpers.contrib.openstack.utils.config')
57 def test_git_reinstall_not_configured(self, _config, config_changed,
58 git_install, action_fail,
59 action_set):
60 _config.return_value = None
61
62 git_reinstall.git_reinstall()
63
64 msg = 'openstack-origin-git is not configured'
65 action_fail.assert_called_with(msg)
66 self.assertFalse(git_install.called)
67 self.assertFalse(action_set.called)
68
69 @patch.object(git_reinstall, 'action_set')
70 @patch.object(git_reinstall, 'action_fail')
71 @patch.object(git_reinstall, 'git_install')
72 @patch.object(git_reinstall, 'config_changed')
73 @patch('traceback.format_exc')
74 @patch('charmhelpers.contrib.openstack.utils.config')
75 def test_git_reinstall_exception(self, _config, format_exc,
76 config_changed, git_install, action_fail,
77 action_set):
78 _config.return_value = openstack_origin_git
79 e = OSError('something bad happened')
80 git_install.side_effect = e
81 traceback = (
82 "Traceback (most recent call last):\n"
83 " File \"actions/git_reinstall.py\", line 37, in git_reinstall\n"
84 " git_install(config(\'openstack-origin-git\'))\n"
85 " File \"/usr/lib/python2.7/dist-packages/mock.py\", line 964, in __call__\n" # noqa
86 " return _mock_self._mock_call(*args, **kwargs)\n"
87 " File \"/usr/lib/python2.7/dist-packages/mock.py\", line 1019, in _mock_call\n" # noqa
88 " raise effect\n"
89 "OSError: something bad happened\n")
90 format_exc.return_value = traceback
91
92 git_reinstall.git_reinstall()
93
94 msg = 'git-reinstall resulted in an unexpected error'
95 action_fail.assert_called_with(msg)
96 action_set.assert_called_with({'traceback': traceback})
097
=== modified file 'unit_tests/test_glance_relations.py'
--- unit_tests/test_glance_relations.py 2015-01-22 16:55:57 +0000
+++ unit_tests/test_glance_relations.py 2015-04-16 21:50:07 +0000
@@ -1,6 +1,7 @@
1from mock import call, patch, MagicMock1from mock import call, patch, MagicMock
2import json2import json
3import os3import os
4import yaml
45
5from test_utils import CharmTestCase6from test_utils import CharmTestCase
67
@@ -38,10 +39,11 @@
38 'apt_install',39 'apt_install',
39 'apt_update',40 'apt_update',
40 'restart_on_change',41 'restart_on_change',
42 'service_reload',
41 'service_stop',43 'service_stop',
42 # charmhelpers.contrib.openstack.utils44 # charmhelpers.contrib.openstack.utils
43 'configure_installation_source',45 'configure_installation_source',
44 'get_os_codename_package',46 'os_release',
45 'openstack_upgrade_available',47 'openstack_upgrade_available',
46 # charmhelpers.contrib.hahelpers.cluster_utils48 # charmhelpers.contrib.hahelpers.cluster_utils
47 'eligible_leader',49 'eligible_leader',
@@ -52,6 +54,7 @@
52 'migrate_database',54 'migrate_database',
53 'ensure_ceph_keyring',55 'ensure_ceph_keyring',
54 'ceph_config_file',56 'ceph_config_file',
57 'git_install',
55 'update_nrpe_config',58 'update_nrpe_config',
56 # other59 # other
57 'call',60 'call',
@@ -64,6 +67,7 @@
64 'get_iface_for_address',67 'get_iface_for_address',
65 'get_ipv6_addr',68 'get_ipv6_addr',
66 'sync_db_with_multi_ipv6_addresses',69 'sync_db_with_multi_ipv6_addresses',
70 'delete_keyring',
67]71]
6872
6973
@@ -73,23 +77,26 @@
73 super(GlanceRelationTests, self).setUp(relations, TO_PATCH)77 super(GlanceRelationTests, self).setUp(relations, TO_PATCH)
74 self.config.side_effect = self.test_config.get78 self.config.side_effect = self.test_config.get
7579
76 def test_install_hook(self):80 @patch.object(utils, 'git_install_requested')
81 def test_install_hook(self, git_requested):
82 git_requested.return_value = False
77 repo = 'cloud:precise-grizzly'83 repo = 'cloud:precise-grizzly'
78 self.test_config.set('openstack-origin', repo)84 self.test_config.set('openstack-origin', repo)
79 self.service_stop.return_value = True85 self.service_stop.return_value = True
80 relations.install_hook()86 relations.install_hook()
81 self.configure_installation_source.assert_called_with(repo)87 self.configure_installation_source.assert_called_with(repo)
82 self.apt_update.assert_called_with(fatal=True)88 self.apt_update.assert_called_with(fatal=True)
83 self.apt_install.assert_called_with(['apache2', 'glance',89 self.apt_install.assert_called_with(['haproxy', 'python-six', 'uuid',
84 'python-mysqldb',90 'python-mysqldb', 'apache2',
85 'python-swiftclient',91 'python-psycopg2', 'glance',
86 'python-psycopg2',
87 'python-keystone',92 'python-keystone',
88 'python-six',93 'python-swiftclient'], fatal=True)
89 'uuid', 'haproxy'], fatal=True)
90 self.assertTrue(self.execd_preinstall.called)94 self.assertTrue(self.execd_preinstall.called)
95 self.git_install.assert_called_with(None)
9196
92 def test_install_hook_precise_distro(self):97 @patch.object(utils, 'git_install_requested')
98 def test_install_hook_precise_distro(self, git_requested):
99 git_requested.return_value = False
93 self.test_config.set('openstack-origin', 'distro')100 self.test_config.set('openstack-origin', 'distro')
94 self.lsb_release.return_value = {'DISTRIB_RELEASE': 12.04,101 self.lsb_release.return_value = {'DISTRIB_RELEASE': 12.04,
95 'DISTRIB_CODENAME': 'precise'}102 'DISTRIB_CODENAME': 'precise'}
@@ -99,6 +106,37 @@
99 "cloud:precise-folsom"106 "cloud:precise-folsom"
100 )107 )
101108
109 @patch.object(utils, 'git_install_requested')
110 def test_install_hook_git(self, git_requested):
111 git_requested.return_value = True
112 repo = 'cloud:trusty-juno'
113 openstack_origin_git = {
114 'repositories': [
115 {'name': 'requirements',
116 'repository': 'git://git.openstack.org/openstack/requirements', # noqa
117 'branch': 'stable/juno'},
118 {'name': 'glance',
119 'repository': 'git://git.openstack.org/openstack/glance',
120 'branch': 'stable/juno'}
121 ],
122 'directory': '/mnt/openstack-git',
123 }
124 projects_yaml = yaml.dump(openstack_origin_git)
125 self.test_config.set('openstack-origin', repo)
126 self.test_config.set('openstack-origin-git', projects_yaml)
127 relations.install_hook()
128 self.assertTrue(self.execd_preinstall.called)
129 self.configure_installation_source.assert_called_with(repo)
130 self.apt_update.assert_called_with(fatal=True)
131 self.apt_install.assert_called_with(['haproxy', 'python-setuptools',
132 'python-six', 'uuid',
133 'python-mysqldb', 'python-pip',
134 'apache2', 'libxslt1-dev',
135 'python-psycopg2', 'zlib1g-dev',
136 'python-dev', 'libxml2-dev'],
137 fatal=True)
138 self.git_install.assert_called_with(projects_yaml)
139
102 def test_db_joined(self):140 def test_db_joined(self):
103 self.unit_get.return_value = 'glance.foohost.com'141 self.unit_get.return_value = 'glance.foohost.com'
104 self.is_relation_made.return_value = False142 self.is_relation_made.return_value = False
@@ -215,7 +253,7 @@
215253
216 @patch.object(relations, 'CONFIGS')254 @patch.object(relations, 'CONFIGS')
217 def test_db_changed_with_essex_not_setting_version_control(self, configs):255 def test_db_changed_with_essex_not_setting_version_control(self, configs):
218 self.get_os_codename_package.return_value = "essex"256 self.os_release.return_value = "essex"
219 self.call.return_value = 0257 self.call.return_value = 0
220 self._shared_db_test(configs, 'glance/0')258 self._shared_db_test(configs, 'glance/0')
221 self.assertEquals([call('/etc/glance/glance-registry.conf')],259 self.assertEquals([call('/etc/glance/glance-registry.conf')],
@@ -228,7 +266,7 @@
228 @patch.object(relations, 'CONFIGS')266 @patch.object(relations, 'CONFIGS')
229 def test_postgresql_db_changed_with_essex_not_setting_version_control(267 def test_postgresql_db_changed_with_essex_not_setting_version_control(
230 self, configs):268 self, configs):
231 self.get_os_codename_package.return_value = "essex"269 self.os_release.return_value = "essex"
232 self.call.return_value = 0270 self.call.return_value = 0
233 self._postgresql_db_test(configs)271 self._postgresql_db_test(configs)
234 self.assertEquals([call('/etc/glance/glance-registry.conf')],272 self.assertEquals([call('/etc/glance/glance-registry.conf')],
@@ -240,7 +278,7 @@
240278
241 @patch.object(relations, 'CONFIGS')279 @patch.object(relations, 'CONFIGS')
242 def test_db_changed_with_essex_setting_version_control(self, configs):280 def test_db_changed_with_essex_setting_version_control(self, configs):
243 self.get_os_codename_package.return_value = "essex"281 self.os_release.return_value = "essex"
244 self.call.return_value = 1282 self.call.return_value = 1
245 self._shared_db_test(configs, 'glance/0')283 self._shared_db_test(configs, 'glance/0')
246 self.assertEquals([call('/etc/glance/glance-registry.conf')],284 self.assertEquals([call('/etc/glance/glance-registry.conf')],
@@ -256,7 +294,7 @@
256 @patch.object(relations, 'CONFIGS')294 @patch.object(relations, 'CONFIGS')
257 def test_postgresql_db_changed_with_essex_setting_version_control(295 def test_postgresql_db_changed_with_essex_setting_version_control(
258 self, configs):296 self, configs):
259 self.get_os_codename_package.return_value = "essex"297 self.os_release.return_value = "essex"
260 self.call.return_value = 1298 self.call.return_value = 1
261 self._postgresql_db_test(configs)299 self._postgresql_db_test(configs)
262 self.assertEquals([call('/etc/glance/glance-registry.conf')],300 self.assertEquals([call('/etc/glance/glance-registry.conf')],
@@ -381,6 +419,13 @@
381 call(self.ceph_config_file())],419 call(self.ceph_config_file())],
382 configs.write.call_args_list)420 configs.write.call_args_list)
383421
422 @patch.object(relations, 'CONFIGS')
423 def test_ceph_broken(self, configs):
424 self.service_name.return_value = 'glance'
425 relations.ceph_broken()
426 self.delete_keyring.assert_called_with(service='glance')
427 self.assertTrue(configs.write_all.called)
428
384 def test_keystone_joined(self):429 def test_keystone_joined(self):
385 self.canonical_url.return_value = 'http://glancehost'430 self.canonical_url.return_value = 'http://glancehost'
386 relations.keystone_joined()431 relations.keystone_joined()
@@ -433,8 +478,12 @@
433 @patch.object(relations, 'configure_https')478 @patch.object(relations, 'configure_https')
434 @patch.object(relations, 'object_store_joined')479 @patch.object(relations, 'object_store_joined')
435 @patch.object(relations, 'CONFIGS')480 @patch.object(relations, 'CONFIGS')
436 def test_keystone_changed_with_object_store_relation(481 @patch.object(utils, 'git_install_requested')
437 self, configs, object_store_joined, configure_https):482 def test_keystone_changed_with_object_store_relation(self, git_requested,
483 configs,
484 object_store_joined,
485 configure_https):
486 git_requested.return_value = False
438 configs.complete_contexts = MagicMock()487 configs.complete_contexts = MagicMock()
439 configs.complete_contexts.return_value = ['identity-service']488 configs.complete_contexts.return_value = ['identity-service']
440 configs.write = MagicMock()489 configs.write = MagicMock()
@@ -449,14 +498,20 @@
449 self.assertTrue(configure_https.called)498 self.assertTrue(configure_https.called)
450499
451 @patch.object(relations, 'configure_https')500 @patch.object(relations, 'configure_https')
452 def test_config_changed_no_openstack_upgrade(self, configure_https):501 @patch.object(relations, 'git_install_requested')
502 def test_config_changed_no_openstack_upgrade(self, git_requested,
503 configure_https):
504 git_requested.return_value = False
453 self.openstack_upgrade_available.return_value = False505 self.openstack_upgrade_available.return_value = False
454 relations.config_changed()506 relations.config_changed()
455 self.open_port.assert_called_with(9292)507 self.open_port.assert_called_with(9292)
456 self.assertTrue(configure_https.called)508 self.assertTrue(configure_https.called)
457509
458 @patch.object(relations, 'configure_https')510 @patch.object(relations, 'configure_https')
459 def test_config_changed_with_openstack_upgrade(self, configure_https):511 @patch.object(relations, 'git_install_requested')
512 def test_config_changed_with_openstack_upgrade(self, git_requested,
513 configure_https):
514 git_requested.return_value = False
460 self.openstack_upgrade_available.return_value = True515 self.openstack_upgrade_available.return_value = True
461 relations.config_changed()516 relations.config_changed()
462 self.juju_log.assert_called_with(517 self.juju_log.assert_called_with(
@@ -465,6 +520,32 @@
465 self.assertTrue(self.do_openstack_upgrade.called)520 self.assertTrue(self.do_openstack_upgrade.called)
466 self.assertTrue(configure_https.called)521 self.assertTrue(configure_https.called)
467522
523 @patch.object(relations, 'configure_https')
524 @patch.object(relations, 'git_install_requested')
525 @patch.object(relations, 'config_value_changed')
526 def test_config_changed_git_updated(self, config_val_changed,
527 git_requested, configure_https):
528 git_requested.return_value = True
529 repo = 'cloud:trusty-juno'
530 openstack_origin_git = {
531 'repositories': [
532 {'name': 'requirements',
533 'repository': 'git://git.openstack.org/openstack/requirements', # noqa
534 'branch': 'stable/juno'},
535 {'name': 'glance',
536 'repository': 'git://git.openstack.org/openstack/glance',
537 'branch': 'stable/juno'}
538 ],
539 'directory': '/mnt/openstack-git',
540 }
541 projects_yaml = yaml.dump(openstack_origin_git)
542 self.test_config.set('openstack-origin', repo)
543 self.test_config.set('openstack-origin-git', projects_yaml)
544 relations.config_changed()
545 self.git_install.assert_called_with(projects_yaml)
546 self.assertFalse(self.do_openstack_upgrade.called)
547 self.assertTrue(configure_https.called)
548
468 @patch.object(relations, 'CONFIGS')549 @patch.object(relations, 'CONFIGS')
469 def test_cluster_changed(self, configs):550 def test_cluster_changed(self, configs):
470 self.test_config.set('prefer-ipv6', False)551 self.test_config.set('prefer-ipv6', False)
@@ -491,7 +572,9 @@
491 configs.write.call_args_list)572 configs.write.call_args_list)
492573
493 @patch.object(relations, 'CONFIGS')574 @patch.object(relations, 'CONFIGS')
494 def test_upgrade_charm(self, configs):575 @patch.object(utils, 'git_install_requested')
576 def test_upgrade_charm(self, git_requested, configs):
577 git_requested.return_value = False
495 self.filter_installed_packages.return_value = ['test']578 self.filter_installed_packages.return_value = ['test']
496 relations.upgrade_charm()579 relations.upgrade_charm()
497 self.apt_install.assert_called_with(['test'], fatal=True)580 self.apt_install.assert_called_with(['test'], fatal=True)
@@ -596,8 +679,9 @@
596 configs.write = MagicMock()679 configs.write = MagicMock()
597 self.relation_ids.return_value = ['identity-service:0']680 self.relation_ids.return_value = ['identity-service:0']
598 relations.configure_https()681 relations.configure_https()
599 cmd = ['a2ensite', 'openstack_https_frontend']682 calls = [call('a2dissite', 'openstack_https_frontend'),
600 self.check_call.assert_called_with(cmd)683 call('service', 'apache2', 'reload')]
684 self.check_call.assert_called_has_calls(calls)
601 keystone_joined.assert_called_with(relation_id='identity-service:0')685 keystone_joined.assert_called_with(relation_id='identity-service:0')
602686
603 @patch.object(relations, 'keystone_joined')687 @patch.object(relations, 'keystone_joined')
@@ -609,8 +693,9 @@
609 configs.write = MagicMock()693 configs.write = MagicMock()
610 self.relation_ids.return_value = ['identity-service:0']694 self.relation_ids.return_value = ['identity-service:0']
611 relations.configure_https()695 relations.configure_https()
612 cmd = ['a2dissite', 'openstack_https_frontend']696 calls = [call('a2dissite', 'openstack_https_frontend'),
613 self.check_call.assert_called_with(cmd)697 call('service', 'apache2', 'reload')]
698 self.check_call.assert_called_has_calls(calls)
614 keystone_joined.assert_called_with(relation_id='identity-service:0')699 keystone_joined.assert_called_with(relation_id='identity-service:0')
615700
616 @patch.object(relations, 'image_service_joined')701 @patch.object(relations, 'image_service_joined')
@@ -622,8 +707,9 @@
622 configs.write = MagicMock()707 configs.write = MagicMock()
623 self.relation_ids.return_value = ['image-service:0']708 self.relation_ids.return_value = ['image-service:0']
624 relations.configure_https()709 relations.configure_https()
625 cmd = ['a2ensite', 'openstack_https_frontend']710 calls = [call('a2dissite', 'openstack_https_frontend'),
626 self.check_call.assert_called_with(cmd)711 call('service', 'apache2', 'reload')]
712 self.check_call.assert_called_has_calls(calls)
627 image_service_joined.assert_called_with(relation_id='image-service:0')713 image_service_joined.assert_called_with(relation_id='image-service:0')
628714
629 @patch.object(relations, 'image_service_joined')715 @patch.object(relations, 'image_service_joined')
@@ -635,8 +721,9 @@
635 configs.write = MagicMock()721 configs.write = MagicMock()
636 self.relation_ids.return_value = ['image-service:0']722 self.relation_ids.return_value = ['image-service:0']
637 relations.configure_https()723 relations.configure_https()
638 cmd = ['a2dissite', 'openstack_https_frontend']724 calls = [call('a2dissite', 'openstack_https_frontend'),
639 self.check_call.assert_called_with(cmd)725 call('service', 'apache2', 'reload')]
726 self.check_call.assert_called_has_calls(calls)
640 image_service_joined.assert_called_with(relation_id='image-service:0')727 image_service_joined.assert_called_with(relation_id='image-service:0')
641728
642 def test_amqp_joined(self):729 def test_amqp_joined(self):
643730
=== modified file 'unit_tests/test_glance_utils.py'
--- unit_tests/test_glance_utils.py 2015-01-06 15:09:51 +0000
+++ unit_tests/test_glance_utils.py 2015-04-16 21:50:07 +0000
@@ -14,7 +14,6 @@
14 'config',14 'config',
15 'log',15 'log',
16 'relation_ids',16 'relation_ids',
17 'get_os_codename_package',
18 'get_os_codename_install_source',17 'get_os_codename_install_source',
19 'configure_installation_source',18 'configure_installation_source',
20 'eligible_leader',19 'eligible_leader',
@@ -23,6 +22,7 @@
23 'apt_upgrade',22 'apt_upgrade',
24 'apt_install',23 'apt_install',
25 'mkdir',24 'mkdir',
25 'os_release',
26 'service_start',26 'service_start',
27 'service_stop',27 'service_stop',
28 'service_name',28 'service_name',
@@ -34,6 +34,15 @@
34 '--option', 'Dpkg::Options::=--force-confdef',34 '--option', 'Dpkg::Options::=--force-confdef',
35]35]
3636
37openstack_origin_git = \
38 """repositories:
39 - {name: requirements,
40 repository: 'git://git.openstack.org/openstack/requirements',
41 branch: stable/juno}
42 - {name: glance,
43 repository: 'git://git.openstack.org/openstack/glance',
44 branch: stable/juno}"""
45
3746
38class TestGlanceUtils(CharmTestCase):47class TestGlanceUtils(CharmTestCase):
3948
@@ -50,7 +59,7 @@
50 @patch('os.path.exists')59 @patch('os.path.exists')
51 def test_register_configs_apache(self, exists):60 def test_register_configs_apache(self, exists):
52 exists.return_value = False61 exists.return_value = False
53 self.get_os_codename_package.return_value = 'grizzly'62 self.os_release.return_value = 'grizzly'
54 self.relation_ids.return_value = False63 self.relation_ids.return_value = False
55 configs = utils.register_configs()64 configs = utils.register_configs()
56 calls = []65 calls = []
@@ -69,7 +78,7 @@
69 @patch('os.path.exists')78 @patch('os.path.exists')
70 def test_register_configs_apache24(self, exists):79 def test_register_configs_apache24(self, exists):
71 exists.return_value = True80 exists.return_value = True
72 self.get_os_codename_package.return_value = 'grizzly'81 self.os_release.return_value = 'grizzly'
73 self.relation_ids.return_value = False82 self.relation_ids.return_value = False
74 configs = utils.register_configs()83 configs = utils.register_configs()
75 calls = []84 calls = []
@@ -88,7 +97,7 @@
88 @patch('os.path.exists')97 @patch('os.path.exists')
89 def test_register_configs_ceph(self, exists):98 def test_register_configs_ceph(self, exists):
90 exists.return_value = True99 exists.return_value = True
91 self.get_os_codename_package.return_value = 'grizzly'100 self.os_release.return_value = 'grizzly'
92 self.relation_ids.return_value = ['ceph:0']101 self.relation_ids.return_value = ['ceph:0']
93 self.service_name.return_value = 'glance'102 self.service_name.return_value = 'glance'
94 configs = utils.register_configs()103 configs = utils.register_configs()
@@ -121,8 +130,26 @@
121 ])130 ])
122 self.assertEquals(ex_map, utils.restart_map())131 self.assertEquals(ex_map, utils.restart_map())
123132
133 @patch('charmhelpers.contrib.openstack.utils.config')
134 def test_determine_packages(self, _config):
135 _config.return_value = None
136 result = utils.determine_packages()
137 ex = utils.PACKAGES
138 self.assertEquals(set(ex), set(result))
139
140 @patch('charmhelpers.contrib.openstack.utils.config')
141 def test_determine_packages_git(self, _config):
142 _config.return_value = openstack_origin_git
143 result = utils.determine_packages()
144 ex = utils.PACKAGES + utils.BASE_GIT_PACKAGES
145 for p in utils.GIT_PACKAGE_BLACKLIST:
146 ex.remove(p)
147 self.assertEquals(set(ex), set(result))
148
124 @patch.object(utils, 'migrate_database')149 @patch.object(utils, 'migrate_database')
125 def test_openstack_upgrade_leader(self, migrate):150 @patch.object(utils, 'git_install_requested')
151 def test_openstack_upgrade_leader(self, git_requested, migrate):
152 git_requested.return_value = True
126 self.config.side_effect = None153 self.config.side_effect = None
127 self.config.return_value = 'cloud:precise-havana'154 self.config.return_value = 'cloud:precise-havana'
128 self.eligible_leader.return_value = True155 self.eligible_leader.return_value = True
@@ -130,14 +157,17 @@
130 configs = MagicMock()157 configs = MagicMock()
131 utils.do_openstack_upgrade(configs)158 utils.do_openstack_upgrade(configs)
132 self.assertTrue(configs.write_all.called)159 self.assertTrue(configs.write_all.called)
133 self.apt_install.assert_called_with(utils.PACKAGES, fatal=True)160 self.apt_install.assert_called_with(utils.determine_packages(),
161 fatal=True)
134 self.apt_upgrade.assert_called_with(options=DPKG_OPTS,162 self.apt_upgrade.assert_called_with(options=DPKG_OPTS,
135 fatal=True, dist=True)163 fatal=True, dist=True)
136 configs.set_release.assert_called_with(openstack_release='havana')164 configs.set_release.assert_called_with(openstack_release='havana')
137 self.assertTrue(migrate.called)165 self.assertTrue(migrate.called)
138166
139 @patch.object(utils, 'migrate_database')167 @patch.object(utils, 'migrate_database')
140 def test_openstack_upgrade_not_leader(self, migrate):168 @patch.object(utils, 'git_install_requested')
169 def test_openstack_upgrade_not_leader(self, git_requested, migrate):
170 git_requested.return_value = True
141 self.config.side_effect = None171 self.config.side_effect = None
142 self.config.return_value = 'cloud:precise-havana'172 self.config.return_value = 'cloud:precise-havana'
143 self.eligible_leader.return_value = False173 self.eligible_leader.return_value = False
@@ -145,8 +175,111 @@
145 configs = MagicMock()175 configs = MagicMock()
146 utils.do_openstack_upgrade(configs)176 utils.do_openstack_upgrade(configs)
147 self.assertTrue(configs.write_all.called)177 self.assertTrue(configs.write_all.called)
148 self.apt_install.assert_called_with(utils.PACKAGES, fatal=True)178 self.apt_install.assert_called_with(utils.determine_packages(),
179 fatal=True)
149 self.apt_upgrade.assert_called_with(options=DPKG_OPTS,180 self.apt_upgrade.assert_called_with(options=DPKG_OPTS,
150 fatal=True, dist=True)181 fatal=True, dist=True)
151 configs.set_release.assert_called_with(openstack_release='havana')182 configs.set_release.assert_called_with(openstack_release='havana')
152 self.assertFalse(migrate.called)183 self.assertFalse(migrate.called)
184
185 @patch.object(utils, 'git_install_requested')
186 @patch.object(utils, 'git_clone_and_install')
187 @patch.object(utils, 'git_post_install')
188 @patch.object(utils, 'git_pre_install')
189 def test_git_install(self, git_pre, git_post, git_clone_and_install,
190 git_requested):
191 projects_yaml = openstack_origin_git
192 git_requested.return_value = True
193 utils.git_install(projects_yaml)
194 self.assertTrue(git_pre.called)
195 git_clone_and_install.assert_called_with(openstack_origin_git,
196 core_project='glance')
197 self.assertTrue(git_post.called)
198
199 @patch.object(utils, 'mkdir')
200 @patch.object(utils, 'write_file')
201 @patch.object(utils, 'add_user_to_group')
202 @patch.object(utils, 'add_group')
203 @patch.object(utils, 'adduser')
204 def test_git_pre_install(self, adduser, add_group, add_user_to_group,
205 write_file, mkdir):
206 utils.git_pre_install()
207 adduser.assert_called_with('glance', shell='/bin/bash',
208 system_user=True)
209 add_group.assert_called_with('glance', system_group=True)
210 add_user_to_group.assert_called_with('glance', 'glance')
211 expected = [
212 call('/var/lib/glance', owner='glance',
213 group='glance', perms=0700, force=False),
214 call('/var/lib/glance/images', owner='glance',
215 group='glance', perms=0700, force=False),
216 call('/var/lib/glance/image-cache', owner='glance',
217 group='glance', perms=0700, force=False),
218 call('/var/lib/glance/image-cache/incomplete', owner='glance',
219 group='glance', perms=0700, force=False),
220 call('/var/lib/glance/image-cache/invalid', owner='glance',
221 group='glance', perms=0700, force=False),
222 call('/var/lib/glance/image-cache/queue', owner='glance',
223 group='glance', perms=0700, force=False),
224 call('/var/log/glance', owner='glance',
225 group='glance', perms=0700, force=False),
226 ]
227 self.assertEquals(mkdir.call_args_list, expected)
228 expected = [
229 call('/var/log/glance/glance-api.log', '', owner='glance',
230 group='glance', perms=0600),
231 call('/var/log/glance/glance-registry.log', '', owner='glance',
232 group='glance', perms=0600),
233 ]
234 self.assertEquals(write_file.call_args_list, expected)
235
236 @patch.object(utils, 'git_src_dir')
237 @patch.object(utils, 'service_restart')
238 @patch.object(utils, 'render')
239 @patch('os.path.join')
240 @patch('os.path.exists')
241 @patch('shutil.copytree')
242 @patch('shutil.rmtree')
243 def test_git_post_install(self, rmtree, copytree, exists, join, render,
244 service_restart, git_src_dir):
245 projects_yaml = openstack_origin_git
246 join.return_value = 'joined-string'
247 utils.git_post_install(projects_yaml)
248 expected = [
249 call('joined-string', '/etc/glance'),
250 ]
251 copytree.assert_has_calls(expected)
252 glance_api_context = {
253 'service_description': 'Glance API server',
254 'service_name': 'Glance',
255 'user_name': 'glance',
256 'start_dir': '/var/lib/glance',
257 'process_name': 'glance-api',
258 'executable_name': '/usr/local/bin/glance-api',
259 'config_files': ['/etc/glance/glance-api.conf'],
260 'log_file': '/var/log/glance/api.log',
261 }
262 glance_registry_context = {
263 'service_description': 'Glance registry server',
264 'service_name': 'Glance',
265 'user_name': 'glance',
266 'start_dir': '/var/lib/glance',
267 'process_name': 'glance-registry',
268 'executable_name': '/usr/local/bin/glance-registry',
269 'config_files': ['/etc/glance/glance-registry.conf'],
270 'log_file': '/var/log/glance/registry.log',
271 }
272 expected = [
273 call('git.upstart', '/etc/init/glance-api.conf',
274 glance_api_context, perms=0o644,
275 templates_dir='joined-string'),
276 call('git.upstart', '/etc/init/glance-registry.conf',
277 glance_registry_context, perms=0o644,
278 templates_dir='joined-string'),
279 ]
280 self.assertEquals(render.call_args_list, expected)
281 expected = [
282 call('glance-api'),
283 call('glance-registry'),
284 ]
285 self.assertEquals(service_restart.call_args_list, expected)

Subscribers

People subscribed via source and target branches