Merge lp:~chad.smith/charms/trusty/glance-simplestreams-sync/sync-charmhelpers-for-add-apt-repo-retry into lp:~openstack-charmers/charms/trusty/glance-simplestreams-sync/next

Proposed by Chad Smith
Status: Needs review
Proposed branch: lp:~chad.smith/charms/trusty/glance-simplestreams-sync/sync-charmhelpers-for-add-apt-repo-retry
Merge into: lp:~openstack-charmers/charms/trusty/glance-simplestreams-sync/next
Diff against target: 10125 lines (+5920/-1725)
74 files modified
charm-helpers-sync.yaml (+1/-0)
charmhelpers/__init__.py (+11/-13)
charmhelpers/contrib/__init__.py (+11/-13)
charmhelpers/contrib/charmsupport/__init__.py (+11/-13)
charmhelpers/contrib/charmsupport/nrpe.py (+103/-34)
charmhelpers/contrib/charmsupport/volumes.py (+11/-13)
charmhelpers/contrib/hahelpers/__init__.py (+11/-13)
charmhelpers/contrib/hahelpers/apache.py (+30/-17)
charmhelpers/contrib/hahelpers/cluster.py (+70/-23)
charmhelpers/contrib/network/__init__.py (+11/-13)
charmhelpers/contrib/network/ip.py (+99/-42)
charmhelpers/contrib/openstack/__init__.py (+11/-13)
charmhelpers/contrib/openstack/alternatives.py (+11/-13)
charmhelpers/contrib/openstack/amulet/__init__.py (+11/-13)
charmhelpers/contrib/openstack/amulet/deployment.py (+200/-69)
charmhelpers/contrib/openstack/amulet/utils.py (+354/-38)
charmhelpers/contrib/openstack/context.py (+313/-127)
charmhelpers/contrib/openstack/exceptions.py (+21/-0)
charmhelpers/contrib/openstack/files/__init__.py (+11/-13)
charmhelpers/contrib/openstack/files/check_haproxy.sh (+7/-5)
charmhelpers/contrib/openstack/ha/__init__.py (+13/-0)
charmhelpers/contrib/openstack/ha/utils.py (+139/-0)
charmhelpers/contrib/openstack/ip.py (+60/-25)
charmhelpers/contrib/openstack/keystone.py (+178/-0)
charmhelpers/contrib/openstack/neutron.py (+57/-23)
charmhelpers/contrib/openstack/templates/__init__.py (+11/-13)
charmhelpers/contrib/openstack/templates/haproxy.cfg (+19/-11)
charmhelpers/contrib/openstack/templates/memcached.conf (+53/-0)
charmhelpers/contrib/openstack/templates/openstack_https_frontend (+5/-0)
charmhelpers/contrib/openstack/templates/openstack_https_frontend.conf (+5/-0)
charmhelpers/contrib/openstack/templates/section-keystone-authtoken (+8/-5)
charmhelpers/contrib/openstack/templates/section-keystone-authtoken-legacy (+10/-0)
charmhelpers/contrib/openstack/templates/section-keystone-authtoken-mitaka (+20/-0)
charmhelpers/contrib/openstack/templates/wsgi-openstack-api.conf (+100/-0)
charmhelpers/contrib/openstack/templating.py (+19/-15)
charmhelpers/contrib/openstack/utils.py (+1163/-151)
charmhelpers/contrib/python/__init__.py (+11/-13)
charmhelpers/contrib/python/packages.py (+59/-26)
charmhelpers/contrib/storage/__init__.py (+11/-13)
charmhelpers/contrib/storage/linux/__init__.py (+11/-13)
charmhelpers/contrib/storage/linux/ceph.py (+766/-72)
charmhelpers/contrib/storage/linux/loopback.py (+21/-13)
charmhelpers/contrib/storage/linux/lvm.py (+11/-13)
charmhelpers/contrib/storage/linux/utils.py (+16/-18)
charmhelpers/core/__init__.py (+11/-13)
charmhelpers/core/decorators.py (+11/-13)
charmhelpers/core/files.py (+11/-13)
charmhelpers/core/fstab.py (+11/-13)
charmhelpers/core/hookenv.py (+157/-19)
charmhelpers/core/host.py (+482/-150)
charmhelpers/core/host_factory/centos.py (+56/-0)
charmhelpers/core/host_factory/ubuntu.py (+56/-0)
charmhelpers/core/hugepage.py (+13/-13)
charmhelpers/core/kernel.py (+34/-30)
charmhelpers/core/kernel_factory/centos.py (+17/-0)
charmhelpers/core/kernel_factory/ubuntu.py (+13/-0)
charmhelpers/core/services/__init__.py (+11/-13)
charmhelpers/core/services/base.py (+11/-13)
charmhelpers/core/services/helpers.py (+25/-18)
charmhelpers/core/strutils.py (+11/-13)
charmhelpers/core/sysctl.py (+11/-13)
charmhelpers/core/templating.py (+40/-24)
charmhelpers/core/unitdata.py (+11/-14)
charmhelpers/fetch/__init__.py (+43/-302)
charmhelpers/fetch/archiveurl.py (+12/-14)
charmhelpers/fetch/bzrurl.py (+48/-50)
charmhelpers/fetch/centos.py (+171/-0)
charmhelpers/fetch/giturl.py (+33/-37)
charmhelpers/fetch/snap.py (+122/-0)
charmhelpers/fetch/ubuntu.py (+364/-0)
charmhelpers/osplatform.py (+25/-0)
charmhelpers/payload/__init__.py (+11/-13)
charmhelpers/payload/archive.py (+11/-13)
charmhelpers/payload/execd.py (+14/-15)
To merge this branch: bzr merge lp:~chad.smith/charms/trusty/glance-simplestreams-sync/sync-charmhelpers-for-add-apt-repo-retry
Reviewer Review Type Date Requested Status
OpenStack Charmers Pending
Review via email: mp+318976@code.launchpad.net

Description of the change

Make sync of charmhelpers updates to get latest fetch.add_source retries logic.

The only official change to this branch is in charm-helpers-sync because host now depends on osplatform
--- charm-helpers-sync.yaml revid:<email address hidden>
+++ charm-helpers-sync.yaml revid:<email address hidden>
@@ -3,6 +3,7 @@
 include:
     - core
     - fetch
+ - osplatform
     - payload
     - contrib.openstack|inc=*
     - contrib.charmsupport

To post a comment you must log in.

Unmerged revisions

60. By Chad Smith

make sync pulling in updated charmhelpers

59. By Chad Smith

Update charmhelpers dependencies prior to make sync since charmhelpers.host now imports get_platform from charmhelpers.osplatform, we need to import osplatform too

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'charm-helpers-sync.yaml'
--- charm-helpers-sync.yaml 2015-09-28 20:36:18 +0000
+++ charm-helpers-sync.yaml 2017-03-04 02:21:16 +0000
@@ -3,6 +3,7 @@
3include:3include:
4 - core4 - core
5 - fetch5 - fetch
6 - osplatform
6 - payload7 - payload
7 - contrib.openstack|inc=*8 - contrib.openstack|inc=*
8 - contrib.charmsupport9 - contrib.charmsupport
910
=== modified file 'charmhelpers/__init__.py'
--- charmhelpers/__init__.py 2015-09-28 20:09:02 +0000
+++ charmhelpers/__init__.py 2017-03-04 02:21:16 +0000
@@ -1,18 +1,16 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4#4# you may not use this file except in compliance with the License.
5# charm-helpers is free software: you can redistribute it and/or modify5# You may obtain a copy of the License at
6# it under the terms of the GNU Lesser General Public License version 3 as6#
7# published by the Free Software Foundation.7# http://www.apache.org/licenses/LICENSE-2.0
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17# Bootstrap charm-helpers, installing its dependencies if necessary using15# Bootstrap charm-helpers, installing its dependencies if necessary using
18# only standard libraries.16# only standard libraries.
1917
=== modified file 'charmhelpers/contrib/__init__.py'
--- charmhelpers/contrib/__init__.py 2015-09-28 20:09:02 +0000
+++ charmhelpers/contrib/__init__.py 2017-03-04 02:21:16 +0000
@@ -1,15 +1,13 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4#4# you may not use this file except in compliance with the License.
5# charm-helpers is free software: you can redistribute it and/or modify5# You may obtain a copy of the License at
6# it under the terms of the GNU Lesser General Public License version 3 as6#
7# published by the Free Software Foundation.7# http://www.apache.org/licenses/LICENSE-2.0
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
=== modified file 'charmhelpers/contrib/charmsupport/__init__.py'
--- charmhelpers/contrib/charmsupport/__init__.py 2015-09-28 20:09:02 +0000
+++ charmhelpers/contrib/charmsupport/__init__.py 2017-03-04 02:21:16 +0000
@@ -1,15 +1,13 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4#4# you may not use this file except in compliance with the License.
5# charm-helpers is free software: you can redistribute it and/or modify5# You may obtain a copy of the License at
6# it under the terms of the GNU Lesser General Public License version 3 as6#
7# published by the Free Software Foundation.7# http://www.apache.org/licenses/LICENSE-2.0
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
=== modified file 'charmhelpers/contrib/charmsupport/nrpe.py'
--- charmhelpers/contrib/charmsupport/nrpe.py 2015-09-28 20:09:02 +0000
+++ charmhelpers/contrib/charmsupport/nrpe.py 2017-03-04 02:21:16 +0000
@@ -1,18 +1,16 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4#4# you may not use this file except in compliance with the License.
5# charm-helpers is free software: you can redistribute it and/or modify5# You may obtain a copy of the License at
6# it under the terms of the GNU Lesser General Public License version 3 as6#
7# published by the Free Software Foundation.7# http://www.apache.org/licenses/LICENSE-2.0
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17"""Compatibility with the nrpe-external-master charm"""15"""Compatibility with the nrpe-external-master charm"""
18# Copyright 2012 Canonical Ltd.16# Copyright 2012 Canonical Ltd.
@@ -40,6 +38,7 @@
40)38)
4139
42from charmhelpers.core.host import service40from charmhelpers.core.host import service
41from charmhelpers.core import host
4342
44# This module adds compatibility with the nrpe-external-master and plain nrpe43# This module adds compatibility with the nrpe-external-master and plain nrpe
45# subordinate charms. To use it in your charm:44# subordinate charms. To use it in your charm:
@@ -110,6 +109,13 @@
110# def local_monitors_relation_changed():109# def local_monitors_relation_changed():
111# update_nrpe_config()110# update_nrpe_config()
112#111#
112# 4.a If your charm is a subordinate charm set primary=False
113#
114# from charmsupport.nrpe import NRPE
115# (...)
116# def update_nrpe_config():
117# nrpe_compat = NRPE(primary=False)
118#
113# 5. ln -s hooks.py nrpe-external-master-relation-changed119# 5. ln -s hooks.py nrpe-external-master-relation-changed
114# ln -s hooks.py local-monitors-relation-changed120# ln -s hooks.py local-monitors-relation-changed
115121
@@ -148,6 +154,13 @@
148 self.description = description154 self.description = description
149 self.check_cmd = self._locate_cmd(check_cmd)155 self.check_cmd = self._locate_cmd(check_cmd)
150156
157 def _get_check_filename(self):
158 return os.path.join(NRPE.nrpe_confdir, '{}.cfg'.format(self.command))
159
160 def _get_service_filename(self, hostname):
161 return os.path.join(NRPE.nagios_exportdir,
162 'service__{}_{}.cfg'.format(hostname, self.command))
163
151 def _locate_cmd(self, check_cmd):164 def _locate_cmd(self, check_cmd):
152 search_path = (165 search_path = (
153 '/usr/lib/nagios/plugins',166 '/usr/lib/nagios/plugins',
@@ -163,9 +176,21 @@
163 log('Check command not found: {}'.format(parts[0]))176 log('Check command not found: {}'.format(parts[0]))
164 return ''177 return ''
165178
179 def _remove_service_files(self):
180 if not os.path.exists(NRPE.nagios_exportdir):
181 return
182 for f in os.listdir(NRPE.nagios_exportdir):
183 if f.endswith('_{}.cfg'.format(self.command)):
184 os.remove(os.path.join(NRPE.nagios_exportdir, f))
185
186 def remove(self, hostname):
187 nrpe_check_file = self._get_check_filename()
188 if os.path.exists(nrpe_check_file):
189 os.remove(nrpe_check_file)
190 self._remove_service_files()
191
166 def write(self, nagios_context, hostname, nagios_servicegroups):192 def write(self, nagios_context, hostname, nagios_servicegroups):
167 nrpe_check_file = '/etc/nagios/nrpe.d/{}.cfg'.format(193 nrpe_check_file = self._get_check_filename()
168 self.command)
169 with open(nrpe_check_file, 'w') as nrpe_check_config:194 with open(nrpe_check_file, 'w') as nrpe_check_config:
170 nrpe_check_config.write("# check {}\n".format(self.shortname))195 nrpe_check_config.write("# check {}\n".format(self.shortname))
171 nrpe_check_config.write("command[{}]={}\n".format(196 nrpe_check_config.write("command[{}]={}\n".format(
@@ -180,9 +205,7 @@
180205
181 def write_service_config(self, nagios_context, hostname,206 def write_service_config(self, nagios_context, hostname,
182 nagios_servicegroups):207 nagios_servicegroups):
183 for f in os.listdir(NRPE.nagios_exportdir):208 self._remove_service_files()
184 if re.search('.*{}.cfg'.format(self.command), f):
185 os.remove(os.path.join(NRPE.nagios_exportdir, f))
186209
187 templ_vars = {210 templ_vars = {
188 'nagios_hostname': hostname,211 'nagios_hostname': hostname,
@@ -192,8 +215,7 @@
192 'command': self.command,215 'command': self.command,
193 }216 }
194 nrpe_service_text = Check.service_template.format(**templ_vars)217 nrpe_service_text = Check.service_template.format(**templ_vars)
195 nrpe_service_file = '{}/service__{}_{}.cfg'.format(218 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:219 with open(nrpe_service_file, 'w') as nrpe_service_config:
198 nrpe_service_config.write(str(nrpe_service_text))220 nrpe_service_config.write(str(nrpe_service_text))
199221
@@ -206,9 +228,10 @@
206 nagios_exportdir = '/var/lib/nagios/export'228 nagios_exportdir = '/var/lib/nagios/export'
207 nrpe_confdir = '/etc/nagios/nrpe.d'229 nrpe_confdir = '/etc/nagios/nrpe.d'
208230
209 def __init__(self, hostname=None):231 def __init__(self, hostname=None, primary=True):
210 super(NRPE, self).__init__()232 super(NRPE, self).__init__()
211 self.config = config()233 self.config = config()
234 self.primary = primary
212 self.nagios_context = self.config['nagios_context']235 self.nagios_context = self.config['nagios_context']
213 if 'nagios_servicegroups' in self.config and self.config['nagios_servicegroups']:236 if 'nagios_servicegroups' in self.config and self.config['nagios_servicegroups']:
214 self.nagios_servicegroups = self.config['nagios_servicegroups']237 self.nagios_servicegroups = self.config['nagios_servicegroups']
@@ -218,12 +241,38 @@
218 if hostname:241 if hostname:
219 self.hostname = hostname242 self.hostname = hostname
220 else:243 else:
221 self.hostname = "{}-{}".format(self.nagios_context, self.unit_name)244 nagios_hostname = get_nagios_hostname()
245 if nagios_hostname:
246 self.hostname = nagios_hostname
247 else:
248 self.hostname = "{}-{}".format(self.nagios_context, self.unit_name)
222 self.checks = []249 self.checks = []
250 # Iff in an nrpe-external-master relation hook, set primary status
251 relation = relation_ids('nrpe-external-master')
252 if relation:
253 log("Setting charm primary status {}".format(primary))
254 for rid in relation_ids('nrpe-external-master'):
255 relation_set(relation_id=rid, relation_settings={'primary': self.primary})
223256
224 def add_check(self, *args, **kwargs):257 def add_check(self, *args, **kwargs):
225 self.checks.append(Check(*args, **kwargs))258 self.checks.append(Check(*args, **kwargs))
226259
260 def remove_check(self, *args, **kwargs):
261 if kwargs.get('shortname') is None:
262 raise ValueError('shortname of check must be specified')
263
264 # Use sensible defaults if they're not specified - these are not
265 # actually used during removal, but they're required for constructing
266 # the Check object; check_disk is chosen because it's part of the
267 # nagios-plugins-basic package.
268 if kwargs.get('check_cmd') is None:
269 kwargs['check_cmd'] = 'check_disk'
270 if kwargs.get('description') is None:
271 kwargs['description'] = ''
272
273 check = Check(*args, **kwargs)
274 check.remove(self.hostname)
275
227 def write(self):276 def write(self):
228 try:277 try:
229 nagios_uid = pwd.getpwnam('nagios').pw_uid278 nagios_uid = pwd.getpwnam('nagios').pw_uid
@@ -260,7 +309,7 @@
260 :param str relation_name: Name of relation nrpe sub joined to309 :param str relation_name: Name of relation nrpe sub joined to
261 """310 """
262 for rel in relations_of_type(relation_name):311 for rel in relations_of_type(relation_name):
263 if 'nagios_hostname' in rel:312 if 'nagios_host_context' in rel:
264 return rel['nagios_host_context']313 return rel['nagios_host_context']
265314
266315
@@ -289,18 +338,30 @@
289 return unit338 return unit
290339
291340
292def add_init_service_checks(nrpe, services, unit_name):341def add_init_service_checks(nrpe, services, unit_name, immediate_check=True):
293 """342 """
294 Add checks for each service in list343 Add checks for each service in list
295344
296 :param NRPE nrpe: NRPE object to add check to345 :param NRPE nrpe: NRPE object to add check to
297 :param list services: List of services to check346 :param list services: List of services to check
298 :param str unit_name: Unit name to use in check description347 :param str unit_name: Unit name to use in check description
348 :param bool immediate_check: For sysv init, run the service check immediately
299 """349 """
300 for svc in services:350 for svc in services:
351 # Don't add a check for these services from neutron-gateway
352 if svc in ['ext-port', 'os-charm-phy-nic-mtu']:
353 next
354
301 upstart_init = '/etc/init/%s.conf' % svc355 upstart_init = '/etc/init/%s.conf' % svc
302 sysv_init = '/etc/init.d/%s' % svc356 sysv_init = '/etc/init.d/%s' % svc
303 if os.path.exists(upstart_init):357
358 if host.init_is_systemd():
359 nrpe.add_check(
360 shortname=svc,
361 description='process check {%s}' % unit_name,
362 check_cmd='check_systemd.py %s' % svc
363 )
364 elif os.path.exists(upstart_init):
304 nrpe.add_check(365 nrpe.add_check(
305 shortname=svc,366 shortname=svc,
306 description='process check {%s}' % unit_name,367 description='process check {%s}' % unit_name,
@@ -308,21 +369,29 @@
308 )369 )
309 elif os.path.exists(sysv_init):370 elif os.path.exists(sysv_init):
310 cronpath = '/etc/cron.d/nagios-service-check-%s' % svc371 cronpath = '/etc/cron.d/nagios-service-check-%s' % svc
311 cron_file = ('*/5 * * * * root '372 checkpath = '/var/lib/nagios/service-check-%s.txt' % svc
312 '/usr/local/lib/nagios/plugins/check_exit_status.pl '373 croncmd = (
313 '-s /etc/init.d/%s status > '374 '/usr/local/lib/nagios/plugins/check_exit_status.pl '
314 '/var/lib/nagios/service-check-%s.txt\n' % (svc,375 '-s /etc/init.d/%s status' % svc
315 svc)376 )
316 )377 cron_file = '*/5 * * * * root %s > %s\n' % (croncmd, checkpath)
317 f = open(cronpath, 'w')378 f = open(cronpath, 'w')
318 f.write(cron_file)379 f.write(cron_file)
319 f.close()380 f.close()
320 nrpe.add_check(381 nrpe.add_check(
321 shortname=svc,382 shortname=svc,
322 description='process check {%s}' % unit_name,383 description='service check {%s}' % unit_name,
323 check_cmd='check_status_file.py -f '384 check_cmd='check_status_file.py -f %s' % checkpath,
324 '/var/lib/nagios/service-check-%s.txt' % svc,
325 )385 )
386 if immediate_check:
387 f = open(checkpath, 'w')
388 subprocess.call(
389 croncmd.split(),
390 stdout=f,
391 stderr=subprocess.STDOUT
392 )
393 f.close()
394 os.chmod(checkpath, 0o644)
326395
327396
328def copy_nrpe_checks():397def copy_nrpe_checks():
329398
=== modified file 'charmhelpers/contrib/charmsupport/volumes.py'
--- charmhelpers/contrib/charmsupport/volumes.py 2015-09-28 20:09:02 +0000
+++ charmhelpers/contrib/charmsupport/volumes.py 2017-03-04 02:21:16 +0000
@@ -1,18 +1,16 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4#4# you may not use this file except in compliance with the License.
5# charm-helpers is free software: you can redistribute it and/or modify5# You may obtain a copy of the License at
6# it under the terms of the GNU Lesser General Public License version 3 as6#
7# published by the Free Software Foundation.7# http://www.apache.org/licenses/LICENSE-2.0
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17'''15'''
18Functions for managing volumes in juju units. One volume is supported per unit.16Functions for managing volumes in juju units. One volume is supported per unit.
1917
=== modified file 'charmhelpers/contrib/hahelpers/__init__.py'
--- charmhelpers/contrib/hahelpers/__init__.py 2015-09-28 20:09:02 +0000
+++ charmhelpers/contrib/hahelpers/__init__.py 2017-03-04 02:21:16 +0000
@@ -1,15 +1,13 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4#4# you may not use this file except in compliance with the License.
5# charm-helpers is free software: you can redistribute it and/or modify5# You may obtain a copy of the License at
6# it under the terms of the GNU Lesser General Public License version 3 as6#
7# published by the Free Software Foundation.7# http://www.apache.org/licenses/LICENSE-2.0
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
=== modified file 'charmhelpers/contrib/hahelpers/apache.py'
--- charmhelpers/contrib/hahelpers/apache.py 2015-09-28 20:09:02 +0000
+++ charmhelpers/contrib/hahelpers/apache.py 2017-03-04 02:21:16 +0000
@@ -1,18 +1,16 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4#4# you may not use this file except in compliance with the License.
5# charm-helpers is free software: you can redistribute it and/or modify5# You may obtain a copy of the License at
6# it under the terms of the GNU Lesser General Public License version 3 as6#
7# published by the Free Software Foundation.7# http://www.apache.org/licenses/LICENSE-2.0
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17#15#
18# Copyright 2012 Canonical Ltd.16# Copyright 2012 Canonical Ltd.
@@ -24,6 +22,7 @@
24# Adam Gandelman <adamg@ubuntu.com>22# Adam Gandelman <adamg@ubuntu.com>
25#23#
2624
25import os
27import subprocess26import subprocess
2827
29from charmhelpers.core.hookenv import (28from charmhelpers.core.hookenv import (
@@ -74,9 +73,23 @@
74 return ca_cert73 return ca_cert
7574
7675
76def retrieve_ca_cert(cert_file):
77 cert = None
78 if os.path.isfile(cert_file):
79 with open(cert_file, 'r') as crt:
80 cert = crt.read()
81 return cert
82
83
77def install_ca_cert(ca_cert):84def install_ca_cert(ca_cert):
78 if ca_cert:85 if ca_cert:
79 with open('/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt',86 cert_file = ('/usr/local/share/ca-certificates/'
80 'w') as crt:87 'keystone_juju_ca_cert.crt')
81 crt.write(ca_cert)88 old_cert = retrieve_ca_cert(cert_file)
82 subprocess.check_call(['update-ca-certificates', '--fresh'])89 if old_cert and old_cert == ca_cert:
90 log("CA cert is the same as installed version", level=INFO)
91 else:
92 log("Installing new CA cert", level=INFO)
93 with open(cert_file, 'w') as crt:
94 crt.write(ca_cert)
95 subprocess.check_call(['update-ca-certificates', '--fresh'])
8396
=== modified file 'charmhelpers/contrib/hahelpers/cluster.py'
--- charmhelpers/contrib/hahelpers/cluster.py 2015-09-28 20:09:02 +0000
+++ charmhelpers/contrib/hahelpers/cluster.py 2017-03-04 02:21:16 +0000
@@ -1,18 +1,16 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4#4# you may not use this file except in compliance with the License.
5# charm-helpers is free software: you can redistribute it and/or modify5# You may obtain a copy of the License at
6# it under the terms of the GNU Lesser General Public License version 3 as6#
7# published by the Free Software Foundation.7# http://www.apache.org/licenses/LICENSE-2.0
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17#15#
18# Copyright 2012 Canonical Ltd.16# Copyright 2012 Canonical Ltd.
@@ -41,10 +39,11 @@
41 relation_get,39 relation_get,
42 config as config_get,40 config as config_get,
43 INFO,41 INFO,
44 ERROR,42 DEBUG,
45 WARNING,43 WARNING,
46 unit_get,44 unit_get,
47 is_leader as juju_is_leader45 is_leader as juju_is_leader,
46 status_set,
48)47)
49from charmhelpers.core.decorators import (48from charmhelpers.core.decorators import (
50 retry_on_exception,49 retry_on_exception,
@@ -60,6 +59,10 @@
60 pass59 pass
6160
6261
62class HAIncorrectConfig(Exception):
63 pass
64
65
63class CRMResourceNotFound(Exception):66class CRMResourceNotFound(Exception):
64 pass67 pass
6568
@@ -274,27 +277,71 @@
274 Obtains all relevant configuration from charm configuration required277 Obtains all relevant configuration from charm configuration required
275 for initiating a relation to hacluster:278 for initiating a relation to hacluster:
276279
277 ha-bindiface, ha-mcastport, vip280 ha-bindiface, ha-mcastport, vip, os-internal-hostname,
281 os-admin-hostname, os-public-hostname, os-access-hostname
278282
279 param: exclude_keys: list of setting key(s) to be excluded.283 param: exclude_keys: list of setting key(s) to be excluded.
280 returns: dict: A dict containing settings keyed by setting name.284 returns: dict: A dict containing settings keyed by setting name.
281 raises: HAIncompleteConfig if settings are missing.285 raises: HAIncompleteConfig if settings are missing or incorrect.
282 '''286 '''
283 settings = ['ha-bindiface', 'ha-mcastport', 'vip']287 settings = ['ha-bindiface', 'ha-mcastport', 'vip', 'os-internal-hostname',
288 'os-admin-hostname', 'os-public-hostname', 'os-access-hostname']
284 conf = {}289 conf = {}
285 for setting in settings:290 for setting in settings:
286 if exclude_keys and setting in exclude_keys:291 if exclude_keys and setting in exclude_keys:
287 continue292 continue
288293
289 conf[setting] = config_get(setting)294 conf[setting] = config_get(setting)
290 missing = []295
291 [missing.append(s) for s, v in six.iteritems(conf) if v is None]296 if not valid_hacluster_config():
292 if missing:297 raise HAIncorrectConfig('Insufficient or incorrect config data to '
293 log('Insufficient config data to configure hacluster.', level=ERROR)298 'configure hacluster.')
294 raise HAIncompleteConfig
295 return conf299 return conf
296300
297301
302def valid_hacluster_config():
303 '''
304 Check that either vip or dns-ha is set. If dns-ha then one of os-*-hostname
305 must be set.
306
307 Note: ha-bindiface and ha-macastport both have defaults and will always
308 be set. We only care that either vip or dns-ha is set.
309
310 :returns: boolean: valid config returns true.
311 raises: HAIncompatibileConfig if settings conflict.
312 raises: HAIncompleteConfig if settings are missing.
313 '''
314 vip = config_get('vip')
315 dns = config_get('dns-ha')
316 if not(bool(vip) ^ bool(dns)):
317 msg = ('HA: Either vip or dns-ha must be set but not both in order to '
318 'use high availability')
319 status_set('blocked', msg)
320 raise HAIncorrectConfig(msg)
321
322 # If dns-ha then one of os-*-hostname must be set
323 if dns:
324 dns_settings = ['os-internal-hostname', 'os-admin-hostname',
325 'os-public-hostname', 'os-access-hostname']
326 # At this point it is unknown if one or all of the possible
327 # network spaces are in HA. Validate at least one is set which is
328 # the minimum required.
329 for setting in dns_settings:
330 if config_get(setting):
331 log('DNS HA: At least one hostname is set {}: {}'
332 ''.format(setting, config_get(setting)),
333 level=DEBUG)
334 return True
335
336 msg = ('DNS HA: At least one os-*-hostname(s) must be set to use '
337 'DNS HA')
338 status_set('blocked', msg)
339 raise HAIncompleteConfig(msg)
340
341 log('VIP HA: VIP is set {}'.format(vip), level=DEBUG)
342 return True
343
344
298def canonical_url(configs, vip_setting='vip'):345def canonical_url(configs, vip_setting='vip'):
299 '''346 '''
300 Returns the correct HTTP URL to this host given the state of HTTPS347 Returns the correct HTTP URL to this host given the state of HTTPS
301348
=== modified file 'charmhelpers/contrib/network/__init__.py'
--- charmhelpers/contrib/network/__init__.py 2015-09-28 20:09:02 +0000
+++ charmhelpers/contrib/network/__init__.py 2017-03-04 02:21:16 +0000
@@ -1,15 +1,13 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4#4# you may not use this file except in compliance with the License.
5# charm-helpers is free software: you can redistribute it and/or modify5# You may obtain a copy of the License at
6# it under the terms of the GNU Lesser General Public License version 3 as6#
7# published by the Free Software Foundation.7# http://www.apache.org/licenses/LICENSE-2.0
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
=== modified file 'charmhelpers/contrib/network/ip.py'
--- charmhelpers/contrib/network/ip.py 2015-09-28 20:09:02 +0000
+++ charmhelpers/contrib/network/ip.py 2017-03-04 02:21:16 +0000
@@ -1,18 +1,16 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4#4# you may not use this file except in compliance with the License.
5# charm-helpers is free software: you can redistribute it and/or modify5# You may obtain a copy of the License at
6# it under the terms of the GNU Lesser General Public License version 3 as6#
7# published by the Free Software Foundation.7# http://www.apache.org/licenses/LICENSE-2.0
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17import glob15import glob
18import re16import re
@@ -33,14 +31,20 @@
33 import netifaces31 import netifaces
34except ImportError:32except ImportError:
35 apt_update(fatal=True)33 apt_update(fatal=True)
36 apt_install('python-netifaces', fatal=True)34 if six.PY2:
35 apt_install('python-netifaces', fatal=True)
36 else:
37 apt_install('python3-netifaces', fatal=True)
37 import netifaces38 import netifaces
3839
39try:40try:
40 import netaddr41 import netaddr
41except ImportError:42except ImportError:
42 apt_update(fatal=True)43 apt_update(fatal=True)
43 apt_install('python-netaddr', fatal=True)44 if six.PY2:
45 apt_install('python-netaddr', fatal=True)
46 else:
47 apt_install('python3-netaddr', fatal=True)
44 import netaddr48 import netaddr
4549
4650
@@ -53,7 +57,7 @@
5357
5458
55def no_ip_found_error_out(network):59def no_ip_found_error_out(network):
56 errmsg = ("No IP address found in network: %s" % network)60 errmsg = ("No IP address found in network(s): %s" % network)
57 raise ValueError(errmsg)61 raise ValueError(errmsg)
5862
5963
@@ -61,7 +65,7 @@
61 """Get an IPv4 or IPv6 address within the network from the host.65 """Get an IPv4 or IPv6 address within the network from the host.
6266
63 :param network (str): CIDR presentation format. For example,67 :param network (str): CIDR presentation format. For example,
64 '192.168.1.0/24'.68 '192.168.1.0/24'. Supports multiple networks as a space-delimited list.
65 :param fallback (str): If no address is found, return fallback.69 :param fallback (str): If no address is found, return fallback.
66 :param fatal (boolean): If no address is found, fallback is not70 :param fatal (boolean): If no address is found, fallback is not
67 set and fatal is True then exit(1).71 set and fatal is True then exit(1).
@@ -75,24 +79,26 @@
75 else:79 else:
76 return None80 return None
7781
78 _validate_cidr(network)82 networks = network.split() or [network]
79 network = netaddr.IPNetwork(network)83 for network in networks:
80 for iface in netifaces.interfaces():84 _validate_cidr(network)
81 addresses = netifaces.ifaddresses(iface)85 network = netaddr.IPNetwork(network)
82 if network.version == 4 and netifaces.AF_INET in addresses:86 for iface in netifaces.interfaces():
83 addr = addresses[netifaces.AF_INET][0]['addr']87 addresses = netifaces.ifaddresses(iface)
84 netmask = addresses[netifaces.AF_INET][0]['netmask']88 if network.version == 4 and netifaces.AF_INET in addresses:
85 cidr = netaddr.IPNetwork("%s/%s" % (addr, netmask))89 addr = addresses[netifaces.AF_INET][0]['addr']
86 if cidr in network:90 netmask = addresses[netifaces.AF_INET][0]['netmask']
87 return str(cidr.ip)91 cidr = netaddr.IPNetwork("%s/%s" % (addr, netmask))
92 if cidr in network:
93 return str(cidr.ip)
8894
89 if network.version == 6 and netifaces.AF_INET6 in addresses:95 if network.version == 6 and netifaces.AF_INET6 in addresses:
90 for addr in addresses[netifaces.AF_INET6]:96 for addr in addresses[netifaces.AF_INET6]:
91 if not addr['addr'].startswith('fe80'):97 if not addr['addr'].startswith('fe80'):
92 cidr = netaddr.IPNetwork("%s/%s" % (addr['addr'],98 cidr = netaddr.IPNetwork("%s/%s" % (addr['addr'],
93 addr['netmask']))99 addr['netmask']))
94 if cidr in network:100 if cidr in network:
95 return str(cidr.ip)101 return str(cidr.ip)
96102
97 if fallback is not None:103 if fallback is not None:
98 return fallback104 return fallback
@@ -189,6 +195,15 @@
189get_netmask_for_address = partial(_get_for_address, key='netmask')195get_netmask_for_address = partial(_get_for_address, key='netmask')
190196
191197
198def resolve_network_cidr(ip_address):
199 '''
200 Resolves the full address cidr of an ip_address based on
201 configured network interfaces
202 '''
203 netmask = get_netmask_for_address(ip_address)
204 return str(netaddr.IPNetwork("%s/%s" % (ip_address, netmask)).cidr)
205
206
192def format_ipv6_addr(address):207def format_ipv6_addr(address):
193 """If address is IPv6, wrap it in '[]' otherwise return None.208 """If address is IPv6, wrap it in '[]' otherwise return None.
194209
@@ -203,7 +218,16 @@
203218
204def get_iface_addr(iface='eth0', inet_type='AF_INET', inc_aliases=False,219def get_iface_addr(iface='eth0', inet_type='AF_INET', inc_aliases=False,
205 fatal=True, exc_list=None):220 fatal=True, exc_list=None):
206 """Return the assigned IP address for a given interface, if any."""221 """Return the assigned IP address for a given interface, if any.
222
223 :param iface: network interface on which address(es) are expected to
224 be found.
225 :param inet_type: inet address family
226 :param inc_aliases: include alias interfaces in search
227 :param fatal: if True, raise exception if address not found
228 :param exc_list: list of addresses to ignore
229 :return: list of ip addresses
230 """
207 # Extract nic if passed /dev/ethX231 # Extract nic if passed /dev/ethX
208 if '/' in iface:232 if '/' in iface:
209 iface = iface.split('/')[-1]233 iface = iface.split('/')[-1]
@@ -304,6 +328,14 @@
304 We currently only support scope global IPv6 addresses i.e. non-temporary328 We currently only support scope global IPv6 addresses i.e. non-temporary
305 addresses. If no global IPv6 address is found, return the first one found329 addresses. If no global IPv6 address is found, return the first one found
306 in the ipv6 address list.330 in the ipv6 address list.
331
332 :param iface: network interface on which ipv6 address(es) are expected to
333 be found.
334 :param inc_aliases: include alias interfaces in search
335 :param fatal: if True, raise exception if address not found
336 :param exc_list: list of addresses to ignore
337 :param dynamic_only: only recognise dynamic addresses
338 :return: list of ipv6 addresses
307 """339 """
308 addresses = get_iface_addr(iface=iface, inet_type='AF_INET6',340 addresses = get_iface_addr(iface=iface, inet_type='AF_INET6',
309 inc_aliases=inc_aliases, fatal=fatal,341 inc_aliases=inc_aliases, fatal=fatal,
@@ -325,7 +357,7 @@
325 cmd = ['ip', 'addr', 'show', iface]357 cmd = ['ip', 'addr', 'show', iface]
326 out = subprocess.check_output(cmd).decode('UTF-8')358 out = subprocess.check_output(cmd).decode('UTF-8')
327 if dynamic_only:359 if dynamic_only:
328 key = re.compile("inet6 (.+)/[0-9]+ scope global dynamic.*")360 key = re.compile("inet6 (.+)/[0-9]+ scope global.* dynamic.*")
329 else:361 else:
330 key = re.compile("inet6 (.+)/[0-9]+ scope global.*")362 key = re.compile("inet6 (.+)/[0-9]+ scope global.*")
331363
@@ -377,10 +409,10 @@
377 Returns True if address is a valid IP address.409 Returns True if address is a valid IP address.
378 """410 """
379 try:411 try:
380 # Test to see if already an IPv4 address412 # Test to see if already an IPv4/IPv6 address
381 socket.inet_aton(address)413 address = netaddr.IPAddress(address)
382 return True414 return True
383 except socket.error:415 except (netaddr.AddrFormatError, ValueError):
384 return False416 return False
385417
386418
@@ -388,7 +420,10 @@
388 try:420 try:
389 import dns.resolver421 import dns.resolver
390 except ImportError:422 except ImportError:
391 apt_install('python-dnspython')423 if six.PY2:
424 apt_install('python-dnspython', fatal=True)
425 else:
426 apt_install('python3-dnspython', fatal=True)
392 import dns.resolver427 import dns.resolver
393428
394 if isinstance(address, dns.name.Name):429 if isinstance(address, dns.name.Name):
@@ -398,7 +433,11 @@
398 else:433 else:
399 return None434 return None
400435
401 answers = dns.resolver.query(address, rtype)436 try:
437 answers = dns.resolver.query(address, rtype)
438 except dns.resolver.NXDOMAIN:
439 return None
440
402 if answers:441 if answers:
403 return str(answers[0])442 return str(answers[0])
404 return None443 return None
@@ -432,7 +471,10 @@
432 try:471 try:
433 import dns.reversename472 import dns.reversename
434 except ImportError:473 except ImportError:
435 apt_install("python-dnspython")474 if six.PY2:
475 apt_install("python-dnspython", fatal=True)
476 else:
477 apt_install("python3-dnspython", fatal=True)
436 import dns.reversename478 import dns.reversename
437479
438 rev = dns.reversename.from_address(address)480 rev = dns.reversename.from_address(address)
@@ -454,3 +496,18 @@
454 return result496 return result
455 else:497 else:
456 return result.split('.')[0]498 return result.split('.')[0]
499
500
501def port_has_listener(address, port):
502 """
503 Returns True if the address:port is open and being listened to,
504 else False.
505
506 @param address: an IP address or hostname
507 @param port: integer port
508
509 Note calls 'zc' via a subprocess shell
510 """
511 cmd = ['nc', '-z', address, str(port)]
512 result = subprocess.call(cmd)
513 return not(bool(result))
457514
=== modified file 'charmhelpers/contrib/openstack/__init__.py'
--- charmhelpers/contrib/openstack/__init__.py 2015-09-28 20:09:02 +0000
+++ charmhelpers/contrib/openstack/__init__.py 2017-03-04 02:21:16 +0000
@@ -1,15 +1,13 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4#4# you may not use this file except in compliance with the License.
5# charm-helpers is free software: you can redistribute it and/or modify5# You may obtain a copy of the License at
6# it under the terms of the GNU Lesser General Public License version 3 as6#
7# published by the Free Software Foundation.7# http://www.apache.org/licenses/LICENSE-2.0
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
=== modified file 'charmhelpers/contrib/openstack/alternatives.py'
--- charmhelpers/contrib/openstack/alternatives.py 2015-09-28 20:09:02 +0000
+++ charmhelpers/contrib/openstack/alternatives.py 2017-03-04 02:21:16 +0000
@@ -1,18 +1,16 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4#4# you may not use this file except in compliance with the License.
5# charm-helpers is free software: you can redistribute it and/or modify5# You may obtain a copy of the License at
6# it under the terms of the GNU Lesser General Public License version 3 as6#
7# published by the Free Software Foundation.7# http://www.apache.org/licenses/LICENSE-2.0
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17''' Helper for managing alternatives for file conflict resolution '''15''' Helper for managing alternatives for file conflict resolution '''
1816
1917
=== modified file 'charmhelpers/contrib/openstack/amulet/__init__.py'
--- charmhelpers/contrib/openstack/amulet/__init__.py 2015-09-28 20:09:02 +0000
+++ charmhelpers/contrib/openstack/amulet/__init__.py 2017-03-04 02:21:16 +0000
@@ -1,15 +1,13 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4#4# you may not use this file except in compliance with the License.
5# charm-helpers is free software: you can redistribute it and/or modify5# You may obtain a copy of the License at
6# it under the terms of the GNU Lesser General Public License version 3 as6#
7# published by the Free Software Foundation.7# http://www.apache.org/licenses/LICENSE-2.0
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
=== modified file 'charmhelpers/contrib/openstack/amulet/deployment.py'
--- charmhelpers/contrib/openstack/amulet/deployment.py 2015-09-28 20:09:02 +0000
+++ charmhelpers/contrib/openstack/amulet/deployment.py 2017-03-04 02:21:16 +0000
@@ -1,25 +1,29 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4#4# you may not use this file except in compliance with the License.
5# charm-helpers is free software: you can redistribute it and/or modify5# You may obtain a copy of the License at
6# it under the terms of the GNU Lesser General Public License version 3 as6#
7# published by the Free Software Foundation.7# http://www.apache.org/licenses/LICENSE-2.0
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
15import logging
16import re
17import sys
17import six18import six
18from collections import OrderedDict19from collections import OrderedDict
19from charmhelpers.contrib.amulet.deployment import (20from charmhelpers.contrib.amulet.deployment import (
20 AmuletDeployment21 AmuletDeployment
21)22)
2223
24DEBUG = logging.DEBUG
25ERROR = logging.ERROR
26
2327
24class OpenStackAmuletDeployment(AmuletDeployment):28class OpenStackAmuletDeployment(AmuletDeployment):
25 """OpenStack amulet deployment.29 """OpenStack amulet deployment.
@@ -28,15 +32,31 @@
28 that is specifically for use by OpenStack charms.32 that is specifically for use by OpenStack charms.
29 """33 """
3034
31 def __init__(self, series=None, openstack=None, source=None, stable=True):35 def __init__(self, series=None, openstack=None, source=None,
36 stable=True, log_level=DEBUG):
32 """Initialize the deployment environment."""37 """Initialize the deployment environment."""
33 super(OpenStackAmuletDeployment, self).__init__(series)38 super(OpenStackAmuletDeployment, self).__init__(series)
39 self.log = self.get_logger(level=log_level)
40 self.log.info('OpenStackAmuletDeployment: init')
34 self.openstack = openstack41 self.openstack = openstack
35 self.source = source42 self.source = source
36 self.stable = stable43 self.stable = stable
37 # Note(coreycb): this needs to be changed when new next branches come44
38 # out.45 def get_logger(self, name="deployment-logger", level=logging.DEBUG):
39 self.current_next = "trusty"46 """Get a logger object that will log to stdout."""
47 log = logging
48 logger = log.getLogger(name)
49 fmt = log.Formatter("%(asctime)s %(funcName)s "
50 "%(levelname)s: %(message)s")
51
52 handler = log.StreamHandler(stream=sys.stdout)
53 handler.setLevel(level)
54 handler.setFormatter(fmt)
55
56 logger.addHandler(handler)
57 logger.setLevel(level)
58
59 return logger
4060
41 def _determine_branch_locations(self, other_services):61 def _determine_branch_locations(self, other_services):
42 """Determine the branch locations for the other services.62 """Determine the branch locations for the other services.
@@ -45,43 +65,82 @@
45 stable or next (dev) branch, and based on this, use the corresonding65 stable or next (dev) branch, and based on this, use the corresonding
46 stable or next branches for the other_services."""66 stable or next branches for the other_services."""
4767
48 # Charms outside the lp:~openstack-charmers namespace68 self.log.info('OpenStackAmuletDeployment: determine branch locations')
49 base_charms = ['mysql', 'mongodb', 'nrpe']69
5070 # Charms outside the ~openstack-charmers
51 # Force these charms to current series even when using an older series.71 base_charms = {
52 # ie. Use trusty/nrpe even when series is precise, as the P charm72 'mysql': ['trusty'],
53 # does not possess the necessary external master config and hooks.73 'mongodb': ['trusty'],
54 force_series_current = ['nrpe']74 'nrpe': ['trusty', 'xenial'],
5575 }
56 if self.series in ['precise', 'trusty']:
57 base_series = self.series
58 else:
59 base_series = self.current_next
6076
61 for svc in other_services:77 for svc in other_services:
62 if svc['name'] in force_series_current:
63 base_series = self.current_next
64 # If a location has been explicitly set, use it78 # If a location has been explicitly set, use it
65 if svc.get('location'):79 if svc.get('location'):
66 continue80 continue
67 if self.stable:81 if svc['name'] in base_charms:
68 temp = 'lp:charms/{}/{}'82 # NOTE: not all charms have support for all series we
69 svc['location'] = temp.format(base_series,83 # want/need to test against, so fix to most recent
70 svc['name'])84 # that each base charm supports
85 target_series = self.series
86 if self.series not in base_charms[svc['name']]:
87 target_series = base_charms[svc['name']][-1]
88 svc['location'] = 'cs:{}/{}'.format(target_series,
89 svc['name'])
90 elif self.stable:
91 svc['location'] = 'cs:{}/{}'.format(self.series,
92 svc['name'])
71 else:93 else:
72 if svc['name'] in base_charms:94 svc['location'] = 'cs:~openstack-charmers-next/{}/{}'.format(
73 temp = 'lp:charms/{}/{}'95 self.series,
74 svc['location'] = temp.format(base_series,96 svc['name']
75 svc['name'])97 )
76 else:
77 temp = 'lp:~openstack-charmers/charms/{}/{}/next'
78 svc['location'] = temp.format(self.current_next,
79 svc['name'])
8098
81 return other_services99 return other_services
82100
83 def _add_services(self, this_service, other_services):101 def _add_services(self, this_service, other_services, use_source=None,
84 """Add services to the deployment and set openstack-origin/source."""102 no_origin=None):
103 """Add services to the deployment and optionally set
104 openstack-origin/source.
105
106 :param this_service dict: Service dictionary describing the service
107 whose amulet tests are being run
108 :param other_services dict: List of service dictionaries describing
109 the services needed to support the target
110 service
111 :param use_source list: List of services which use the 'source' config
112 option rather than 'openstack-origin'
113 :param no_origin list: List of services which do not support setting
114 the Cloud Archive.
115 Service Dict:
116 {
117 'name': str charm-name,
118 'units': int number of units,
119 'constraints': dict of juju constraints,
120 'location': str location of charm,
121 }
122 eg
123 this_service = {
124 'name': 'openvswitch-odl',
125 'constraints': {'mem': '8G'},
126 }
127 other_services = [
128 {
129 'name': 'nova-compute',
130 'units': 2,
131 'constraints': {'mem': '4G'},
132 'location': cs:~bob/xenial/nova-compute
133 },
134 {
135 'name': 'mysql',
136 'constraints': {'mem': '2G'},
137 },
138 {'neutron-api-odl'}]
139 use_source = ['mysql']
140 no_origin = ['neutron-api-odl']
141 """
142 self.log.info('OpenStackAmuletDeployment: adding services')
143
85 other_services = self._determine_branch_locations(other_services)144 other_services = self._determine_branch_locations(other_services)
86145
87 super(OpenStackAmuletDeployment, self)._add_services(this_service,146 super(OpenStackAmuletDeployment, self)._add_services(this_service,
@@ -90,12 +149,22 @@
90 services = other_services149 services = other_services
91 services.append(this_service)150 services.append(this_service)
92151
152 use_source = use_source or []
153 no_origin = no_origin or []
154
93 # Charms which should use the source config option155 # Charms which should use the source config option
94 use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph',156 use_source = list(set(
95 'ceph-osd', 'ceph-radosgw']157 use_source + ['mysql', 'mongodb', 'rabbitmq-server', 'ceph',
158 'ceph-osd', 'ceph-radosgw', 'ceph-mon',
159 'ceph-proxy', 'percona-cluster', 'lxd']))
96160
97 # Charms which can not use openstack-origin, ie. many subordinates161 # Charms which can not use openstack-origin, ie. many subordinates
98 no_origin = ['cinder-ceph', 'hacluster', 'neutron-openvswitch', 'nrpe']162 no_origin = list(set(
163 no_origin + ['cinder-ceph', 'hacluster', 'neutron-openvswitch',
164 'nrpe', 'openvswitch-odl', 'neutron-api-odl',
165 'odl-controller', 'cinder-backup', 'nexentaedge-data',
166 'nexentaedge-iscsi-gw', 'nexentaedge-swift-gw',
167 'cinder-nexentaedge', 'nexentaedge-mgmt']))
99168
100 if self.openstack:169 if self.openstack:
101 for svc in services:170 for svc in services:
@@ -111,9 +180,79 @@
111180
112 def _configure_services(self, configs):181 def _configure_services(self, configs):
113 """Configure all of the services."""182 """Configure all of the services."""
183 self.log.info('OpenStackAmuletDeployment: configure services')
114 for service, config in six.iteritems(configs):184 for service, config in six.iteritems(configs):
115 self.d.configure(service, config)185 self.d.configure(service, config)
116186
187 def _auto_wait_for_status(self, message=None, exclude_services=None,
188 include_only=None, timeout=1800):
189 """Wait for all units to have a specific extended status, except
190 for any defined as excluded. Unless specified via message, any
191 status containing any case of 'ready' will be considered a match.
192
193 Examples of message usage:
194
195 Wait for all unit status to CONTAIN any case of 'ready' or 'ok':
196 message = re.compile('.*ready.*|.*ok.*', re.IGNORECASE)
197
198 Wait for all units to reach this status (exact match):
199 message = re.compile('^Unit is ready and clustered$')
200
201 Wait for all units to reach any one of these (exact match):
202 message = re.compile('Unit is ready|OK|Ready')
203
204 Wait for at least one unit to reach this status (exact match):
205 message = {'ready'}
206
207 See Amulet's sentry.wait_for_messages() for message usage detail.
208 https://github.com/juju/amulet/blob/master/amulet/sentry.py
209
210 :param message: Expected status match
211 :param exclude_services: List of juju service names to ignore,
212 not to be used in conjuction with include_only.
213 :param include_only: List of juju service names to exclusively check,
214 not to be used in conjuction with exclude_services.
215 :param timeout: Maximum time in seconds to wait for status match
216 :returns: None. Raises if timeout is hit.
217 """
218 self.log.info('Waiting for extended status on units...')
219
220 all_services = self.d.services.keys()
221
222 if exclude_services and include_only:
223 raise ValueError('exclude_services can not be used '
224 'with include_only')
225
226 if message:
227 if isinstance(message, re._pattern_type):
228 match = message.pattern
229 else:
230 match = message
231
232 self.log.debug('Custom extended status wait match: '
233 '{}'.format(match))
234 else:
235 self.log.debug('Default extended status wait match: contains '
236 'READY (case-insensitive)')
237 message = re.compile('.*ready.*', re.IGNORECASE)
238
239 if exclude_services:
240 self.log.debug('Excluding services from extended status match: '
241 '{}'.format(exclude_services))
242 else:
243 exclude_services = []
244
245 if include_only:
246 services = include_only
247 else:
248 services = list(set(all_services) - set(exclude_services))
249
250 self.log.debug('Waiting up to {}s for extended status on services: '
251 '{}'.format(timeout, services))
252 service_messages = {service: message for service in services}
253 self.d.sentry.wait_for_messages(service_messages, timeout=timeout)
254 self.log.info('OK')
255
117 def _get_openstack_release(self):256 def _get_openstack_release(self):
118 """Get openstack release.257 """Get openstack release.
119258
@@ -121,25 +260,21 @@
121 release.260 release.
122 """261 """
123 # Must be ordered by OpenStack release (not by Ubuntu release):262 # Must be ordered by OpenStack release (not by Ubuntu release):
124 (self.precise_essex, self.precise_folsom, self.precise_grizzly,263 (self.trusty_icehouse, self.trusty_kilo, self.trusty_liberty,
125 self.precise_havana, self.precise_icehouse,264 self.trusty_mitaka, self.xenial_mitaka, self.xenial_newton,
126 self.trusty_icehouse, self.trusty_juno, self.utopic_juno,265 self.yakkety_newton, self.xenial_ocata, self.zesty_ocata) = range(9)
127 self.trusty_kilo, self.vivid_kilo, self.trusty_liberty,
128 self.wily_liberty) = range(12)
129266
130 releases = {267 releases = {
131 ('precise', None): self.precise_essex,
132 ('precise', 'cloud:precise-folsom'): self.precise_folsom,
133 ('precise', 'cloud:precise-grizzly'): self.precise_grizzly,
134 ('precise', 'cloud:precise-havana'): self.precise_havana,
135 ('precise', 'cloud:precise-icehouse'): self.precise_icehouse,
136 ('trusty', None): self.trusty_icehouse,268 ('trusty', None): self.trusty_icehouse,
137 ('trusty', 'cloud:trusty-juno'): self.trusty_juno,
138 ('trusty', 'cloud:trusty-kilo'): self.trusty_kilo,269 ('trusty', 'cloud:trusty-kilo'): self.trusty_kilo,
139 ('trusty', 'cloud:trusty-liberty'): self.trusty_liberty,270 ('trusty', 'cloud:trusty-liberty'): self.trusty_liberty,
140 ('utopic', None): self.utopic_juno,271 ('trusty', 'cloud:trusty-mitaka'): self.trusty_mitaka,
141 ('vivid', None): self.vivid_kilo,272 ('xenial', None): self.xenial_mitaka,
142 ('wily', None): self.wily_liberty}273 ('xenial', 'cloud:xenial-newton'): self.xenial_newton,
274 ('xenial', 'cloud:xenial-ocata'): self.xenial_ocata,
275 ('yakkety', None): self.yakkety_newton,
276 ('zesty', None): self.zesty_ocata,
277 }
143 return releases[(self.series, self.openstack)]278 return releases[(self.series, self.openstack)]
144279
145 def _get_openstack_release_string(self):280 def _get_openstack_release_string(self):
@@ -148,14 +283,10 @@
148 Return a string representing the openstack release.283 Return a string representing the openstack release.
149 """284 """
150 releases = OrderedDict([285 releases = OrderedDict([
151 ('precise', 'essex'),
152 ('quantal', 'folsom'),
153 ('raring', 'grizzly'),
154 ('saucy', 'havana'),
155 ('trusty', 'icehouse'),286 ('trusty', 'icehouse'),
156 ('utopic', 'juno'),287 ('xenial', 'mitaka'),
157 ('vivid', 'kilo'),288 ('yakkety', 'newton'),
158 ('wily', 'liberty'),289 ('zesty', 'ocata'),
159 ])290 ])
160 if self.openstack:291 if self.openstack:
161 os_origin = self.openstack.split(':')[1]292 os_origin = self.openstack.split(':')[1]
162293
=== modified file 'charmhelpers/contrib/openstack/amulet/utils.py'
--- charmhelpers/contrib/openstack/amulet/utils.py 2015-09-28 20:38:07 +0000
+++ charmhelpers/contrib/openstack/amulet/utils.py 2017-03-04 02:21:16 +0000
@@ -1,42 +1,51 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4#4# you may not use this file except in compliance with the License.
5# charm-helpers is free software: you can redistribute it and/or modify5# You may obtain a copy of the License at
6# it under the terms of the GNU Lesser General Public License version 3 as6#
7# published by the Free Software Foundation.7# http://www.apache.org/licenses/LICENSE-2.0
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17import amulet15import amulet
18import json16import json
19import logging17import logging
20import os18import os
19import re
21import six20import six
22import time21import time
23import urllib22import urllib
23import urlparse
2424
25import cinderclient.v1.client as cinder_client25import cinderclient.v1.client as cinder_client
26import glanceclient.v1.client as glance_client26import glanceclient.v1.client as glance_client
27import heatclient.v1.client as heat_client27import heatclient.v1.client as heat_client
28import keystoneclient.v2_0 as keystone_client28import keystoneclient.v2_0 as keystone_client
29import novaclient.v1_1.client as nova_client29from keystoneclient.auth.identity import v3 as keystone_id_v3
30from keystoneclient import session as keystone_session
31from keystoneclient.v3 import client as keystone_client_v3
32from novaclient import exceptions
33
34import novaclient.client as nova_client
35import novaclient
30import pika36import pika
31import swiftclient37import swiftclient
3238
33from charmhelpers.contrib.amulet.utils import (39from charmhelpers.contrib.amulet.utils import (
34 AmuletUtils40 AmuletUtils
35)41)
42from charmhelpers.core.decorators import retry_on_exception
3643
37DEBUG = logging.DEBUG44DEBUG = logging.DEBUG
38ERROR = logging.ERROR45ERROR = logging.ERROR
3946
47NOVA_CLIENT_VERSION = "2"
48
4049
41class OpenStackAmuletUtils(AmuletUtils):50class OpenStackAmuletUtils(AmuletUtils):
42 """OpenStack amulet utilities.51 """OpenStack amulet utilities.
@@ -78,6 +87,56 @@
78 if not found:87 if not found:
79 return 'endpoint not found'88 return 'endpoint not found'
8089
90 def validate_v3_endpoint_data(self, endpoints, admin_port, internal_port,
91 public_port, expected):
92 """Validate keystone v3 endpoint data.
93
94 Validate the v3 endpoint data which has changed from v2. The
95 ports are used to find the matching endpoint.
96
97 The new v3 endpoint data looks like:
98
99 [<Endpoint enabled=True,
100 id=0432655fc2f74d1e9fa17bdaa6f6e60b,
101 interface=admin,
102 links={u'self': u'<RESTful URL of this endpoint>'},
103 region=RegionOne,
104 region_id=RegionOne,
105 service_id=17f842a0dc084b928e476fafe67e4095,
106 url=http://10.5.6.5:9312>,
107 <Endpoint enabled=True,
108 id=6536cb6cb92f4f41bf22b079935c7707,
109 interface=admin,
110 links={u'self': u'<RESTful url of this endpoint>'},
111 region=RegionOne,
112 region_id=RegionOne,
113 service_id=72fc8736fb41435e8b3584205bb2cfa3,
114 url=http://10.5.6.6:35357/v3>,
115 ... ]
116 """
117 self.log.debug('Validating v3 endpoint data...')
118 self.log.debug('actual: {}'.format(repr(endpoints)))
119 found = []
120 for ep in endpoints:
121 self.log.debug('endpoint: {}'.format(repr(ep)))
122 if ((admin_port in ep.url and ep.interface == 'admin') or
123 (internal_port in ep.url and ep.interface == 'internal') or
124 (public_port in ep.url and ep.interface == 'public')):
125 found.append(ep.interface)
126 # note we ignore the links member.
127 actual = {'id': ep.id,
128 'region': ep.region,
129 'region_id': ep.region_id,
130 'interface': self.not_null,
131 'url': ep.url,
132 'service_id': ep.service_id, }
133 ret = self._validate_dict_data(expected, actual)
134 if ret:
135 return 'unexpected endpoint data - {}'.format(ret)
136
137 if len(found) != 3:
138 return 'Unexpected number of endpoints found'
139
81 def validate_svc_catalog_endpoint_data(self, expected, actual):140 def validate_svc_catalog_endpoint_data(self, expected, actual):
82 """Validate service catalog endpoint data.141 """Validate service catalog endpoint data.
83142
@@ -95,6 +154,72 @@
95 return "endpoint {} does not exist".format(k)154 return "endpoint {} does not exist".format(k)
96 return ret155 return ret
97156
157 def validate_v3_svc_catalog_endpoint_data(self, expected, actual):
158 """Validate the keystone v3 catalog endpoint data.
159
160 Validate a list of dictinaries that make up the keystone v3 service
161 catalogue.
162
163 It is in the form of:
164
165
166 {u'identity': [{u'id': u'48346b01c6804b298cdd7349aadb732e',
167 u'interface': u'admin',
168 u'region': u'RegionOne',
169 u'region_id': u'RegionOne',
170 u'url': u'http://10.5.5.224:35357/v3'},
171 {u'id': u'8414f7352a4b47a69fddd9dbd2aef5cf',
172 u'interface': u'public',
173 u'region': u'RegionOne',
174 u'region_id': u'RegionOne',
175 u'url': u'http://10.5.5.224:5000/v3'},
176 {u'id': u'd5ca31440cc24ee1bf625e2996fb6a5b',
177 u'interface': u'internal',
178 u'region': u'RegionOne',
179 u'region_id': u'RegionOne',
180 u'url': u'http://10.5.5.224:5000/v3'}],
181 u'key-manager': [{u'id': u'68ebc17df0b045fcb8a8a433ebea9e62',
182 u'interface': u'public',
183 u'region': u'RegionOne',
184 u'region_id': u'RegionOne',
185 u'url': u'http://10.5.5.223:9311'},
186 {u'id': u'9cdfe2a893c34afd8f504eb218cd2f9d',
187 u'interface': u'internal',
188 u'region': u'RegionOne',
189 u'region_id': u'RegionOne',
190 u'url': u'http://10.5.5.223:9311'},
191 {u'id': u'f629388955bc407f8b11d8b7ca168086',
192 u'interface': u'admin',
193 u'region': u'RegionOne',
194 u'region_id': u'RegionOne',
195 u'url': u'http://10.5.5.223:9312'}]}
196
197 Note, that an added complication is that the order of admin, public,
198 internal against 'interface' in each region.
199
200 Thus, the function sorts the expected and actual lists using the
201 interface key as a sort key, prior to the comparison.
202 """
203 self.log.debug('Validating v3 service catalog endpoint data...')
204 self.log.debug('actual: {}'.format(repr(actual)))
205 for k, v in six.iteritems(expected):
206 if k in actual:
207 l_expected = sorted(v, key=lambda x: x['interface'])
208 l_actual = sorted(actual[k], key=lambda x: x['interface'])
209 if len(l_actual) != len(l_expected):
210 return ("endpoint {} has differing number of interfaces "
211 " - expected({}), actual({})"
212 .format(k, len(l_expected), len(l_actual)))
213 for i_expected, i_actual in zip(l_expected, l_actual):
214 self.log.debug("checking interface {}"
215 .format(i_expected['interface']))
216 ret = self._validate_dict_data(i_expected, i_actual)
217 if ret:
218 return self.endpoint_error(k, ret)
219 else:
220 return "endpoint {} does not exist".format(k)
221 return ret
222
98 def validate_tenant_data(self, expected, actual):223 def validate_tenant_data(self, expected, actual):
99 """Validate tenant data.224 """Validate tenant data.
100225
@@ -138,7 +263,7 @@
138 return "role {} does not exist".format(e['name'])263 return "role {} does not exist".format(e['name'])
139 return ret264 return ret
140265
141 def validate_user_data(self, expected, actual):266 def validate_user_data(self, expected, actual, api_version=None):
142 """Validate user data.267 """Validate user data.
143268
144 Validate a list of actual user data vs a list of expected user269 Validate a list of actual user data vs a list of expected user
@@ -149,10 +274,15 @@
149 for e in expected:274 for e in expected:
150 found = False275 found = False
151 for act in actual:276 for act in actual:
152 a = {'enabled': act.enabled, 'name': act.name,277 if e['name'] == act.name:
153 'email': act.email, 'tenantId': act.tenantId,278 a = {'enabled': act.enabled, 'name': act.name,
154 'id': act.id}279 'email': act.email, 'id': act.id}
155 if e['name'] == a['name']:280 if api_version == 3:
281 a['default_project_id'] = getattr(act,
282 'default_project_id',
283 'none')
284 else:
285 a['tenantId'] = act.tenantId
156 found = True286 found = True
157 ret = self._validate_dict_data(e, a)287 ret = self._validate_dict_data(e, a)
158 if ret:288 if ret:
@@ -176,34 +306,115 @@
176 self.log.debug('Checking if tenant exists ({})...'.format(tenant))306 self.log.debug('Checking if tenant exists ({})...'.format(tenant))
177 return tenant in [t.name for t in keystone.tenants.list()]307 return tenant in [t.name for t in keystone.tenants.list()]
178308
309 @retry_on_exception(5, base_delay=10)
310 def keystone_wait_for_propagation(self, sentry_relation_pairs,
311 api_version):
312 """Iterate over list of sentry and relation tuples and verify that
313 api_version has the expected value.
314
315 :param sentry_relation_pairs: list of sentry, relation name tuples used
316 for monitoring propagation of relation
317 data
318 :param api_version: api_version to expect in relation data
319 :returns: None if successful. Raise on error.
320 """
321 for (sentry, relation_name) in sentry_relation_pairs:
322 rel = sentry.relation('identity-service',
323 relation_name)
324 self.log.debug('keystone relation data: {}'.format(rel))
325 if rel['api_version'] != str(api_version):
326 raise Exception("api_version not propagated through relation"
327 " data yet ('{}' != '{}')."
328 "".format(rel['api_version'], api_version))
329
330 def keystone_configure_api_version(self, sentry_relation_pairs, deployment,
331 api_version):
332 """Configure preferred-api-version of keystone in deployment and
333 monitor provided list of relation objects for propagation
334 before returning to caller.
335
336 :param sentry_relation_pairs: list of sentry, relation tuples used for
337 monitoring propagation of relation data
338 :param deployment: deployment to configure
339 :param api_version: value preferred-api-version will be set to
340 :returns: None if successful. Raise on error.
341 """
342 self.log.debug("Setting keystone preferred-api-version: '{}'"
343 "".format(api_version))
344
345 config = {'preferred-api-version': api_version}
346 deployment.d.configure('keystone', config)
347 self.keystone_wait_for_propagation(sentry_relation_pairs, api_version)
348
179 def authenticate_cinder_admin(self, keystone_sentry, username,349 def authenticate_cinder_admin(self, keystone_sentry, username,
180 password, tenant):350 password, tenant):
181 """Authenticates admin user with cinder."""351 """Authenticates admin user with cinder."""
182 # NOTE(beisner): cinder python client doesn't accept tokens.352 # NOTE(beisner): cinder python client doesn't accept tokens.
183 service_ip = \353 keystone_ip = keystone_sentry.info['public-address']
184 keystone_sentry.relation('shared-db',354 ept = "http://{}:5000/v2.0".format(keystone_ip.strip().decode('utf-8'))
185 'mysql:shared-db')['private-address']
186 ept = "http://{}:5000/v2.0".format(service_ip.strip().decode('utf-8'))
187 return cinder_client.Client(username, password, tenant, ept)355 return cinder_client.Client(username, password, tenant, ept)
188356
357 def authenticate_keystone(self, keystone_ip, username, password,
358 api_version=False, admin_port=False,
359 user_domain_name=None, domain_name=None,
360 project_domain_name=None, project_name=None):
361 """Authenticate with Keystone"""
362 self.log.debug('Authenticating with keystone...')
363 port = 5000
364 if admin_port:
365 port = 35357
366 base_ep = "http://{}:{}".format(keystone_ip.strip().decode('utf-8'),
367 port)
368 if not api_version or api_version == 2:
369 ep = base_ep + "/v2.0"
370 return keystone_client.Client(username=username, password=password,
371 tenant_name=project_name,
372 auth_url=ep)
373 else:
374 ep = base_ep + "/v3"
375 auth = keystone_id_v3.Password(
376 user_domain_name=user_domain_name,
377 username=username,
378 password=password,
379 domain_name=domain_name,
380 project_domain_name=project_domain_name,
381 project_name=project_name,
382 auth_url=ep
383 )
384 return keystone_client_v3.Client(
385 session=keystone_session.Session(auth=auth)
386 )
387
189 def authenticate_keystone_admin(self, keystone_sentry, user, password,388 def authenticate_keystone_admin(self, keystone_sentry, user, password,
190 tenant):389 tenant=None, api_version=None,
390 keystone_ip=None):
191 """Authenticates admin user with the keystone admin endpoint."""391 """Authenticates admin user with the keystone admin endpoint."""
192 self.log.debug('Authenticating keystone admin...')392 self.log.debug('Authenticating keystone admin...')
193 unit = keystone_sentry393 if not keystone_ip:
194 service_ip = unit.relation('shared-db',394 keystone_ip = keystone_sentry.info['public-address']
195 'mysql:shared-db')['private-address']395
196 ep = "http://{}:35357/v2.0".format(service_ip.strip().decode('utf-8'))396 user_domain_name = None
197 return keystone_client.Client(username=user, password=password,397 domain_name = None
198 tenant_name=tenant, auth_url=ep)398 if api_version == 3:
399 user_domain_name = 'admin_domain'
400 domain_name = user_domain_name
401
402 return self.authenticate_keystone(keystone_ip, user, password,
403 project_name=tenant,
404 api_version=api_version,
405 user_domain_name=user_domain_name,
406 domain_name=domain_name,
407 admin_port=True)
199408
200 def authenticate_keystone_user(self, keystone, user, password, tenant):409 def authenticate_keystone_user(self, keystone, user, password, tenant):
201 """Authenticates a regular user with the keystone public endpoint."""410 """Authenticates a regular user with the keystone public endpoint."""
202 self.log.debug('Authenticating keystone user ({})...'.format(user))411 self.log.debug('Authenticating keystone user ({})...'.format(user))
203 ep = keystone.service_catalog.url_for(service_type='identity',412 ep = keystone.service_catalog.url_for(service_type='identity',
204 endpoint_type='publicURL')413 endpoint_type='publicURL')
205 return keystone_client.Client(username=user, password=password,414 keystone_ip = urlparse.urlparse(ep).hostname
206 tenant_name=tenant, auth_url=ep)415
416 return self.authenticate_keystone(keystone_ip, user, password,
417 project_name=tenant)
207418
208 def authenticate_glance_admin(self, keystone):419 def authenticate_glance_admin(self, keystone):
209 """Authenticates admin user with glance."""420 """Authenticates admin user with glance."""
@@ -224,8 +435,14 @@
224 self.log.debug('Authenticating nova user ({})...'.format(user))435 self.log.debug('Authenticating nova user ({})...'.format(user))
225 ep = keystone.service_catalog.url_for(service_type='identity',436 ep = keystone.service_catalog.url_for(service_type='identity',
226 endpoint_type='publicURL')437 endpoint_type='publicURL')
227 return nova_client.Client(username=user, api_key=password,438 if novaclient.__version__[0] >= "7":
228 project_id=tenant, auth_url=ep)439 return nova_client.Client(NOVA_CLIENT_VERSION,
440 username=user, password=password,
441 project_name=tenant, auth_url=ep)
442 else:
443 return nova_client.Client(NOVA_CLIENT_VERSION,
444 username=user, api_key=password,
445 project_id=tenant, auth_url=ep)
229446
230 def authenticate_swift_user(self, keystone, user, password, tenant):447 def authenticate_swift_user(self, keystone, user, password, tenant):
231 """Authenticates a regular user with swift api."""448 """Authenticates a regular user with swift api."""
@@ -238,6 +455,16 @@
238 tenant_name=tenant,455 tenant_name=tenant,
239 auth_version='2.0')456 auth_version='2.0')
240457
458 def create_flavor(self, nova, name, ram, vcpus, disk, flavorid="auto",
459 ephemeral=0, swap=0, rxtx_factor=1.0, is_public=True):
460 """Create the specified flavor."""
461 try:
462 nova.flavors.find(name=name)
463 except (exceptions.NotFound, exceptions.NoUniqueMatch):
464 self.log.debug('Creating flavor ({})'.format(name))
465 nova.flavors.create(name, ram, vcpus, disk, flavorid,
466 ephemeral, swap, rxtx_factor, is_public)
467
241 def create_cirros_image(self, glance, image_name):468 def create_cirros_image(self, glance, image_name):
242 """Download the latest cirros image and upload it to glance,469 """Download the latest cirros image and upload it to glance,
243 validate and return a resource pointer.470 validate and return a resource pointer.
@@ -604,7 +831,22 @@
604 '{}'.format(sample_type, samples))831 '{}'.format(sample_type, samples))
605 return None832 return None
606833
607# rabbitmq/amqp specific helpers:834 # rabbitmq/amqp specific helpers:
835
836 def rmq_wait_for_cluster(self, deployment, init_sleep=15, timeout=1200):
837 """Wait for rmq units extended status to show cluster readiness,
838 after an optional initial sleep period. Initial sleep is likely
839 necessary to be effective following a config change, as status
840 message may not instantly update to non-ready."""
841
842 if init_sleep:
843 time.sleep(init_sleep)
844
845 message = re.compile('^Unit is ready and clustered$')
846 deployment._auto_wait_for_status(message=message,
847 timeout=timeout,
848 include_only=['rabbitmq-server'])
849
608 def add_rmq_test_user(self, sentry_units,850 def add_rmq_test_user(self, sentry_units,
609 username="testuser1", password="changeme"):851 username="testuser1", password="changeme"):
610 """Add a test user via the first rmq juju unit, check connection as852 """Add a test user via the first rmq juju unit, check connection as
@@ -805,7 +1047,10 @@
805 if port:1047 if port:
806 config['ssl_port'] = port1048 config['ssl_port'] = port
8071049
808 deployment.configure('rabbitmq-server', config)1050 deployment.d.configure('rabbitmq-server', config)
1051
1052 # Wait for unit status
1053 self.rmq_wait_for_cluster(deployment)
8091054
810 # Confirm1055 # Confirm
811 tries = 01056 tries = 0
@@ -832,7 +1077,10 @@
8321077
833 # Disable RMQ SSL1078 # Disable RMQ SSL
834 config = {'ssl': 'off'}1079 config = {'ssl': 'off'}
835 deployment.configure('rabbitmq-server', config)1080 deployment.d.configure('rabbitmq-server', config)
1081
1082 # Wait for unit status
1083 self.rmq_wait_for_cluster(deployment)
8361084
837 # Confirm1085 # Confirm
838 tries = 01086 tries = 0
@@ -881,7 +1129,8 @@
881 retry_delay=5,1129 retry_delay=5,
882 socket_timeout=1)1130 socket_timeout=1)
883 connection = pika.BlockingConnection(parameters)1131 connection = pika.BlockingConnection(parameters)
884 assert connection.server_properties['product'] == 'RabbitMQ'1132 assert connection.is_open is True
1133 assert connection.is_closing is False
885 self.log.debug('Connect OK')1134 self.log.debug('Connect OK')
886 return connection1135 return connection
887 except Exception as e:1136 except Exception as e:
@@ -961,3 +1210,70 @@
961 else:1210 else:
962 msg = 'No message retrieved.'1211 msg = 'No message retrieved.'
963 amulet.raise_status(amulet.FAIL, msg)1212 amulet.raise_status(amulet.FAIL, msg)
1213
1214 def validate_memcache(self, sentry_unit, conf, os_release,
1215 earliest_release=5, section='keystone_authtoken',
1216 check_kvs=None):
1217 """Check Memcache is running and is configured to be used
1218
1219 Example call from Amulet test:
1220
1221 def test_110_memcache(self):
1222 u.validate_memcache(self.neutron_api_sentry,
1223 '/etc/neutron/neutron.conf',
1224 self._get_openstack_release())
1225
1226 :param sentry_unit: sentry unit
1227 :param conf: OpenStack config file to check memcache settings
1228 :param os_release: Current OpenStack release int code
1229 :param earliest_release: Earliest Openstack release to check int code
1230 :param section: OpenStack config file section to check
1231 :param check_kvs: Dict of settings to check in config file
1232 :returns: None
1233 """
1234 if os_release < earliest_release:
1235 self.log.debug('Skipping memcache checks for deployment. {} <'
1236 'mitaka'.format(os_release))
1237 return
1238 _kvs = check_kvs or {'memcached_servers': 'inet6:[::1]:11211'}
1239 self.log.debug('Checking memcached is running')
1240 ret = self.validate_services_by_name({sentry_unit: ['memcached']})
1241 if ret:
1242 amulet.raise_status(amulet.FAIL, msg='Memcache running check'
1243 'failed {}'.format(ret))
1244 else:
1245 self.log.debug('OK')
1246 self.log.debug('Checking memcache url is configured in {}'.format(
1247 conf))
1248 if self.validate_config_data(sentry_unit, conf, section, _kvs):
1249 message = "Memcache config error in: {}".format(conf)
1250 amulet.raise_status(amulet.FAIL, msg=message)
1251 else:
1252 self.log.debug('OK')
1253 self.log.debug('Checking memcache configuration in '
1254 '/etc/memcached.conf')
1255 contents = self.file_contents_safe(sentry_unit, '/etc/memcached.conf',
1256 fatal=True)
1257 ubuntu_release, _ = self.run_cmd_unit(sentry_unit, 'lsb_release -cs')
1258 if ubuntu_release <= 'trusty':
1259 memcache_listen_addr = 'ip6-localhost'
1260 else:
1261 memcache_listen_addr = '::1'
1262 expected = {
1263 '-p': '11211',
1264 '-l': memcache_listen_addr}
1265 found = []
1266 for key, value in expected.items():
1267 for line in contents.split('\n'):
1268 if line.startswith(key):
1269 self.log.debug('Checking {} is set to {}'.format(
1270 key,
1271 value))
1272 assert value == line.split()[-1]
1273 self.log.debug(line.split()[-1])
1274 found.append(key)
1275 if sorted(found) == sorted(expected.keys()):
1276 self.log.debug('OK')
1277 else:
1278 message = "Memcache config error in: /etc/memcached.conf"
1279 amulet.raise_status(amulet.FAIL, msg=message)
9641280
=== modified file 'charmhelpers/contrib/openstack/context.py'
--- charmhelpers/contrib/openstack/context.py 2015-09-28 20:09:02 +0000
+++ charmhelpers/contrib/openstack/context.py 2017-03-04 02:21:16 +0000
@@ -1,29 +1,27 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4#4# you may not use this file except in compliance with the License.
5# charm-helpers is free software: you can redistribute it and/or modify5# You may obtain a copy of the License at
6# it under the terms of the GNU Lesser General Public License version 3 as6#
7# published by the Free Software Foundation.7# http://www.apache.org/licenses/LICENSE-2.0
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17import glob15import glob
18import json16import json
17import math
19import os18import os
20import re19import re
21import time20import time
22from base64 import b64decode21from base64 import b64decode
23from subprocess import check_call22from subprocess import check_call, CalledProcessError
2423
25import six24import six
26import yaml
2725
28from charmhelpers.fetch import (26from charmhelpers.fetch import (
29 apt_install,27 apt_install,
@@ -45,10 +43,12 @@
45 INFO,43 INFO,
46 WARNING,44 WARNING,
47 ERROR,45 ERROR,
46 status_set,
48)47)
4948
50from charmhelpers.core.sysctl import create as sysctl_create49from charmhelpers.core.sysctl import create as sysctl_create
51from charmhelpers.core.strutils import bool_from_string50from charmhelpers.core.strutils import bool_from_string
51from charmhelpers.contrib.openstack.exceptions import OSContextError
5252
53from charmhelpers.core.host import (53from charmhelpers.core.host import (
54 get_bond_master,54 get_bond_master,
@@ -57,6 +57,8 @@
57 get_nic_hwaddr,57 get_nic_hwaddr,
58 mkdir,58 mkdir,
59 write_file,59 write_file,
60 pwgen,
61 lsb_release,
60)62)
61from charmhelpers.contrib.hahelpers.cluster import (63from charmhelpers.contrib.hahelpers.cluster import (
62 determine_apache_port,64 determine_apache_port,
@@ -86,15 +88,28 @@
86 is_address_in_network,88 is_address_in_network,
87 is_bridge_member,89 is_bridge_member,
88)90)
89from charmhelpers.contrib.openstack.utils import get_host_ip91from charmhelpers.contrib.openstack.utils import (
92 config_flags_parser,
93 get_host_ip,
94 git_determine_usr_bin,
95 git_determine_python_path,
96 enable_memcache,
97)
98from charmhelpers.core.unitdata import kv
99
100try:
101 import psutil
102except ImportError:
103 if six.PY2:
104 apt_install('python-psutil', fatal=True)
105 else:
106 apt_install('python3-psutil', fatal=True)
107 import psutil
108
90CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'109CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
91ADDRESS_TYPES = ['admin', 'internal', 'public']110ADDRESS_TYPES = ['admin', 'internal', 'public']
92111
93112
94class OSContextError(Exception):
95 pass
96
97
98def ensure_packages(packages):113def ensure_packages(packages):
99 """Install but do not upgrade required plugin packages."""114 """Install but do not upgrade required plugin packages."""
100 required = filter_installed_packages(packages)115 required = filter_installed_packages(packages)
@@ -115,83 +130,6 @@
115 return True130 return True
116131
117132
118def config_flags_parser(config_flags):
119 """Parses config flags string into dict.
120
121 This parsing method supports a few different formats for the config
122 flag values to be parsed:
123
124 1. A string in the simple format of key=value pairs, with the possibility
125 of specifying multiple key value pairs within the same string. For
126 example, a string in the format of 'key1=value1, key2=value2' will
127 return a dict of:
128
129 {'key1': 'value1',
130 'key2': 'value2'}.
131
132 2. A string in the above format, but supporting a comma-delimited list
133 of values for the same key. For example, a string in the format of
134 'key1=value1, key2=value3,value4,value5' will return a dict of:
135
136 {'key1', 'value1',
137 'key2', 'value2,value3,value4'}
138
139 3. A string containing a colon character (:) prior to an equal
140 character (=) will be treated as yaml and parsed as such. This can be
141 used to specify more complex key value pairs. For example,
142 a string in the format of 'key1: subkey1=value1, subkey2=value2' will
143 return a dict of:
144
145 {'key1', 'subkey1=value1, subkey2=value2'}
146
147 The provided config_flags string may be a list of comma-separated values
148 which themselves may be comma-separated list of values.
149 """
150 # If we find a colon before an equals sign then treat it as yaml.
151 # Note: limit it to finding the colon first since this indicates assignment
152 # for inline yaml.
153 colon = config_flags.find(':')
154 equals = config_flags.find('=')
155 if colon > 0:
156 if colon < equals or equals < 0:
157 return yaml.safe_load(config_flags)
158
159 if config_flags.find('==') >= 0:
160 log("config_flags is not in expected format (key=value)", level=ERROR)
161 raise OSContextError
162
163 # strip the following from each value.
164 post_strippers = ' ,'
165 # we strip any leading/trailing '=' or ' ' from the string then
166 # split on '='.
167 split = config_flags.strip(' =').split('=')
168 limit = len(split)
169 flags = {}
170 for i in range(0, limit - 1):
171 current = split[i]
172 next = split[i + 1]
173 vindex = next.rfind(',')
174 if (i == limit - 2) or (vindex < 0):
175 value = next
176 else:
177 value = next[:vindex]
178
179 if i == 0:
180 key = current
181 else:
182 # if this not the first entry, expect an embedded key.
183 index = current.rfind(',')
184 if index < 0:
185 log("Invalid config value(s) at index %s" % (i), level=ERROR)
186 raise OSContextError
187 key = current[index + 1:]
188
189 # Add to collection.
190 flags[key.strip(post_strippers)] = value.rstrip(post_strippers)
191
192 return flags
193
194
195class OSContextGenerator(object):133class OSContextGenerator(object):
196 """Base class for all context generators."""134 """Base class for all context generators."""
197 interfaces = []135 interfaces = []
@@ -401,6 +339,7 @@
401 auth_host = format_ipv6_addr(auth_host) or auth_host339 auth_host = format_ipv6_addr(auth_host) or auth_host
402 svc_protocol = rdata.get('service_protocol') or 'http'340 svc_protocol = rdata.get('service_protocol') or 'http'
403 auth_protocol = rdata.get('auth_protocol') or 'http'341 auth_protocol = rdata.get('auth_protocol') or 'http'
342 api_version = rdata.get('api_version') or '2.0'
404 ctxt.update({'service_port': rdata.get('service_port'),343 ctxt.update({'service_port': rdata.get('service_port'),
405 'service_host': serv_host,344 'service_host': serv_host,
406 'auth_host': auth_host,345 'auth_host': auth_host,
@@ -409,7 +348,12 @@
409 'admin_user': rdata.get('service_username'),348 'admin_user': rdata.get('service_username'),
410 'admin_password': rdata.get('service_password'),349 'admin_password': rdata.get('service_password'),
411 'service_protocol': svc_protocol,350 'service_protocol': svc_protocol,
412 'auth_protocol': auth_protocol})351 'auth_protocol': auth_protocol,
352 'api_version': api_version})
353
354 if float(api_version) > 2:
355 ctxt.update({'admin_domain_name':
356 rdata.get('service_domain')})
413357
414 if self.context_complete(ctxt):358 if self.context_complete(ctxt):
415 # NOTE(jamespage) this is required for >= icehouse359 # NOTE(jamespage) this is required for >= icehouse
@@ -451,16 +395,20 @@
451 for rid in relation_ids(self.rel_name):395 for rid in relation_ids(self.rel_name):
452 ha_vip_only = False396 ha_vip_only = False
453 self.related = True397 self.related = True
398 transport_hosts = None
399 rabbitmq_port = '5672'
454 for unit in related_units(rid):400 for unit in related_units(rid):
455 if relation_get('clustered', rid=rid, unit=unit):401 if relation_get('clustered', rid=rid, unit=unit):
456 ctxt['clustered'] = True402 ctxt['clustered'] = True
457 vip = relation_get('vip', rid=rid, unit=unit)403 vip = relation_get('vip', rid=rid, unit=unit)
458 vip = format_ipv6_addr(vip) or vip404 vip = format_ipv6_addr(vip) or vip
459 ctxt['rabbitmq_host'] = vip405 ctxt['rabbitmq_host'] = vip
406 transport_hosts = [vip]
460 else:407 else:
461 host = relation_get('private-address', rid=rid, unit=unit)408 host = relation_get('private-address', rid=rid, unit=unit)
462 host = format_ipv6_addr(host) or host409 host = format_ipv6_addr(host) or host
463 ctxt['rabbitmq_host'] = host410 ctxt['rabbitmq_host'] = host
411 transport_hosts = [host]
464412
465 ctxt.update({413 ctxt.update({
466 'rabbitmq_user': username,414 'rabbitmq_user': username,
@@ -472,6 +420,7 @@
472 ssl_port = relation_get('ssl_port', rid=rid, unit=unit)420 ssl_port = relation_get('ssl_port', rid=rid, unit=unit)
473 if ssl_port:421 if ssl_port:
474 ctxt['rabbit_ssl_port'] = ssl_port422 ctxt['rabbit_ssl_port'] = ssl_port
423 rabbitmq_port = ssl_port
475424
476 ssl_ca = relation_get('ssl_ca', rid=rid, unit=unit)425 ssl_ca = relation_get('ssl_ca', rid=rid, unit=unit)
477 if ssl_ca:426 if ssl_ca:
@@ -509,6 +458,20 @@
509 rabbitmq_hosts.append(host)458 rabbitmq_hosts.append(host)
510459
511 ctxt['rabbitmq_hosts'] = ','.join(sorted(rabbitmq_hosts))460 ctxt['rabbitmq_hosts'] = ','.join(sorted(rabbitmq_hosts))
461 transport_hosts = rabbitmq_hosts
462
463 if transport_hosts:
464 transport_url_hosts = ''
465 for host in transport_hosts:
466 if transport_url_hosts:
467 format_string = ",{}:{}@{}:{}"
468 else:
469 format_string = "{}:{}@{}:{}"
470 transport_url_hosts += format_string.format(
471 ctxt['rabbitmq_user'], ctxt['rabbitmq_password'],
472 host, rabbitmq_port)
473 ctxt['transport_url'] = "rabbit://{}/{}".format(
474 transport_url_hosts, vhost)
512475
513 oslo_messaging_flags = conf.get('oslo-messaging-flags', None)476 oslo_messaging_flags = conf.get('oslo-messaging-flags', None)
514 if oslo_messaging_flags:477 if oslo_messaging_flags:
@@ -540,13 +503,16 @@
540 ctxt['auth'] = relation_get('auth', rid=rid, unit=unit)503 ctxt['auth'] = relation_get('auth', rid=rid, unit=unit)
541 if not ctxt.get('key'):504 if not ctxt.get('key'):
542 ctxt['key'] = relation_get('key', rid=rid, unit=unit)505 ctxt['key'] = relation_get('key', rid=rid, unit=unit)
543 ceph_pub_addr = relation_get('ceph-public-address', rid=rid,506
507 ceph_addrs = relation_get('ceph-public-address', rid=rid,
508 unit=unit)
509 if ceph_addrs:
510 for addr in ceph_addrs.split(' '):
511 mon_hosts.append(format_ipv6_addr(addr) or addr)
512 else:
513 priv_addr = relation_get('private-address', rid=rid,
544 unit=unit)514 unit=unit)
545 unit_priv_addr = relation_get('private-address', rid=rid,515 mon_hosts.append(format_ipv6_addr(priv_addr) or priv_addr)
546 unit=unit)
547 ceph_addr = ceph_pub_addr or unit_priv_addr
548 ceph_addr = format_ipv6_addr(ceph_addr) or ceph_addr
549 mon_hosts.append(ceph_addr)
550516
551 ctxt['mon_hosts'] = ' '.join(sorted(mon_hosts))517 ctxt['mon_hosts'] = ' '.join(sorted(mon_hosts))
552518
@@ -626,15 +592,28 @@
626 if config('haproxy-client-timeout'):592 if config('haproxy-client-timeout'):
627 ctxt['haproxy_client_timeout'] = config('haproxy-client-timeout')593 ctxt['haproxy_client_timeout'] = config('haproxy-client-timeout')
628594
595 if config('haproxy-queue-timeout'):
596 ctxt['haproxy_queue_timeout'] = config('haproxy-queue-timeout')
597
598 if config('haproxy-connect-timeout'):
599 ctxt['haproxy_connect_timeout'] = config('haproxy-connect-timeout')
600
629 if config('prefer-ipv6'):601 if config('prefer-ipv6'):
630 ctxt['ipv6'] = True602 ctxt['ipv6'] = True
631 ctxt['local_host'] = 'ip6-localhost'603 ctxt['local_host'] = 'ip6-localhost'
632 ctxt['haproxy_host'] = '::'604 ctxt['haproxy_host'] = '::'
633 ctxt['stat_port'] = ':::8888'
634 else:605 else:
635 ctxt['local_host'] = '127.0.0.1'606 ctxt['local_host'] = '127.0.0.1'
636 ctxt['haproxy_host'] = '0.0.0.0'607 ctxt['haproxy_host'] = '0.0.0.0'
637 ctxt['stat_port'] = ':8888'608
609 ctxt['stat_port'] = '8888'
610
611 db = kv()
612 ctxt['stat_password'] = db.get('stat-password')
613 if not ctxt['stat_password']:
614 ctxt['stat_password'] = db.set('stat-password',
615 pwgen(32))
616 db.flush()
638617
639 for frontend in cluster_hosts:618 for frontend in cluster_hosts:
640 if (len(cluster_hosts[frontend]['backends']) > 1 or619 if (len(cluster_hosts[frontend]['backends']) > 1 or
@@ -698,7 +677,7 @@
698 service_namespace = None677 service_namespace = None
699678
700 def enable_modules(self):679 def enable_modules(self):
701 cmd = ['a2enmod', 'ssl', 'proxy', 'proxy_http']680 cmd = ['a2enmod', 'ssl', 'proxy', 'proxy_http', 'headers']
702 check_call(cmd)681 check_call(cmd)
703682
704 def configure_cert(self, cn=None):683 def configure_cert(self, cn=None):
@@ -952,6 +931,19 @@
952 'config': config}931 'config': config}
953 return ovs_ctxt932 return ovs_ctxt
954933
934 def midonet_ctxt(self):
935 driver = neutron_plugin_attribute(self.plugin, 'driver',
936 self.network_manager)
937 midonet_config = neutron_plugin_attribute(self.plugin, 'config',
938 self.network_manager)
939 mido_ctxt = {'core_plugin': driver,
940 'neutron_plugin': 'midonet',
941 'neutron_security_groups': self.neutron_security_groups,
942 'local_ip': unit_private_ip(),
943 'config': midonet_config}
944
945 return mido_ctxt
946
955 def __call__(self):947 def __call__(self):
956 if self.network_manager not in ['quantum', 'neutron']:948 if self.network_manager not in ['quantum', 'neutron']:
957 return {}949 return {}
@@ -973,6 +965,8 @@
973 ctxt.update(self.nuage_ctxt())965 ctxt.update(self.nuage_ctxt())
974 elif self.plugin == 'plumgrid':966 elif self.plugin == 'plumgrid':
975 ctxt.update(self.pg_ctxt())967 ctxt.update(self.pg_ctxt())
968 elif self.plugin == 'midonet':
969 ctxt.update(self.midonet_ctxt())
976970
977 alchemy_flags = config('neutron-alchemy-flags')971 alchemy_flags = config('neutron-alchemy-flags')
978 if alchemy_flags:972 if alchemy_flags:
@@ -1073,6 +1067,20 @@
1073 config_flags_parser(config_flags)}1067 config_flags_parser(config_flags)}
10741068
10751069
1070class LibvirtConfigFlagsContext(OSContextGenerator):
1071 """
1072 This context provides support for extending
1073 the libvirt section through user-defined flags.
1074 """
1075 def __call__(self):
1076 ctxt = {}
1077 libvirt_flags = config('libvirt-flags')
1078 if libvirt_flags:
1079 ctxt['libvirt_flags'] = config_flags_parser(
1080 libvirt_flags)
1081 return ctxt
1082
1083
1076class SubordinateConfigContext(OSContextGenerator):1084class SubordinateConfigContext(OSContextGenerator):
10771085
1078 """1086 """
@@ -1105,7 +1113,7 @@
11051113
1106 ctxt = {1114 ctxt = {
1107 ... other context ...1115 ... other context ...
1108 'subordinate_config': {1116 'subordinate_configuration': {
1109 'DEFAULT': {1117 'DEFAULT': {
1110 'key1': 'value1',1118 'key1': 'value1',
1111 },1119 },
@@ -1146,22 +1154,23 @@
1146 try:1154 try:
1147 sub_config = json.loads(sub_config)1155 sub_config = json.loads(sub_config)
1148 except:1156 except:
1149 log('Could not parse JSON from subordinate_config '1157 log('Could not parse JSON from '
1150 'setting from %s' % rid, level=ERROR)1158 'subordinate_configuration setting from %s'
1159 % rid, level=ERROR)
1151 continue1160 continue
11521161
1153 for service in self.services:1162 for service in self.services:
1154 if service not in sub_config:1163 if service not in sub_config:
1155 log('Found subordinate_config on %s but it contained'1164 log('Found subordinate_configuration on %s but it '
1156 'nothing for %s service' % (rid, service),1165 'contained nothing for %s service'
1157 level=INFO)1166 % (rid, service), level=INFO)
1158 continue1167 continue
11591168
1160 sub_config = sub_config[service]1169 sub_config = sub_config[service]
1161 if self.config_file not in sub_config:1170 if self.config_file not in sub_config:
1162 log('Found subordinate_config on %s but it contained'1171 log('Found subordinate_configuration on %s but it '
1163 'nothing for %s' % (rid, self.config_file),1172 'contained nothing for %s'
1164 level=INFO)1173 % (rid, self.config_file), level=INFO)
1165 continue1174 continue
11661175
1167 sub_config = sub_config[self.config_file]1176 sub_config = sub_config[self.config_file]
@@ -1212,17 +1221,55 @@
12121221
1213 @property1222 @property
1214 def num_cpus(self):1223 def num_cpus(self):
1215 try:1224 # NOTE: use cpu_count if present (16.04 support)
1216 from psutil import NUM_CPUS1225 if hasattr(psutil, 'cpu_count'):
1217 except ImportError:1226 return psutil.cpu_count()
1218 apt_install('python-psutil', fatal=True)1227 else:
1219 from psutil import NUM_CPUS1228 return psutil.NUM_CPUS
1220
1221 return NUM_CPUS
12221229
1223 def __call__(self):1230 def __call__(self):
1224 multiplier = config('worker-multiplier') or 01231 multiplier = config('worker-multiplier') or 0
1225 ctxt = {"workers": self.num_cpus * multiplier}1232 count = int(self.num_cpus * multiplier)
1233 if multiplier > 0 and count == 0:
1234 count = 1
1235 ctxt = {"workers": count}
1236 return ctxt
1237
1238
1239class WSGIWorkerConfigContext(WorkerConfigContext):
1240
1241 def __init__(self, name=None, script=None, admin_script=None,
1242 public_script=None, process_weight=1.00,
1243 admin_process_weight=0.75, public_process_weight=0.25):
1244 self.service_name = name
1245 self.user = name
1246 self.group = name
1247 self.script = script
1248 self.admin_script = admin_script
1249 self.public_script = public_script
1250 self.process_weight = process_weight
1251 self.admin_process_weight = admin_process_weight
1252 self.public_process_weight = public_process_weight
1253
1254 def __call__(self):
1255 multiplier = config('worker-multiplier') or 1
1256 total_processes = self.num_cpus * multiplier
1257 ctxt = {
1258 "service_name": self.service_name,
1259 "user": self.user,
1260 "group": self.group,
1261 "script": self.script,
1262 "admin_script": self.admin_script,
1263 "public_script": self.public_script,
1264 "processes": int(math.ceil(self.process_weight * total_processes)),
1265 "admin_processes": int(math.ceil(self.admin_process_weight *
1266 total_processes)),
1267 "public_processes": int(math.ceil(self.public_process_weight *
1268 total_processes)),
1269 "threads": 1,
1270 "usr_bin": git_determine_usr_bin(),
1271 "python_path": git_determine_python_path(),
1272 }
1226 return ctxt1273 return ctxt
12271274
12281275
@@ -1364,7 +1411,7 @@
1364 normalized.update({port: port for port in resolved1411 normalized.update({port: port for port in resolved
1365 if port in ports})1412 if port in ports})
1366 if resolved:1413 if resolved:
1367 return {bridge: normalized[port] for port, bridge in1414 return {normalized[port]: bridge for port, bridge in
1368 six.iteritems(portmap) if port in normalized.keys()}1415 six.iteritems(portmap) if port in normalized.keys()}
13691416
1370 return None1417 return None
@@ -1375,8 +1422,8 @@
1375 def __call__(self):1422 def __call__(self):
1376 ctxt = {}1423 ctxt = {}
1377 mappings = super(PhyNICMTUContext, self).__call__()1424 mappings = super(PhyNICMTUContext, self).__call__()
1378 if mappings and mappings.values():1425 if mappings and mappings.keys():
1379 ports = mappings.values()1426 ports = sorted(mappings.keys())
1380 napi_settings = NeutronAPIContext()()1427 napi_settings = NeutronAPIContext()()
1381 mtu = napi_settings.get('network_device_mtu')1428 mtu = napi_settings.get('network_device_mtu')
1382 all_ports = set()1429 all_ports = set()
@@ -1421,7 +1468,146 @@
1421 rdata.get('service_protocol') or 'http',1468 rdata.get('service_protocol') or 'http',
1422 'auth_protocol':1469 'auth_protocol':
1423 rdata.get('auth_protocol') or 'http',1470 rdata.get('auth_protocol') or 'http',
1471 'api_version':
1472 rdata.get('api_version') or '2.0',
1424 }1473 }
1425 if self.context_complete(ctxt):1474 if self.context_complete(ctxt):
1426 return ctxt1475 return ctxt
1427 return {}1476 return {}
1477
1478
1479class InternalEndpointContext(OSContextGenerator):
1480 """Internal endpoint context.
1481
1482 This context provides the endpoint type used for communication between
1483 services e.g. between Nova and Cinder internally. Openstack uses Public
1484 endpoints by default so this allows admins to optionally use internal
1485 endpoints.
1486 """
1487 def __call__(self):
1488 return {'use_internal_endpoints': config('use-internal-endpoints')}
1489
1490
1491class AppArmorContext(OSContextGenerator):
1492 """Base class for apparmor contexts."""
1493
1494 def __init__(self, profile_name=None):
1495 self._ctxt = None
1496 self.aa_profile = profile_name
1497 self.aa_utils_packages = ['apparmor-utils']
1498
1499 @property
1500 def ctxt(self):
1501 if self._ctxt is not None:
1502 return self._ctxt
1503 self._ctxt = self._determine_ctxt()
1504 return self._ctxt
1505
1506 def _determine_ctxt(self):
1507 """
1508 Validate aa-profile-mode settings is disable, enforce, or complain.
1509
1510 :return ctxt: Dictionary of the apparmor profile or None
1511 """
1512 if config('aa-profile-mode') in ['disable', 'enforce', 'complain']:
1513 ctxt = {'aa_profile_mode': config('aa-profile-mode'),
1514 'ubuntu_release': lsb_release()['DISTRIB_RELEASE']}
1515 if self.aa_profile:
1516 ctxt['aa_profile'] = self.aa_profile
1517 else:
1518 ctxt = None
1519 return ctxt
1520
1521 def __call__(self):
1522 return self.ctxt
1523
1524 def install_aa_utils(self):
1525 """
1526 Install packages required for apparmor configuration.
1527 """
1528 log("Installing apparmor utils.")
1529 ensure_packages(self.aa_utils_packages)
1530
1531 def manually_disable_aa_profile(self):
1532 """
1533 Manually disable an apparmor profile.
1534
1535 If aa-profile-mode is set to disabled (default) this is required as the
1536 template has been written but apparmor is yet unaware of the profile
1537 and aa-disable aa-profile fails. Without this the profile would kick
1538 into enforce mode on the next service restart.
1539
1540 """
1541 profile_path = '/etc/apparmor.d'
1542 disable_path = '/etc/apparmor.d/disable'
1543 if not os.path.lexists(os.path.join(disable_path, self.aa_profile)):
1544 os.symlink(os.path.join(profile_path, self.aa_profile),
1545 os.path.join(disable_path, self.aa_profile))
1546
1547 def setup_aa_profile(self):
1548 """
1549 Setup an apparmor profile.
1550 The ctxt dictionary will contain the apparmor profile mode and
1551 the apparmor profile name.
1552 Makes calls out to aa-disable, aa-complain, or aa-enforce to setup
1553 the apparmor profile.
1554 """
1555 self()
1556 if not self.ctxt:
1557 log("Not enabling apparmor Profile")
1558 return
1559 self.install_aa_utils()
1560 cmd = ['aa-{}'.format(self.ctxt['aa_profile_mode'])]
1561 cmd.append(self.ctxt['aa_profile'])
1562 log("Setting up the apparmor profile for {} in {} mode."
1563 "".format(self.ctxt['aa_profile'], self.ctxt['aa_profile_mode']))
1564 try:
1565 check_call(cmd)
1566 except CalledProcessError as e:
1567 # If aa-profile-mode is set to disabled (default) manual
1568 # disabling is required as the template has been written but
1569 # apparmor is yet unaware of the profile and aa-disable aa-profile
1570 # fails. If aa-disable learns to read profile files first this can
1571 # be removed.
1572 if self.ctxt['aa_profile_mode'] == 'disable':
1573 log("Manually disabling the apparmor profile for {}."
1574 "".format(self.ctxt['aa_profile']))
1575 self.manually_disable_aa_profile()
1576 return
1577 status_set('blocked', "Apparmor profile {} failed to be set to {}."
1578 "".format(self.ctxt['aa_profile'],
1579 self.ctxt['aa_profile_mode']))
1580 raise e
1581
1582
1583class MemcacheContext(OSContextGenerator):
1584 """Memcache context
1585
1586 This context provides options for configuring a local memcache client and
1587 server
1588 """
1589
1590 def __init__(self, package=None):
1591 """
1592 @param package: Package to examine to extrapolate OpenStack release.
1593 Used when charms have no openstack-origin config
1594 option (ie subordinates)
1595 """
1596 self.package = package
1597
1598 def __call__(self):
1599 ctxt = {}
1600 ctxt['use_memcache'] = enable_memcache(package=self.package)
1601 if ctxt['use_memcache']:
1602 # Trusty version of memcached does not support ::1 as a listen
1603 # address so use host file entry instead
1604 if lsb_release()['DISTRIB_CODENAME'].lower() > 'trusty':
1605 ctxt['memcache_server'] = '::1'
1606 else:
1607 ctxt['memcache_server'] = 'ip6-localhost'
1608 ctxt['memcache_server_formatted'] = '[::1]'
1609 ctxt['memcache_port'] = '11211'
1610 ctxt['memcache_url'] = 'inet6:{}:{}'.format(
1611 ctxt['memcache_server_formatted'],
1612 ctxt['memcache_port'])
1613 return ctxt
14281614
=== added file 'charmhelpers/contrib/openstack/exceptions.py'
--- charmhelpers/contrib/openstack/exceptions.py 1970-01-01 00:00:00 +0000
+++ charmhelpers/contrib/openstack/exceptions.py 2017-03-04 02:21:16 +0000
@@ -0,0 +1,21 @@
1# Copyright 2016 Canonical Ltd
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15
16class OSContextError(Exception):
17 """Raised when an error occurs during context generation.
18
19 This exception is principally used in contrib.openstack.context
20 """
21 pass
022
=== modified file 'charmhelpers/contrib/openstack/files/__init__.py'
--- charmhelpers/contrib/openstack/files/__init__.py 2015-09-28 20:09:02 +0000
+++ charmhelpers/contrib/openstack/files/__init__.py 2017-03-04 02:21:16 +0000
@@ -1,18 +1,16 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4#4# you may not use this file except in compliance with the License.
5# charm-helpers is free software: you can redistribute it and/or modify5# You may obtain a copy of the License at
6# it under the terms of the GNU Lesser General Public License version 3 as6#
7# published by the Free Software Foundation.7# http://www.apache.org/licenses/LICENSE-2.0
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17# dummy __init__.py to fool syncer into thinking this is a syncable python15# dummy __init__.py to fool syncer into thinking this is a syncable python
18# module16# module
1917
=== modified file 'charmhelpers/contrib/openstack/files/check_haproxy.sh'
--- charmhelpers/contrib/openstack/files/check_haproxy.sh 2015-09-28 20:09:02 +0000
+++ charmhelpers/contrib/openstack/files/check_haproxy.sh 2017-03-04 02:21:16 +0000
@@ -9,15 +9,17 @@
9CRITICAL=09CRITICAL=0
10NOTACTIVE=''10NOTACTIVE=''
11LOGFILE=/var/log/nagios/check_haproxy.log11LOGFILE=/var/log/nagios/check_haproxy.log
12AUTH=$(grep -r "stats auth" /etc/haproxy | head -1 | awk '{print $4}')12AUTH=$(grep -r "stats auth" /etc/haproxy | awk 'NR=1{print $4}')
1313
14for appserver in $(grep ' server' /etc/haproxy/haproxy.cfg | awk '{print $2'});14typeset -i N_INSTANCES=0
15for appserver in $(awk '/^\s+server/{print $2}' /etc/haproxy/haproxy.cfg)
15do16do
16 output=$(/usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 --regex="class=\"(active|backup)(2|3).*${appserver}" -e ' 200 OK')17 N_INSTANCES=N_INSTANCES+1
18 output=$(/usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 -u '/;csv' --regex=",${appserver},.*,UP.*" -e ' 200 OK')
17 if [ $? != 0 ]; then19 if [ $? != 0 ]; then
18 date >> $LOGFILE20 date >> $LOGFILE
19 echo $output >> $LOGFILE21 echo $output >> $LOGFILE
20 /usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 -v | grep $appserver >> $LOGFILE 2>&122 /usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 -u '/;csv' -v | grep ",${appserver}," >> $LOGFILE 2>&1
21 CRITICAL=123 CRITICAL=1
22 NOTACTIVE="${NOTACTIVE} $appserver"24 NOTACTIVE="${NOTACTIVE} $appserver"
23 fi25 fi
@@ -28,5 +30,5 @@
28 exit 230 exit 2
29fi31fi
3032
31echo "OK: All haproxy instances looking good"33echo "OK: All haproxy instances ($N_INSTANCES) looking good"
32exit 034exit 0
3335
=== added directory 'charmhelpers/contrib/openstack/ha'
=== added file 'charmhelpers/contrib/openstack/ha/__init__.py'
--- charmhelpers/contrib/openstack/ha/__init__.py 1970-01-01 00:00:00 +0000
+++ charmhelpers/contrib/openstack/ha/__init__.py 2017-03-04 02:21:16 +0000
@@ -0,0 +1,13 @@
1# Copyright 2016 Canonical Ltd
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
014
=== added file 'charmhelpers/contrib/openstack/ha/utils.py'
--- charmhelpers/contrib/openstack/ha/utils.py 1970-01-01 00:00:00 +0000
+++ charmhelpers/contrib/openstack/ha/utils.py 2017-03-04 02:21:16 +0000
@@ -0,0 +1,139 @@
1# Copyright 2014-2016 Canonical Limited.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15#
16# Copyright 2016 Canonical Ltd.
17#
18# Authors:
19# Openstack Charmers <
20#
21
22"""
23Helpers for high availability.
24"""
25
26import re
27
28from charmhelpers.core.hookenv import (
29 log,
30 relation_set,
31 charm_name,
32 config,
33 status_set,
34 DEBUG,
35)
36
37from charmhelpers.core.host import (
38 lsb_release
39)
40
41from charmhelpers.contrib.openstack.ip import (
42 resolve_address,
43)
44
45
46class DNSHAException(Exception):
47 """Raised when an error occurs setting up DNS HA
48 """
49
50 pass
51
52
53def update_dns_ha_resource_params(resources, resource_params,
54 relation_id=None,
55 crm_ocf='ocf:maas:dns'):
56 """ Check for os-*-hostname settings and update resource dictionaries for
57 the HA relation.
58
59 @param resources: Pointer to dictionary of resources.
60 Usually instantiated in ha_joined().
61 @param resource_params: Pointer to dictionary of resource parameters.
62 Usually instantiated in ha_joined()
63 @param relation_id: Relation ID of the ha relation
64 @param crm_ocf: Corosync Open Cluster Framework resource agent to use for
65 DNS HA
66 """
67
68 # Validate the charm environment for DNS HA
69 assert_charm_supports_dns_ha()
70
71 settings = ['os-admin-hostname', 'os-internal-hostname',
72 'os-public-hostname', 'os-access-hostname']
73
74 # Check which DNS settings are set and update dictionaries
75 hostname_group = []
76 for setting in settings:
77 hostname = config(setting)
78 if hostname is None:
79 log('DNS HA: Hostname setting {} is None. Ignoring.'
80 ''.format(setting),
81 DEBUG)
82 continue
83 m = re.search('os-(.+?)-hostname', setting)
84 if m:
85 networkspace = m.group(1)
86 else:
87 msg = ('Unexpected DNS hostname setting: {}. '
88 'Cannot determine network space name'
89 ''.format(setting))
90 status_set('blocked', msg)
91 raise DNSHAException(msg)
92
93 hostname_key = 'res_{}_{}_hostname'.format(charm_name(), networkspace)
94 if hostname_key in hostname_group:
95 log('DNS HA: Resource {}: {} already exists in '
96 'hostname group - skipping'.format(hostname_key, hostname),
97 DEBUG)
98 continue
99
100 hostname_group.append(hostname_key)
101 resources[hostname_key] = crm_ocf
102 resource_params[hostname_key] = (
103 'params fqdn="{}" ip_address="{}" '
104 ''.format(hostname, resolve_address(endpoint_type=networkspace,
105 override=False)))
106
107 if len(hostname_group) >= 1:
108 log('DNS HA: Hostname group is set with {} as members. '
109 'Informing the ha relation'.format(' '.join(hostname_group)),
110 DEBUG)
111 relation_set(relation_id=relation_id, groups={
112 'grp_{}_hostnames'.format(charm_name()): ' '.join(hostname_group)})
113 else:
114 msg = 'DNS HA: Hostname group has no members.'
115 status_set('blocked', msg)
116 raise DNSHAException(msg)
117
118
119def assert_charm_supports_dns_ha():
120 """Validate prerequisites for DNS HA
121 The MAAS client is only available on Xenial or greater
122 """
123 if lsb_release().get('DISTRIB_RELEASE') < '16.04':
124 msg = ('DNS HA is only supported on 16.04 and greater '
125 'versions of Ubuntu.')
126 status_set('blocked', msg)
127 raise DNSHAException(msg)
128 return True
129
130
131def expect_ha():
132 """ Determine if the unit expects to be in HA
133
134 Check for VIP or dns-ha settings which indicate the unit should expect to
135 be related to hacluster.
136
137 @returns boolean
138 """
139 return config('vip') or config('dns-ha')
0140
=== modified file 'charmhelpers/contrib/openstack/ip.py'
--- charmhelpers/contrib/openstack/ip.py 2015-09-28 20:09:02 +0000
+++ charmhelpers/contrib/openstack/ip.py 2017-03-04 02:21:16 +0000
@@ -1,52 +1,62 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4#4# you may not use this file except in compliance with the License.
5# charm-helpers is free software: you can redistribute it and/or modify5# You may obtain a copy of the License at
6# it under the terms of the GNU Lesser General Public License version 3 as6#
7# published by the Free Software Foundation.7# http://www.apache.org/licenses/LICENSE-2.0
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17from charmhelpers.core.hookenv import (15from charmhelpers.core.hookenv import (
18 config,16 config,
19 unit_get,17 unit_get,
20 service_name,18 service_name,
19 network_get_primary_address,
21)20)
22from charmhelpers.contrib.network.ip import (21from charmhelpers.contrib.network.ip import (
23 get_address_in_network,22 get_address_in_network,
24 is_address_in_network,23 is_address_in_network,
25 is_ipv6,24 is_ipv6,
26 get_ipv6_addr,25 get_ipv6_addr,
26 resolve_network_cidr,
27)27)
28from charmhelpers.contrib.hahelpers.cluster import is_clustered28from charmhelpers.contrib.hahelpers.cluster import is_clustered
2929
30PUBLIC = 'public'30PUBLIC = 'public'
31INTERNAL = 'int'31INTERNAL = 'int'
32ADMIN = 'admin'32ADMIN = 'admin'
33ACCESS = 'access'
3334
34ADDRESS_MAP = {35ADDRESS_MAP = {
35 PUBLIC: {36 PUBLIC: {
37 'binding': 'public',
36 'config': 'os-public-network',38 'config': 'os-public-network',
37 'fallback': 'public-address',39 'fallback': 'public-address',
38 'override': 'os-public-hostname',40 'override': 'os-public-hostname',
39 },41 },
40 INTERNAL: {42 INTERNAL: {
43 'binding': 'internal',
41 'config': 'os-internal-network',44 'config': 'os-internal-network',
42 'fallback': 'private-address',45 'fallback': 'private-address',
43 'override': 'os-internal-hostname',46 'override': 'os-internal-hostname',
44 },47 },
45 ADMIN: {48 ADMIN: {
49 'binding': 'admin',
46 'config': 'os-admin-network',50 'config': 'os-admin-network',
47 'fallback': 'private-address',51 'fallback': 'private-address',
48 'override': 'os-admin-hostname',52 'override': 'os-admin-hostname',
49 }53 },
54 ACCESS: {
55 'binding': 'access',
56 'config': 'access-network',
57 'fallback': 'private-address',
58 'override': 'os-access-hostname',
59 },
50}60}
5161
5262
@@ -103,20 +113,23 @@
103 return addr_override.format(service_name=service_name())113 return addr_override.format(service_name=service_name())
104114
105115
106def resolve_address(endpoint_type=PUBLIC):116def resolve_address(endpoint_type=PUBLIC, override=True):
107 """Return unit address depending on net config.117 """Return unit address depending on net config.
108118
109 If unit is clustered with vip(s) and has net splits defined, return vip on119 If unit is clustered with vip(s) and has net splits defined, return vip on
110 correct network. If clustered with no nets defined, return primary vip.120 correct network. If clustered with no nets defined, return primary vip.
111121
112 If not clustered, return unit address ensuring address is on configured net122 If not clustered, return unit address ensuring address is on configured net
113 split if one is configured.123 split if one is configured, or a Juju 2.0 extra-binding has been used.
114124
115 :param endpoint_type: Network endpoing type125 :param endpoint_type: Network endpoing type
126 :param override: Accept hostname overrides or not
116 """127 """
117 resolved_address = _get_address_override(endpoint_type)128 resolved_address = None
118 if resolved_address:129 if override:
119 return resolved_address130 resolved_address = _get_address_override(endpoint_type)
131 if resolved_address:
132 return resolved_address
120133
121 vips = config('vip')134 vips = config('vip')
122 if vips:135 if vips:
@@ -125,23 +138,45 @@
125 net_type = ADDRESS_MAP[endpoint_type]['config']138 net_type = ADDRESS_MAP[endpoint_type]['config']
126 net_addr = config(net_type)139 net_addr = config(net_type)
127 net_fallback = ADDRESS_MAP[endpoint_type]['fallback']140 net_fallback = ADDRESS_MAP[endpoint_type]['fallback']
141 binding = ADDRESS_MAP[endpoint_type]['binding']
128 clustered = is_clustered()142 clustered = is_clustered()
129 if clustered:143
130 if not net_addr:144 if clustered and vips:
131 # If no net-splits defined, we expect a single vip145 if net_addr:
132 resolved_address = vips[0]
133 else:
134 for vip in vips:146 for vip in vips:
135 if is_address_in_network(net_addr, vip):147 if is_address_in_network(net_addr, vip):
136 resolved_address = vip148 resolved_address = vip
137 break149 break
150 else:
151 # NOTE: endeavour to check vips against network space
152 # bindings
153 try:
154 bound_cidr = resolve_network_cidr(
155 network_get_primary_address(binding)
156 )
157 for vip in vips:
158 if is_address_in_network(bound_cidr, vip):
159 resolved_address = vip
160 break
161 except NotImplementedError:
162 # If no net-splits configured and no support for extra
163 # bindings/network spaces so we expect a single vip
164 resolved_address = vips[0]
138 else:165 else:
139 if config('prefer-ipv6'):166 if config('prefer-ipv6'):
140 fallback_addr = get_ipv6_addr(exc_list=vips)[0]167 fallback_addr = get_ipv6_addr(exc_list=vips)[0]
141 else:168 else:
142 fallback_addr = unit_get(net_fallback)169 fallback_addr = unit_get(net_fallback)
143170
144 resolved_address = get_address_in_network(net_addr, fallback_addr)171 if net_addr:
172 resolved_address = get_address_in_network(net_addr, fallback_addr)
173 else:
174 # NOTE: only try to use extra bindings if legacy network
175 # configuration is not in use
176 try:
177 resolved_address = network_get_primary_address(binding)
178 except NotImplementedError:
179 resolved_address = fallback_addr
145180
146 if resolved_address is None:181 if resolved_address is None:
147 raise ValueError("Unable to resolve a suitable IP address based on "182 raise ValueError("Unable to resolve a suitable IP address based on "
148183
=== added file 'charmhelpers/contrib/openstack/keystone.py'
--- charmhelpers/contrib/openstack/keystone.py 1970-01-01 00:00:00 +0000
+++ charmhelpers/contrib/openstack/keystone.py 2017-03-04 02:21:16 +0000
@@ -0,0 +1,178 @@
1#!/usr/bin/python
2#
3# Copyright 2017 Canonical Ltd
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17import six
18from charmhelpers.fetch import apt_install
19from charmhelpers.contrib.openstack.context import IdentityServiceContext
20from charmhelpers.core.hookenv import (
21 log,
22 ERROR,
23)
24
25
26def get_api_suffix(api_version):
27 """Return the formatted api suffix for the given version
28 @param api_version: version of the keystone endpoint
29 @returns the api suffix formatted according to the given api
30 version
31 """
32 return 'v2.0' if api_version in (2, "2.0") else 'v3'
33
34
35def format_endpoint(schema, addr, port, api_version):
36 """Return a formatted keystone endpoint
37 @param schema: http or https
38 @param addr: ipv4/ipv6 host of the keystone service
39 @param port: port of the keystone service
40 @param api_version: 2 or 3
41 @returns a fully formatted keystone endpoint
42 """
43 return '{}://{}:{}/{}/'.format(schema, addr, port,
44 get_api_suffix(api_version))
45
46
47def get_keystone_manager(endpoint, api_version, **kwargs):
48 """Return a keystonemanager for the correct API version
49
50 @param endpoint: the keystone endpoint to point client at
51 @param api_version: version of the keystone api the client should use
52 @param kwargs: token or username/tenant/password information
53 @returns keystonemanager class used for interrogating keystone
54 """
55 if api_version == 2:
56 return KeystoneManager2(endpoint, **kwargs)
57 if api_version == 3:
58 return KeystoneManager3(endpoint, **kwargs)
59 raise ValueError('No manager found for api version {}'.format(api_version))
60
61
62def get_keystone_manager_from_identity_service_context():
63 """Return a keystonmanager generated from a
64 instance of charmhelpers.contrib.openstack.context.IdentityServiceContext
65 @returns keystonamenager instance
66 """
67 context = IdentityServiceContext()()
68 if not context:
69 msg = "Identity service context cannot be generated"
70 log(msg, level=ERROR)
71 raise ValueError(msg)
72
73 endpoint = format_endpoint(context['service_protocol'],
74 context['service_host'],
75 context['service_port'],
76 context['api_version'])
77
78 if context['api_version'] in (2, "2.0"):
79 api_version = 2
80 else:
81 api_version = 3
82
83 return get_keystone_manager(endpoint, api_version,
84 username=context['admin_user'],
85 password=context['admin_password'],
86 tenant_name=context['admin_tenant_name'])
87
88
89class KeystoneManager(object):
90
91 def resolve_service_id(self, service_name=None, service_type=None):
92 """Find the service_id of a given service"""
93 services = [s._info for s in self.api.services.list()]
94
95 service_name = service_name.lower()
96 for s in services:
97 name = s['name'].lower()
98 if service_type and service_name:
99 if (service_name == name and service_type == s['type']):
100 return s['id']
101 elif service_name and service_name == name:
102 return s['id']
103 elif service_type and service_type == s['type']:
104 return s['id']
105 return None
106
107 def service_exists(self, service_name=None, service_type=None):
108 """Determine if the given service exists on the service list"""
109 return self.resolve_service_id(service_name, service_type) is not None
110
111
112class KeystoneManager2(KeystoneManager):
113
114 def __init__(self, endpoint, **kwargs):
115 try:
116 from keystoneclient.v2_0 import client
117 from keystoneclient.auth.identity import v2
118 from keystoneclient import session
119 except ImportError:
120 if six.PY2:
121 apt_install(["python-keystoneclient"], fatal=True)
122 else:
123 apt_install(["python3-keystoneclient"], fatal=True)
124
125 from keystoneclient.v2_0 import client
126 from keystoneclient.auth.identity import v2
127 from keystoneclient import session
128
129 self.api_version = 2
130
131 token = kwargs.get("token", None)
132 if token:
133 api = client.Client(endpoint=endpoint, token=token)
134 else:
135 auth = v2.Password(username=kwargs.get("username"),
136 password=kwargs.get("password"),
137 tenant_name=kwargs.get("tenant_name"),
138 auth_url=endpoint)
139 sess = session.Session(auth=auth)
140 api = client.Client(session=sess)
141
142 self.api = api
143
144
145class KeystoneManager3(KeystoneManager):
146
147 def __init__(self, endpoint, **kwargs):
148 try:
149 from keystoneclient.v3 import client
150 from keystoneclient.auth import token_endpoint
151 from keystoneclient import session
152 from keystoneclient.auth.identity import v3
153 except ImportError:
154 if six.PY2:
155 apt_install(["python-keystoneclient"], fatal=True)
156 else:
157 apt_install(["python3-keystoneclient"], fatal=True)
158
159 from keystoneclient.v3 import client
160 from keystoneclient.auth import token_endpoint
161 from keystoneclient import session
162 from keystoneclient.auth.identity import v3
163
164 self.api_version = 3
165
166 token = kwargs.get("token", None)
167 if token:
168 auth = token_endpoint.Token(endpoint=endpoint,
169 token=token)
170 sess = session.Session(auth=auth)
171 else:
172 auth = v3.Password(auth_url=endpoint,
173 user_id=kwargs.get("username"),
174 password=kwargs.get("password"),
175 project_id=kwargs.get("tenant_name"))
176 sess = session.Session(auth=auth)
177
178 self.api = client.Client(session=sess)
0179
=== modified file 'charmhelpers/contrib/openstack/neutron.py'
--- charmhelpers/contrib/openstack/neutron.py 2015-09-28 20:09:02 +0000
+++ charmhelpers/contrib/openstack/neutron.py 2017-03-04 02:21:16 +0000
@@ -1,18 +1,16 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4#4# you may not use this file except in compliance with the License.
5# charm-helpers is free software: you can redistribute it and/or modify5# You may obtain a copy of the License at
6# it under the terms of the GNU Lesser General Public License version 3 as6#
7# published by the Free Software Foundation.7# http://www.apache.org/licenses/LICENSE-2.0
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17# Various utilies for dealing with Neutron and the renaming from Quantum.15# Various utilies for dealing with Neutron and the renaming from Quantum.
1816
@@ -34,6 +32,7 @@
34 kver = check_output(['uname', '-r']).decode('UTF-8').strip()32 kver = check_output(['uname', '-r']).decode('UTF-8').strip()
35 return 'linux-headers-%s' % kver33 return 'linux-headers-%s' % kver
3634
35
37QUANTUM_CONF_DIR = '/etc/quantum'36QUANTUM_CONF_DIR = '/etc/quantum'
3837
3938
@@ -50,7 +49,7 @@
50 if kernel_version() >= (3, 13):49 if kernel_version() >= (3, 13):
51 return []50 return []
52 else:51 else:
53 return ['openvswitch-datapath-dkms']52 return [headers_package(), 'openvswitch-datapath-dkms']
5453
5554
56# legacy55# legacy
@@ -70,7 +69,7 @@
70 relation_prefix='neutron',69 relation_prefix='neutron',
71 ssl_dir=QUANTUM_CONF_DIR)],70 ssl_dir=QUANTUM_CONF_DIR)],
72 'services': ['quantum-plugin-openvswitch-agent'],71 'services': ['quantum-plugin-openvswitch-agent'],
73 'packages': [[headers_package()] + determine_dkms_package(),72 'packages': [determine_dkms_package(),
74 ['quantum-plugin-openvswitch-agent']],73 ['quantum-plugin-openvswitch-agent']],
75 'server_packages': ['quantum-server',74 'server_packages': ['quantum-server',
76 'quantum-plugin-openvswitch'],75 'quantum-plugin-openvswitch'],
@@ -93,6 +92,7 @@
93 }92 }
94 }93 }
9594
95
96NEUTRON_CONF_DIR = '/etc/neutron'96NEUTRON_CONF_DIR = '/etc/neutron'
9797
9898
@@ -111,7 +111,7 @@
111 relation_prefix='neutron',111 relation_prefix='neutron',
112 ssl_dir=NEUTRON_CONF_DIR)],112 ssl_dir=NEUTRON_CONF_DIR)],
113 'services': ['neutron-plugin-openvswitch-agent'],113 'services': ['neutron-plugin-openvswitch-agent'],
114 'packages': [[headers_package()] + determine_dkms_package(),114 'packages': [determine_dkms_package(),
115 ['neutron-plugin-openvswitch-agent']],115 ['neutron-plugin-openvswitch-agent']],
116 'server_packages': ['neutron-server',116 'server_packages': ['neutron-server',
117 'neutron-plugin-openvswitch'],117 'neutron-plugin-openvswitch'],
@@ -155,7 +155,7 @@
155 relation_prefix='neutron',155 relation_prefix='neutron',
156 ssl_dir=NEUTRON_CONF_DIR)],156 ssl_dir=NEUTRON_CONF_DIR)],
157 'services': [],157 'services': [],
158 'packages': [[headers_package()] + determine_dkms_package(),158 'packages': [determine_dkms_package(),
159 ['neutron-plugin-cisco']],159 ['neutron-plugin-cisco']],
160 'server_packages': ['neutron-server',160 'server_packages': ['neutron-server',
161 'neutron-plugin-cisco'],161 'neutron-plugin-cisco'],
@@ -174,7 +174,7 @@
174 'neutron-dhcp-agent',174 'neutron-dhcp-agent',
175 'nova-api-metadata',175 'nova-api-metadata',
176 'etcd'],176 'etcd'],
177 'packages': [[headers_package()] + determine_dkms_package(),177 'packages': [determine_dkms_package(),
178 ['calico-compute',178 ['calico-compute',
179 'bird',179 'bird',
180 'neutron-dhcp-agent',180 'neutron-dhcp-agent',
@@ -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': [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':
@@ -219,6 +233,26 @@
219 'neutron-plugin-ml2']233 'neutron-plugin-ml2']
220 # NOTE: patch in vmware renames nvp->nsx for icehouse onwards234 # NOTE: patch in vmware renames nvp->nsx for icehouse onwards
221 plugins['nvp'] = plugins['nsx']235 plugins['nvp'] = plugins['nsx']
236 if release >= 'kilo':
237 plugins['midonet']['driver'] = (
238 'neutron.plugins.midonet.plugin.MidonetPluginV2')
239 if release >= 'liberty':
240 plugins['midonet']['driver'] = (
241 'midonet.neutron.plugin_v1.MidonetPluginV2')
242 plugins['midonet']['server_packages'].remove(
243 'python-neutron-plugin-midonet')
244 plugins['midonet']['server_packages'].append(
245 'python-networking-midonet')
246 plugins['plumgrid']['driver'] = (
247 'networking_plumgrid.neutron.plugins.plugin.NeutronPluginPLUMgridV2')
248 plugins['plumgrid']['server_packages'].remove(
249 'neutron-plugin-plumgrid')
250 if release >= 'mitaka':
251 plugins['nsx']['server_packages'].remove('neutron-plugin-vmware')
252 plugins['nsx']['server_packages'].append('python-vmware-nsx')
253 plugins['nsx']['config'] = '/etc/neutron/nsx.ini'
254 plugins['vsp']['driver'] = (
255 'nuage_neutron.plugins.nuage.plugin.NuagePlugin')
222 return plugins256 return plugins
223257
224258
@@ -310,10 +344,10 @@
310def parse_data_port_mappings(mappings, default_bridge='br-data'):344def parse_data_port_mappings(mappings, default_bridge='br-data'):
311 """Parse data port mappings.345 """Parse data port mappings.
312346
313 Mappings must be a space-delimited list of port:bridge mappings.347 Mappings must be a space-delimited list of bridge:port.
314348
315 Returns dict of the form {port:bridge} where port may be an mac address or349 Returns dict of the form {port:bridge} where ports may be mac addresses or
316 interface name.350 interface names.
317 """351 """
318352
319 # NOTE(dosaboy): we use rvalue for key to allow multiple values to be353 # NOTE(dosaboy): we use rvalue for key to allow multiple values to be
320354
=== modified file 'charmhelpers/contrib/openstack/templates/__init__.py'
--- charmhelpers/contrib/openstack/templates/__init__.py 2015-09-28 20:09:02 +0000
+++ charmhelpers/contrib/openstack/templates/__init__.py 2017-03-04 02:21:16 +0000
@@ -1,18 +1,16 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4#4# you may not use this file except in compliance with the License.
5# charm-helpers is free software: you can redistribute it and/or modify5# You may obtain a copy of the License at
6# it under the terms of the GNU Lesser General Public License version 3 as6#
7# published by the Free Software Foundation.7# http://www.apache.org/licenses/LICENSE-2.0
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17# dummy __init__.py to fool syncer into thinking this is a syncable python15# dummy __init__.py to fool syncer into thinking this is a syncable python
18# module16# module
1917
=== modified file 'charmhelpers/contrib/openstack/templates/haproxy.cfg'
--- charmhelpers/contrib/openstack/templates/haproxy.cfg 2015-09-28 20:09:02 +0000
+++ charmhelpers/contrib/openstack/templates/haproxy.cfg 2017-03-04 02:21:16 +0000
@@ -12,27 +12,35 @@
12 option tcplog12 option tcplog
13 option dontlognull13 option dontlognull
14 retries 314 retries 3
15 timeout queue 100015{%- if haproxy_queue_timeout %}
16 timeout connect 100016 timeout queue {{ haproxy_queue_timeout }}
17{% if haproxy_client_timeout -%}17{%- else %}
18 timeout queue 5000
19{%- endif %}
20{%- if haproxy_connect_timeout %}
21 timeout connect {{ haproxy_connect_timeout }}
22{%- else %}
23 timeout connect 5000
24{%- endif %}
25{%- if haproxy_client_timeout %}
18 timeout client {{ haproxy_client_timeout }}26 timeout client {{ haproxy_client_timeout }}
19{% else -%}27{%- else %}
20 timeout client 3000028 timeout client 30000
21{% endif -%}29{%- endif %}
2230{%- if haproxy_server_timeout %}
23{% if haproxy_server_timeout -%}
24 timeout server {{ haproxy_server_timeout }}31 timeout server {{ haproxy_server_timeout }}
25{% else -%}32{%- else %}
26 timeout server 3000033 timeout server 30000
27{% endif -%}34{%- endif %}
2835
29listen stats {{ stat_port }}36listen stats
37 bind {{ local_host }}:{{ stat_port }}
30 mode http38 mode http
31 stats enable39 stats enable
32 stats hide-version40 stats hide-version
33 stats realm Haproxy\ Statistics41 stats realm Haproxy\ Statistics
34 stats uri /42 stats uri /
35 stats auth admin:password43 stats auth admin:{{ stat_password }}
3644
37{% if frontends -%}45{% if frontends -%}
38{% for service, ports in service_ports.items() -%}46{% for service, ports in service_ports.items() -%}
3947
=== added file 'charmhelpers/contrib/openstack/templates/memcached.conf'
--- charmhelpers/contrib/openstack/templates/memcached.conf 1970-01-01 00:00:00 +0000
+++ charmhelpers/contrib/openstack/templates/memcached.conf 2017-03-04 02:21:16 +0000
@@ -0,0 +1,53 @@
1###############################################################################
2# [ WARNING ]
3# memcached configuration file maintained by Juju
4# local changes may be overwritten.
5###############################################################################
6
7# memcached default config file
8# 2003 - Jay Bonci <jaybonci@debian.org>
9# This configuration file is read by the start-memcached script provided as
10# part of the Debian GNU/Linux distribution.
11
12# Run memcached as a daemon. This command is implied, and is not needed for the
13# daemon to run. See the README.Debian that comes with this package for more
14# information.
15-d
16
17# Log memcached's output to /var/log/memcached
18logfile /var/log/memcached.log
19
20# Be verbose
21# -v
22
23# Be even more verbose (print client commands as well)
24# -vv
25
26# Start with a cap of 64 megs of memory. It's reasonable, and the daemon default
27# Note that the daemon will grow to this size, but does not start out holding this much
28# memory
29-m 64
30
31# Default connection port is 11211
32-p {{ memcache_port }}
33
34# Run the daemon as root. The start-memcached will default to running as root if no
35# -u command is present in this config file
36-u memcache
37
38# Specify which IP address to listen on. The default is to listen on all IP addresses
39# This parameter is one of the only security measures that memcached has, so make sure
40# it's listening on a firewalled interface.
41-l {{ memcache_server }}
42
43# Limit the number of simultaneous incoming connections. The daemon default is 1024
44# -c 1024
45
46# Lock down all paged memory. Consult with the README and homepage before you do this
47# -k
48
49# Return error when memory is exhausted (rather than removing items)
50# -M
51
52# Maximize core file limit
53# -r
054
=== modified file 'charmhelpers/contrib/openstack/templates/openstack_https_frontend'
--- charmhelpers/contrib/openstack/templates/openstack_https_frontend 2015-09-28 20:09:02 +0000
+++ charmhelpers/contrib/openstack/templates/openstack_https_frontend 2017-03-04 02:21:16 +0000
@@ -6,11 +6,16 @@
6<VirtualHost {{ address }}:{{ ext }}>6<VirtualHost {{ address }}:{{ ext }}>
7 ServerName {{ endpoint }}7 ServerName {{ endpoint }}
8 SSLEngine on8 SSLEngine on
9 SSLProtocol +TLSv1 +TLSv1.1 +TLSv1.2
10 SSLCipherSuite HIGH:!RC4:!MD5:!aNULL:!eNULL:!EXP:!LOW:!MEDIUM
9 SSLCertificateFile /etc/apache2/ssl/{{ namespace }}/cert_{{ endpoint }}11 SSLCertificateFile /etc/apache2/ssl/{{ namespace }}/cert_{{ endpoint }}
12 # See LP 1484489 - this is to support <= 2.4.7 and >= 2.4.8
13 SSLCertificateChainFile /etc/apache2/ssl/{{ namespace }}/cert_{{ endpoint }}
10 SSLCertificateKeyFile /etc/apache2/ssl/{{ namespace }}/key_{{ endpoint }}14 SSLCertificateKeyFile /etc/apache2/ssl/{{ namespace }}/key_{{ endpoint }}
11 ProxyPass / http://localhost:{{ int }}/15 ProxyPass / http://localhost:{{ int }}/
12 ProxyPassReverse / http://localhost:{{ int }}/16 ProxyPassReverse / http://localhost:{{ int }}/
13 ProxyPreserveHost on17 ProxyPreserveHost on
18 RequestHeader set X-Forwarded-Proto "https"
14</VirtualHost>19</VirtualHost>
15{% endfor -%}20{% endfor -%}
16<Proxy *>21<Proxy *>
1722
=== modified file 'charmhelpers/contrib/openstack/templates/openstack_https_frontend.conf'
--- charmhelpers/contrib/openstack/templates/openstack_https_frontend.conf 2015-09-28 20:09:02 +0000
+++ charmhelpers/contrib/openstack/templates/openstack_https_frontend.conf 2017-03-04 02:21:16 +0000
@@ -6,11 +6,16 @@
6<VirtualHost {{ address }}:{{ ext }}>6<VirtualHost {{ address }}:{{ ext }}>
7 ServerName {{ endpoint }}7 ServerName {{ endpoint }}
8 SSLEngine on8 SSLEngine on
9 SSLProtocol +TLSv1 +TLSv1.1 +TLSv1.2
10 SSLCipherSuite HIGH:!RC4:!MD5:!aNULL:!eNULL:!EXP:!LOW:!MEDIUM
9 SSLCertificateFile /etc/apache2/ssl/{{ namespace }}/cert_{{ endpoint }}11 SSLCertificateFile /etc/apache2/ssl/{{ namespace }}/cert_{{ endpoint }}
12 # See LP 1484489 - this is to support <= 2.4.7 and >= 2.4.8
13 SSLCertificateChainFile /etc/apache2/ssl/{{ namespace }}/cert_{{ endpoint }}
10 SSLCertificateKeyFile /etc/apache2/ssl/{{ namespace }}/key_{{ endpoint }}14 SSLCertificateKeyFile /etc/apache2/ssl/{{ namespace }}/key_{{ endpoint }}
11 ProxyPass / http://localhost:{{ int }}/15 ProxyPass / http://localhost:{{ int }}/
12 ProxyPassReverse / http://localhost:{{ int }}/16 ProxyPassReverse / http://localhost:{{ int }}/
13 ProxyPreserveHost on17 ProxyPreserveHost on
18 RequestHeader set X-Forwarded-Proto "https"
14</VirtualHost>19</VirtualHost>
15{% endfor -%}20{% endfor -%}
16<Proxy *>21<Proxy *>
1722
=== modified file 'charmhelpers/contrib/openstack/templates/section-keystone-authtoken'
--- charmhelpers/contrib/openstack/templates/section-keystone-authtoken 2015-09-28 20:09:02 +0000
+++ charmhelpers/contrib/openstack/templates/section-keystone-authtoken 2017-03-04 02:21:16 +0000
@@ -1,9 +1,12 @@
1{% if auth_host -%}1{% if auth_host -%}
2[keystone_authtoken]2[keystone_authtoken]
3identity_uri = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}/{{ auth_admin_prefix }}3auth_uri = {{ service_protocol }}://{{ service_host }}:{{ service_port }}
4auth_uri = {{ service_protocol }}://{{ service_host }}:{{ service_port }}/{{ service_admin_prefix }}4auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}
5admin_tenant_name = {{ admin_tenant_name }}5auth_plugin = password
6admin_user = {{ admin_user }}6project_domain_id = default
7admin_password = {{ admin_password }}7user_domain_id = default
8project_name = {{ admin_tenant_name }}
9username = {{ admin_user }}
10password = {{ admin_password }}
8signing_dir = {{ signing_dir }}11signing_dir = {{ signing_dir }}
9{% endif -%}12{% endif -%}
1013
=== added file 'charmhelpers/contrib/openstack/templates/section-keystone-authtoken-legacy'
--- charmhelpers/contrib/openstack/templates/section-keystone-authtoken-legacy 1970-01-01 00:00:00 +0000
+++ charmhelpers/contrib/openstack/templates/section-keystone-authtoken-legacy 2017-03-04 02:21:16 +0000
@@ -0,0 +1,10 @@
1{% if auth_host -%}
2[keystone_authtoken]
3# Juno specific config (Bug #1557223)
4auth_uri = {{ service_protocol }}://{{ service_host }}:{{ service_port }}/{{ service_admin_prefix }}
5identity_uri = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}
6admin_tenant_name = {{ admin_tenant_name }}
7admin_user = {{ admin_user }}
8admin_password = {{ admin_password }}
9signing_dir = {{ signing_dir }}
10{% endif -%}
011
=== added file 'charmhelpers/contrib/openstack/templates/section-keystone-authtoken-mitaka'
--- charmhelpers/contrib/openstack/templates/section-keystone-authtoken-mitaka 1970-01-01 00:00:00 +0000
+++ charmhelpers/contrib/openstack/templates/section-keystone-authtoken-mitaka 2017-03-04 02:21:16 +0000
@@ -0,0 +1,20 @@
1{% if auth_host -%}
2[keystone_authtoken]
3auth_uri = {{ service_protocol }}://{{ service_host }}:{{ service_port }}
4auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}
5auth_type = password
6{% if api_version == "3" -%}
7project_domain_name = {{ admin_domain_name }}
8user_domain_name = {{ admin_domain_name }}
9{% else -%}
10project_domain_name = default
11user_domain_name = default
12{% endif -%}
13project_name = {{ admin_tenant_name }}
14username = {{ admin_user }}
15password = {{ admin_password }}
16signing_dir = {{ signing_dir }}
17{% if use_memcache == true %}
18memcached_servers = {{ memcache_url }}
19{% endif -%}
20{% endif -%}
021
=== added file 'charmhelpers/contrib/openstack/templates/wsgi-openstack-api.conf'
--- charmhelpers/contrib/openstack/templates/wsgi-openstack-api.conf 1970-01-01 00:00:00 +0000
+++ charmhelpers/contrib/openstack/templates/wsgi-openstack-api.conf 2017-03-04 02:21:16 +0000
@@ -0,0 +1,100 @@
1# Configuration file maintained by Juju. Local changes may be overwritten.
2
3{% if port -%}
4Listen {{ port }}
5{% endif -%}
6
7{% if admin_port -%}
8Listen {{ admin_port }}
9{% endif -%}
10
11{% if public_port -%}
12Listen {{ public_port }}
13{% endif -%}
14
15{% if port -%}
16<VirtualHost *:{{ port }}>
17 WSGIDaemonProcess {{ service_name }} processes={{ processes }} threads={{ threads }} user={{ service_name }} group={{ service_name }} \
18{% if python_path -%}
19 python-path={{ python_path }} \
20{% endif -%}
21 display-name=%{GROUP}
22 WSGIProcessGroup {{ service_name }}
23 WSGIScriptAlias / {{ script }}
24 WSGIApplicationGroup %{GLOBAL}
25 WSGIPassAuthorization On
26 <IfVersion >= 2.4>
27 ErrorLogFormat "%{cu}t %M"
28 </IfVersion>
29 ErrorLog /var/log/apache2/{{ service_name }}_error.log
30 CustomLog /var/log/apache2/{{ service_name }}_access.log combined
31
32 <Directory {{ usr_bin }}>
33 <IfVersion >= 2.4>
34 Require all granted
35 </IfVersion>
36 <IfVersion < 2.4>
37 Order allow,deny
38 Allow from all
39 </IfVersion>
40 </Directory>
41</VirtualHost>
42{% endif -%}
43
44{% if admin_port -%}
45<VirtualHost *:{{ admin_port }}>
46 WSGIDaemonProcess {{ service_name }}-admin processes={{ admin_processes }} threads={{ threads }} user={{ service_name }} group={{ service_name }} \
47{% if python_path -%}
48 python-path={{ python_path }} \
49{% endif -%}
50 display-name=%{GROUP}
51 WSGIProcessGroup {{ service_name }}-admin
52 WSGIScriptAlias / {{ admin_script }}
53 WSGIApplicationGroup %{GLOBAL}
54 WSGIPassAuthorization On
55 <IfVersion >= 2.4>
56 ErrorLogFormat "%{cu}t %M"
57 </IfVersion>
58 ErrorLog /var/log/apache2/{{ service_name }}_error.log
59 CustomLog /var/log/apache2/{{ service_name }}_access.log combined
60
61 <Directory {{ usr_bin }}>
62 <IfVersion >= 2.4>
63 Require all granted
64 </IfVersion>
65 <IfVersion < 2.4>
66 Order allow,deny
67 Allow from all
68 </IfVersion>
69 </Directory>
70</VirtualHost>
71{% endif -%}
72
73{% if public_port -%}
74<VirtualHost *:{{ public_port }}>
75 WSGIDaemonProcess {{ service_name }}-public processes={{ public_processes }} threads={{ threads }} user={{ service_name }} group={{ service_name }} \
76{% if python_path -%}
77 python-path={{ python_path }} \
78{% endif -%}
79 display-name=%{GROUP}
80 WSGIProcessGroup {{ service_name }}-public
81 WSGIScriptAlias / {{ public_script }}
82 WSGIApplicationGroup %{GLOBAL}
83 WSGIPassAuthorization On
84 <IfVersion >= 2.4>
85 ErrorLogFormat "%{cu}t %M"
86 </IfVersion>
87 ErrorLog /var/log/apache2/{{ service_name }}_error.log
88 CustomLog /var/log/apache2/{{ service_name }}_access.log combined
89
90 <Directory {{ usr_bin }}>
91 <IfVersion >= 2.4>
92 Require all granted
93 </IfVersion>
94 <IfVersion < 2.4>
95 Order allow,deny
96 Allow from all
97 </IfVersion>
98 </Directory>
99</VirtualHost>
100{% endif -%}
0101
=== modified file 'charmhelpers/contrib/openstack/templating.py'
--- charmhelpers/contrib/openstack/templating.py 2015-09-28 20:09:02 +0000
+++ charmhelpers/contrib/openstack/templating.py 2017-03-04 02:21:16 +0000
@@ -1,18 +1,16 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4#4# you may not use this file except in compliance with the License.
5# charm-helpers is free software: you can redistribute it and/or modify5# You may obtain a copy of the License at
6# it under the terms of the GNU Lesser General Public License version 3 as6#
7# published by the Free Software Foundation.7# http://www.apache.org/licenses/LICENSE-2.0
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17import os15import os
1816
@@ -30,7 +28,10 @@
30 from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions28 from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions
31except ImportError:29except ImportError:
32 apt_update(fatal=True)30 apt_update(fatal=True)
33 apt_install('python-jinja2', fatal=True)31 if six.PY2:
32 apt_install('python-jinja2', fatal=True)
33 else:
34 apt_install('python3-jinja2', fatal=True)
34 from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions35 from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions
3536
3637
@@ -209,7 +210,10 @@
209 # if this code is running, the object is created pre-install hook.210 # if this code is running, the object is created pre-install hook.
210 # jinja2 shouldn't get touched until the module is reloaded on next211 # jinja2 shouldn't get touched until the module is reloaded on next
211 # hook execution, with proper jinja2 bits successfully imported.212 # hook execution, with proper jinja2 bits successfully imported.
212 apt_install('python-jinja2')213 if six.PY2:
214 apt_install('python-jinja2')
215 else:
216 apt_install('python3-jinja2')
213217
214 def register(self, config_file, contexts):218 def register(self, config_file, contexts):
215 """219 """
216220
=== modified file 'charmhelpers/contrib/openstack/utils.py'
--- charmhelpers/contrib/openstack/utils.py 2015-09-28 20:09:02 +0000
+++ charmhelpers/contrib/openstack/utils.py 2017-03-04 02:21:16 +0000
@@ -1,18 +1,16 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4#4# you may not use this file except in compliance with the License.
5# charm-helpers is free software: you can redistribute it and/or modify5# You may obtain a copy of the License at
6# it under the terms of the GNU Lesser General Public License version 3 as6#
7# published by the Free Software Foundation.7# http://www.apache.org/licenses/LICENSE-2.0
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17# Common python helper functions used for OpenStack charms.15# Common python helper functions used for OpenStack charms.
18from collections import OrderedDict16from collections import OrderedDict
@@ -23,9 +21,14 @@
23import os21import os
24import sys22import sys
25import re23import re
24import itertools
25import functools
26import shutil
2627
27import six28import six
29import tempfile
28import traceback30import traceback
31import uuid
29import yaml32import yaml
3033
31from charmhelpers.contrib.network import ip34from charmhelpers.contrib.network import ip
@@ -40,11 +43,16 @@
40 config,43 config,
41 log as juju_log,44 log as juju_log,
42 charm_dir,45 charm_dir,
46 DEBUG,
43 INFO,47 INFO,
48 ERROR,
49 related_units,
44 relation_ids,50 relation_ids,
45 relation_set,51 relation_set,
52 service_name,
46 status_set,53 status_set,
47 hook_name54 hook_name,
55 application_version_set,
48)56)
4957
50from charmhelpers.contrib.storage.linux.lvm import (58from charmhelpers.contrib.storage.linux.lvm import (
@@ -56,6 +64,7 @@
56from charmhelpers.contrib.network.ip import (64from charmhelpers.contrib.network.ip import (
57 get_ipv6_addr,65 get_ipv6_addr,
58 is_ipv6,66 is_ipv6,
67 port_has_listener,
59)68)
6069
61from charmhelpers.contrib.python.packages import (70from charmhelpers.contrib.python.packages import (
@@ -63,10 +72,24 @@
63 pip_install,72 pip_install,
64)73)
6574
66from charmhelpers.core.host import lsb_release, mounts, umount75from charmhelpers.core.host import (
67from charmhelpers.fetch import apt_install, apt_cache, install_remote76 lsb_release,
77 mounts,
78 umount,
79 service_running,
80 service_pause,
81 service_resume,
82 restart_on_change_helper,
83)
84from charmhelpers.fetch import (
85 apt_install,
86 apt_cache,
87 install_remote,
88 get_upstream_version
89)
68from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk90from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk
69from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device91from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device
92from charmhelpers.contrib.openstack.exceptions import OSContextError
7093
71CLOUD_ARCHIVE_URL = "http://ubuntu-cloud.archive.canonical.com/ubuntu"94CLOUD_ARCHIVE_URL = "http://ubuntu-cloud.archive.canonical.com/ubuntu"
72CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA'95CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA'
@@ -84,6 +107,9 @@
84 ('utopic', 'juno'),107 ('utopic', 'juno'),
85 ('vivid', 'kilo'),108 ('vivid', 'kilo'),
86 ('wily', 'liberty'),109 ('wily', 'liberty'),
110 ('xenial', 'mitaka'),
111 ('yakkety', 'newton'),
112 ('zesty', 'ocata'),
87])113])
88114
89115
@@ -97,63 +123,118 @@
97 ('2014.2', 'juno'),123 ('2014.2', 'juno'),
98 ('2015.1', 'kilo'),124 ('2015.1', 'kilo'),
99 ('2015.2', 'liberty'),125 ('2015.2', 'liberty'),
126 ('2016.1', 'mitaka'),
127 ('2016.2', 'newton'),
128 ('2017.1', 'ocata'),
100])129])
101130
102# The ugly duckling131# The ugly duckling - must list releases oldest to newest
103SWIFT_CODENAMES = OrderedDict([132SWIFT_CODENAMES = OrderedDict([
104 ('1.4.3', 'diablo'),133 ('diablo',
105 ('1.4.8', 'essex'),134 ['1.4.3']),
106 ('1.7.4', 'folsom'),135 ('essex',
107 ('1.8.0', 'grizzly'),136 ['1.4.8']),
108 ('1.7.7', 'grizzly'),137 ('folsom',
109 ('1.7.6', 'grizzly'),138 ['1.7.4']),
110 ('1.10.0', 'havana'),139 ('grizzly',
111 ('1.9.1', 'havana'),140 ['1.7.6', '1.7.7', '1.8.0']),
112 ('1.9.0', 'havana'),141 ('havana',
113 ('1.13.1', 'icehouse'),142 ['1.9.0', '1.9.1', '1.10.0']),
114 ('1.13.0', 'icehouse'),143 ('icehouse',
115 ('1.12.0', 'icehouse'),144 ['1.11.0', '1.12.0', '1.13.0', '1.13.1']),
116 ('1.11.0', 'icehouse'),145 ('juno',
117 ('2.0.0', 'juno'),146 ['2.0.0', '2.1.0', '2.2.0']),
118 ('2.1.0', 'juno'),147 ('kilo',
119 ('2.2.0', 'juno'),148 ['2.2.1', '2.2.2']),
120 ('2.2.1', 'kilo'),149 ('liberty',
121 ('2.2.2', 'kilo'),150 ['2.3.0', '2.4.0', '2.5.0']),
122 ('2.3.0', 'liberty'),151 ('mitaka',
123 ('2.4.0', 'liberty'),152 ['2.5.0', '2.6.0', '2.7.0']),
153 ('newton',
154 ['2.8.0', '2.9.0', '2.10.0']),
155 ('ocata',
156 ['2.11.0', '2.12.0', '2.13.0']),
124])157])
125158
126# >= Liberty version->codename mapping159# >= Liberty version->codename mapping
127PACKAGE_CODENAMES = {160PACKAGE_CODENAMES = {
128 'nova-common': OrderedDict([161 'nova-common': OrderedDict([
129 ('12.0.0', 'liberty'),162 ('12', 'liberty'),
163 ('13', 'mitaka'),
164 ('14', 'newton'),
165 ('15', 'ocata'),
130 ]),166 ]),
131 'neutron-common': OrderedDict([167 'neutron-common': OrderedDict([
132 ('7.0.0', 'liberty'),168 ('7', 'liberty'),
169 ('8', 'mitaka'),
170 ('9', 'newton'),
171 ('10', 'ocata'),
133 ]),172 ]),
134 'cinder-common': OrderedDict([173 'cinder-common': OrderedDict([
135 ('7.0.0', 'liberty'),174 ('7', 'liberty'),
175 ('8', 'mitaka'),
176 ('9', 'newton'),
177 ('10', 'ocata'),
136 ]),178 ]),
137 'keystone': OrderedDict([179 'keystone': OrderedDict([
138 ('8.0.0', 'liberty'),180 ('8', 'liberty'),
181 ('9', 'mitaka'),
182 ('10', 'newton'),
183 ('11', 'ocata'),
139 ]),184 ]),
140 'horizon-common': OrderedDict([185 'horizon-common': OrderedDict([
141 ('8.0.0', 'liberty'),186 ('8', 'liberty'),
187 ('9', 'mitaka'),
188 ('10', 'newton'),
189 ('11', 'ocata'),
142 ]),190 ]),
143 'ceilometer-common': OrderedDict([191 'ceilometer-common': OrderedDict([
144 ('5.0.0', 'liberty'),192 ('5', 'liberty'),
193 ('6', 'mitaka'),
194 ('7', 'newton'),
195 ('8', 'ocata'),
145 ]),196 ]),
146 'heat-common': OrderedDict([197 'heat-common': OrderedDict([
147 ('5.0.0', 'liberty'),198 ('5', 'liberty'),
199 ('6', 'mitaka'),
200 ('7', 'newton'),
201 ('8', 'ocata'),
148 ]),202 ]),
149 'glance-common': OrderedDict([203 'glance-common': OrderedDict([
150 ('11.0.0', 'liberty'),204 ('11', 'liberty'),
205 ('12', 'mitaka'),
206 ('13', 'newton'),
207 ('14', 'ocata'),
151 ]),208 ]),
152 'openstack-dashboard': OrderedDict([209 'openstack-dashboard': OrderedDict([
153 ('8.0.0', 'liberty'),210 ('8', 'liberty'),
211 ('9', 'mitaka'),
212 ('10', 'newton'),
213 ('11', 'ocata'),
154 ]),214 ]),
155}215}
156216
217GIT_DEFAULT_REPOS = {
218 'requirements': 'git://github.com/openstack/requirements',
219 'cinder': 'git://github.com/openstack/cinder',
220 'glance': 'git://github.com/openstack/glance',
221 'horizon': 'git://github.com/openstack/horizon',
222 'keystone': 'git://github.com/openstack/keystone',
223 'networking-hyperv': 'git://github.com/openstack/networking-hyperv',
224 'neutron': 'git://github.com/openstack/neutron',
225 'neutron-fwaas': 'git://github.com/openstack/neutron-fwaas',
226 'neutron-lbaas': 'git://github.com/openstack/neutron-lbaas',
227 'neutron-vpnaas': 'git://github.com/openstack/neutron-vpnaas',
228 'nova': 'git://github.com/openstack/nova',
229}
230
231GIT_DEFAULT_BRANCHES = {
232 'liberty': 'stable/liberty',
233 'mitaka': 'stable/mitaka',
234 'newton': 'stable/newton',
235 'master': 'master',
236}
237
157DEFAULT_LOOPBACK_SIZE = '5G'238DEFAULT_LOOPBACK_SIZE = '5G'
158239
159240
@@ -213,6 +294,44 @@
213 error_out(e)294 error_out(e)
214295
215296
297def get_os_version_codename_swift(codename):
298 '''Determine OpenStack version number of swift from codename.'''
299 for k, v in six.iteritems(SWIFT_CODENAMES):
300 if k == codename:
301 return v[-1]
302 e = 'Could not derive swift version for '\
303 'codename: %s' % codename
304 error_out(e)
305
306
307def get_swift_codename(version):
308 '''Determine OpenStack codename that corresponds to swift version.'''
309 codenames = [k for k, v in six.iteritems(SWIFT_CODENAMES) if version in v]
310
311 if len(codenames) > 1:
312 # If more than one release codename contains this version we determine
313 # the actual codename based on the highest available install source.
314 for codename in reversed(codenames):
315 releases = UBUNTU_OPENSTACK_RELEASE
316 release = [k for k, v in six.iteritems(releases) if codename in v]
317 ret = subprocess.check_output(['apt-cache', 'policy', 'swift'])
318 if codename in ret or release[0] in ret:
319 return codename
320 elif len(codenames) == 1:
321 return codenames[0]
322
323 # NOTE: fallback - attempt to match with just major.minor version
324 match = re.match('^(\d+)\.(\d+)', version)
325 if match:
326 major_minor_version = match.group(0)
327 for codename, versions in six.iteritems(SWIFT_CODENAMES):
328 for release_version in versions:
329 if release_version.startswith(major_minor_version):
330 return codename
331
332 return None
333
334
216def get_os_codename_package(package, fatal=True):335def get_os_codename_package(package, fatal=True):
217 '''Derive OpenStack release codename from an installed package.'''336 '''Derive OpenStack release codename from an installed package.'''
218 import apt_pkg as apt337 import apt_pkg as apt
@@ -237,25 +356,30 @@
237 error_out(e)356 error_out(e)
238357
239 vers = apt.upstream_version(pkg.current_ver.ver_str)358 vers = apt.upstream_version(pkg.current_ver.ver_str)
240 match = re.match('^(\d+)\.(\d+)\.(\d+)', vers)359 if 'swift' in pkg.name:
360 # Fully x.y.z match for swift versions
361 match = re.match('^(\d+)\.(\d+)\.(\d+)', vers)
362 else:
363 # x.y match only for 20XX.X
364 # and ignore patch level for other packages
365 match = re.match('^(\d+)\.(\d+)', vers)
366
241 if match:367 if match:
242 vers = match.group(0)368 vers = match.group(0)
243369
370 # Generate a major version number for newer semantic
371 # versions of openstack projects
372 major_vers = vers.split('.')[0]
244 # >= Liberty independent project versions373 # >= Liberty indepe