Merge lp:~fcorrea/charms/trusty/glance/fix-pause-action into lp:~openstack-charmers-archive/charms/trusty/glance/next

Proposed by Fernando Correa Neto on 2015-11-24
Status: Merged
Merge reported by: James Page
Merged at revision: not available
Proposed branch: lp:~fcorrea/charms/trusty/glance/fix-pause-action
Merge into: lp:~openstack-charmers-archive/charms/trusty/glance/next
Diff against target: 1327 lines (+543/-93)
20 files modified
actions/actions.py (+6/-3)
charmhelpers/cli/__init__.py (+3/-3)
charmhelpers/contrib/charmsupport/nrpe.py (+44/-8)
charmhelpers/contrib/openstack/amulet/deployment.py (+102/-2)
charmhelpers/contrib/openstack/amulet/utils.py (+25/-3)
charmhelpers/contrib/openstack/context.py (+39/-9)
charmhelpers/contrib/openstack/neutron.py (+16/-2)
charmhelpers/contrib/openstack/utils.py (+22/-1)
charmhelpers/contrib/storage/linux/ceph.py (+51/-35)
charmhelpers/core/hookenv.py (+16/-2)
charmhelpers/core/host.py (+34/-3)
charmhelpers/core/hugepage.py (+2/-0)
charmhelpers/core/services/helpers.py (+5/-2)
charmhelpers/core/templating.py (+13/-6)
charmhelpers/fetch/__init__.py (+1/-1)
charmhelpers/fetch/bzrurl.py (+7/-3)
hooks/glance_utils.py (+20/-0)
tests/charmhelpers/contrib/openstack/amulet/deployment.py (+102/-2)
tests/charmhelpers/contrib/openstack/amulet/utils.py (+25/-3)
unit_tests/test_actions.py (+10/-5)
To merge this branch: bzr merge lp:~fcorrea/charms/trusty/glance/fix-pause-action
Reviewer Review Type Date Requested Status
Chad Smith 2015-11-24 Pending
Review via email: mp+278505@code.launchpad.net

This proposal supersedes a proposal from 2015-11-24.

Description of the change

This branch changes the pause action to change the kv database instead of calling set_os_workload_status directly, which is the same pattern used in the swift charm.
This prevents the charm from immediately bouncing back to active after a 'pause' action was performed.

A follow up branch will add a bit more logic to deal with hacluster so it stops sending requests for the unit.

To post a comment you must log in.
uosci-testing-bot (uosci-testing-bot) wrote : Posted in a previous version of this proposal

charm_lint_check #14311 glance for fcorrea mp278498
    LINT OK: passed

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

uosci-testing-bot (uosci-testing-bot) wrote : Posted in a previous version of this proposal

charm_unit_test #13339 glance for fcorrea mp278498
    UNIT OK: passed

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

charm_lint_check #14312 glance-next for fcorrea mp278505
    LINT OK: passed

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

charm_unit_test #13340 glance-next for fcorrea mp278505
    UNIT OK: passed

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

charm_amulet_test #8030 glance-next for fcorrea mp278505
    AMULET FAIL: amulet-test failed

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

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

charm_amulet_test #8031 glance-next for fcorrea mp278505
    AMULET FAIL: amulet-test failed

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

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

Ryan Beisner (1chb1n) wrote :

FYI, undercloud issue caused the last amulet failure. Rerunning...

machine
3 error pending trusty

charm_amulet_test #8050 glance-next for fcorrea mp278505
    AMULET FAIL: amulet-test failed

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

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

James Page (james-page) wrote :

Same change already found in /next - assuming that someone else got to it first.

Thanks for your work on this!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'actions/actions.py'
--- actions/actions.py 2015-08-26 13:16:31 +0000
+++ actions/actions.py 2015-11-24 20:07:18 +0000
@@ -5,8 +5,9 @@
55
6from charmhelpers.core.host import service_pause, service_resume6from charmhelpers.core.host import service_pause, service_resume
7from charmhelpers.core.hookenv import action_fail, status_set7from charmhelpers.core.hookenv import action_fail, status_set
8from charmhelpers.core.unitdata import HookData, kv
89
9from hooks.glance_utils import services10from hooks.glance_utils import services, assess_status
1011
1112
12def pause(args):13def pause(args):
@@ -18,8 +19,10 @@
18 stopped = service_pause(service)19 stopped = service_pause(service)
19 if not stopped:20 if not stopped:
20 raise Exception("{} didn't stop cleanly.".format(service))21 raise Exception("{} didn't stop cleanly.".format(service))
21 status_set(22 with HookData()():
22 "maintenance", "Paused. Use 'resume' action to resume normal service.")23 kv().set('unit-paused', True)
24 state, message = assess_status()
25 status_set(state, message)
2326
2427
25def resume(args):28def resume(args):
2629
=== modified file 'charmhelpers/cli/__init__.py'
--- charmhelpers/cli/__init__.py 2015-08-18 17:34:34 +0000
+++ charmhelpers/cli/__init__.py 2015-11-24 20:07:18 +0000
@@ -20,7 +20,7 @@
2020
21from six.moves import zip21from six.moves import zip
2222
23from charmhelpers.core import unitdata23import charmhelpers.core.unitdata
2424
2525
26class OutputFormatter(object):26class OutputFormatter(object):
@@ -163,8 +163,8 @@
163 if getattr(arguments.func, '_cli_no_output', False):163 if getattr(arguments.func, '_cli_no_output', False):
164 output = ''164 output = ''
165 self.formatter.format_output(output, arguments.format)165 self.formatter.format_output(output, arguments.format)
166 if unitdata._KV:166 if charmhelpers.core.unitdata._KV:
167 unitdata._KV.flush()167 charmhelpers.core.unitdata._KV.flush()
168168
169169
170cmdline = CommandLine()170cmdline = CommandLine()
171171
=== modified file 'charmhelpers/contrib/charmsupport/nrpe.py'
--- charmhelpers/contrib/charmsupport/nrpe.py 2015-04-19 09:00:04 +0000
+++ charmhelpers/contrib/charmsupport/nrpe.py 2015-11-24 20:07:18 +0000
@@ -148,6 +148,13 @@
148 self.description = description148 self.description = description
149 self.check_cmd = self._locate_cmd(check_cmd)149 self.check_cmd = self._locate_cmd(check_cmd)
150150
151 def _get_check_filename(self):
152 return os.path.join(NRPE.nrpe_confdir, '{}.cfg'.format(self.command))
153
154 def _get_service_filename(self, hostname):
155 return os.path.join(NRPE.nagios_exportdir,
156 'service__{}_{}.cfg'.format(hostname, self.command))
157
151 def _locate_cmd(self, check_cmd):158 def _locate_cmd(self, check_cmd):
152 search_path = (159 search_path = (
153 '/usr/lib/nagios/plugins',160 '/usr/lib/nagios/plugins',
@@ -163,9 +170,21 @@
163 log('Check command not found: {}'.format(parts[0]))170 log('Check command not found: {}'.format(parts[0]))
164 return ''171 return ''
165172
173 def _remove_service_files(self):
174 if not os.path.exists(NRPE.nagios_exportdir):
175 return
176 for f in os.listdir(NRPE.nagios_exportdir):
177 if f.endswith('_{}.cfg'.format(self.command)):
178 os.remove(os.path.join(NRPE.nagios_exportdir, f))
179
180 def remove(self, hostname):
181 nrpe_check_file = self._get_check_filename()
182 if os.path.exists(nrpe_check_file):
183 os.remove(nrpe_check_file)
184 self._remove_service_files()
185
166 def write(self, nagios_context, hostname, nagios_servicegroups):186 def write(self, nagios_context, hostname, nagios_servicegroups):
167 nrpe_check_file = '/etc/nagios/nrpe.d/{}.cfg'.format(187 nrpe_check_file = self._get_check_filename()
168 self.command)
169 with open(nrpe_check_file, 'w') as nrpe_check_config:188 with open(nrpe_check_file, 'w') as nrpe_check_config:
170 nrpe_check_config.write("# check {}\n".format(self.shortname))189 nrpe_check_config.write("# check {}\n".format(self.shortname))
171 nrpe_check_config.write("command[{}]={}\n".format(190 nrpe_check_config.write("command[{}]={}\n".format(
@@ -180,9 +199,7 @@
180199
181 def write_service_config(self, nagios_context, hostname,200 def write_service_config(self, nagios_context, hostname,
182 nagios_servicegroups):201 nagios_servicegroups):
183 for f in os.listdir(NRPE.nagios_exportdir):202 self._remove_service_files()
184 if re.search('.*{}.cfg'.format(self.command), f):
185 os.remove(os.path.join(NRPE.nagios_exportdir, f))
186203
187 templ_vars = {204 templ_vars = {
188 'nagios_hostname': hostname,205 'nagios_hostname': hostname,
@@ -192,8 +209,7 @@
192 'command': self.command,209 'command': self.command,
193 }210 }
194 nrpe_service_text = Check.service_template.format(**templ_vars)211 nrpe_service_text = Check.service_template.format(**templ_vars)
195 nrpe_service_file = '{}/service__{}_{}.cfg'.format(212 nrpe_service_file = self._get_service_filename(hostname)
196 NRPE.nagios_exportdir, hostname, self.command)
197 with open(nrpe_service_file, 'w') as nrpe_service_config:213 with open(nrpe_service_file, 'w') as nrpe_service_config:
198 nrpe_service_config.write(str(nrpe_service_text))214 nrpe_service_config.write(str(nrpe_service_text))
199215
@@ -218,12 +234,32 @@
218 if hostname:234 if hostname:
219 self.hostname = hostname235 self.hostname = hostname
220 else:236 else:
221 self.hostname = "{}-{}".format(self.nagios_context, self.unit_name)237 nagios_hostname = get_nagios_hostname()
238 if nagios_hostname:
239 self.hostname = nagios_hostname
240 else:
241 self.hostname = "{}-{}".format(self.nagios_context, self.unit_name)
222 self.checks = []242 self.checks = []
223243
224 def add_check(self, *args, **kwargs):244 def add_check(self, *args, **kwargs):
225 self.checks.append(Check(*args, **kwargs))245 self.checks.append(Check(*args, **kwargs))
226246
247 def remove_check(self, *args, **kwargs):
248 if kwargs.get('shortname') is None:
249 raise ValueError('shortname of check must be specified')
250
251 # Use sensible defaults if they're not specified - these are not
252 # actually used during removal, but they're required for constructing
253 # the Check object; check_disk is chosen because it's part of the
254 # nagios-plugins-basic package.
255 if kwargs.get('check_cmd') is None:
256 kwargs['check_cmd'] = 'check_disk'
257 if kwargs.get('description') is None:
258 kwargs['description'] = ''
259
260 check = Check(*args, **kwargs)
261 check.remove(self.hostname)
262
227 def write(self):263 def write(self):
228 try:264 try:
229 nagios_uid = pwd.getpwnam('nagios').pw_uid265 nagios_uid = pwd.getpwnam('nagios').pw_uid
230266
=== modified file 'charmhelpers/contrib/openstack/amulet/deployment.py'
--- charmhelpers/contrib/openstack/amulet/deployment.py 2015-09-30 15:01:18 +0000
+++ charmhelpers/contrib/openstack/amulet/deployment.py 2015-11-24 20:07:18 +0000
@@ -14,12 +14,18 @@
14# You should have received a copy of the GNU Lesser General Public License14# 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/>.15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1616
17import logging
18import re
19import sys
17import six20import six
18from collections import OrderedDict21from collections import OrderedDict
19from charmhelpers.contrib.amulet.deployment import (22from charmhelpers.contrib.amulet.deployment import (
20 AmuletDeployment23 AmuletDeployment
21)24)
2225
26DEBUG = logging.DEBUG
27ERROR = logging.ERROR
28
2329
24class OpenStackAmuletDeployment(AmuletDeployment):30class OpenStackAmuletDeployment(AmuletDeployment):
25 """OpenStack amulet deployment.31 """OpenStack amulet deployment.
@@ -28,9 +34,12 @@
28 that is specifically for use by OpenStack charms.34 that is specifically for use by OpenStack charms.
29 """35 """
3036
31 def __init__(self, series=None, openstack=None, source=None, stable=True):37 def __init__(self, series=None, openstack=None, source=None,
38 stable=True, log_level=DEBUG):
32 """Initialize the deployment environment."""39 """Initialize the deployment environment."""
33 super(OpenStackAmuletDeployment, self).__init__(series)40 super(OpenStackAmuletDeployment, self).__init__(series)
41 self.log = self.get_logger(level=log_level)
42 self.log.info('OpenStackAmuletDeployment: init')
34 self.openstack = openstack43 self.openstack = openstack
35 self.source = source44 self.source = source
36 self.stable = stable45 self.stable = stable
@@ -38,6 +47,22 @@
38 # out.47 # out.
39 self.current_next = "trusty"48 self.current_next = "trusty"
4049
50 def get_logger(self, name="deployment-logger", level=logging.DEBUG):
51 """Get a logger object that will log to stdout."""
52 log = logging
53 logger = log.getLogger(name)
54 fmt = log.Formatter("%(asctime)s %(funcName)s "
55 "%(levelname)s: %(message)s")
56
57 handler = log.StreamHandler(stream=sys.stdout)
58 handler.setLevel(level)
59 handler.setFormatter(fmt)
60
61 logger.addHandler(handler)
62 logger.setLevel(level)
63
64 return logger
65
41 def _determine_branch_locations(self, other_services):66 def _determine_branch_locations(self, other_services):
42 """Determine the branch locations for the other services.67 """Determine the branch locations for the other services.
4368
@@ -45,6 +70,8 @@
45 stable or next (dev) branch, and based on this, use the corresonding70 stable or next (dev) branch, and based on this, use the corresonding
46 stable or next branches for the other_services."""71 stable or next branches for the other_services."""
4772
73 self.log.info('OpenStackAmuletDeployment: determine branch locations')
74
48 # Charms outside the lp:~openstack-charmers namespace75 # Charms outside the lp:~openstack-charmers namespace
49 base_charms = ['mysql', 'mongodb', 'nrpe']76 base_charms = ['mysql', 'mongodb', 'nrpe']
5077
@@ -82,6 +109,8 @@
82109
83 def _add_services(self, this_service, other_services):110 def _add_services(self, this_service, other_services):
84 """Add services to the deployment and set openstack-origin/source."""111 """Add services to the deployment and set openstack-origin/source."""
112 self.log.info('OpenStackAmuletDeployment: adding services')
113
85 other_services = self._determine_branch_locations(other_services)114 other_services = self._determine_branch_locations(other_services)
86115
87 super(OpenStackAmuletDeployment, self)._add_services(this_service,116 super(OpenStackAmuletDeployment, self)._add_services(this_service,
@@ -95,7 +124,8 @@
95 'ceph-osd', 'ceph-radosgw']124 'ceph-osd', 'ceph-radosgw']
96125
97 # Charms which can not use openstack-origin, ie. many subordinates126 # Charms which can not use openstack-origin, ie. many subordinates
98 no_origin = ['cinder-ceph', 'hacluster', 'neutron-openvswitch', 'nrpe']127 no_origin = ['cinder-ceph', 'hacluster', 'neutron-openvswitch', 'nrpe',
128 'openvswitch-odl', 'neutron-api-odl', 'odl-controller']
99129
100 if self.openstack:130 if self.openstack:
101 for svc in services:131 for svc in services:
@@ -111,9 +141,79 @@
111141
112 def _configure_services(self, configs):142 def _configure_services(self, configs):
113 """Configure all of the services."""143 """Configure all of the services."""
144 self.log.info('OpenStackAmuletDeployment: configure services')
114 for service, config in six.iteritems(configs):145 for service, config in six.iteritems(configs):
115 self.d.configure(service, config)146 self.d.configure(service, config)
116147
148 def _auto_wait_for_status(self, message=None, exclude_services=None,
149 include_only=None, timeout=1800):
150 """Wait for all units to have a specific extended status, except
151 for any defined as excluded. Unless specified via message, any
152 status containing any case of 'ready' will be considered a match.
153
154 Examples of message usage:
155
156 Wait for all unit status to CONTAIN any case of 'ready' or 'ok':
157 message = re.compile('.*ready.*|.*ok.*', re.IGNORECASE)
158
159 Wait for all units to reach this status (exact match):
160 message = re.compile('^Unit is ready and clustered$')
161
162 Wait for all units to reach any one of these (exact match):
163 message = re.compile('Unit is ready|OK|Ready')
164
165 Wait for at least one unit to reach this status (exact match):
166 message = {'ready'}
167
168 See Amulet's sentry.wait_for_messages() for message usage detail.
169 https://github.com/juju/amulet/blob/master/amulet/sentry.py
170
171 :param message: Expected status match
172 :param exclude_services: List of juju service names to ignore,
173 not to be used in conjuction with include_only.
174 :param include_only: List of juju service names to exclusively check,
175 not to be used in conjuction with exclude_services.
176 :param timeout: Maximum time in seconds to wait for status match
177 :returns: None. Raises if timeout is hit.
178 """
179 self.log.info('Waiting for extended status on units...')
180
181 all_services = self.d.services.keys()
182
183 if exclude_services and include_only:
184 raise ValueError('exclude_services can not be used '
185 'with include_only')
186
187 if message:
188 if isinstance(message, re._pattern_type):
189 match = message.pattern
190 else:
191 match = message
192
193 self.log.debug('Custom extended status wait match: '
194 '{}'.format(match))
195 else:
196 self.log.debug('Default extended status wait match: contains '
197 'READY (case-insensitive)')
198 message = re.compile('.*ready.*', re.IGNORECASE)
199
200 if exclude_services:
201 self.log.debug('Excluding services from extended status match: '
202 '{}'.format(exclude_services))
203 else:
204 exclude_services = []
205
206 if include_only:
207 services = include_only
208 else:
209 services = list(set(all_services) - set(exclude_services))
210
211 self.log.debug('Waiting up to {}s for extended status on services: '
212 '{}'.format(timeout, services))
213 service_messages = {service: message for service in services}
214 self.d.sentry.wait_for_messages(service_messages, timeout=timeout)
215 self.log.info('OK')
216
117 def _get_openstack_release(self):217 def _get_openstack_release(self):
118 """Get openstack release.218 """Get openstack release.
119219
120220
=== modified file 'charmhelpers/contrib/openstack/amulet/utils.py'
--- charmhelpers/contrib/openstack/amulet/utils.py 2015-09-30 15:01:18 +0000
+++ charmhelpers/contrib/openstack/amulet/utils.py 2015-11-24 20:07:18 +0000
@@ -18,6 +18,7 @@
18import json18import json
19import logging19import logging
20import os20import os
21import re
21import six22import six
22import time23import time
23import urllib24import urllib
@@ -604,7 +605,22 @@
604 '{}'.format(sample_type, samples))605 '{}'.format(sample_type, samples))
605 return None606 return None
606607
607# rabbitmq/amqp specific helpers:608 # rabbitmq/amqp specific helpers:
609
610 def rmq_wait_for_cluster(self, deployment, init_sleep=15, timeout=1200):
611 """Wait for rmq units extended status to show cluster readiness,
612 after an optional initial sleep period. Initial sleep is likely
613 necessary to be effective following a config change, as status
614 message may not instantly update to non-ready."""
615
616 if init_sleep:
617 time.sleep(init_sleep)
618
619 message = re.compile('^Unit is ready and clustered$')
620 deployment._auto_wait_for_status(message=message,
621 timeout=timeout,
622 include_only=['rabbitmq-server'])
623
608 def add_rmq_test_user(self, sentry_units,624 def add_rmq_test_user(self, sentry_units,
609 username="testuser1", password="changeme"):625 username="testuser1", password="changeme"):
610 """Add a test user via the first rmq juju unit, check connection as626 """Add a test user via the first rmq juju unit, check connection as
@@ -805,7 +821,10 @@
805 if port:821 if port:
806 config['ssl_port'] = port822 config['ssl_port'] = port
807823
808 deployment.configure('rabbitmq-server', config)824 deployment.d.configure('rabbitmq-server', config)
825
826 # Wait for unit status
827 self.rmq_wait_for_cluster(deployment)
809828
810 # Confirm829 # Confirm
811 tries = 0830 tries = 0
@@ -832,7 +851,10 @@
832851
833 # Disable RMQ SSL852 # Disable RMQ SSL
834 config = {'ssl': 'off'}853 config = {'ssl': 'off'}
835 deployment.configure('rabbitmq-server', config)854 deployment.d.configure('rabbitmq-server', config)
855
856 # Wait for unit status
857 self.rmq_wait_for_cluster(deployment)
836858
837 # Confirm859 # Confirm
838 tries = 0860 tries = 0
839861
=== modified file 'charmhelpers/contrib/openstack/context.py'
--- charmhelpers/contrib/openstack/context.py 2015-09-30 15:01:18 +0000
+++ charmhelpers/contrib/openstack/context.py 2015-11-24 20:07:18 +0000
@@ -952,6 +952,19 @@
952 'config': config}952 'config': config}
953 return ovs_ctxt953 return ovs_ctxt
954954
955 def midonet_ctxt(self):
956 driver = neutron_plugin_attribute(self.plugin, 'driver',
957 self.network_manager)
958 midonet_config = neutron_plugin_attribute(self.plugin, 'config',
959 self.network_manager)
960 mido_ctxt = {'core_plugin': driver,
961 'neutron_plugin': 'midonet',
962 'neutron_security_groups': self.neutron_security_groups,
963 'local_ip': unit_private_ip(),
964 'config': midonet_config}
965
966 return mido_ctxt
967
955 def __call__(self):968 def __call__(self):
956 if self.network_manager not in ['quantum', 'neutron']:969 if self.network_manager not in ['quantum', 'neutron']:
957 return {}970 return {}
@@ -973,6 +986,8 @@
973 ctxt.update(self.nuage_ctxt())986 ctxt.update(self.nuage_ctxt())
974 elif self.plugin == 'plumgrid':987 elif self.plugin == 'plumgrid':
975 ctxt.update(self.pg_ctxt())988 ctxt.update(self.pg_ctxt())
989 elif self.plugin == 'midonet':
990 ctxt.update(self.midonet_ctxt())
976991
977 alchemy_flags = config('neutron-alchemy-flags')992 alchemy_flags = config('neutron-alchemy-flags')
978 if alchemy_flags:993 if alchemy_flags:
@@ -1073,6 +1088,20 @@
1073 config_flags_parser(config_flags)}1088 config_flags_parser(config_flags)}
10741089
10751090
1091class LibvirtConfigFlagsContext(OSContextGenerator):
1092 """
1093 This context provides support for extending
1094 the libvirt section through user-defined flags.
1095 """
1096 def __call__(self):
1097 ctxt = {}
1098 libvirt_flags = config('libvirt-flags')
1099 if libvirt_flags:
1100 ctxt['libvirt_flags'] = config_flags_parser(
1101 libvirt_flags)
1102 return ctxt
1103
1104
1076class SubordinateConfigContext(OSContextGenerator):1105class SubordinateConfigContext(OSContextGenerator):
10771106
1078 """1107 """
@@ -1105,7 +1134,7 @@
11051134
1106 ctxt = {1135 ctxt = {
1107 ... other context ...1136 ... other context ...
1108 'subordinate_config': {1137 'subordinate_configuration': {
1109 'DEFAULT': {1138 'DEFAULT': {
1110 'key1': 'value1',1139 'key1': 'value1',
1111 },1140 },
@@ -1146,22 +1175,23 @@
1146 try:1175 try:
1147 sub_config = json.loads(sub_config)1176 sub_config = json.loads(sub_config)
1148 except:1177 except:
1149 log('Could not parse JSON from subordinate_config '1178 log('Could not parse JSON from '
1150 'setting from %s' % rid, level=ERROR)1179 'subordinate_configuration setting from %s'
1180 % rid, level=ERROR)
1151 continue1181 continue
11521182
1153 for service in self.services:1183 for service in self.services:
1154 if service not in sub_config:1184 if service not in sub_config:
1155 log('Found subordinate_config on %s but it contained'1185 log('Found subordinate_configuration on %s but it '
1156 'nothing for %s service' % (rid, service),1186 'contained nothing for %s service'
1157 level=INFO)1187 % (rid, service), level=INFO)
1158 continue1188 continue
11591189
1160 sub_config = sub_config[service]1190 sub_config = sub_config[service]
1161 if self.config_file not in sub_config:1191 if self.config_file not in sub_config:
1162 log('Found subordinate_config on %s but it contained'1192 log('Found subordinate_configuration on %s but it '
1163 'nothing for %s' % (rid, self.config_file),1193 'contained nothing for %s'
1164 level=INFO)1194 % (rid, self.config_file), level=INFO)
1165 continue1195 continue
11661196
1167 sub_config = sub_config[self.config_file]1197 sub_config = sub_config[self.config_file]
11681198
=== modified file 'charmhelpers/contrib/openstack/neutron.py'
--- charmhelpers/contrib/openstack/neutron.py 2015-09-30 15:01:18 +0000
+++ charmhelpers/contrib/openstack/neutron.py 2015-11-24 20:07:18 +0000
@@ -204,11 +204,25 @@
204 database=config('database'),204 database=config('database'),
205 ssl_dir=NEUTRON_CONF_DIR)],205 ssl_dir=NEUTRON_CONF_DIR)],
206 'services': [],206 'services': [],
207 'packages': [['plumgrid-lxc'],207 'packages': ['plumgrid-lxc',
208 ['iovisor-dkms']],208 'iovisor-dkms'],
209 'server_packages': ['neutron-server',209 'server_packages': ['neutron-server',
210 'neutron-plugin-plumgrid'],210 'neutron-plugin-plumgrid'],
211 'server_services': ['neutron-server']211 'server_services': ['neutron-server']
212 },
213 'midonet': {
214 'config': '/etc/neutron/plugins/midonet/midonet.ini',
215 'driver': 'midonet.neutron.plugin.MidonetPluginV2',
216 'contexts': [
217 context.SharedDBContext(user=config('neutron-database-user'),
218 database=config('neutron-database'),
219 relation_prefix='neutron',
220 ssl_dir=NEUTRON_CONF_DIR)],
221 'services': [],
222 'packages': [[headers_package()] + determine_dkms_package()],
223 'server_packages': ['neutron-server',
224 'python-neutron-plugin-midonet'],
225 'server_services': ['neutron-server']
212 }226 }
213 }227 }
214 if release >= 'icehouse':228 if release >= 'icehouse':
215229
=== modified file 'charmhelpers/contrib/openstack/utils.py'
--- charmhelpers/contrib/openstack/utils.py 2015-09-30 15:01:18 +0000
+++ charmhelpers/contrib/openstack/utils.py 2015-11-24 20:07:18 +0000
@@ -26,6 +26,7 @@
2626
27import six27import six
28import traceback28import traceback
29import uuid
29import yaml30import yaml
3031
31from charmhelpers.contrib.network import ip32from charmhelpers.contrib.network import ip
@@ -41,6 +42,7 @@
41 log as juju_log,42 log as juju_log,
42 charm_dir,43 charm_dir,
43 INFO,44 INFO,
45 related_units,
44 relation_ids,46 relation_ids,
45 relation_set,47 relation_set,
46 status_set,48 status_set,
@@ -121,6 +123,7 @@
121 ('2.2.2', 'kilo'),123 ('2.2.2', 'kilo'),
122 ('2.3.0', 'liberty'),124 ('2.3.0', 'liberty'),
123 ('2.4.0', 'liberty'),125 ('2.4.0', 'liberty'),
126 ('2.5.0', 'liberty'),
124])127])
125128
126# >= Liberty version->codename mapping129# >= Liberty version->codename mapping
@@ -858,7 +861,9 @@
858 if charm_state != 'active' and charm_state != 'unknown':861 if charm_state != 'active' and charm_state != 'unknown':
859 state = workload_state_compare(state, charm_state)862 state = workload_state_compare(state, charm_state)
860 if message:863 if message:
861 message = "{} {}".format(message, charm_message)864 charm_message = charm_message.replace("Incomplete relations: ",
865 "")
866 message = "{}, {}".format(message, charm_message)
862 else:867 else:
863 message = charm_message868 message = charm_message
864869
@@ -975,3 +980,19 @@
975 action_set({'outcome': 'no upgrade available.'})980 action_set({'outcome': 'no upgrade available.'})
976981
977 return ret982 return ret
983
984
985def remote_restart(rel_name, remote_service=None):
986 trigger = {
987 'restart-trigger': str(uuid.uuid4()),
988 }
989 if remote_service:
990 trigger['remote-service'] = remote_service
991 for rid in relation_ids(rel_name):
992 # This subordinate can be related to two seperate services using
993 # different subordinate relations so only issue the restart if
994 # the principle is conencted down the relation we think it is
995 if related_units(relid=rid):
996 relation_set(relation_id=rid,
997 relation_settings=trigger,
998 )
978999
=== modified file 'charmhelpers/contrib/storage/linux/ceph.py'
--- charmhelpers/contrib/storage/linux/ceph.py 2015-09-30 15:01:18 +0000
+++ charmhelpers/contrib/storage/linux/ceph.py 2015-11-24 20:07:18 +0000
@@ -26,6 +26,7 @@
2626
27import os27import os
28import shutil28import shutil
29import six
29import json30import json
30import time31import time
31import uuid32import uuid
@@ -125,29 +126,37 @@
125 return None126 return None
126127
127128
128def create_pool(service, name, replicas=3):129def update_pool(client, pool, settings):
130 cmd = ['ceph', '--id', client, 'osd', 'pool', 'set', pool]
131 for k, v in six.iteritems(settings):
132 cmd.append(k)
133 cmd.append(v)
134
135 check_call(cmd)
136
137
138def create_pool(service, name, replicas=3, pg_num=None):
129 """Create a new RADOS pool."""139 """Create a new RADOS pool."""
130 if pool_exists(service, name):140 if pool_exists(service, name):
131 log("Ceph pool {} already exists, skipping creation".format(name),141 log("Ceph pool {} already exists, skipping creation".format(name),
132 level=WARNING)142 level=WARNING)
133 return143 return
134144
135 # Calculate the number of placement groups based145 if not pg_num:
136 # on upstream recommended best practices.146 # Calculate the number of placement groups based
137 osds = get_osds(service)147 # on upstream recommended best practices.
138 if osds:148 osds = get_osds(service)
139 pgnum = (len(osds) * 100 // replicas)149 if osds:
140 else:150 pg_num = (len(osds) * 100 // replicas)
141 # NOTE(james-page): Default to 200 for older ceph versions151 else:
142 # which don't support OSD query from cli152 # NOTE(james-page): Default to 200 for older ceph versions
143 pgnum = 200153 # which don't support OSD query from cli
144154 pg_num = 200
145 cmd = ['ceph', '--id', service, 'osd', 'pool', 'create', name, str(pgnum)]155
146 check_call(cmd)156 cmd = ['ceph', '--id', service, 'osd', 'pool', 'create', name, str(pg_num)]
147157 check_call(cmd)
148 cmd = ['ceph', '--id', service, 'osd', 'pool', 'set', name, 'size',158
149 str(replicas)]159 update_pool(service, name, settings={'size': str(replicas)})
150 check_call(cmd)
151160
152161
153def delete_pool(service, name):162def delete_pool(service, name):
@@ -202,10 +211,10 @@
202 log('Created new keyfile at %s.' % keyfile, level=INFO)211 log('Created new keyfile at %s.' % keyfile, level=INFO)
203212
204213
205def get_ceph_nodes():214def get_ceph_nodes(relation='ceph'):
206 """Query named relation 'ceph' to determine current nodes."""215 """Query named relation to determine current nodes."""
207 hosts = []216 hosts = []
208 for r_id in relation_ids('ceph'):217 for r_id in relation_ids(relation):
209 for unit in related_units(r_id):218 for unit in related_units(r_id):
210 hosts.append(relation_get('private-address', unit=unit, rid=r_id))219 hosts.append(relation_get('private-address', unit=unit, rid=r_id))
211220
@@ -357,14 +366,14 @@
357 service_start(svc)366 service_start(svc)
358367
359368
360def ensure_ceph_keyring(service, user=None, group=None):369def ensure_ceph_keyring(service, user=None, group=None, relation='ceph'):
361 """Ensures a ceph keyring is created for a named service and optionally370 """Ensures a ceph keyring is created for a named service and optionally
362 ensures user and group ownership.371 ensures user and group ownership.
363372
364 Returns False if no ceph key is available in relation state.373 Returns False if no ceph key is available in relation state.
365 """374 """
366 key = None375 key = None
367 for rid in relation_ids('ceph'):376 for rid in relation_ids(relation):
368 for unit in related_units(rid):377 for unit in related_units(rid):
369 key = relation_get('key', rid=rid, unit=unit)378 key = relation_get('key', rid=rid, unit=unit)
370 if key:379 if key:
@@ -413,9 +422,16 @@
413 self.request_id = str(uuid.uuid1())422 self.request_id = str(uuid.uuid1())
414 self.ops = []423 self.ops = []
415424
416 def add_op_create_pool(self, name, replica_count=3):425 def add_op_create_pool(self, name, replica_count=3, pg_num=None):
426 """Adds an operation to create a pool.
427
428 @param pg_num setting: optional setting. If not provided, this value
429 will be calculated by the broker based on how many OSDs are in the
430 cluster at the time of creation. Note that, if provided, this value
431 will be capped at the current available maximum.
432 """
417 self.ops.append({'op': 'create-pool', 'name': name,433 self.ops.append({'op': 'create-pool', 'name': name,
418 'replicas': replica_count})434 'replicas': replica_count, 'pg_num': pg_num})
419435
420 def set_ops(self, ops):436 def set_ops(self, ops):
421 """Set request ops to provided value.437 """Set request ops to provided value.
@@ -433,8 +449,8 @@
433 def _ops_equal(self, other):449 def _ops_equal(self, other):
434 if len(self.ops) == len(other.ops):450 if len(self.ops) == len(other.ops):
435 for req_no in range(0, len(self.ops)):451 for req_no in range(0, len(self.ops)):
436 for key in ['replicas', 'name', 'op']:452 for key in ['replicas', 'name', 'op', 'pg_num']:
437 if self.ops[req_no][key] != other.ops[req_no][key]:453 if self.ops[req_no].get(key) != other.ops[req_no].get(key):
438 return False454 return False
439 else:455 else:
440 return False456 return False
@@ -540,7 +556,7 @@
540 return request556 return request
541557
542558
543def get_request_states(request):559def get_request_states(request, relation='ceph'):
544 """Return a dict of requests per relation id with their corresponding560 """Return a dict of requests per relation id with their corresponding
545 completion state.561 completion state.
546562
@@ -552,7 +568,7 @@
552 """568 """
553 complete = []569 complete = []
554 requests = {}570 requests = {}
555 for rid in relation_ids('ceph'):571 for rid in relation_ids(relation):
556 complete = False572 complete = False
557 previous_request = get_previous_request(rid)573 previous_request = get_previous_request(rid)
558 if request == previous_request:574 if request == previous_request:
@@ -570,14 +586,14 @@
570 return requests586 return requests
571587
572588
573def is_request_sent(request):589def is_request_sent(request, relation='ceph'):
574 """Check to see if a functionally equivalent request has already been sent590 """Check to see if a functionally equivalent request has already been sent
575591
576 Returns True if a similair request has been sent592 Returns True if a similair request has been sent
577593
578 @param request: A CephBrokerRq object594 @param request: A CephBrokerRq object
579 """595 """
580 states = get_request_states(request)596 states = get_request_states(request, relation=relation)
581 for rid in states.keys():597 for rid in states.keys():
582 if not states[rid]['sent']:598 if not states[rid]['sent']:
583 return False599 return False
@@ -585,7 +601,7 @@
585 return True601 return True
586602
587603
588def is_request_complete(request):604def is_request_complete(request, relation='ceph'):
589 """Check to see if a functionally equivalent request has already been605 """Check to see if a functionally equivalent request has already been
590 completed606 completed
591607
@@ -593,7 +609,7 @@
593609
594 @param request: A CephBrokerRq object610 @param request: A CephBrokerRq object
595 """611 """
596 states = get_request_states(request)612 states = get_request_states(request, relation=relation)
597 for rid in states.keys():613 for rid in states.keys():
598 if not states[rid]['complete']:614 if not states[rid]['complete']:
599 return False615 return False
@@ -643,15 +659,15 @@
643 return 'broker-rsp-' + local_unit().replace('/', '-')659 return 'broker-rsp-' + local_unit().replace('/', '-')
644660
645661
646def send_request_if_needed(request):662def send_request_if_needed(request, relation='ceph'):
647 """Send broker request if an equivalent request has not already been sent663 """Send broker request if an equivalent request has not already been sent
648664
649 @param request: A CephBrokerRq object665 @param request: A CephBrokerRq object
650 """666 """
651 if is_request_sent(request):667 if is_request_sent(request, relation=relation):
652 log('Request already sent but not complete, not sending new request',668 log('Request already sent but not complete, not sending new request',
653 level=DEBUG)669 level=DEBUG)
654 else:670 else:
655 for rid in relation_ids('ceph'):671 for rid in relation_ids(relation):
656 log('Sending request {}'.format(request.request_id), level=DEBUG)672 log('Sending request {}'.format(request.request_id), level=DEBUG)
657 relation_set(relation_id=rid, broker_req=request.request)673 relation_set(relation_id=rid, broker_req=request.request)
658674
=== modified file 'charmhelpers/core/hookenv.py'
--- charmhelpers/core/hookenv.py 2015-09-30 15:01:18 +0000
+++ charmhelpers/core/hookenv.py 2015-11-24 20:07:18 +0000
@@ -491,6 +491,19 @@
491491
492492
493@cached493@cached
494def peer_relation_id():
495 '''Get a peer relation id if a peer relation has been joined, else None.'''
496 md = metadata()
497 section = md.get('peers')
498 if section:
499 for key in section:
500 relids = relation_ids(key)
501 if relids:
502 return relids[0]
503 return None
504
505
506@cached
494def relation_to_interface(relation_name):507def relation_to_interface(relation_name):
495 """508 """
496 Given the name of a relation, return the interface that relation uses.509 Given the name of a relation, return the interface that relation uses.
@@ -624,7 +637,7 @@
624637
625638
626@cached639@cached
627def storage_get(attribute="", storage_id=""):640def storage_get(attribute=None, storage_id=None):
628 """Get storage attributes"""641 """Get storage attributes"""
629 _args = ['storage-get', '--format=json']642 _args = ['storage-get', '--format=json']
630 if storage_id:643 if storage_id:
@@ -638,7 +651,7 @@
638651
639652
640@cached653@cached
641def storage_list(storage_name=""):654def storage_list(storage_name=None):
642 """List the storage IDs for the unit"""655 """List the storage IDs for the unit"""
643 _args = ['storage-list', '--format=json']656 _args = ['storage-list', '--format=json']
644 if storage_name:657 if storage_name:
@@ -820,6 +833,7 @@
820833
821def translate_exc(from_exc, to_exc):834def translate_exc(from_exc, to_exc):
822 def inner_translate_exc1(f):835 def inner_translate_exc1(f):
836 @wraps(f)
823 def inner_translate_exc2(*args, **kwargs):837 def inner_translate_exc2(*args, **kwargs):
824 try:838 try:
825 return f(*args, **kwargs)839 return f(*args, **kwargs)
826840
=== modified file 'charmhelpers/core/host.py'
--- charmhelpers/core/host.py 2015-09-30 15:01:18 +0000
+++ charmhelpers/core/host.py 2015-11-24 20:07:18 +0000
@@ -67,7 +67,9 @@
67 """Pause a system service.67 """Pause a system service.
6868
69 Stop it, and prevent it from starting again at boot."""69 Stop it, and prevent it from starting again at boot."""
70 stopped = service_stop(service_name)70 stopped = True
71 if service_running(service_name):
72 stopped = service_stop(service_name)
71 upstart_file = os.path.join(init_dir, "{}.conf".format(service_name))73 upstart_file = os.path.join(init_dir, "{}.conf".format(service_name))
72 sysv_file = os.path.join(initd_dir, service_name)74 sysv_file = os.path.join(initd_dir, service_name)
73 if os.path.exists(upstart_file):75 if os.path.exists(upstart_file):
@@ -105,7 +107,9 @@
105 "Unable to detect {0} as either Upstart {1} or SysV {2}".format(107 "Unable to detect {0} as either Upstart {1} or SysV {2}".format(
106 service_name, upstart_file, sysv_file))108 service_name, upstart_file, sysv_file))
107109
108 started = service_start(service_name)110 started = service_running(service_name)
111 if not started:
112 started = service_start(service_name)
109 return started113 return started
110114
111115
@@ -566,7 +570,14 @@
566 os.chdir(cur)570 os.chdir(cur)
567571
568572
569def chownr(path, owner, group, follow_links=True):573def chownr(path, owner, group, follow_links=True, chowntopdir=False):
574 """
575 Recursively change user and group ownership of files and directories
576 in given path. Doesn't chown path itself by default, only its children.
577
578 :param bool follow_links: Also Chown links if True
579 :param bool chowntopdir: Also chown path itself if True
580 """
570 uid = pwd.getpwnam(owner).pw_uid581 uid = pwd.getpwnam(owner).pw_uid
571 gid = grp.getgrnam(group).gr_gid582 gid = grp.getgrnam(group).gr_gid
572 if follow_links:583 if follow_links:
@@ -574,6 +585,10 @@
574 else:585 else:
575 chown = os.lchown586 chown = os.lchown
576587
588 if chowntopdir:
589 broken_symlink = os.path.lexists(path) and not os.path.exists(path)
590 if not broken_symlink:
591 chown(path, uid, gid)
577 for root, dirs, files in os.walk(path):592 for root, dirs, files in os.walk(path):
578 for name in dirs + files:593 for name in dirs + files:
579 full = os.path.join(root, name)594 full = os.path.join(root, name)
@@ -584,3 +599,19 @@
584599
585def lchownr(path, owner, group):600def lchownr(path, owner, group):
586 chownr(path, owner, group, follow_links=False)601 chownr(path, owner, group, follow_links=False)
602
603
604def get_total_ram():
605 '''The total amount of system RAM in bytes.
606
607 This is what is reported by the OS, and may be overcommitted when
608 there are multiple containers hosted on the same machine.
609 '''
610 with open('/proc/meminfo', 'r') as f:
611 for line in f.readlines():
612 if line:
613 key, value, unit = line.split()
614 if key == 'MemTotal:':
615 assert unit == 'kB', 'Unknown unit'
616 return int(value) * 1024 # Classic, not KiB.
617 raise NotImplementedError()
587618
=== modified file 'charmhelpers/core/hugepage.py'
--- charmhelpers/core/hugepage.py 2015-09-30 15:01:18 +0000
+++ charmhelpers/core/hugepage.py 2015-11-24 20:07:18 +0000
@@ -46,6 +46,8 @@
46 group_info = add_group(group)46 group_info = add_group(group)
47 gid = group_info.gr_gid47 gid = group_info.gr_gid
48 add_user_to_group(user, group)48 add_user_to_group(user, group)
49 if max_map_count < 2 * nr_hugepages:
50 max_map_count = 2 * nr_hugepages
49 sysctl_settings = {51 sysctl_settings = {
50 'vm.nr_hugepages': nr_hugepages,52 'vm.nr_hugepages': nr_hugepages,
51 'vm.max_map_count': max_map_count,53 'vm.max_map_count': max_map_count,
5254
=== modified file 'charmhelpers/core/services/helpers.py'
--- charmhelpers/core/services/helpers.py 2015-08-18 17:34:34 +0000
+++ charmhelpers/core/services/helpers.py 2015-11-24 20:07:18 +0000
@@ -249,16 +249,18 @@
249 :param int perms: The permissions of the rendered file249 :param int perms: The permissions of the rendered file
250 :param partial on_change_action: functools partial to be executed when250 :param partial on_change_action: functools partial to be executed when
251 rendered file changes251 rendered file changes
252 :param jinja2 loader template_loader: A jinja2 template loader
252 """253 """
253 def __init__(self, source, target,254 def __init__(self, source, target,
254 owner='root', group='root', perms=0o444,255 owner='root', group='root', perms=0o444,
255 on_change_action=None):256 on_change_action=None, template_loader=None):
256 self.source = source257 self.source = source
257 self.target = target258 self.target = target
258 self.owner = owner259 self.owner = owner
259 self.group = group260 self.group = group
260 self.perms = perms261 self.perms = perms
261 self.on_change_action = on_change_action262 self.on_change_action = on_change_action
263 self.template_loader = template_loader
262264
263 def __call__(self, manager, service_name, event_name):265 def __call__(self, manager, service_name, event_name):
264 pre_checksum = ''266 pre_checksum = ''
@@ -269,7 +271,8 @@
269 for ctx in service.get('required_data', []):271 for ctx in service.get('required_data', []):
270 context.update(ctx)272 context.update(ctx)
271 templating.render(self.source, self.target, context,273 templating.render(self.source, self.target, context,
272 self.owner, self.group, self.perms)274 self.owner, self.group, self.perms,
275 template_loader=self.template_loader)
273 if self.on_change_action:276 if self.on_change_action:
274 if pre_checksum == host.file_hash(self.target):277 if pre_checksum == host.file_hash(self.target):
275 hookenv.log(278 hookenv.log(
276279
=== modified file 'charmhelpers/core/templating.py'
--- charmhelpers/core/templating.py 2015-03-20 17:15:02 +0000
+++ charmhelpers/core/templating.py 2015-11-24 20:07:18 +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, encoding='UTF-8'):24 perms=0o444, templates_dir=None, encoding='UTF-8', template_loader=None):
25 """25 """
26 Render a template.26 Render a template.
2727
@@ -52,17 +52,24 @@
52 apt_install('python-jinja2', fatal=True)52 apt_install('python-jinja2', fatal=True)
53 from jinja2 import FileSystemLoader, Environment, exceptions53 from jinja2 import FileSystemLoader, Environment, exceptions
5454
55 if templates_dir is None:55 if template_loader:
56 templates_dir = os.path.join(hookenv.charm_dir(), 'templates')56 template_env = Environment(loader=template_loader)
57 loader = Environment(loader=FileSystemLoader(templates_dir))57 else:
58 if templates_dir is None:
59 templates_dir = os.path.join(hookenv.charm_dir(), 'templates')
60 template_env = Environment(loader=FileSystemLoader(templates_dir))
58 try:61 try:
59 source = source62 source = source
60 template = loader.get_template(source)63 template = template_env.get_template(source)
61 except exceptions.TemplateNotFound as e:64 except exceptions.TemplateNotFound as e:
62 hookenv.log('Could not load template %s from %s.' %65 hookenv.log('Could not load template %s from %s.' %
63 (source, templates_dir),66 (source, templates_dir),
64 level=hookenv.ERROR)67 level=hookenv.ERROR)
65 raise e68 raise e
66 content = template.render(context)69 content = template.render(context)
67 host.mkdir(os.path.dirname(target), owner, group, perms=0o755)70 target_dir = os.path.dirname(target)
71 if not os.path.exists(target_dir):
72 # This is a terrible default directory permission, as the file
73 # or its siblings will often contain secrets.
74 host.mkdir(os.path.dirname(target), owner, group, perms=0o755)
68 host.write_file(target, content.encode(encoding), owner, group, perms)75 host.write_file(target, content.encode(encoding), owner, group, perms)
6976
=== modified file 'charmhelpers/fetch/__init__.py'
--- charmhelpers/fetch/__init__.py 2015-08-18 17:34:34 +0000
+++ charmhelpers/fetch/__init__.py 2015-11-24 20:07:18 +0000
@@ -225,12 +225,12 @@
225225
226def apt_mark(packages, mark, fatal=False):226def apt_mark(packages, mark, fatal=False):
227 """Flag one or more packages using apt-mark"""227 """Flag one or more packages using apt-mark"""
228 log("Marking {} as {}".format(packages, mark))
228 cmd = ['apt-mark', mark]229 cmd = ['apt-mark', mark]
229 if isinstance(packages, six.string_types):230 if isinstance(packages, six.string_types):
230 cmd.append(packages)231 cmd.append(packages)
231 else:232 else:
232 cmd.extend(packages)233 cmd.extend(packages)
233 log("Holding {}".format(packages))
234234
235 if fatal:235 if fatal:
236 subprocess.check_call(cmd, universal_newlines=True)236 subprocess.check_call(cmd, universal_newlines=True)
237237
=== modified file 'charmhelpers/fetch/bzrurl.py'
--- charmhelpers/fetch/bzrurl.py 2015-03-20 17:15:02 +0000
+++ charmhelpers/fetch/bzrurl.py 2015-11-24 20:07:18 +0000
@@ -64,11 +64,15 @@
64 except Exception as e:64 except Exception as e:
65 raise e65 raise e
6666
67 def install(self, source):67 def install(self, source, dest=None):
68 url_parts = self.parse_url(source)68 url_parts = self.parse_url(source)
69 branch_name = url_parts.path.strip("/").split("/")[-1]69 branch_name = url_parts.path.strip("/").split("/")[-1]
70 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched",70 if dest:
71 branch_name)71 dest_dir = os.path.join(dest, branch_name)
72 else:
73 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched",
74 branch_name)
75
72 if not os.path.exists(dest_dir):76 if not os.path.exists(dest_dir):
73 mkdir(dest_dir, perms=0o755)77 mkdir(dest_dir, perms=0o755)
74 try:78 try:
7579
=== modified file 'hooks/glance_utils.py'
--- hooks/glance_utils.py 2015-10-30 22:11:38 +0000
+++ hooks/glance_utils.py 2015-11-24 20:07:18 +0000
@@ -70,6 +70,8 @@
70 retry_on_exception,70 retry_on_exception,
71)71)
7272
73from charmhelpers.core.unitdata import HookData, kv
74
7375
74CLUSTER_RES = "grp_glance_vips"76CLUSTER_RES = "grp_glance_vips"
7577
@@ -491,3 +493,21 @@
491 swift_connection.post_account(headers={'x-account-meta-temp-url-key':493 swift_connection.post_account(headers={'x-account-meta-temp-url-key':
492 temp_url_key})494 temp_url_key})
493 return temp_url_key495 return temp_url_key
496
497
498def assess_status():
499 """Assess status of current unit"""
500 if is_paused():
501 return ("maintenance",
502 "Paused. Use 'resume' action to resume normal service.")
503 else:
504 return ("active", "Unit is ready")
505
506
507def is_paused():
508 """Is the unit paused?"""
509 with HookData()():
510 if kv().get('unit-paused'):
511 return True
512 else:
513 return False
494514
=== modified file 'tests/charmhelpers/contrib/openstack/amulet/deployment.py'
--- tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-09-30 15:01:18 +0000
+++ tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-11-24 20:07:18 +0000
@@ -14,12 +14,18 @@
14# You should have received a copy of the GNU Lesser General Public License14# 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/>.15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1616
17import logging
18import re
19import sys
17import six20import six
18from collections import OrderedDict21from collections import OrderedDict
19from charmhelpers.contrib.amulet.deployment import (22from charmhelpers.contrib.amulet.deployment import (
20 AmuletDeployment23 AmuletDeployment
21)24)
2225
26DEBUG = logging.DEBUG
27ERROR = logging.ERROR
28
2329
24class OpenStackAmuletDeployment(AmuletDeployment):30class OpenStackAmuletDeployment(AmuletDeployment):
25 """OpenStack amulet deployment.31 """OpenStack amulet deployment.
@@ -28,9 +34,12 @@
28 that is specifically for use by OpenStack charms.34 that is specifically for use by OpenStack charms.
29 """35 """
3036
31 def __init__(self, series=None, openstack=None, source=None, stable=True):37 def __init__(self, series=None, openstack=None, source=None,
38 stable=True, log_level=DEBUG):
32 """Initialize the deployment environment."""39 """Initialize the deployment environment."""
33 super(OpenStackAmuletDeployment, self).__init__(series)40 super(OpenStackAmuletDeployment, self).__init__(series)
41 self.log = self.get_logger(level=log_level)
42 self.log.info('OpenStackAmuletDeployment: init')
34 self.openstack = openstack43 self.openstack = openstack
35 self.source = source44 self.source = source
36 self.stable = stable45 self.stable = stable
@@ -38,6 +47,22 @@
38 # out.47 # out.
39 self.current_next = "trusty"48 self.current_next = "trusty"
4049
50 def get_logger(self, name="deployment-logger", level=logging.DEBUG):
51 """Get a logger object that will log to stdout."""
52 log = logging
53 logger = log.getLogger(name)
54 fmt = log.Formatter("%(asctime)s %(funcName)s "
55 "%(levelname)s: %(message)s")
56
57 handler = log.StreamHandler(stream=sys.stdout)
58 handler.setLevel(level)
59 handler.setFormatter(fmt)
60
61 logger.addHandler(handler)
62 logger.setLevel(level)
63
64 return logger
65
41 def _determine_branch_locations(self, other_services):66 def _determine_branch_locations(self, other_services):
42 """Determine the branch locations for the other services.67 """Determine the branch locations for the other services.
4368
@@ -45,6 +70,8 @@
45 stable or next (dev) branch, and based on this, use the corresonding70 stable or next (dev) branch, and based on this, use the corresonding
46 stable or next branches for the other_services."""71 stable or next branches for the other_services."""
4772
73 self.log.info('OpenStackAmuletDeployment: determine branch locations')
74
48 # Charms outside the lp:~openstack-charmers namespace75 # Charms outside the lp:~openstack-charmers namespace
49 base_charms = ['mysql', 'mongodb', 'nrpe']76 base_charms = ['mysql', 'mongodb', 'nrpe']
5077
@@ -82,6 +109,8 @@
82109
83 def _add_services(self, this_service, other_services):110 def _add_services(self, this_service, other_services):
84 """Add services to the deployment and set openstack-origin/source."""111 """Add services to the deployment and set openstack-origin/source."""
112 self.log.info('OpenStackAmuletDeployment: adding services')
113
85 other_services = self._determine_branch_locations(other_services)114 other_services = self._determine_branch_locations(other_services)
86115
87 super(OpenStackAmuletDeployment, self)._add_services(this_service,116 super(OpenStackAmuletDeployment, self)._add_services(this_service,
@@ -95,7 +124,8 @@
95 'ceph-osd', 'ceph-radosgw']124 'ceph-osd', 'ceph-radosgw']
96125
97 # Charms which can not use openstack-origin, ie. many subordinates126 # Charms which can not use openstack-origin, ie. many subordinates
98 no_origin = ['cinder-ceph', 'hacluster', 'neutron-openvswitch', 'nrpe']127 no_origin = ['cinder-ceph', 'hacluster', 'neutron-openvswitch', 'nrpe',
128 'openvswitch-odl', 'neutron-api-odl', 'odl-controller']
99129
100 if self.openstack:130 if self.openstack:
101 for svc in services:131 for svc in services:
@@ -111,9 +141,79 @@
111141
112 def _configure_services(self, configs):142 def _configure_services(self, configs):
113 """Configure all of the services."""143 """Configure all of the services."""
144 self.log.info('OpenStackAmuletDeployment: configure services')
114 for service, config in six.iteritems(configs):145 for service, config in six.iteritems(configs):
115 self.d.configure(service, config)146 self.d.configure(service, config)
116147
148 def _auto_wait_for_status(self, message=None, exclude_services=None,
149 include_only=None, timeout=1800):
150 """Wait for all units to have a specific extended status, except
151 for any defined as excluded. Unless specified via message, any
152 status containing any case of 'ready' will be considered a match.
153
154 Examples of message usage:
155
156 Wait for all unit status to CONTAIN any case of 'ready' or 'ok':
157 message = re.compile('.*ready.*|.*ok.*', re.IGNORECASE)
158
159 Wait for all units to reach this status (exact match):
160 message = re.compile('^Unit is ready and clustered$')
161
162 Wait for all units to reach any one of these (exact match):
163 message = re.compile('Unit is ready|OK|Ready')
164
165 Wait for at least one unit to reach this status (exact match):
166 message = {'ready'}
167
168 See Amulet's sentry.wait_for_messages() for message usage detail.
169 https://github.com/juju/amulet/blob/master/amulet/sentry.py
170
171 :param message: Expected status match
172 :param exclude_services: List of juju service names to ignore,
173 not to be used in conjuction with include_only.
174 :param include_only: List of juju service names to exclusively check,
175 not to be used in conjuction with exclude_services.
176 :param timeout: Maximum time in seconds to wait for status match
177 :returns: None. Raises if timeout is hit.
178 """
179 self.log.info('Waiting for extended status on units...')
180
181 all_services = self.d.services.keys()
182
183 if exclude_services and include_only:
184 raise ValueError('exclude_services can not be used '
185 'with include_only')
186
187 if message:
188 if isinstance(message, re._pattern_type):
189 match = message.pattern
190 else:
191 match = message
192
193 self.log.debug('Custom extended status wait match: '
194 '{}'.format(match))
195 else:
196 self.log.debug('Default extended status wait match: contains '
197 'READY (case-insensitive)')
198 message = re.compile('.*ready.*', re.IGNORECASE)
199
200 if exclude_services:
201 self.log.debug('Excluding services from extended status match: '
202 '{}'.format(exclude_services))
203 else:
204 exclude_services = []
205
206 if include_only:
207 services = include_only
208 else:
209 services = list(set(all_services) - set(exclude_services))
210
211 self.log.debug('Waiting up to {}s for extended status on services: '
212 '{}'.format(timeout, services))
213 service_messages = {service: message for service in services}
214 self.d.sentry.wait_for_messages(service_messages, timeout=timeout)
215 self.log.info('OK')
216
117 def _get_openstack_release(self):217 def _get_openstack_release(self):
118 """Get openstack release.218 """Get openstack release.
119219
120220
=== modified file 'tests/charmhelpers/contrib/openstack/amulet/utils.py'
--- tests/charmhelpers/contrib/openstack/amulet/utils.py 2015-09-30 15:01:18 +0000
+++ tests/charmhelpers/contrib/openstack/amulet/utils.py 2015-11-24 20:07:18 +0000
@@ -18,6 +18,7 @@
18import json18import json
19import logging19import logging
20import os20import os
21import re
21import six22import six
22import time23import time
23import urllib24import urllib
@@ -604,7 +605,22 @@
604 '{}'.format(sample_type, samples))605 '{}'.format(sample_type, samples))
605 return None606 return None
606607
607# rabbitmq/amqp specific helpers:608 # rabbitmq/amqp specific helpers:
609
610 def rmq_wait_for_cluster(self, deployment, init_sleep=15, timeout=1200):
611 """Wait for rmq units extended status to show cluster readiness,
612 after an optional initial sleep period. Initial sleep is likely
613 necessary to be effective following a config change, as status
614 message may not instantly update to non-ready."""
615
616 if init_sleep:
617 time.sleep(init_sleep)
618
619 message = re.compile('^Unit is ready and clustered$')
620 deployment._auto_wait_for_status(message=message,
621 timeout=timeout,
622 include_only=['rabbitmq-server'])
623
608 def add_rmq_test_user(self, sentry_units,624 def add_rmq_test_user(self, sentry_units,
609 username="testuser1", password="changeme"):625 username="testuser1", password="changeme"):
610 """Add a test user via the first rmq juju unit, check connection as626 """Add a test user via the first rmq juju unit, check connection as
@@ -805,7 +821,10 @@
805 if port:821 if port:
806 config['ssl_port'] = port822 config['ssl_port'] = port
807823
808 deployment.configure('rabbitmq-server', config)824 deployment.d.configure('rabbitmq-server', config)
825
826 # Wait for unit status
827 self.rmq_wait_for_cluster(deployment)
809828
810 # Confirm829 # Confirm
811 tries = 0830 tries = 0
@@ -832,7 +851,10 @@
832851
833 # Disable RMQ SSL852 # Disable RMQ SSL
834 config = {'ssl': 'off'}853 config = {'ssl': 'off'}
835 deployment.configure('rabbitmq-server', config)854 deployment.d.configure('rabbitmq-server', config)
855
856 # Wait for unit status
857 self.rmq_wait_for_cluster(deployment)
836858
837 # Confirm859 # Confirm
838 tries = 0860 tries = 0
839861
=== modified file 'unit_tests/test_actions.py'
--- unit_tests/test_actions.py 2015-09-02 11:40:06 +0000
+++ unit_tests/test_actions.py 2015-11-24 20:07:18 +0000
@@ -12,7 +12,8 @@
1212
13 def setUp(self):13 def setUp(self):
14 super(PauseTestCase, self).setUp(14 super(PauseTestCase, self).setUp(
15 actions.actions, ["service_pause", "status_set"])15 actions.actions, ["service_pause", "status_set",
16 "HookData", "kv", "assess_status"])
1617
17 def test_pauses_services(self):18 def test_pauses_services(self):
18 """Pause action pauses all Glance services."""19 """Pause action pauses all Glance services."""
@@ -23,6 +24,7 @@
23 return True24 return True
2425
25 self.service_pause.side_effect = fake_service_pause26 self.service_pause.side_effect = fake_service_pause
27 self.assess_status.return_value = ("maintenance", "Foo",)
2628
27 actions.actions.pause([])29 actions.actions.pause([])
28 self.assertItemsEqual(30 self.assertItemsEqual(
@@ -48,18 +50,21 @@
4850
49 def test_status_mode(self):51 def test_status_mode(self):
50 """Pause action sets the status to maintenance."""52 """Pause action sets the status to maintenance."""
51 status_calls = []53 self.HookData()().return_value = True
52 self.status_set.side_effect = lambda state, msg: status_calls.append(54 self.assess_status.return_value = ("maintenance", "Foo",)
53 state)
5455
55 actions.actions.pause([])56 actions.actions.pause([])
56 self.assertEqual(status_calls, ["maintenance"])57 self.kv().set.assert_called_with('unit-paused', True)
5758
58 def test_status_message(self):59 def test_status_message(self):
59 """Pause action sets a status message reflecting that it's paused."""60 """Pause action sets a status message reflecting that it's paused."""
60 status_calls = []61 status_calls = []
61 self.status_set.side_effect = lambda state, msg: status_calls.append(62 self.status_set.side_effect = lambda state, msg: status_calls.append(
62 msg)63 msg)
64 self.HookData()().return_value = True
65 self.assess_status.return_value = (
66 "maintenance", "Paused. Use 'resume' action to resume normal"
67 " service.")
6368
64 actions.actions.pause([])69 actions.actions.pause([])
65 self.assertEqual(70 self.assertEqual(

Subscribers

People subscribed via source and target branches