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 independent project versions
245 if (package in PACKAGE_CODENAMES and374 if (package in PACKAGE_CODENAMES and
246 vers in PACKAGE_CODENAMES[package]):375 major_vers in PACKAGE_CODENAMES[package]):
247 return PACKAGE_CODENAMES[package][vers]376 return PACKAGE_CODENAMES[package][major_vers]
248 else:377 else:
249 # < Liberty co-ordinated project versions378 # < Liberty co-ordinated project versions
250 try:379 try:
251 if 'swift' in pkg.name:380 if 'swift' in pkg.name:
252 swift_vers = vers[:5]381 return get_swift_codename(vers)
253 if swift_vers not in SWIFT_CODENAMES:
254 # Deal with 1.10.0 upward
255 swift_vers = vers[:6]
256 return SWIFT_CODENAMES[swift_vers]
257 else:382 else:
258 vers = vers[:6]
259 return OPENSTACK_CODENAMES[vers]383 return OPENSTACK_CODENAMES[vers]
260 except KeyError:384 except KeyError:
261 if not fatal:385 if not fatal:
@@ -273,12 +397,14 @@
273397
274 if 'swift' in pkg:398 if 'swift' in pkg:
275 vers_map = SWIFT_CODENAMES399 vers_map = SWIFT_CODENAMES
400 for cname, version in six.iteritems(vers_map):
401 if cname == codename:
402 return version[-1]
276 else:403 else:
277 vers_map = OPENSTACK_CODENAMES404 vers_map = OPENSTACK_CODENAMES
278405 for version, cname in six.iteritems(vers_map):
279 for version, cname in six.iteritems(vers_map):406 if cname == codename:
280 if cname == codename:407 return version
281 return version
282 # e = "Could not determine OpenStack version for package: %s" % pkg408 # e = "Could not determine OpenStack version for package: %s" % pkg
283 # error_out(e)409 # error_out(e)
284410
@@ -286,29 +412,72 @@
286os_rel = None412os_rel = None
287413
288414
289def os_release(package, base='essex'):415def reset_os_release():
416 '''Unset the cached os_release version'''
417 global os_rel
418 os_rel = None
419
420
421def os_release(package, base='essex', reset_cache=False):
290 '''422 '''
291 Returns OpenStack release codename from a cached global.423 Returns OpenStack release codename from a cached global.
424
425 If reset_cache then unset the cached os_release version and return the
426 freshly determined version.
427
292 If the codename can not be determined from either an installed package or428 If the codename can not be determined from either an installed package or
293 the installation source, the earliest release supported by the charm should429 the installation source, the earliest release supported by the charm should
294 be returned.430 be returned.
295 '''431 '''
296 global os_rel432 global os_rel
433 if reset_cache:
434 reset_os_release()
297 if os_rel:435 if os_rel:
298 return os_rel436 return os_rel
299 os_rel = (get_os_codename_package(package, fatal=False) or437 os_rel = (git_os_codename_install_source(config('openstack-origin-git')) or
438 get_os_codename_package(package, fatal=False) or
300 get_os_codename_install_source(config('openstack-origin')) or439 get_os_codename_install_source(config('openstack-origin')) or
301 base)440 base)
302 return os_rel441 return os_rel
303442
304443
305def import_key(keyid):444def import_key(keyid):
306 cmd = "apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 " \445 key = keyid.strip()
307 "--recv-keys %s" % keyid446 if (key.startswith('-----BEGIN PGP PUBLIC KEY BLOCK-----') and
308 try:447 key.endswith('-----END PGP PUBLIC KEY BLOCK-----')):
309 subprocess.check_call(cmd.split(' '))448 juju_log("PGP key found (looks like ASCII Armor format)", level=DEBUG)
310 except subprocess.CalledProcessError:449 juju_log("Importing ASCII Armor PGP key", level=DEBUG)
311 error_out("Error importing repo key %s" % keyid)450 with tempfile.NamedTemporaryFile() as keyfile:
451 with open(keyfile.name, 'w') as fd:
452 fd.write(key)
453 fd.write("\n")
454
455 cmd = ['apt-key', 'add', keyfile.name]
456 try:
457 subprocess.check_call(cmd)
458 except subprocess.CalledProcessError:
459 error_out("Error importing PGP key '%s'" % key)
460 else:
461 juju_log("PGP key found (looks like Radix64 format)", level=DEBUG)
462 juju_log("Importing PGP key from keyserver", level=DEBUG)
463 cmd = ['apt-key', 'adv', '--keyserver',
464 'hkp://keyserver.ubuntu.com:80', '--recv-keys', key]
465 try:
466 subprocess.check_call(cmd)
467 except subprocess.CalledProcessError:
468 error_out("Error importing PGP key '%s'" % key)
469
470
471def get_source_and_pgp_key(input):
472 """Look for a pgp key ID or ascii-armor key in the given input."""
473 index = input.strip()
474 index = input.rfind('|')
475 if index < 0:
476 return input, None
477
478 key = input[index + 1:].strip('|')
479 source = input[:index]
480 return source, key
312481
313482
314def configure_installation_source(rel):483def configure_installation_source(rel):
@@ -320,16 +489,16 @@
320 with open('/etc/apt/sources.list.d/juju_deb.list', 'w') as f:489 with open('/etc/apt/sources.list.d/juju_deb.list', 'w') as f:
321 f.write(DISTRO_PROPOSED % ubuntu_rel)490 f.write(DISTRO_PROPOSED % ubuntu_rel)
322 elif rel[:4] == "ppa:":491 elif rel[:4] == "ppa:":
323 src = rel492 src, key = get_source_and_pgp_key(rel)
493 if key:
494 import_key(key)
495
324 subprocess.check_call(["add-apt-repository", "-y", src])496 subprocess.check_call(["add-apt-repository", "-y", src])
325 elif rel[:3] == "deb":497 elif rel[:3] == "deb":
326 l = len(rel.split('|'))498 src, key = get_source_and_pgp_key(rel)
327 if l == 2:499 if key:
328 src, key = rel.split('|')
329 juju_log("Importing PPA key from keyserver for %s" % src)
330 import_key(key)500 import_key(key)
331 elif l == 1:501
332 src = rel
333 with open('/etc/apt/sources.list.d/juju_deb.list', 'w') as f:502 with open('/etc/apt/sources.list.d/juju_deb.list', 'w') as f:
334 f.write(src)503 f.write(src)
335 elif rel[:6] == 'cloud:':504 elif rel[:6] == 'cloud:':
@@ -374,6 +543,15 @@
374 'liberty': 'trusty-updates/liberty',543 'liberty': 'trusty-updates/liberty',
375 'liberty/updates': 'trusty-updates/liberty',544 'liberty/updates': 'trusty-updates/liberty',
376 'liberty/proposed': 'trusty-proposed/liberty',545 'liberty/proposed': 'trusty-proposed/liberty',
546 'mitaka': 'trusty-updates/mitaka',
547 'mitaka/updates': 'trusty-updates/mitaka',
548 'mitaka/proposed': 'trusty-proposed/mitaka',
549 'newton': 'xenial-updates/newton',
550 'newton/updates': 'xenial-updates/newton',
551 'newton/proposed': 'xenial-proposed/newton',
552 'ocata': 'xenial-updates/ocata',
553 'ocata/updates': 'xenial-updates/ocata',
554 'ocata/proposed': 'xenial-proposed/ocata',
377 }555 }
378556
379 try:557 try:
@@ -441,11 +619,16 @@
441 cur_vers = get_os_version_package(package)619 cur_vers = get_os_version_package(package)
442 if "swift" in package:620 if "swift" in package:
443 codename = get_os_codename_install_source(src)621 codename = get_os_codename_install_source(src)
444 available_vers = get_os_version_codename(codename, SWIFT_CODENAMES)622 avail_vers = get_os_version_codename_swift(codename)
445 else:623 else:
446 available_vers = get_os_version_install_source(src)624 avail_vers = get_os_version_install_source(src)
447 apt.init()625 apt.init()
448 return apt.version_compare(available_vers, cur_vers) == 1626 if "swift" in package:
627 major_cur_vers = cur_vers.split('.', 1)[0]
628 major_avail_vers = avail_vers.split('.', 1)[0]
629 major_diff = apt.version_compare(major_avail_vers, major_cur_vers)
630 return avail_vers > cur_vers and (major_diff == 1 or major_diff == 0)
631 return apt.version_compare(avail_vers, cur_vers) == 1
449632
450633
451def ensure_block_device(block_device):634def ensure_block_device(block_device):
@@ -502,6 +685,7 @@
502 else:685 else:
503 zap_disk(block_device)686 zap_disk(block_device)
504687
688
505is_ip = ip.is_ip689is_ip = ip.is_ip
506ns_query = ip.ns_query690ns_query = ip.ns_query
507get_host_ip = ip.get_host_ip691get_host_ip = ip.get_host_ip
@@ -561,7 +745,86 @@
561 return config('openstack-origin-git') is not None745 return config('openstack-origin-git') is not None
562746
563747
564requirements_dir = None748def git_os_codename_install_source(projects_yaml):
749 """
750 Returns OpenStack codename of release being installed from source.
751 """
752 if git_install_requested():
753 projects = _git_yaml_load(projects_yaml)
754
755 if projects in GIT_DEFAULT_BRANCHES.keys():
756 if projects == 'master':
757 return 'ocata'
758 return projects
759
760 if 'release' in projects:
761 if projects['release'] == 'master':
762 return 'ocata'
763 return projects['release']
764
765 return None
766
767
768def git_default_repos(projects_yaml):
769 """
770 Returns default repos if a default openstack-origin-git value is specified.
771 """
772 service = service_name()
773 core_project = service
774
775 for default, branch in GIT_DEFAULT_BRANCHES.iteritems():
776 if projects_yaml == default:
777
778 # add the requirements repo first
779 repo = {
780 'name': 'requirements',
781 'repository': GIT_DEFAULT_REPOS['requirements'],
782 'branch': branch,
783 }
784 repos = [repo]
785
786 # neutron-* and nova-* charms require some additional repos
787 if service in ['neutron-api', 'neutron-gateway',
788 'neutron-openvswitch']:
789 core_project = 'neutron'
790 if service == 'neutron-api':
791 repo = {
792 'name': 'networking-hyperv',
793 'repository': GIT_DEFAULT_REPOS['networking-hyperv'],
794 'branch': branch,
795 }
796 repos.append(repo)
797 for project in ['neutron-fwaas', 'neutron-lbaas',
798 'neutron-vpnaas', 'nova']:
799 repo = {
800 'name': project,
801 'repository': GIT_DEFAULT_REPOS[project],
802 'branch': branch,
803 }
804 repos.append(repo)
805
806 elif service in ['nova-cloud-controller', 'nova-compute']:
807 core_project = 'nova'
808 repo = {
809 'name': 'neutron',
810 'repository': GIT_DEFAULT_REPOS['neutron'],
811 'branch': branch,
812 }
813 repos.append(repo)
814 elif service == 'openstack-dashboard':
815 core_project = 'horizon'
816
817 # finally add the current service's core project repo
818 repo = {
819 'name': core_project,
820 'repository': GIT_DEFAULT_REPOS[core_project],
821 'branch': branch,
822 }
823 repos.append(repo)
824
825 return yaml.dump(dict(repositories=repos, release=default))
826
827 return projects_yaml
565828
566829
567def _git_yaml_load(projects_yaml):830def _git_yaml_load(projects_yaml):
@@ -574,7 +837,10 @@
574 return yaml.load(projects_yaml)837 return yaml.load(projects_yaml)
575838
576839
577def git_clone_and_install(projects_yaml, core_project, depth=1):840requirements_dir = None
841
842
843def git_clone_and_install(projects_yaml, core_project):
578 """844 """
579 Clone/install all specified OpenStack repositories.845 Clone/install all specified OpenStack repositories.
580846
@@ -621,18 +887,31 @@
621 pip_install(p, upgrade=True, proxy=http_proxy,887 pip_install(p, upgrade=True, proxy=http_proxy,
622 venv=os.path.join(parent_dir, 'venv'))888 venv=os.path.join(parent_dir, 'venv'))
623889
890 constraints = None
624 for p in projects['repositories']:891 for p in projects['repositories']:
625 repo = p['repository']892 repo = p['repository']
626 branch = p['branch']893 branch = p['branch']
894 depth = '1'
895 if 'depth' in p.keys():
896 depth = p['depth']
627 if p['name'] == 'requirements':897 if p['name'] == 'requirements':
628 repo_dir = _git_clone_and_install_single(repo, branch, depth,898 repo_dir = _git_clone_and_install_single(repo, branch, depth,
629 parent_dir, http_proxy,899 parent_dir, http_proxy,
630 update_requirements=False)900 update_requirements=False)
631 requirements_dir = repo_dir901 requirements_dir = repo_dir
902 constraints = os.path.join(repo_dir, "upper-constraints.txt")
903 # upper-constraints didn't exist until after icehouse
904 if not os.path.isfile(constraints):
905 constraints = None
906 # use constraints unless project yaml sets use_constraints to false
907 if 'use_constraints' in projects.keys():
908 if not projects['use_constraints']:
909 constraints = None
632 else:910 else:
633 repo_dir = _git_clone_and_install_single(repo, branch, depth,911 repo_dir = _git_clone_and_install_single(repo, branch, depth,
634 parent_dir, http_proxy,912 parent_dir, http_proxy,
635 update_requirements=True)913 update_requirements=True,
914 constraints=constraints)
636915
637 os.environ = old_environ916 os.environ = old_environ
638917
@@ -654,6 +933,8 @@
654 if projects['repositories'][-1]['name'] != core_project:933 if projects['repositories'][-1]['name'] != core_project:
655 error_out('{} git repo must be specified last'.format(core_project))934 error_out('{} git repo must be specified last'.format(core_project))
656935
936 _git_ensure_key_exists('release', projects)
937
657938
658def _git_ensure_key_exists(key, keys):939def _git_ensure_key_exists(key, keys):
659 """940 """
@@ -664,23 +945,18 @@
664945
665946
666def _git_clone_and_install_single(repo, branch, depth, parent_dir, http_proxy,947def _git_clone_and_install_single(repo, branch, depth, parent_dir, http_proxy,
667 update_requirements):948 update_requirements, constraints=None):
668 """949 """
669 Clone and install a single git repository.950 Clone and install a single git repository.
670 """951 """
671 dest_dir = os.path.join(parent_dir, os.path.basename(repo))
672
673 if not os.path.exists(parent_dir):952 if not os.path.exists(parent_dir):
674 juju_log('Directory already exists at {}. '953 juju_log('Directory already exists at {}. '
675 'No need to create directory.'.format(parent_dir))954 'No need to create directory.'.format(parent_dir))
676 os.mkdir(parent_dir)955 os.mkdir(parent_dir)
677956
678 if not os.path.exists(dest_dir):957 juju_log('Cloning git repo: {}, branch: {}'.format(repo, branch))
679 juju_log('Cloning git repo: {}, branch: {}'.format(repo, branch))958 repo_dir = install_remote(
680 repo_dir = install_remote(repo, dest=parent_dir, branch=branch,959 repo, dest=parent_dir, branch=branch, depth=depth)
681 depth=depth)
682 else:
683 repo_dir = dest_dir
684960
685 venv = os.path.join(parent_dir, 'venv')961 venv = os.path.join(parent_dir, 'venv')
686962
@@ -692,9 +968,10 @@
692968
693 juju_log('Installing git repo from dir: {}'.format(repo_dir))969 juju_log('Installing git repo from dir: {}'.format(repo_dir))
694 if http_proxy:970 if http_proxy:
695 pip_install(repo_dir, proxy=http_proxy, venv=venv)971 pip_install(repo_dir, proxy=http_proxy, venv=venv,
972 constraints=constraints)
696 else:973 else:
697 pip_install(repo_dir, venv=venv)974 pip_install(repo_dir, venv=venv, constraints=constraints)
698975
699 return repo_dir976 return repo_dir
700977
@@ -763,6 +1040,114 @@
763 return None1040 return None
7641041
7651042
1043def git_generate_systemd_init_files(templates_dir):
1044 """
1045 Generate systemd init files.
1046
1047 Generates and installs systemd init units and script files based on the
1048 *.init.in files contained in the templates_dir directory.
1049
1050 This code is based on the openstack-pkg-tools package and its init
1051 script generation, which is used by the OpenStack packages.
1052 """
1053 for f in os.listdir(templates_dir):
1054 # Create the init script and systemd unit file from the template
1055 if f.endswith(".init.in"):
1056 init_in_file = f
1057 init_file = f[:-8]
1058 service_file = "{}.service".format(init_file)
1059
1060 init_in_source = os.path.join(templates_dir, init_in_file)
1061 init_source = os.path.join(templates_dir, init_file)
1062 service_source = os.path.join(templates_dir, service_file)
1063
1064 init_dest = os.path.join('/etc/init.d', init_file)
1065 service_dest = os.path.join('/lib/systemd/system', service_file)
1066
1067 shutil.copyfile(init_in_source, init_source)
1068 with open(init_source, 'a') as outfile:
1069 template = '/usr/share/openstack-pkg-tools/init-script-template'
1070 with open(template) as infile:
1071 outfile.write('\n\n{}'.format(infile.read()))
1072
1073 cmd = ['pkgos-gen-systemd-unit', init_in_source]
1074 subprocess.check_call(cmd)
1075
1076 if os.path.exists(init_dest):
1077 os.remove(init_dest)
1078 if os.path.exists(service_dest):
1079 os.remove(service_dest)
1080 shutil.copyfile(init_source, init_dest)
1081 shutil.copyfile(service_source, service_dest)
1082 os.chmod(init_dest, 0o755)
1083
1084 for f in os.listdir(templates_dir):
1085 # If there's a service.in file, use it instead of the generated one
1086 if f.endswith(".service.in"):
1087 service_in_file = f
1088 service_file = f[:-3]
1089
1090 service_in_source = os.path.join(templates_dir, service_in_file)
1091 service_source = os.path.join(templates_dir, service_file)
1092 service_dest = os.path.join('/lib/systemd/system', service_file)
1093
1094 shutil.copyfile(service_in_source, service_source)
1095
1096 if os.path.exists(service_dest):
1097 os.remove(service_dest)
1098 shutil.copyfile(service_source, service_dest)
1099
1100 for f in os.listdir(templates_dir):
1101 # Generate the systemd unit if there's no existing .service.in
1102 if f.endswith(".init.in"):
1103 init_in_file = f
1104 init_file = f[:-8]
1105 service_in_file = "{}.service.in".format(init_file)
1106 service_file = "{}.service".format(init_file)
1107
1108 init_in_source = os.path.join(templates_dir, init_in_file)
1109 service_in_source = os.path.join(templates_dir, service_in_file)
1110 service_source = os.path.join(templates_dir, service_file)
1111 service_dest = os.path.join('/lib/systemd/system', service_file)
1112
1113 if not os.path.exists(service_in_source):
1114 cmd = ['pkgos-gen-systemd-unit', init_in_source]
1115 subprocess.check_call(cmd)
1116
1117 if os.path.exists(service_dest):
1118 os.remove(service_dest)
1119 shutil.copyfile(service_source, service_dest)
1120
1121
1122def git_determine_usr_bin():
1123 """Return the /usr/bin path for Apache2 config.
1124
1125 The /usr/bin path will be located in the virtualenv if the charm
1126 is configured to deploy from source.
1127 """
1128 if git_install_requested():
1129 projects_yaml = config('openstack-origin-git')
1130 projects_yaml = git_default_repos(projects_yaml)
1131 return os.path.join(git_pip_venv_dir(projects_yaml), 'bin')
1132 else:
1133 return '/usr/bin'
1134
1135
1136def git_determine_python_path():
1137 """Return the python-path for Apache2 config.
1138
1139 Returns 'None' unless the charm is configured to deploy from source,
1140 in which case the path of the virtualenv's site-packages is returned.
1141 """
1142 if git_install_requested():
1143 projects_yaml = config('openstack-origin-git')
1144 projects_yaml = git_default_repos(projects_yaml)
1145 return os.path.join(git_pip_venv_dir(projects_yaml),
1146 'lib/python2.7/site-packages')
1147 else:
1148 return None
1149
1150
766def os_workload_status(configs, required_interfaces, charm_func=None):1151def os_workload_status(configs, required_interfaces, charm_func=None):
767 """1152 """
768 Decorator to set workload status based on complete contexts1153 Decorator to set workload status based on complete contexts
@@ -779,56 +1164,155 @@
779 return wrap1164 return wrap
7801165
7811166
782def set_os_workload_status(configs, required_interfaces, charm_func=None):1167def set_os_workload_status(configs, required_interfaces, charm_func=None,
783 """1168 services=None, ports=None):
784 Set workload status based on complete contexts.1169 """Set the state of the workload status for the charm.
785 status-set missing or incomplete contexts1170
786 and juju-log details of missing required data.1171 This calls _determine_os_workload_status() to get the new state, message
787 charm_func is a charm specific function to run checking1172 and sets the status using status_set()
788 for charm specific requirements such as a VIP setting.1173
789 """1174 @param configs: a templating.OSConfigRenderer() object
790 incomplete_rel_data = incomplete_relation_data(configs, required_interfaces)1175 @param required_interfaces: {generic: [specific, specific2, ...]}
791 state = 'active'1176 @param charm_func: a callable function that returns state, message. The
792 missing_relations = []1177 signature is charm_func(configs) -> (state, message)
793 incomplete_relations = []1178 @param services: list of strings OR dictionary specifying services/ports
1179 @param ports: OPTIONAL list of port numbers.
1180 @returns state, message: the new workload status, user message
1181 """
1182 state, message = _determine_os_workload_status(
1183 configs, required_interfaces, charm_func, services, ports)
1184 status_set(state, message)
1185
1186
1187def _determine_os_workload_status(
1188 configs, required_interfaces, charm_func=None,
1189 services=None, ports=None):
1190 """Determine the state of the workload status for the charm.
1191
1192 This function returns the new workload status for the charm based
1193 on the state of the interfaces, the paused state and whether the
1194 services are actually running and any specified ports are open.
1195
1196 This checks:
1197
1198 1. if the unit should be paused, that it is actually paused. If so the
1199 state is 'maintenance' + message, else 'broken'.
1200 2. that the interfaces/relations are complete. If they are not then
1201 it sets the state to either 'broken' or 'waiting' and an appropriate
1202 message.
1203 3. If all the relation data is set, then it checks that the actual
1204 services really are running. If not it sets the state to 'broken'.
1205
1206 If everything is okay then the state returns 'active'.
1207
1208 @param configs: a templating.OSConfigRenderer() object
1209 @param required_interfaces: {generic: [specific, specific2, ...]}
1210 @param charm_func: a callable function that returns state, message. The
1211 signature is charm_func(configs) -> (state, message)
1212 @param services: list of strings OR dictionary specifying services/ports
1213 @param ports: OPTIONAL list of port numbers.
1214 @returns state, message: the new workload status, user message
1215 """
1216 state, message = _ows_check_if_paused(services, ports)
1217
1218 if state is None:
1219 state, message = _ows_check_generic_interfaces(
1220 configs, required_interfaces)
1221
1222 if state != 'maintenance' and charm_func:
1223 # _ows_check_charm_func() may modify the state, message
1224 state, message = _ows_check_charm_func(
1225 state, message, lambda: charm_func(configs))
1226
1227 if state is None:
1228 state, message = _ows_check_services_running(services, ports)
1229
1230 if state is None:
1231 state = 'active'
1232 message = "Unit is ready"
1233 juju_log(message, 'INFO')
1234
1235 return state, message
1236
1237
1238def _ows_check_if_paused(services=None, ports=None):
1239 """Check if the unit is supposed to be paused, and if so check that the
1240 services/ports (if passed) are actually stopped/not being listened to.
1241
1242 if the unit isn't supposed to be paused, just return None, None
1243
1244 @param services: OPTIONAL services spec or list of service names.
1245 @param ports: OPTIONAL list of port numbers.
1246 @returns state, message or None, None
1247 """
1248 if is_unit_paused_set():
1249 state, message = check_actually_paused(services=services,
1250 ports=ports)
1251 if state is None:
1252 # we're paused okay, so set maintenance and return
1253 state = "maintenance"
1254 message = "Paused. Use 'resume' action to resume normal service."
1255 return state, message
1256 return None, None
1257
1258
1259def _ows_check_generic_interfaces(configs, required_interfaces):
1260 """Check the complete contexts to determine the workload status.
1261
1262 - Checks for missing or incomplete contexts
1263 - juju log details of missing required data.
1264 - determines the correct workload status
1265 - creates an appropriate message for status_set(...)
1266
1267 if there are no problems then the function returns None, None
1268
1269 @param configs: a templating.OSConfigRenderer() object
1270 @params required_interfaces: {generic_interface: [specific_interface], }
1271 @returns state, message or None, None
1272 """
1273 incomplete_rel_data = incomplete_relation_data(configs,
1274 required_interfaces)
1275 state = None
794 message = None1276 message = None
795 charm_state = None1277 missing_relations = set()
796 charm_message = None1278 incomplete_relations = set()
7971279
798 for generic_interface in incomplete_rel_data.keys():1280 for generic_interface, relations_states in incomplete_rel_data.items():
799 related_interface = None1281 related_interface = None
800 missing_data = {}1282 missing_data = {}
801 # Related or not?1283 # Related or not?
802 for interface in incomplete_rel_data[generic_interface]:1284 for interface, relation_state in relations_states.items():
803 if incomplete_rel_data[generic_interface][interface].get('related'):1285 if relation_state.get('related'):
804 related_interface = interface1286 related_interface = interface
805 missing_data = incomplete_rel_data[generic_interface][interface].get('missing_data')1287 missing_data = relation_state.get('missing_data')
806 # No relation ID for the generic_interface1288 break
1289 # No relation ID for the generic_interface?
807 if not related_interface:1290 if not related_interface:
808 juju_log("{} relation is missing and must be related for "1291 juju_log("{} relation is missing and must be related for "
809 "functionality. ".format(generic_interface), 'WARN')1292 "functionality. ".format(generic_interface), 'WARN')
810 state = 'blocked'1293 state = 'blocked'
811 if generic_interface not in missing_relations:1294 missing_relations.add(generic_interface)
812 missing_relations.append(generic_interface)
813 else:1295 else:
814 # Relation ID exists but no related unit1296 # Relation ID eists but no related unit
815 if not missing_data:1297 if not missing_data:
816 # Edge case relation ID exists but departing1298 # Edge case - relation ID exists but departings
817 if ('departed' in hook_name() or 'broken' in hook_name()) \1299 _hook_name = hook_name()
818 and related_interface in hook_name():1300 if (('departed' in _hook_name or 'broken' in _hook_name) and
1301 related_interface in _hook_name):
819 state = 'blocked'1302 state = 'blocked'
820 if generic_interface not in missing_relations:1303 missing_relations.add(generic_interface)
821 missing_relations.append(generic_interface)
822 juju_log("{} relation's interface, {}, "1304 juju_log("{} relation's interface, {}, "
823 "relationship is departed or broken "1305 "relationship is departed or broken "
824 "and is required for functionality."1306 "and is required for functionality."
825 "".format(generic_interface, related_interface), "WARN")1307 "".format(generic_interface, related_interface),
1308 "WARN")
826 # Normal case relation ID exists but no related unit1309 # Normal case relation ID exists but no related unit
827 # (joining)1310 # (joining)
828 else:1311 else:
829 juju_log("{} relations's interface, {}, is related but has "1312 juju_log("{} relations's interface, {}, is related but has"
830 "no units in the relation."1313 " no units in the relation."
831 "".format(generic_interface, related_interface), "INFO")1314 "".format(generic_interface, related_interface),
1315 "INFO")
832 # Related unit exists and data missing on the relation1316 # Related unit exists and data missing on the relation
833 else:1317 else:
834 juju_log("{} relation's interface, {}, is related awaiting "1318 juju_log("{} relation's interface, {}, is related awaiting "
@@ -837,9 +1321,8 @@
837 ", ".join(missing_data)), "INFO")1321 ", ".join(missing_data)), "INFO")
838 if state != 'blocked':1322 if state != 'blocked':
839 state = 'waiting'1323 state = 'waiting'
840 if generic_interface not in incomplete_relations \1324 if generic_interface not in missing_relations:
841 and generic_interface not in missing_relations:1325 incomplete_relations.add(generic_interface)
842 incomplete_relations.append(generic_interface)
8431326
844 if missing_relations:1327 if missing_relations:
845 message = "Missing relations: {}".format(", ".join(missing_relations))1328 message = "Missing relations: {}".format(", ".join(missing_relations))
@@ -852,22 +1335,175 @@
852 "".format(", ".join(incomplete_relations))1335 "".format(", ".join(incomplete_relations))
853 state = 'waiting'1336 state = 'waiting'
8541337
855 # Run charm specific checks1338 return state, message
856 if charm_func:1339
857 charm_state, charm_message = charm_func(configs)1340
1341def _ows_check_charm_func(state, message, charm_func_with_configs):
1342 """Run a custom check function for the charm to see if it wants to
1343 change the state. This is only run if not in 'maintenance' and
1344 tests to see if the new state is more important that the previous
1345 one determined by the interfaces/relations check.
1346
1347 @param state: the previously determined state so far.
1348 @param message: the user orientated message so far.
1349 @param charm_func: a callable function that returns state, message
1350 @returns state, message strings.
1351 """
1352 if charm_func_with_configs:
1353 charm_state, charm_message = charm_func_with_configs()
858 if charm_state != 'active' and charm_state != 'unknown':1354 if charm_state != 'active' and charm_state != 'unknown':
859 state = workload_state_compare(state, charm_state)1355 state = workload_state_compare(state, charm_state)
860 if message:1356 if message:
861 message = "{} {}".format(message, charm_message)1357 charm_message = charm_message.replace("Incomplete relations: ",
1358 "")
1359 message = "{}, {}".format(message, charm_message)
862 else:1360 else:
863 message = charm_message1361 message = charm_message
8641362 return state, message
865 # Set to active if all requirements have been met1363
866 if state == 'active':1364
867 message = "Unit is ready"1365def _ows_check_services_running(services, ports):
868 juju_log(message, "INFO")1366 """Check that the services that should be running are actually running
8691367 and that any ports specified are being listened to.
870 status_set(state, message)1368
1369 @param services: list of strings OR dictionary specifying services/ports
1370 @param ports: list of ports
1371 @returns state, message: strings or None, None
1372 """
1373 messages = []
1374 state = None
1375 if services is not None:
1376 services = _extract_services_list_helper(services)
1377 services_running, running = _check_running_services(services)
1378 if not all(running):
1379 messages.append(
1380 "Services not running that should be: {}"
1381 .format(", ".join(_filter_tuples(services_running, False))))
1382 state = 'blocked'
1383 # also verify that the ports that should be open are open
1384 # NB, that ServiceManager objects only OPTIONALLY have ports
1385 map_not_open, ports_open = (
1386 _check_listening_on_services_ports(services))
1387 if not all(ports_open):
1388 # find which service has missing ports. They are in service
1389 # order which makes it a bit easier.
1390 message_parts = {service: ", ".join([str(v) for v in open_ports])
1391 for service, open_ports in map_not_open.items()}
1392 message = ", ".join(
1393 ["{}: [{}]".format(s, sp) for s, sp in message_parts.items()])
1394 messages.append(
1395 "Services with ports not open that should be: {}"
1396 .format(message))
1397 state = 'blocked'
1398
1399 if ports is not None:
1400 # and we can also check ports which we don't know the service for
1401 ports_open, ports_open_bools = _check_listening_on_ports_list(ports)
1402 if not all(ports_open_bools):
1403 messages.append(
1404 "Ports which should be open, but are not: {}"
1405 .format(", ".join([str(p) for p, v in ports_open
1406 if not v])))
1407 state = 'blocked'
1408
1409 if state is not None:
1410 message = "; ".join(messages)
1411 return state, message
1412
1413 return None, None
1414
1415
1416def _extract_services_list_helper(services):
1417 """Extract a OrderedDict of {service: [ports]} of the supplied services
1418 for use by the other functions.
1419
1420 The services object can either be:
1421 - None : no services were passed (an empty dict is returned)
1422 - a list of strings
1423 - A dictionary (optionally OrderedDict) {service_name: {'service': ..}}
1424 - An array of [{'service': service_name, ...}, ...]
1425
1426 @param services: see above
1427 @returns OrderedDict(service: [ports], ...)
1428 """
1429 if services is None:
1430 return {}
1431 if isinstance(services, dict):
1432 services = services.values()
1433 # either extract the list of services from the dictionary, or if
1434 # it is a simple string, use that. i.e. works with mixed lists.
1435 _s = OrderedDict()
1436 for s in services:
1437 if isinstance(s, dict) and 'service' in s:
1438 _s[s['service']] = s.get('ports', [])
1439 if isinstance(s, str):
1440 _s[s] = []
1441 return _s
1442
1443
1444def _check_running_services(services):
1445 """Check that the services dict provided is actually running and provide
1446 a list of (service, boolean) tuples for each service.
1447
1448 Returns both a zipped list of (service, boolean) and a list of booleans
1449 in the same order as the services.
1450
1451 @param services: OrderedDict of strings: [ports], one for each service to
1452 check.
1453 @returns [(service, boolean), ...], : results for checks
1454 [boolean] : just the result of the service checks
1455 """
1456 services_running = [service_running(s) for s in services]
1457 return list(zip(services, services_running)), services_running
1458
1459
1460def _check_listening_on_services_ports(services, test=False):
1461 """Check that the unit is actually listening (has the port open) on the
1462 ports that the service specifies are open. If test is True then the
1463 function returns the services with ports that are open rather than
1464 closed.
1465
1466 Returns an OrderedDict of service: ports and a list of booleans
1467
1468 @param services: OrderedDict(service: [port, ...], ...)
1469 @param test: default=False, if False, test for closed, otherwise open.
1470 @returns OrderedDict(service: [port-not-open, ...]...), [boolean]
1471 """
1472 test = not(not(test)) # ensure test is True or False
1473 all_ports = list(itertools.chain(*services.values()))
1474 ports_states = [port_has_listener('0.0.0.0', p) for p in all_ports]
1475 map_ports = OrderedDict()
1476 matched_ports = [p for p, opened in zip(all_ports, ports_states)
1477 if opened == test] # essentially opened xor test
1478 for service, ports in services.items():
1479 set_ports = set(ports).intersection(matched_ports)
1480 if set_ports:
1481 map_ports[service] = set_ports
1482 return map_ports, ports_states
1483
1484
1485def _check_listening_on_ports_list(ports):
1486 """Check that the ports list given are being listened to
1487
1488 Returns a list of ports being listened to and a list of the
1489 booleans.
1490
1491 @param ports: LIST or port numbers.
1492 @returns [(port_num, boolean), ...], [boolean]
1493 """
1494 ports_open = [port_has_listener('0.0.0.0', p) for p in ports]
1495 return zip(ports, ports_open), ports_open
1496
1497
1498def _filter_tuples(services_states, state):
1499 """Return a simple list from a list of tuples according to the condition
1500
1501 @param services_states: LIST of (string, boolean): service and running
1502 state.
1503 @param state: Boolean to match the tuple against.
1504 @returns [LIST of strings] that matched the tuple RHS.
1505 """
1506 return [s for s, b in services_states if b == state]
8711507
8721508
873def workload_state_compare(current_workload_state, workload_state):1509def workload_state_compare(current_workload_state, workload_state):
@@ -892,8 +1528,7 @@
8921528
8931529
894def incomplete_relation_data(configs, required_interfaces):1530def incomplete_relation_data(configs, required_interfaces):
895 """1531 """Check complete contexts against required_interfaces
896 Check complete contexts against required_interfaces
897 Return dictionary of incomplete relation data.1532 Return dictionary of incomplete relation data.
8981533
899 configs is an OSConfigRenderer object with configs registered1534 configs is an OSConfigRenderer object with configs registered
@@ -918,19 +1553,13 @@
918 'shared-db': {'related': True}}}1553 'shared-db': {'related': True}}}
919 """1554 """
920 complete_ctxts = configs.complete_contexts()1555 complete_ctxts = configs.complete_contexts()
921 incomplete_relations = []1556 incomplete_relations = [
922 for svc_type in required_interfaces.keys():1557 svc_type
923 # Avoid duplicates1558 for svc_type, interfaces in required_interfaces.items()
924 found_ctxt = False1559 if not set(interfaces).intersection(complete_ctxts)]
925 for interface in required_interfaces[svc_type]:1560 return {
926 if interface in complete_ctxts:1561 i: configs.get_incomplete_context_data(required_interfaces[i])
927 found_ctxt = True1562 for i in incomplete_relations}
928 if not found_ctxt:
929 incomplete_relations.append(svc_type)
930 incomplete_context_data = {}
931 for i in incomplete_relations:
932 incomplete_context_data[i] = configs.get_incomplete_context_data(required_interfaces[i])
933 return incomplete_context_data
9341563
9351564
936def do_action_openstack_upgrade(package, upgrade_callback, configs):1565def do_action_openstack_upgrade(package, upgrade_callback, configs):
@@ -975,3 +1604,386 @@
975 action_set({'outcome': 'no upgrade available.'})1604 action_set({'outcome': 'no upgrade available.'})
9761605
977 return ret1606 return ret
1607
1608
1609def remote_restart(rel_name, remote_service=None):
1610 trigger = {
1611 'restart-trigger': str(uuid.uuid4()),
1612 }
1613 if remote_service:
1614 trigger['remote-service'] = remote_service
1615 for rid in relation_ids(rel_name):
1616 # This subordinate can be related to two seperate services using
1617 # different subordinate relations so only issue the restart if
1618 # the principle is conencted down the relation we think it is
1619 if related_units(relid=rid):
1620 relation_set(relation_id=rid,
1621 relation_settings=trigger,
1622 )
1623
1624
1625def check_actually_paused(services=None, ports=None):
1626 """Check that services listed in the services object and and ports
1627 are actually closed (not listened to), to verify that the unit is
1628 properly paused.
1629
1630 @param services: See _extract_services_list_helper
1631 @returns status, : string for status (None if okay)
1632 message : string for problem for status_set
1633 """
1634 state = None
1635 message = None
1636 messages = []
1637 if services is not None:
1638 services = _extract_services_list_helper(services)
1639 services_running, services_states = _check_running_services(services)
1640 if any(services_states):
1641 # there shouldn't be any running so this is a problem
1642 messages.append("these services running: {}"
1643 .format(", ".join(
1644 _filter_tuples(services_running, True))))
1645 state = "blocked"
1646 ports_open, ports_open_bools = (
1647 _check_listening_on_services_ports(services, True))
1648 if any(ports_open_bools):
1649 message_parts = {service: ", ".join([str(v) for v in open_ports])
1650 for service, open_ports in ports_open.items()}
1651 message = ", ".join(
1652 ["{}: [{}]".format(s, sp) for s, sp in message_parts.items()])
1653 messages.append(
1654 "these service:ports are open: {}".format(message))
1655 state = 'blocked'
1656 if ports is not None:
1657 ports_open, bools = _check_listening_on_ports_list(ports)
1658 if any(bools):
1659 messages.append(
1660 "these ports which should be closed, but are open: {}"
1661 .format(", ".join([str(p) for p, v in ports_open if v])))
1662 state = 'blocked'
1663 if messages:
1664 message = ("Services should be paused but {}"
1665 .format(", ".join(messages)))
1666 return state, message
1667
1668
1669def set_unit_paused():
1670 """Set the unit to a paused state in the local kv() store.
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: