Merge lp:~james-page/charms/trusty/heat/lp1531102-trunk into lp:~openstack-charmers-archive/charms/trusty/heat/trunk

Proposed by James Page
Status: Merged
Merged at revision: 48
Proposed branch: lp:~james-page/charms/trusty/heat/lp1531102-trunk
Merge into: lp:~openstack-charmers-archive/charms/trusty/heat/trunk
Diff against target: 1024 lines (+506/-61)
13 files modified
hooks/charmhelpers/contrib/openstack/amulet/deployment.py (+100/-1)
hooks/charmhelpers/contrib/openstack/amulet/utils.py (+26/-4)
hooks/charmhelpers/contrib/openstack/context.py (+40/-13)
hooks/charmhelpers/contrib/openstack/neutron.py (+17/-3)
hooks/charmhelpers/contrib/openstack/templates/ceph.conf (+6/-0)
hooks/charmhelpers/contrib/openstack/utils.py (+30/-18)
hooks/charmhelpers/core/hookenv.py (+32/-0)
hooks/charmhelpers/core/host.py (+12/-1)
hooks/charmhelpers/core/hugepage.py (+2/-0)
hooks/charmhelpers/core/kernel.py (+68/-0)
tests/charmhelpers/contrib/amulet/utils.py (+47/-16)
tests/charmhelpers/contrib/openstack/amulet/deployment.py (+100/-1)
tests/charmhelpers/contrib/openstack/amulet/utils.py (+26/-4)
To merge this branch: bzr merge lp:~james-page/charms/trusty/heat/lp1531102-trunk
Reviewer Review Type Date Requested Status
Liam Young (community) Approve
Review via email: mp+281869@code.launchpad.net

Commit message

Resync helpers

Description of the change

Resync stable helpers to resolve issues with liberty point release detection.

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

charm_unit_test #15627 heat for james-page mp281869
    UNIT OK: passed

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

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

charm_lint_check #16745 heat for james-page mp281869
    LINT OK: passed

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

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

charm_amulet_test #8565 heat for james-page mp281869
    AMULET OK: passed

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

Revision history for this message
Liam Young (gnuoy) wrote :

Approved

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'hooks/charmhelpers/contrib/openstack/amulet/deployment.py'
--- hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2015-10-22 13:20:40 +0000
+++ hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2016-01-07 14:27:41 +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,
@@ -111,9 +140,79 @@
111140
112 def _configure_services(self, configs):141 def _configure_services(self, configs):
113 """Configure all of the services."""142 """Configure all of the services."""
143 self.log.info('OpenStackAmuletDeployment: configure services')
114 for service, config in six.iteritems(configs):144 for service, config in six.iteritems(configs):
115 self.d.configure(service, config)145 self.d.configure(service, config)
116146
147 def _auto_wait_for_status(self, message=None, exclude_services=None,
148 include_only=None, timeout=1800):
149 """Wait for all units to have a specific extended status, except
150 for any defined as excluded. Unless specified via message, any
151 status containing any case of 'ready' will be considered a match.
152
153 Examples of message usage:
154
155 Wait for all unit status to CONTAIN any case of 'ready' or 'ok':
156 message = re.compile('.*ready.*|.*ok.*', re.IGNORECASE)
157
158 Wait for all units to reach this status (exact match):
159 message = re.compile('^Unit is ready and clustered$')
160
161 Wait for all units to reach any one of these (exact match):
162 message = re.compile('Unit is ready|OK|Ready')
163
164 Wait for at least one unit to reach this status (exact match):
165 message = {'ready'}
166
167 See Amulet's sentry.wait_for_messages() for message usage detail.
168 https://github.com/juju/amulet/blob/master/amulet/sentry.py
169
170 :param message: Expected status match
171 :param exclude_services: List of juju service names to ignore,
172 not to be used in conjuction with include_only.
173 :param include_only: List of juju service names to exclusively check,
174 not to be used in conjuction with exclude_services.
175 :param timeout: Maximum time in seconds to wait for status match
176 :returns: None. Raises if timeout is hit.
177 """
178 self.log.info('Waiting for extended status on units...')
179
180 all_services = self.d.services.keys()
181
182 if exclude_services and include_only:
183 raise ValueError('exclude_services can not be used '
184 'with include_only')
185
186 if message:
187 if isinstance(message, re._pattern_type):
188 match = message.pattern
189 else:
190 match = message
191
192 self.log.debug('Custom extended status wait match: '
193 '{}'.format(match))
194 else:
195 self.log.debug('Default extended status wait match: contains '
196 'READY (case-insensitive)')
197 message = re.compile('.*ready.*', re.IGNORECASE)
198
199 if exclude_services:
200 self.log.debug('Excluding services from extended status match: '
201 '{}'.format(exclude_services))
202 else:
203 exclude_services = []
204
205 if include_only:
206 services = include_only
207 else:
208 services = list(set(all_services) - set(exclude_services))
209
210 self.log.debug('Waiting up to {}s for extended status on services: '
211 '{}'.format(timeout, services))
212 service_messages = {service: message for service in services}
213 self.d.sentry.wait_for_messages(service_messages, timeout=timeout)
214 self.log.info('OK')
215
117 def _get_openstack_release(self):216 def _get_openstack_release(self):
118 """Get openstack release.217 """Get openstack release.
119218
120219
=== modified file 'hooks/charmhelpers/contrib/openstack/amulet/utils.py'
--- hooks/charmhelpers/contrib/openstack/amulet/utils.py 2015-10-22 13:20:40 +0000
+++ hooks/charmhelpers/contrib/openstack/amulet/utils.py 2016-01-07 14:27:41 +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
@@ -752,7 +768,7 @@
752 self.log.debug('SSL is enabled @{}:{} '768 self.log.debug('SSL is enabled @{}:{} '
753 '({})'.format(host, port, unit_name))769 '({})'.format(host, port, unit_name))
754 return True770 return True
755 elif not port and not conf_ssl:771 elif not conf_ssl:
756 self.log.debug('SSL not enabled @{}:{} '772 self.log.debug('SSL not enabled @{}:{} '
757 '({})'.format(host, port, unit_name))773 '({})'.format(host, port, unit_name))
758 return False774 return False
@@ -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 'hooks/charmhelpers/contrib/openstack/context.py'
--- hooks/charmhelpers/contrib/openstack/context.py 2015-10-22 13:20:40 +0000
+++ hooks/charmhelpers/contrib/openstack/context.py 2016-01-07 14:27:41 +0000
@@ -14,6 +14,7 @@
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 glob
17import json18import json
18import os19import os
19import re20import re
@@ -951,6 +952,19 @@
951 'config': config}952 'config': config}
952 return ovs_ctxt953 return ovs_ctxt
953954
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
954 def __call__(self):968 def __call__(self):
955 if self.network_manager not in ['quantum', 'neutron']:969 if self.network_manager not in ['quantum', 'neutron']:
956 return {}970 return {}
@@ -972,6 +986,8 @@
972 ctxt.update(self.nuage_ctxt())986 ctxt.update(self.nuage_ctxt())
973 elif self.plugin == 'plumgrid':987 elif self.plugin == 'plumgrid':
974 ctxt.update(self.pg_ctxt())988 ctxt.update(self.pg_ctxt())
989 elif self.plugin == 'midonet':
990 ctxt.update(self.midonet_ctxt())
975991
976 alchemy_flags = config('neutron-alchemy-flags')992 alchemy_flags = config('neutron-alchemy-flags')
977 if alchemy_flags:993 if alchemy_flags:
@@ -1104,7 +1120,7 @@
11041120
1105 ctxt = {1121 ctxt = {
1106 ... other context ...1122 ... other context ...
1107 'subordinate_config': {1123 'subordinate_configuration': {
1108 'DEFAULT': {1124 'DEFAULT': {
1109 'key1': 'value1',1125 'key1': 'value1',
1110 },1126 },
@@ -1145,22 +1161,23 @@
1145 try:1161 try:
1146 sub_config = json.loads(sub_config)1162 sub_config = json.loads(sub_config)
1147 except:1163 except:
1148 log('Could not parse JSON from subordinate_config '1164 log('Could not parse JSON from '
1149 'setting from %s' % rid, level=ERROR)1165 'subordinate_configuration setting from %s'
1166 % rid, level=ERROR)
1150 continue1167 continue
11511168
1152 for service in self.services:1169 for service in self.services:
1153 if service not in sub_config:1170 if service not in sub_config:
1154 log('Found subordinate_config on %s but it contained'1171 log('Found subordinate_configuration on %s but it '
1155 'nothing for %s service' % (rid, service),1172 'contained nothing for %s service'
1156 level=INFO)1173 % (rid, service), level=INFO)
1157 continue1174 continue
11581175
1159 sub_config = sub_config[service]1176 sub_config = sub_config[service]
1160 if self.config_file not in sub_config:1177 if self.config_file not in sub_config:
1161 log('Found subordinate_config on %s but it contained'1178 log('Found subordinate_configuration on %s but it '
1162 'nothing for %s' % (rid, self.config_file),1179 'contained nothing for %s'
1163 level=INFO)1180 % (rid, self.config_file), level=INFO)
1164 continue1181 continue
11651182
1166 sub_config = sub_config[self.config_file]1183 sub_config = sub_config[self.config_file]
@@ -1363,7 +1380,7 @@
1363 normalized.update({port: port for port in resolved1380 normalized.update({port: port for port in resolved
1364 if port in ports})1381 if port in ports})
1365 if resolved:1382 if resolved:
1366 return {bridge: normalized[port] for port, bridge in1383 return {normalized[port]: bridge for port, bridge in
1367 six.iteritems(portmap) if port in normalized.keys()}1384 six.iteritems(portmap) if port in normalized.keys()}
13681385
1369 return None1386 return None
@@ -1374,12 +1391,22 @@
1374 def __call__(self):1391 def __call__(self):
1375 ctxt = {}1392 ctxt = {}
1376 mappings = super(PhyNICMTUContext, self).__call__()1393 mappings = super(PhyNICMTUContext, self).__call__()
1377 if mappings and mappings.values():1394 if mappings and mappings.keys():
1378 ports = mappings.values()1395 ports = sorted(mappings.keys())
1379 napi_settings = NeutronAPIContext()()1396 napi_settings = NeutronAPIContext()()
1380 mtu = napi_settings.get('network_device_mtu')1397 mtu = napi_settings.get('network_device_mtu')
1398 all_ports = set()
1399 # If any of ports is a vlan device, its underlying device must have
1400 # mtu applied first.
1401 for port in ports:
1402 for lport in glob.glob("/sys/class/net/%s/lower_*" % port):
1403 lport = os.path.basename(lport)
1404 all_ports.add(lport.split('_')[1])
1405
1406 all_ports = list(all_ports)
1407 all_ports.extend(ports)
1381 if mtu:1408 if mtu:
1382 ctxt["devs"] = '\\n'.join(ports)1409 ctxt["devs"] = '\\n'.join(all_ports)
1383 ctxt['mtu'] = mtu1410 ctxt['mtu'] = mtu
13841411
1385 return ctxt1412 return ctxt
13861413
=== modified file 'hooks/charmhelpers/contrib/openstack/neutron.py'
--- hooks/charmhelpers/contrib/openstack/neutron.py 2015-10-22 13:20:40 +0000
+++ hooks/charmhelpers/contrib/openstack/neutron.py 2016-01-07 14:27:41 +0000
@@ -209,6 +209,20 @@
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':
@@ -310,10 +324,10 @@
310def parse_data_port_mappings(mappings, default_bridge='br-data'):324def parse_data_port_mappings(mappings, default_bridge='br-data'):
311 """Parse data port mappings.325 """Parse data port mappings.
312326
313 Mappings must be a space-delimited list of port:bridge mappings.327 Mappings must be a space-delimited list of bridge:port.
314328
315 Returns dict of the form {port:bridge} where port may be an mac address or329 Returns dict of the form {port:bridge} where ports may be mac addresses or
316 interface name.330 interface names.
317 """331 """
318332
319 # NOTE(dosaboy): we use rvalue for key to allow multiple values to be333 # NOTE(dosaboy): we use rvalue for key to allow multiple values to be
320334
=== modified file 'hooks/charmhelpers/contrib/openstack/templates/ceph.conf'
--- hooks/charmhelpers/contrib/openstack/templates/ceph.conf 2015-08-10 16:35:15 +0000
+++ hooks/charmhelpers/contrib/openstack/templates/ceph.conf 2016-01-07 14:27:41 +0000
@@ -13,3 +13,9 @@
13err to syslog = {{ use_syslog }}13err to syslog = {{ use_syslog }}
14clog to syslog = {{ use_syslog }}14clog to syslog = {{ use_syslog }}
1515
16[client]
17{% if rbd_client_cache_settings -%}
18{% for key, value in rbd_client_cache_settings.iteritems() -%}
19{{ key }} = {{ value }}
20{% endfor -%}
21{%- endif %}
16\ No newline at end of file22\ No newline at end of file
1723
=== modified file 'hooks/charmhelpers/contrib/openstack/utils.py'
--- hooks/charmhelpers/contrib/openstack/utils.py 2015-10-22 13:20:40 +0000
+++ hooks/charmhelpers/contrib/openstack/utils.py 2016-01-07 14:27:41 +0000
@@ -54,7 +54,8 @@
54)54)
5555
56from charmhelpers.contrib.network.ip import (56from charmhelpers.contrib.network.ip import (
57 get_ipv6_addr57 get_ipv6_addr,
58 is_ipv6,
58)59)
5960
60from charmhelpers.contrib.python.packages import (61from charmhelpers.contrib.python.packages import (
@@ -120,36 +121,37 @@
120 ('2.2.2', 'kilo'),121 ('2.2.2', 'kilo'),
121 ('2.3.0', 'liberty'),122 ('2.3.0', 'liberty'),
122 ('2.4.0', 'liberty'),123 ('2.4.0', 'liberty'),
124 ('2.5.0', 'liberty'),
123])125])
124126
125# >= Liberty version->codename mapping127# >= Liberty version->codename mapping
126PACKAGE_CODENAMES = {128PACKAGE_CODENAMES = {
127 'nova-common': OrderedDict([129 'nova-common': OrderedDict([
128 ('12.0.0', 'liberty'),130 ('12.0', 'liberty'),
129 ]),131 ]),
130 'neutron-common': OrderedDict([132 'neutron-common': OrderedDict([
131 ('7.0.0', 'liberty'),133 ('7.0', 'liberty'),
132 ]),134 ]),
133 'cinder-common': OrderedDict([135 'cinder-common': OrderedDict([
134 ('7.0.0', 'liberty'),136 ('7.0', 'liberty'),
135 ]),137 ]),
136 'keystone': OrderedDict([138 'keystone': OrderedDict([
137 ('8.0.0', 'liberty'),139 ('8.0', 'liberty'),
138 ]),140 ]),
139 'horizon-common': OrderedDict([141 'horizon-common': OrderedDict([
140 ('8.0.0', 'liberty'),142 ('8.0', 'liberty'),
141 ]),143 ]),
142 'ceilometer-common': OrderedDict([144 'ceilometer-common': OrderedDict([
143 ('5.0.0', 'liberty'),145 ('5.0', 'liberty'),
144 ]),146 ]),
145 'heat-common': OrderedDict([147 'heat-common': OrderedDict([
146 ('5.0.0', 'liberty'),148 ('5.0', 'liberty'),
147 ]),149 ]),
148 'glance-common': OrderedDict([150 'glance-common': OrderedDict([
149 ('11.0.0', 'liberty'),151 ('11.0', 'liberty'),
150 ]),152 ]),
151 'openstack-dashboard': OrderedDict([153 'openstack-dashboard': OrderedDict([
152 ('8.0.0', 'liberty'),154 ('8.0', 'liberty'),
153 ]),155 ]),
154}156}
155157
@@ -236,7 +238,14 @@
236 error_out(e)238 error_out(e)
237239
238 vers = apt.upstream_version(pkg.current_ver.ver_str)240 vers = apt.upstream_version(pkg.current_ver.ver_str)
239 match = re.match('^(\d+)\.(\d+)\.(\d+)', vers)241 if 'swift' in pkg.name:
242 # Fully x.y.z match for swift versions
243 match = re.match('^(\d+)\.(\d+)\.(\d+)', vers)
244 else:
245 # x.y match only for 20XX.X
246 # and ignore patch level for other packages
247 match = re.match('^(\d+)\.(\d+)', vers)
248
240 if match:249 if match:
241 vers = match.group(0)250 vers = match.group(0)
242251
@@ -248,13 +257,8 @@
248 # < Liberty co-ordinated project versions257 # < Liberty co-ordinated project versions
249 try:258 try:
250 if 'swift' in pkg.name:259 if 'swift' in pkg.name:
251 swift_vers = vers[:5]260 return SWIFT_CODENAMES[vers]
252 if swift_vers not in SWIFT_CODENAMES:
253 # Deal with 1.10.0 upward
254 swift_vers = vers[:6]
255 return SWIFT_CODENAMES[swift_vers]
256 else:261 else:
257 vers = vers[:6]
258 return OPENSTACK_CODENAMES[vers]262 return OPENSTACK_CODENAMES[vers]
259 except KeyError:263 except KeyError:
260 if not fatal:264 if not fatal:
@@ -519,6 +523,12 @@
519 relation_prefix=None):523 relation_prefix=None):
520 hosts = get_ipv6_addr(dynamic_only=False)524 hosts = get_ipv6_addr(dynamic_only=False)
521525
526 if config('vip'):
527 vips = config('vip').split()
528 for vip in vips:
529 if vip and is_ipv6(vip):
530 hosts.append(vip)
531
522 kwargs = {'database': database,532 kwargs = {'database': database,
523 'username': database_user,533 'username': database_user,
524 'hostname': json.dumps(hosts)}534 'hostname': json.dumps(hosts)}
@@ -851,7 +861,9 @@
851 if charm_state != 'active' and charm_state != 'unknown':861 if charm_state != 'active' and charm_state != 'unknown':
852 state = workload_state_compare(state, charm_state)862 state = workload_state_compare(state, charm_state)
853 if message:863 if message:
854 message = "{} {}".format(message, charm_message)864 charm_message = charm_message.replace("Incomplete relations: ",
865 "")
866 message = "{}, {}".format(message, charm_message)
855 else:867 else:
856 message = charm_message868 message = charm_message
857869
858870
=== modified file 'hooks/charmhelpers/core/hookenv.py'
--- hooks/charmhelpers/core/hookenv.py 2015-10-22 13:20:40 +0000
+++ hooks/charmhelpers/core/hookenv.py 2016-01-07 14:27:41 +0000
@@ -623,6 +623,38 @@
623 return unit_get('private-address')623 return unit_get('private-address')
624624
625625
626@cached
627def storage_get(attribute="", storage_id=""):
628 """Get storage attributes"""
629 _args = ['storage-get', '--format=json']
630 if storage_id:
631 _args.extend(('-s', storage_id))
632 if attribute:
633 _args.append(attribute)
634 try:
635 return json.loads(subprocess.check_output(_args).decode('UTF-8'))
636 except ValueError:
637 return None
638
639
640@cached
641def storage_list(storage_name=""):
642 """List the storage IDs for the unit"""
643 _args = ['storage-list', '--format=json']
644 if storage_name:
645 _args.append(storage_name)
646 try:
647 return json.loads(subprocess.check_output(_args).decode('UTF-8'))
648 except ValueError:
649 return None
650 except OSError as e:
651 import errno
652 if e.errno == errno.ENOENT:
653 # storage-list does not exist
654 return []
655 raise
656
657
626class UnregisteredHookError(Exception):658class UnregisteredHookError(Exception):
627 """Raised when an undefined hook is called"""659 """Raised when an undefined hook is called"""
628 pass660 pass
629661
=== modified file 'hooks/charmhelpers/core/host.py'
--- hooks/charmhelpers/core/host.py 2015-10-22 13:20:40 +0000
+++ hooks/charmhelpers/core/host.py 2016-01-07 14:27:41 +0000
@@ -566,7 +566,14 @@
566 os.chdir(cur)566 os.chdir(cur)
567567
568568
569def chownr(path, owner, group, follow_links=True):569def chownr(path, owner, group, follow_links=True, chowntopdir=False):
570 """
571 Recursively change user and group ownership of files and directories
572 in given path. Doesn't chown path itself by default, only its children.
573
574 :param bool follow_links: Also Chown links if True
575 :param bool chowntopdir: Also chown path itself if True
576 """
570 uid = pwd.getpwnam(owner).pw_uid577 uid = pwd.getpwnam(owner).pw_uid
571 gid = grp.getgrnam(group).gr_gid578 gid = grp.getgrnam(group).gr_gid
572 if follow_links:579 if follow_links:
@@ -574,6 +581,10 @@
574 else:581 else:
575 chown = os.lchown582 chown = os.lchown
576583
584 if chowntopdir:
585 broken_symlink = os.path.lexists(path) and not os.path.exists(path)
586 if not broken_symlink:
587 chown(path, uid, gid)
577 for root, dirs, files in os.walk(path):588 for root, dirs, files in os.walk(path):
578 for name in dirs + files:589 for name in dirs + files:
579 full = os.path.join(root, name)590 full = os.path.join(root, name)
580591
=== modified file 'hooks/charmhelpers/core/hugepage.py'
--- hooks/charmhelpers/core/hugepage.py 2015-10-22 13:20:40 +0000
+++ hooks/charmhelpers/core/hugepage.py 2016-01-07 14:27:41 +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
=== added file 'hooks/charmhelpers/core/kernel.py'
--- hooks/charmhelpers/core/kernel.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/core/kernel.py 2016-01-07 14:27:41 +0000
@@ -0,0 +1,68 @@
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__author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>"
21
22from charmhelpers.core.hookenv import (
23 log,
24 INFO
25)
26
27from subprocess import check_call, check_output
28import re
29
30
31def modprobe(module, persist=True):
32 """Load a kernel module and configure for auto-load on reboot."""
33 cmd = ['modprobe', module]
34
35 log('Loading kernel module %s' % module, level=INFO)
36
37 check_call(cmd)
38 if persist:
39 with open('/etc/modules', 'r+') as modules:
40 if module not in modules.read():
41 modules.write(module)
42
43
44def rmmod(module, force=False):
45 """Remove a module from the linux kernel"""
46 cmd = ['rmmod']
47 if force:
48 cmd.append('-f')
49 cmd.append(module)
50 log('Removing kernel module %s' % module, level=INFO)
51 return check_call(cmd)
52
53
54def lsmod():
55 """Shows what kernel modules are currently loaded"""
56 return check_output(['lsmod'],
57 universal_newlines=True)
58
59
60def is_module_loaded(module):
61 """Checks if a kernel module is already loaded"""
62 matches = re.findall('^%s[ ]+' % module, lsmod(), re.M)
63 return len(matches) > 0
64
65
66def update_initramfs(version='all'):
67 """Updates an initramfs image"""
68 return check_call(["update-initramfs", "-k", version, "-u"])
069
=== modified file 'tests/charmhelpers/contrib/amulet/utils.py'
--- tests/charmhelpers/contrib/amulet/utils.py 2015-10-22 13:20:40 +0000
+++ tests/charmhelpers/contrib/amulet/utils.py 2016-01-07 14:27:41 +0000
@@ -326,7 +326,7 @@
326326
327 def service_restarted_since(self, sentry_unit, mtime, service,327 def service_restarted_since(self, sentry_unit, mtime, service,
328 pgrep_full=None, sleep_time=20,328 pgrep_full=None, sleep_time=20,
329 retry_count=2, retry_sleep_time=30):329 retry_count=30, retry_sleep_time=10):
330 """Check if service was been started after a given time.330 """Check if service was been started after a given time.
331331
332 Args:332 Args:
@@ -334,8 +334,9 @@
334 mtime (float): The epoch time to check against334 mtime (float): The epoch time to check against
335 service (string): service name to look for in process table335 service (string): service name to look for in process table
336 pgrep_full: [Deprecated] Use full command line search mode with pgrep336 pgrep_full: [Deprecated] Use full command line search mode with pgrep
337 sleep_time (int): Seconds to sleep before looking for process337 sleep_time (int): Initial sleep time (s) before looking for file
338 retry_count (int): If service is not found, how many times to retry338 retry_sleep_time (int): Time (s) to sleep between retries
339 retry_count (int): If file is not found, how many times to retry
339340
340 Returns:341 Returns:
341 bool: True if service found and its start time it newer than mtime,342 bool: True if service found and its start time it newer than mtime,
@@ -359,11 +360,12 @@
359 pgrep_full)360 pgrep_full)
360 self.log.debug('Attempt {} to get {} proc start time on {} '361 self.log.debug('Attempt {} to get {} proc start time on {} '
361 'OK'.format(tries, service, unit_name))362 'OK'.format(tries, service, unit_name))
362 except IOError:363 except IOError as e:
363 # NOTE(beisner) - race avoidance, proc may not exist yet.364 # NOTE(beisner) - race avoidance, proc may not exist yet.
364 # https://bugs.launchpad.net/charm-helpers/+bug/1474030365 # https://bugs.launchpad.net/charm-helpers/+bug/1474030
365 self.log.debug('Attempt {} to get {} proc start time on {} '366 self.log.debug('Attempt {} to get {} proc start time on {} '
366 'failed'.format(tries, service, unit_name))367 'failed\n{}'.format(tries, service,
368 unit_name, e))
367 time.sleep(retry_sleep_time)369 time.sleep(retry_sleep_time)
368 tries += 1370 tries += 1
369371
@@ -383,35 +385,62 @@
383 return False385 return False
384386
385 def config_updated_since(self, sentry_unit, filename, mtime,387 def config_updated_since(self, sentry_unit, filename, mtime,
386 sleep_time=20):388 sleep_time=20, retry_count=30,
389 retry_sleep_time=10):
387 """Check if file was modified after a given time.390 """Check if file was modified after a given time.
388391
389 Args:392 Args:
390 sentry_unit (sentry): The sentry unit to check the file mtime on393 sentry_unit (sentry): The sentry unit to check the file mtime on
391 filename (string): The file to check mtime of394 filename (string): The file to check mtime of
392 mtime (float): The epoch time to check against395 mtime (float): The epoch time to check against
393 sleep_time (int): Seconds to sleep before looking for process396 sleep_time (int): Initial sleep time (s) before looking for file
397 retry_sleep_time (int): Time (s) to sleep between retries
398 retry_count (int): If file is not found, how many times to retry
394399
395 Returns:400 Returns:
396 bool: True if file was modified more recently than mtime, False if401 bool: True if file was modified more recently than mtime, False if
397 file was modified before mtime,402 file was modified before mtime, or if file not found.
398 """403 """
399 self.log.debug('Checking %s updated since %s' % (filename, mtime))404 unit_name = sentry_unit.info['unit_name']
405 self.log.debug('Checking that %s updated since %s on '
406 '%s' % (filename, mtime, unit_name))
400 time.sleep(sleep_time)407 time.sleep(sleep_time)
401 file_mtime = self._get_file_mtime(sentry_unit, filename)408 file_mtime = None
409 tries = 0
410 while tries <= retry_count and not file_mtime:
411 try:
412 file_mtime = self._get_file_mtime(sentry_unit, filename)
413 self.log.debug('Attempt {} to get {} file mtime on {} '
414 'OK'.format(tries, filename, unit_name))
415 except IOError as e:
416 # NOTE(beisner) - race avoidance, file may not exist yet.
417 # https://bugs.launchpad.net/charm-helpers/+bug/1474030
418 self.log.debug('Attempt {} to get {} file mtime on {} '
419 'failed\n{}'.format(tries, filename,
420 unit_name, e))
421 time.sleep(retry_sleep_time)
422 tries += 1
423
424 if not file_mtime:
425 self.log.warn('Could not determine file mtime, assuming '
426 'file does not exist')
427 return False
428
402 if file_mtime >= mtime:429 if file_mtime >= mtime:
403 self.log.debug('File mtime is newer than provided mtime '430 self.log.debug('File mtime is newer than provided mtime '
404 '(%s >= %s)' % (file_mtime, mtime))431 '(%s >= %s) on %s (OK)' % (file_mtime,
432 mtime, unit_name))
405 return True433 return True
406 else:434 else:
407 self.log.warn('File mtime %s is older than provided mtime %s'435 self.log.warn('File mtime is older than provided mtime'
408 % (file_mtime, mtime))436 '(%s < on %s) on %s' % (file_mtime,
437 mtime, unit_name))
409 return False438 return False
410439
411 def validate_service_config_changed(self, sentry_unit, mtime, service,440 def validate_service_config_changed(self, sentry_unit, mtime, service,
412 filename, pgrep_full=None,441 filename, pgrep_full=None,
413 sleep_time=20, retry_count=2,442 sleep_time=20, retry_count=30,
414 retry_sleep_time=30):443 retry_sleep_time=10):
415 """Check service and file were updated after mtime444 """Check service and file were updated after mtime
416445
417 Args:446 Args:
@@ -456,7 +485,9 @@
456 sentry_unit,485 sentry_unit,
457 filename,486 filename,
458 mtime,487 mtime,
459 sleep_time=0)488 sleep_time=sleep_time,
489 retry_count=retry_count,
490 retry_sleep_time=retry_sleep_time)
460491
461 return service_restart and config_update492 return service_restart and config_update
462493
463494
=== modified file 'tests/charmhelpers/contrib/openstack/amulet/deployment.py'
--- tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-10-22 13:20:40 +0000
+++ tests/charmhelpers/contrib/openstack/amulet/deployment.py 2016-01-07 14:27:41 +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,
@@ -111,9 +140,79 @@
111140
112 def _configure_services(self, configs):141 def _configure_services(self, configs):
113 """Configure all of the services."""142 """Configure all of the services."""
143 self.log.info('OpenStackAmuletDeployment: configure services')
114 for service, config in six.iteritems(configs):144 for service, config in six.iteritems(configs):
115 self.d.configure(service, config)145 self.d.configure(service, config)
116146
147 def _auto_wait_for_status(self, message=None, exclude_services=None,
148 include_only=None, timeout=1800):
149 """Wait for all units to have a specific extended status, except
150 for any defined as excluded. Unless specified via message, any
151 status containing any case of 'ready' will be considered a match.
152
153 Examples of message usage:
154
155 Wait for all unit status to CONTAIN any case of 'ready' or 'ok':
156 message = re.compile('.*ready.*|.*ok.*', re.IGNORECASE)
157
158 Wait for all units to reach this status (exact match):
159 message = re.compile('^Unit is ready and clustered$')
160
161 Wait for all units to reach any one of these (exact match):
162 message = re.compile('Unit is ready|OK|Ready')
163
164 Wait for at least one unit to reach this status (exact match):
165 message = {'ready'}
166
167 See Amulet's sentry.wait_for_messages() for message usage detail.
168 https://github.com/juju/amulet/blob/master/amulet/sentry.py
169
170 :param message: Expected status match
171 :param exclude_services: List of juju service names to ignore,
172 not to be used in conjuction with include_only.
173 :param include_only: List of juju service names to exclusively check,
174 not to be used in conjuction with exclude_services.
175 :param timeout: Maximum time in seconds to wait for status match
176 :returns: None. Raises if timeout is hit.
177 """
178 self.log.info('Waiting for extended status on units...')
179
180 all_services = self.d.services.keys()
181
182 if exclude_services and include_only:
183 raise ValueError('exclude_services can not be used '
184 'with include_only')
185
186 if message:
187 if isinstance(message, re._pattern_type):
188 match = message.pattern
189 else:
190 match = message
191
192 self.log.debug('Custom extended status wait match: '
193 '{}'.format(match))
194 else:
195 self.log.debug('Default extended status wait match: contains '
196 'READY (case-insensitive)')
197 message = re.compile('.*ready.*', re.IGNORECASE)
198
199 if exclude_services:
200 self.log.debug('Excluding services from extended status match: '
201 '{}'.format(exclude_services))
202 else:
203 exclude_services = []
204
205 if include_only:
206 services = include_only
207 else:
208 services = list(set(all_services) - set(exclude_services))
209
210 self.log.debug('Waiting up to {}s for extended status on services: '
211 '{}'.format(timeout, services))
212 service_messages = {service: message for service in services}
213 self.d.sentry.wait_for_messages(service_messages, timeout=timeout)
214 self.log.info('OK')
215
117 def _get_openstack_release(self):216 def _get_openstack_release(self):
118 """Get openstack release.217 """Get openstack release.
119218
120219
=== modified file 'tests/charmhelpers/contrib/openstack/amulet/utils.py'
--- tests/charmhelpers/contrib/openstack/amulet/utils.py 2015-10-22 13:20:40 +0000
+++ tests/charmhelpers/contrib/openstack/amulet/utils.py 2016-01-07 14:27:41 +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
@@ -752,7 +768,7 @@
752 self.log.debug('SSL is enabled @{}:{} '768 self.log.debug('SSL is enabled @{}:{} '
753 '({})'.format(host, port, unit_name))769 '({})'.format(host, port, unit_name))
754 return True770 return True
755 elif not port and not conf_ssl:771 elif not conf_ssl:
756 self.log.debug('SSL not enabled @{}:{} '772 self.log.debug('SSL not enabled @{}:{} '
757 '({})'.format(host, port, unit_name))773 '({})'.format(host, port, unit_name))
758 return False774 return False
@@ -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

Subscribers

People subscribed via source and target branches