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
- Trusty Tahr (14.04)
- sync-charmhelpers-for-add-apt-repo-retry
- Merge into 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 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
OpenStack Charmers | Pending | ||
Review via email: mp+318976@code.launchpad.net |
Commit message
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-
+++ charm-helpers-
@@ -3,6 +3,7 @@
include:
- core
- fetch
+ - osplatform
- payload
- contrib.
- contrib.
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
1 | === modified file 'charm-helpers-sync.yaml' | |||
2 | --- charm-helpers-sync.yaml 2015-09-28 20:36:18 +0000 | |||
3 | +++ charm-helpers-sync.yaml 2017-03-04 02:21:16 +0000 | |||
4 | @@ -3,6 +3,7 @@ | |||
5 | 3 | include: | 3 | include: |
6 | 4 | - core | 4 | - core |
7 | 5 | - fetch | 5 | - fetch |
8 | 6 | - osplatform | ||
9 | 6 | - payload | 7 | - payload |
10 | 7 | - contrib.openstack|inc=* | 8 | - contrib.openstack|inc=* |
11 | 8 | - contrib.charmsupport | 9 | - contrib.charmsupport |
12 | 9 | 10 | ||
13 | === modified file 'charmhelpers/__init__.py' | |||
14 | --- charmhelpers/__init__.py 2015-09-28 20:09:02 +0000 | |||
15 | +++ charmhelpers/__init__.py 2017-03-04 02:21:16 +0000 | |||
16 | @@ -1,18 +1,16 @@ | |||
17 | 1 | # Copyright 2014-2015 Canonical Limited. | 1 | # Copyright 2014-2015 Canonical Limited. |
18 | 2 | # | 2 | # |
32 | 3 | # This file is part of charm-helpers. | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
33 | 4 | # | 4 | # you may not use this file except in compliance with the License. |
34 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | 5 | # You may obtain a copy of the License at |
35 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | 6 | # |
36 | 7 | # published by the Free Software Foundation. | 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
37 | 8 | # | 8 | # |
38 | 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 |
39 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
40 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
41 | 12 | # GNU Lesser General Public License for more details. | 12 | # See the License for the specific language governing permissions and |
42 | 13 | # | 13 | # limitations under the License. |
30 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
31 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
43 | 16 | 14 | ||
44 | 17 | # Bootstrap charm-helpers, installing its dependencies if necessary using | 15 | # Bootstrap charm-helpers, installing its dependencies if necessary using |
45 | 18 | # only standard libraries. | 16 | # only standard libraries. |
46 | 19 | 17 | ||
47 | === modified file 'charmhelpers/contrib/__init__.py' | |||
48 | --- charmhelpers/contrib/__init__.py 2015-09-28 20:09:02 +0000 | |||
49 | +++ charmhelpers/contrib/__init__.py 2017-03-04 02:21:16 +0000 | |||
50 | @@ -1,15 +1,13 @@ | |||
51 | 1 | # Copyright 2014-2015 Canonical Limited. | 1 | # Copyright 2014-2015 Canonical Limited. |
52 | 2 | # | 2 | # |
66 | 3 | # This file is part of charm-helpers. | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
67 | 4 | # | 4 | # you may not use this file except in compliance with the License. |
68 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | 5 | # You may obtain a copy of the License at |
69 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | 6 | # |
70 | 7 | # published by the Free Software Foundation. | 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
71 | 8 | # | 8 | # |
72 | 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 |
73 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
74 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
75 | 12 | # GNU Lesser General Public License for more details. | 12 | # See the License for the specific language governing permissions and |
76 | 13 | # | 13 | # limitations under the License. |
64 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
65 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
77 | 16 | 14 | ||
78 | === modified file 'charmhelpers/contrib/charmsupport/__init__.py' | |||
79 | --- charmhelpers/contrib/charmsupport/__init__.py 2015-09-28 20:09:02 +0000 | |||
80 | +++ charmhelpers/contrib/charmsupport/__init__.py 2017-03-04 02:21:16 +0000 | |||
81 | @@ -1,15 +1,13 @@ | |||
82 | 1 | # Copyright 2014-2015 Canonical Limited. | 1 | # Copyright 2014-2015 Canonical Limited. |
83 | 2 | # | 2 | # |
97 | 3 | # This file is part of charm-helpers. | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
98 | 4 | # | 4 | # you may not use this file except in compliance with the License. |
99 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | 5 | # You may obtain a copy of the License at |
100 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | 6 | # |
101 | 7 | # published by the Free Software Foundation. | 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
102 | 8 | # | 8 | # |
103 | 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 |
104 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
105 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
106 | 12 | # GNU Lesser General Public License for more details. | 12 | # See the License for the specific language governing permissions and |
107 | 13 | # | 13 | # limitations under the License. |
95 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
96 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
108 | 16 | 14 | ||
109 | === modified file 'charmhelpers/contrib/charmsupport/nrpe.py' | |||
110 | --- charmhelpers/contrib/charmsupport/nrpe.py 2015-09-28 20:09:02 +0000 | |||
111 | +++ charmhelpers/contrib/charmsupport/nrpe.py 2017-03-04 02:21:16 +0000 | |||
112 | @@ -1,18 +1,16 @@ | |||
113 | 1 | # Copyright 2014-2015 Canonical Limited. | 1 | # Copyright 2014-2015 Canonical Limited. |
114 | 2 | # | 2 | # |
128 | 3 | # This file is part of charm-helpers. | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
129 | 4 | # | 4 | # you may not use this file except in compliance with the License. |
130 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | 5 | # You may obtain a copy of the License at |
131 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | 6 | # |
132 | 7 | # published by the Free Software Foundation. | 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
133 | 8 | # | 8 | # |
134 | 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 |
135 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
136 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
137 | 12 | # GNU Lesser General Public License for more details. | 12 | # See the License for the specific language governing permissions and |
138 | 13 | # | 13 | # limitations under the License. |
126 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
127 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
139 | 16 | 14 | ||
140 | 17 | """Compatibility with the nrpe-external-master charm""" | 15 | """Compatibility with the nrpe-external-master charm""" |
141 | 18 | # Copyright 2012 Canonical Ltd. | 16 | # Copyright 2012 Canonical Ltd. |
142 | @@ -40,6 +38,7 @@ | |||
143 | 40 | ) | 38 | ) |
144 | 41 | 39 | ||
145 | 42 | from charmhelpers.core.host import service | 40 | from charmhelpers.core.host import service |
146 | 41 | from charmhelpers.core import host | ||
147 | 43 | 42 | ||
148 | 44 | # This module adds compatibility with the nrpe-external-master and plain nrpe | 43 | # This module adds compatibility with the nrpe-external-master and plain nrpe |
149 | 45 | # subordinate charms. To use it in your charm: | 44 | # subordinate charms. To use it in your charm: |
150 | @@ -110,6 +109,13 @@ | |||
151 | 110 | # def local_monitors_relation_changed(): | 109 | # def local_monitors_relation_changed(): |
152 | 111 | # update_nrpe_config() | 110 | # update_nrpe_config() |
153 | 112 | # | 111 | # |
154 | 112 | # 4.a If your charm is a subordinate charm set primary=False | ||
155 | 113 | # | ||
156 | 114 | # from charmsupport.nrpe import NRPE | ||
157 | 115 | # (...) | ||
158 | 116 | # def update_nrpe_config(): | ||
159 | 117 | # nrpe_compat = NRPE(primary=False) | ||
160 | 118 | # | ||
161 | 113 | # 5. ln -s hooks.py nrpe-external-master-relation-changed | 119 | # 5. ln -s hooks.py nrpe-external-master-relation-changed |
162 | 114 | # ln -s hooks.py local-monitors-relation-changed | 120 | # ln -s hooks.py local-monitors-relation-changed |
163 | 115 | 121 | ||
164 | @@ -148,6 +154,13 @@ | |||
165 | 148 | self.description = description | 154 | self.description = description |
166 | 149 | self.check_cmd = self._locate_cmd(check_cmd) | 155 | self.check_cmd = self._locate_cmd(check_cmd) |
167 | 150 | 156 | ||
168 | 157 | def _get_check_filename(self): | ||
169 | 158 | return os.path.join(NRPE.nrpe_confdir, '{}.cfg'.format(self.command)) | ||
170 | 159 | |||
171 | 160 | def _get_service_filename(self, hostname): | ||
172 | 161 | return os.path.join(NRPE.nagios_exportdir, | ||
173 | 162 | 'service__{}_{}.cfg'.format(hostname, self.command)) | ||
174 | 163 | |||
175 | 151 | def _locate_cmd(self, check_cmd): | 164 | def _locate_cmd(self, check_cmd): |
176 | 152 | search_path = ( | 165 | search_path = ( |
177 | 153 | '/usr/lib/nagios/plugins', | 166 | '/usr/lib/nagios/plugins', |
178 | @@ -163,9 +176,21 @@ | |||
179 | 163 | log('Check command not found: {}'.format(parts[0])) | 176 | log('Check command not found: {}'.format(parts[0])) |
180 | 164 | return '' | 177 | return '' |
181 | 165 | 178 | ||
182 | 179 | def _remove_service_files(self): | ||
183 | 180 | if not os.path.exists(NRPE.nagios_exportdir): | ||
184 | 181 | return | ||
185 | 182 | for f in os.listdir(NRPE.nagios_exportdir): | ||
186 | 183 | if f.endswith('_{}.cfg'.format(self.command)): | ||
187 | 184 | os.remove(os.path.join(NRPE.nagios_exportdir, f)) | ||
188 | 185 | |||
189 | 186 | def remove(self, hostname): | ||
190 | 187 | nrpe_check_file = self._get_check_filename() | ||
191 | 188 | if os.path.exists(nrpe_check_file): | ||
192 | 189 | os.remove(nrpe_check_file) | ||
193 | 190 | self._remove_service_files() | ||
194 | 191 | |||
195 | 166 | def write(self, nagios_context, hostname, nagios_servicegroups): | 192 | def write(self, nagios_context, hostname, nagios_servicegroups): |
198 | 167 | nrpe_check_file = '/etc/nagios/nrpe.d/{}.cfg'.format( | 193 | nrpe_check_file = self._get_check_filename() |
197 | 168 | self.command) | ||
199 | 169 | with open(nrpe_check_file, 'w') as nrpe_check_config: | 194 | with open(nrpe_check_file, 'w') as nrpe_check_config: |
200 | 170 | nrpe_check_config.write("# check {}\n".format(self.shortname)) | 195 | nrpe_check_config.write("# check {}\n".format(self.shortname)) |
201 | 171 | nrpe_check_config.write("command[{}]={}\n".format( | 196 | nrpe_check_config.write("command[{}]={}\n".format( |
202 | @@ -180,9 +205,7 @@ | |||
203 | 180 | 205 | ||
204 | 181 | def write_service_config(self, nagios_context, hostname, | 206 | def write_service_config(self, nagios_context, hostname, |
205 | 182 | nagios_servicegroups): | 207 | nagios_servicegroups): |
209 | 183 | for f in os.listdir(NRPE.nagios_exportdir): | 208 | self._remove_service_files() |
207 | 184 | if re.search('.*{}.cfg'.format(self.command), f): | ||
208 | 185 | os.remove(os.path.join(NRPE.nagios_exportdir, f)) | ||
210 | 186 | 209 | ||
211 | 187 | templ_vars = { | 210 | templ_vars = { |
212 | 188 | 'nagios_hostname': hostname, | 211 | 'nagios_hostname': hostname, |
213 | @@ -192,8 +215,7 @@ | |||
214 | 192 | 'command': self.command, | 215 | 'command': self.command, |
215 | 193 | } | 216 | } |
216 | 194 | nrpe_service_text = Check.service_template.format(**templ_vars) | 217 | nrpe_service_text = Check.service_template.format(**templ_vars) |
219 | 195 | nrpe_service_file = '{}/service__{}_{}.cfg'.format( | 218 | nrpe_service_file = self._get_service_filename(hostname) |
218 | 196 | NRPE.nagios_exportdir, hostname, self.command) | ||
220 | 197 | with open(nrpe_service_file, 'w') as nrpe_service_config: | 219 | with open(nrpe_service_file, 'w') as nrpe_service_config: |
221 | 198 | nrpe_service_config.write(str(nrpe_service_text)) | 220 | nrpe_service_config.write(str(nrpe_service_text)) |
222 | 199 | 221 | ||
223 | @@ -206,9 +228,10 @@ | |||
224 | 206 | nagios_exportdir = '/var/lib/nagios/export' | 228 | nagios_exportdir = '/var/lib/nagios/export' |
225 | 207 | nrpe_confdir = '/etc/nagios/nrpe.d' | 229 | nrpe_confdir = '/etc/nagios/nrpe.d' |
226 | 208 | 230 | ||
228 | 209 | def __init__(self, hostname=None): | 231 | def __init__(self, hostname=None, primary=True): |
229 | 210 | super(NRPE, self).__init__() | 232 | super(NRPE, self).__init__() |
230 | 211 | self.config = config() | 233 | self.config = config() |
231 | 234 | self.primary = primary | ||
232 | 212 | self.nagios_context = self.config['nagios_context'] | 235 | self.nagios_context = self.config['nagios_context'] |
233 | 213 | if 'nagios_servicegroups' in self.config and self.config['nagios_servicegroups']: | 236 | if 'nagios_servicegroups' in self.config and self.config['nagios_servicegroups']: |
234 | 214 | self.nagios_servicegroups = self.config['nagios_servicegroups'] | 237 | self.nagios_servicegroups = self.config['nagios_servicegroups'] |
235 | @@ -218,12 +241,38 @@ | |||
236 | 218 | if hostname: | 241 | if hostname: |
237 | 219 | self.hostname = hostname | 242 | self.hostname = hostname |
238 | 220 | else: | 243 | else: |
240 | 221 | self.hostname = "{}-{}".format(self.nagios_context, self.unit_name) | 244 | nagios_hostname = get_nagios_hostname() |
241 | 245 | if nagios_hostname: | ||
242 | 246 | self.hostname = nagios_hostname | ||
243 | 247 | else: | ||
244 | 248 | self.hostname = "{}-{}".format(self.nagios_context, self.unit_name) | ||
245 | 222 | self.checks = [] | 249 | self.checks = [] |
246 | 250 | # Iff in an nrpe-external-master relation hook, set primary status | ||
247 | 251 | relation = relation_ids('nrpe-external-master') | ||
248 | 252 | if relation: | ||
249 | 253 | log("Setting charm primary status {}".format(primary)) | ||
250 | 254 | for rid in relation_ids('nrpe-external-master'): | ||
251 | 255 | relation_set(relation_id=rid, relation_settings={'primary': self.primary}) | ||
252 | 223 | 256 | ||
253 | 224 | def add_check(self, *args, **kwargs): | 257 | def add_check(self, *args, **kwargs): |
254 | 225 | self.checks.append(Check(*args, **kwargs)) | 258 | self.checks.append(Check(*args, **kwargs)) |
255 | 226 | 259 | ||
256 | 260 | def remove_check(self, *args, **kwargs): | ||
257 | 261 | if kwargs.get('shortname') is None: | ||
258 | 262 | raise ValueError('shortname of check must be specified') | ||
259 | 263 | |||
260 | 264 | # Use sensible defaults if they're not specified - these are not | ||
261 | 265 | # actually used during removal, but they're required for constructing | ||
262 | 266 | # the Check object; check_disk is chosen because it's part of the | ||
263 | 267 | # nagios-plugins-basic package. | ||
264 | 268 | if kwargs.get('check_cmd') is None: | ||
265 | 269 | kwargs['check_cmd'] = 'check_disk' | ||
266 | 270 | if kwargs.get('description') is None: | ||
267 | 271 | kwargs['description'] = '' | ||
268 | 272 | |||
269 | 273 | check = Check(*args, **kwargs) | ||
270 | 274 | check.remove(self.hostname) | ||
271 | 275 | |||
272 | 227 | def write(self): | 276 | def write(self): |
273 | 228 | try: | 277 | try: |
274 | 229 | nagios_uid = pwd.getpwnam('nagios').pw_uid | 278 | nagios_uid = pwd.getpwnam('nagios').pw_uid |
275 | @@ -260,7 +309,7 @@ | |||
276 | 260 | :param str relation_name: Name of relation nrpe sub joined to | 309 | :param str relation_name: Name of relation nrpe sub joined to |
277 | 261 | """ | 310 | """ |
278 | 262 | for rel in relations_of_type(relation_name): | 311 | for rel in relations_of_type(relation_name): |
280 | 263 | if 'nagios_hostname' in rel: | 312 | if 'nagios_host_context' in rel: |
281 | 264 | return rel['nagios_host_context'] | 313 | return rel['nagios_host_context'] |
282 | 265 | 314 | ||
283 | 266 | 315 | ||
284 | @@ -289,18 +338,30 @@ | |||
285 | 289 | return unit | 338 | return unit |
286 | 290 | 339 | ||
287 | 291 | 340 | ||
289 | 292 | def add_init_service_checks(nrpe, services, unit_name): | 341 | def add_init_service_checks(nrpe, services, unit_name, immediate_check=True): |
290 | 293 | """ | 342 | """ |
291 | 294 | Add checks for each service in list | 343 | Add checks for each service in list |
292 | 295 | 344 | ||
293 | 296 | :param NRPE nrpe: NRPE object to add check to | 345 | :param NRPE nrpe: NRPE object to add check to |
294 | 297 | :param list services: List of services to check | 346 | :param list services: List of services to check |
295 | 298 | :param str unit_name: Unit name to use in check description | 347 | :param str unit_name: Unit name to use in check description |
296 | 348 | :param bool immediate_check: For sysv init, run the service check immediately | ||
297 | 299 | """ | 349 | """ |
298 | 300 | for svc in services: | 350 | for svc in services: |
299 | 351 | # Don't add a check for these services from neutron-gateway | ||
300 | 352 | if svc in ['ext-port', 'os-charm-phy-nic-mtu']: | ||
301 | 353 | next | ||
302 | 354 | |||
303 | 301 | upstart_init = '/etc/init/%s.conf' % svc | 355 | upstart_init = '/etc/init/%s.conf' % svc |
304 | 302 | sysv_init = '/etc/init.d/%s' % svc | 356 | sysv_init = '/etc/init.d/%s' % svc |
306 | 303 | if os.path.exists(upstart_init): | 357 | |
307 | 358 | if host.init_is_systemd(): | ||
308 | 359 | nrpe.add_check( | ||
309 | 360 | shortname=svc, | ||
310 | 361 | description='process check {%s}' % unit_name, | ||
311 | 362 | check_cmd='check_systemd.py %s' % svc | ||
312 | 363 | ) | ||
313 | 364 | elif os.path.exists(upstart_init): | ||
314 | 304 | nrpe.add_check( | 365 | nrpe.add_check( |
315 | 305 | shortname=svc, | 366 | shortname=svc, |
316 | 306 | description='process check {%s}' % unit_name, | 367 | description='process check {%s}' % unit_name, |
317 | @@ -308,21 +369,29 @@ | |||
318 | 308 | ) | 369 | ) |
319 | 309 | elif os.path.exists(sysv_init): | 370 | elif os.path.exists(sysv_init): |
320 | 310 | cronpath = '/etc/cron.d/nagios-service-check-%s' % svc | 371 | cronpath = '/etc/cron.d/nagios-service-check-%s' % svc |
327 | 311 | cron_file = ('*/5 * * * * root ' | 372 | checkpath = '/var/lib/nagios/service-check-%s.txt' % svc |
328 | 312 | '/usr/local/lib/nagios/plugins/check_exit_status.pl ' | 373 | croncmd = ( |
329 | 313 | '-s /etc/init.d/%s status > ' | 374 | '/usr/local/lib/nagios/plugins/check_exit_status.pl ' |
330 | 314 | '/var/lib/nagios/service-check-%s.txt\n' % (svc, | 375 | '-s /etc/init.d/%s status' % svc |
331 | 315 | svc) | 376 | ) |
332 | 316 | ) | 377 | cron_file = '*/5 * * * * root %s > %s\n' % (croncmd, checkpath) |
333 | 317 | f = open(cronpath, 'w') | 378 | f = open(cronpath, 'w') |
334 | 318 | f.write(cron_file) | 379 | f.write(cron_file) |
335 | 319 | f.close() | 380 | f.close() |
336 | 320 | nrpe.add_check( | 381 | nrpe.add_check( |
337 | 321 | shortname=svc, | 382 | shortname=svc, |
341 | 322 | description='process check {%s}' % unit_name, | 383 | description='service check {%s}' % unit_name, |
342 | 323 | check_cmd='check_status_file.py -f ' | 384 | check_cmd='check_status_file.py -f %s' % checkpath, |
340 | 324 | '/var/lib/nagios/service-check-%s.txt' % svc, | ||
343 | 325 | ) | 385 | ) |
344 | 386 | if immediate_check: | ||
345 | 387 | f = open(checkpath, 'w') | ||
346 | 388 | subprocess.call( | ||
347 | 389 | croncmd.split(), | ||
348 | 390 | stdout=f, | ||
349 | 391 | stderr=subprocess.STDOUT | ||
350 | 392 | ) | ||
351 | 393 | f.close() | ||
352 | 394 | os.chmod(checkpath, 0o644) | ||
353 | 326 | 395 | ||
354 | 327 | 396 | ||
355 | 328 | def copy_nrpe_checks(): | 397 | def copy_nrpe_checks(): |
356 | 329 | 398 | ||
357 | === modified file 'charmhelpers/contrib/charmsupport/volumes.py' | |||
358 | --- charmhelpers/contrib/charmsupport/volumes.py 2015-09-28 20:09:02 +0000 | |||
359 | +++ charmhelpers/contrib/charmsupport/volumes.py 2017-03-04 02:21:16 +0000 | |||
360 | @@ -1,18 +1,16 @@ | |||
361 | 1 | # Copyright 2014-2015 Canonical Limited. | 1 | # Copyright 2014-2015 Canonical Limited. |
362 | 2 | # | 2 | # |
376 | 3 | # This file is part of charm-helpers. | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
377 | 4 | # | 4 | # you may not use this file except in compliance with the License. |
378 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | 5 | # You may obtain a copy of the License at |
379 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | 6 | # |
380 | 7 | # published by the Free Software Foundation. | 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
381 | 8 | # | 8 | # |
382 | 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 |
383 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
384 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
385 | 12 | # GNU Lesser General Public License for more details. | 12 | # See the License for the specific language governing permissions and |
386 | 13 | # | 13 | # limitations under the License. |
374 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
375 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
387 | 16 | 14 | ||
388 | 17 | ''' | 15 | ''' |
389 | 18 | Functions for managing volumes in juju units. One volume is supported per unit. | 16 | Functions for managing volumes in juju units. One volume is supported per unit. |
390 | 19 | 17 | ||
391 | === modified file 'charmhelpers/contrib/hahelpers/__init__.py' | |||
392 | --- charmhelpers/contrib/hahelpers/__init__.py 2015-09-28 20:09:02 +0000 | |||
393 | +++ charmhelpers/contrib/hahelpers/__init__.py 2017-03-04 02:21:16 +0000 | |||
394 | @@ -1,15 +1,13 @@ | |||
395 | 1 | # Copyright 2014-2015 Canonical Limited. | 1 | # Copyright 2014-2015 Canonical Limited. |
396 | 2 | # | 2 | # |
410 | 3 | # This file is part of charm-helpers. | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
411 | 4 | # | 4 | # you may not use this file except in compliance with the License. |
412 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | 5 | # You may obtain a copy of the License at |
413 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | 6 | # |
414 | 7 | # published by the Free Software Foundation. | 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
415 | 8 | # | 8 | # |
416 | 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 |
417 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
418 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
419 | 12 | # GNU Lesser General Public License for more details. | 12 | # See the License for the specific language governing permissions and |
420 | 13 | # | 13 | # limitations under the License. |
408 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
409 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
421 | 16 | 14 | ||
422 | === modified file 'charmhelpers/contrib/hahelpers/apache.py' | |||
423 | --- charmhelpers/contrib/hahelpers/apache.py 2015-09-28 20:09:02 +0000 | |||
424 | +++ charmhelpers/contrib/hahelpers/apache.py 2017-03-04 02:21:16 +0000 | |||
425 | @@ -1,18 +1,16 @@ | |||
426 | 1 | # Copyright 2014-2015 Canonical Limited. | 1 | # Copyright 2014-2015 Canonical Limited. |
427 | 2 | # | 2 | # |
441 | 3 | # This file is part of charm-helpers. | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
442 | 4 | # | 4 | # you may not use this file except in compliance with the License. |
443 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | 5 | # You may obtain a copy of the License at |
444 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | 6 | # |
445 | 7 | # published by the Free Software Foundation. | 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
446 | 8 | # | 8 | # |
447 | 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 |
448 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
449 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
450 | 12 | # GNU Lesser General Public License for more details. | 12 | # See the License for the specific language governing permissions and |
451 | 13 | # | 13 | # limitations under the License. |
439 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
440 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
452 | 16 | 14 | ||
453 | 17 | # | 15 | # |
454 | 18 | # Copyright 2012 Canonical Ltd. | 16 | # Copyright 2012 Canonical Ltd. |
455 | @@ -24,6 +22,7 @@ | |||
456 | 24 | # Adam Gandelman <adamg@ubuntu.com> | 22 | # Adam Gandelman <adamg@ubuntu.com> |
457 | 25 | # | 23 | # |
458 | 26 | 24 | ||
459 | 25 | import os | ||
460 | 27 | import subprocess | 26 | import subprocess |
461 | 28 | 27 | ||
462 | 29 | from charmhelpers.core.hookenv import ( | 28 | from charmhelpers.core.hookenv import ( |
463 | @@ -74,9 +73,23 @@ | |||
464 | 74 | return ca_cert | 73 | return ca_cert |
465 | 75 | 74 | ||
466 | 76 | 75 | ||
467 | 76 | def retrieve_ca_cert(cert_file): | ||
468 | 77 | cert = None | ||
469 | 78 | if os.path.isfile(cert_file): | ||
470 | 79 | with open(cert_file, 'r') as crt: | ||
471 | 80 | cert = crt.read() | ||
472 | 81 | return cert | ||
473 | 82 | |||
474 | 83 | |||
475 | 77 | def install_ca_cert(ca_cert): | 84 | def install_ca_cert(ca_cert): |
476 | 78 | if ca_cert: | 85 | if ca_cert: |
481 | 79 | with open('/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt', | 86 | cert_file = ('/usr/local/share/ca-certificates/' |
482 | 80 | 'w') as crt: | 87 | 'keystone_juju_ca_cert.crt') |
483 | 81 | crt.write(ca_cert) | 88 | old_cert = retrieve_ca_cert(cert_file) |
484 | 82 | subprocess.check_call(['update-ca-certificates', '--fresh']) | 89 | if old_cert and old_cert == ca_cert: |
485 | 90 | log("CA cert is the same as installed version", level=INFO) | ||
486 | 91 | else: | ||
487 | 92 | log("Installing new CA cert", level=INFO) | ||
488 | 93 | with open(cert_file, 'w') as crt: | ||
489 | 94 | crt.write(ca_cert) | ||
490 | 95 | subprocess.check_call(['update-ca-certificates', '--fresh']) | ||
491 | 83 | 96 | ||
492 | === modified file 'charmhelpers/contrib/hahelpers/cluster.py' | |||
493 | --- charmhelpers/contrib/hahelpers/cluster.py 2015-09-28 20:09:02 +0000 | |||
494 | +++ charmhelpers/contrib/hahelpers/cluster.py 2017-03-04 02:21:16 +0000 | |||
495 | @@ -1,18 +1,16 @@ | |||
496 | 1 | # Copyright 2014-2015 Canonical Limited. | 1 | # Copyright 2014-2015 Canonical Limited. |
497 | 2 | # | 2 | # |
511 | 3 | # This file is part of charm-helpers. | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
512 | 4 | # | 4 | # you may not use this file except in compliance with the License. |
513 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | 5 | # You may obtain a copy of the License at |
514 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | 6 | # |
515 | 7 | # published by the Free Software Foundation. | 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
516 | 8 | # | 8 | # |
517 | 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 |
518 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
519 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
520 | 12 | # GNU Lesser General Public License for more details. | 12 | # See the License for the specific language governing permissions and |
521 | 13 | # | 13 | # limitations under the License. |
509 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
510 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
522 | 16 | 14 | ||
523 | 17 | # | 15 | # |
524 | 18 | # Copyright 2012 Canonical Ltd. | 16 | # Copyright 2012 Canonical Ltd. |
525 | @@ -41,10 +39,11 @@ | |||
526 | 41 | relation_get, | 39 | relation_get, |
527 | 42 | config as config_get, | 40 | config as config_get, |
528 | 43 | INFO, | 41 | INFO, |
530 | 44 | ERROR, | 42 | DEBUG, |
531 | 45 | WARNING, | 43 | WARNING, |
532 | 46 | unit_get, | 44 | unit_get, |
534 | 47 | is_leader as juju_is_leader | 45 | is_leader as juju_is_leader, |
535 | 46 | status_set, | ||
536 | 48 | ) | 47 | ) |
537 | 49 | from charmhelpers.core.decorators import ( | 48 | from charmhelpers.core.decorators import ( |
538 | 50 | retry_on_exception, | 49 | retry_on_exception, |
539 | @@ -60,6 +59,10 @@ | |||
540 | 60 | pass | 59 | pass |
541 | 61 | 60 | ||
542 | 62 | 61 | ||
543 | 62 | class HAIncorrectConfig(Exception): | ||
544 | 63 | pass | ||
545 | 64 | |||
546 | 65 | |||
547 | 63 | class CRMResourceNotFound(Exception): | 66 | class CRMResourceNotFound(Exception): |
548 | 64 | pass | 67 | pass |
549 | 65 | 68 | ||
550 | @@ -274,27 +277,71 @@ | |||
551 | 274 | Obtains all relevant configuration from charm configuration required | 277 | Obtains all relevant configuration from charm configuration required |
552 | 275 | for initiating a relation to hacluster: | 278 | for initiating a relation to hacluster: |
553 | 276 | 279 | ||
555 | 277 | ha-bindiface, ha-mcastport, vip | 280 | ha-bindiface, ha-mcastport, vip, os-internal-hostname, |
556 | 281 | os-admin-hostname, os-public-hostname, os-access-hostname | ||
557 | 278 | 282 | ||
558 | 279 | param: exclude_keys: list of setting key(s) to be excluded. | 283 | param: exclude_keys: list of setting key(s) to be excluded. |
559 | 280 | returns: dict: A dict containing settings keyed by setting name. | 284 | returns: dict: A dict containing settings keyed by setting name. |
561 | 281 | raises: HAIncompleteConfig if settings are missing. | 285 | raises: HAIncompleteConfig if settings are missing or incorrect. |
562 | 282 | ''' | 286 | ''' |
564 | 283 | settings = ['ha-bindiface', 'ha-mcastport', 'vip'] | 287 | settings = ['ha-bindiface', 'ha-mcastport', 'vip', 'os-internal-hostname', |
565 | 288 | 'os-admin-hostname', 'os-public-hostname', 'os-access-hostname'] | ||
566 | 284 | conf = {} | 289 | conf = {} |
567 | 285 | for setting in settings: | 290 | for setting in settings: |
568 | 286 | if exclude_keys and setting in exclude_keys: | 291 | if exclude_keys and setting in exclude_keys: |
569 | 287 | continue | 292 | continue |
570 | 288 | 293 | ||
571 | 289 | conf[setting] = config_get(setting) | 294 | conf[setting] = config_get(setting) |
577 | 290 | missing = [] | 295 | |
578 | 291 | [missing.append(s) for s, v in six.iteritems(conf) if v is None] | 296 | if not valid_hacluster_config(): |
579 | 292 | if missing: | 297 | raise HAIncorrectConfig('Insufficient or incorrect config data to ' |
580 | 293 | log('Insufficient config data to configure hacluster.', level=ERROR) | 298 | 'configure hacluster.') |
576 | 294 | raise HAIncompleteConfig | ||
581 | 295 | return conf | 299 | return conf |
582 | 296 | 300 | ||
583 | 297 | 301 | ||
584 | 302 | def valid_hacluster_config(): | ||
585 | 303 | ''' | ||
586 | 304 | Check that either vip or dns-ha is set. If dns-ha then one of os-*-hostname | ||
587 | 305 | must be set. | ||
588 | 306 | |||
589 | 307 | Note: ha-bindiface and ha-macastport both have defaults and will always | ||
590 | 308 | be set. We only care that either vip or dns-ha is set. | ||
591 | 309 | |||
592 | 310 | :returns: boolean: valid config returns true. | ||
593 | 311 | raises: HAIncompatibileConfig if settings conflict. | ||
594 | 312 | raises: HAIncompleteConfig if settings are missing. | ||
595 | 313 | ''' | ||
596 | 314 | vip = config_get('vip') | ||
597 | 315 | dns = config_get('dns-ha') | ||
598 | 316 | if not(bool(vip) ^ bool(dns)): | ||
599 | 317 | msg = ('HA: Either vip or dns-ha must be set but not both in order to ' | ||
600 | 318 | 'use high availability') | ||
601 | 319 | status_set('blocked', msg) | ||
602 | 320 | raise HAIncorrectConfig(msg) | ||
603 | 321 | |||
604 | 322 | # If dns-ha then one of os-*-hostname must be set | ||
605 | 323 | if dns: | ||
606 | 324 | dns_settings = ['os-internal-hostname', 'os-admin-hostname', | ||
607 | 325 | 'os-public-hostname', 'os-access-hostname'] | ||
608 | 326 | # At this point it is unknown if one or all of the possible | ||
609 | 327 | # network spaces are in HA. Validate at least one is set which is | ||
610 | 328 | # the minimum required. | ||
611 | 329 | for setting in dns_settings: | ||
612 | 330 | if config_get(setting): | ||
613 | 331 | log('DNS HA: At least one hostname is set {}: {}' | ||
614 | 332 | ''.format(setting, config_get(setting)), | ||
615 | 333 | level=DEBUG) | ||
616 | 334 | return True | ||
617 | 335 | |||
618 | 336 | msg = ('DNS HA: At least one os-*-hostname(s) must be set to use ' | ||
619 | 337 | 'DNS HA') | ||
620 | 338 | status_set('blocked', msg) | ||
621 | 339 | raise HAIncompleteConfig(msg) | ||
622 | 340 | |||
623 | 341 | log('VIP HA: VIP is set {}'.format(vip), level=DEBUG) | ||
624 | 342 | return True | ||
625 | 343 | |||
626 | 344 | |||
627 | 298 | def canonical_url(configs, vip_setting='vip'): | 345 | def canonical_url(configs, vip_setting='vip'): |
628 | 299 | ''' | 346 | ''' |
629 | 300 | Returns the correct HTTP URL to this host given the state of HTTPS | 347 | Returns the correct HTTP URL to this host given the state of HTTPS |
630 | 301 | 348 | ||
631 | === modified file 'charmhelpers/contrib/network/__init__.py' | |||
632 | --- charmhelpers/contrib/network/__init__.py 2015-09-28 20:09:02 +0000 | |||
633 | +++ charmhelpers/contrib/network/__init__.py 2017-03-04 02:21:16 +0000 | |||
634 | @@ -1,15 +1,13 @@ | |||
635 | 1 | # Copyright 2014-2015 Canonical Limited. | 1 | # Copyright 2014-2015 Canonical Limited. |
636 | 2 | # | 2 | # |
650 | 3 | # This file is part of charm-helpers. | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
651 | 4 | # | 4 | # you may not use this file except in compliance with the License. |
652 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | 5 | # You may obtain a copy of the License at |
653 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | 6 | # |
654 | 7 | # published by the Free Software Foundation. | 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
655 | 8 | # | 8 | # |
656 | 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 |
657 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
658 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
659 | 12 | # GNU Lesser General Public License for more details. | 12 | # See the License for the specific language governing permissions and |
660 | 13 | # | 13 | # limitations under the License. |
648 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
649 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
661 | 16 | 14 | ||
662 | === modified file 'charmhelpers/contrib/network/ip.py' | |||
663 | --- charmhelpers/contrib/network/ip.py 2015-09-28 20:09:02 +0000 | |||
664 | +++ charmhelpers/contrib/network/ip.py 2017-03-04 02:21:16 +0000 | |||
665 | @@ -1,18 +1,16 @@ | |||
666 | 1 | # Copyright 2014-2015 Canonical Limited. | 1 | # Copyright 2014-2015 Canonical Limited. |
667 | 2 | # | 2 | # |
681 | 3 | # This file is part of charm-helpers. | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
682 | 4 | # | 4 | # you may not use this file except in compliance with the License. |
683 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | 5 | # You may obtain a copy of the License at |
684 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | 6 | # |
685 | 7 | # published by the Free Software Foundation. | 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
686 | 8 | # | 8 | # |
687 | 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 |
688 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
689 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
690 | 12 | # GNU Lesser General Public License for more details. | 12 | # See the License for the specific language governing permissions and |
691 | 13 | # | 13 | # limitations under the License. |
679 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
680 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
692 | 16 | 14 | ||
693 | 17 | import glob | 15 | import glob |
694 | 18 | import re | 16 | import re |
695 | @@ -33,14 +31,20 @@ | |||
696 | 33 | import netifaces | 31 | import netifaces |
697 | 34 | except ImportError: | 32 | except ImportError: |
698 | 35 | apt_update(fatal=True) | 33 | apt_update(fatal=True) |
700 | 36 | apt_install('python-netifaces', fatal=True) | 34 | if six.PY2: |
701 | 35 | apt_install('python-netifaces', fatal=True) | ||
702 | 36 | else: | ||
703 | 37 | apt_install('python3-netifaces', fatal=True) | ||
704 | 37 | import netifaces | 38 | import netifaces |
705 | 38 | 39 | ||
706 | 39 | try: | 40 | try: |
707 | 40 | import netaddr | 41 | import netaddr |
708 | 41 | except ImportError: | 42 | except ImportError: |
709 | 42 | apt_update(fatal=True) | 43 | apt_update(fatal=True) |
711 | 43 | apt_install('python-netaddr', fatal=True) | 44 | if six.PY2: |
712 | 45 | apt_install('python-netaddr', fatal=True) | ||
713 | 46 | else: | ||
714 | 47 | apt_install('python3-netaddr', fatal=True) | ||
715 | 44 | import netaddr | 48 | import netaddr |
716 | 45 | 49 | ||
717 | 46 | 50 | ||
718 | @@ -53,7 +57,7 @@ | |||
719 | 53 | 57 | ||
720 | 54 | 58 | ||
721 | 55 | def no_ip_found_error_out(network): | 59 | def no_ip_found_error_out(network): |
723 | 56 | errmsg = ("No IP address found in network: %s" % network) | 60 | errmsg = ("No IP address found in network(s): %s" % network) |
724 | 57 | raise ValueError(errmsg) | 61 | raise ValueError(errmsg) |
725 | 58 | 62 | ||
726 | 59 | 63 | ||
727 | @@ -61,7 +65,7 @@ | |||
728 | 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. |
729 | 62 | 66 | ||
730 | 63 | :param network (str): CIDR presentation format. For example, | 67 | :param network (str): CIDR presentation format. For example, |
732 | 64 | '192.168.1.0/24'. | 68 | '192.168.1.0/24'. Supports multiple networks as a space-delimited list. |
733 | 65 | :param fallback (str): If no address is found, return fallback. | 69 | :param fallback (str): If no address is found, return fallback. |
734 | 66 | :param fatal (boolean): If no address is found, fallback is not | 70 | :param fatal (boolean): If no address is found, fallback is not |
735 | 67 | set and fatal is True then exit(1). | 71 | set and fatal is True then exit(1). |
736 | @@ -75,24 +79,26 @@ | |||
737 | 75 | else: | 79 | else: |
738 | 76 | return None | 80 | return None |
739 | 77 | 81 | ||
750 | 78 | _validate_cidr(network) | 82 | networks = network.split() or [network] |
751 | 79 | network = netaddr.IPNetwork(network) | 83 | for network in networks: |
752 | 80 | for iface in netifaces.interfaces(): | 84 | _validate_cidr(network) |
753 | 81 | addresses = netifaces.ifaddresses(iface) | 85 | network = netaddr.IPNetwork(network) |
754 | 82 | if network.version == 4 and netifaces.AF_INET in addresses: | 86 | for iface in netifaces.interfaces(): |
755 | 83 | addr = addresses[netifaces.AF_INET][0]['addr'] | 87 | addresses = netifaces.ifaddresses(iface) |
756 | 84 | netmask = addresses[netifaces.AF_INET][0]['netmask'] | 88 | if network.version == 4 and netifaces.AF_INET in addresses: |
757 | 85 | cidr = netaddr.IPNetwork("%s/%s" % (addr, netmask)) | 89 | addr = addresses[netifaces.AF_INET][0]['addr'] |
758 | 86 | if cidr in network: | 90 | netmask = addresses[netifaces.AF_INET][0]['netmask'] |
759 | 87 | return str(cidr.ip) | 91 | cidr = netaddr.IPNetwork("%s/%s" % (addr, netmask)) |
760 | 92 | if cidr in network: | ||
761 | 93 | return str(cidr.ip) | ||
762 | 88 | 94 | ||
770 | 89 | if network.version == 6 and netifaces.AF_INET6 in addresses: | 95 | if network.version == 6 and netifaces.AF_INET6 in addresses: |
771 | 90 | for addr in addresses[netifaces.AF_INET6]: | 96 | for addr in addresses[netifaces.AF_INET6]: |
772 | 91 | if not addr['addr'].startswith('fe80'): | 97 | if not addr['addr'].startswith('fe80'): |
773 | 92 | cidr = netaddr.IPNetwork("%s/%s" % (addr['addr'], | 98 | cidr = netaddr.IPNetwork("%s/%s" % (addr['addr'], |
774 | 93 | addr['netmask'])) | 99 | addr['netmask'])) |
775 | 94 | if cidr in network: | 100 | if cidr in network: |
776 | 95 | return str(cidr.ip) | 101 | return str(cidr.ip) |
777 | 96 | 102 | ||
778 | 97 | if fallback is not None: | 103 | if fallback is not None: |
779 | 98 | return fallback | 104 | return fallback |
780 | @@ -189,6 +195,15 @@ | |||
781 | 189 | get_netmask_for_address = partial(_get_for_address, key='netmask') | 195 | get_netmask_for_address = partial(_get_for_address, key='netmask') |
782 | 190 | 196 | ||
783 | 191 | 197 | ||
784 | 198 | def resolve_network_cidr(ip_address): | ||
785 | 199 | ''' | ||
786 | 200 | Resolves the full address cidr of an ip_address based on | ||
787 | 201 | configured network interfaces | ||
788 | 202 | ''' | ||
789 | 203 | netmask = get_netmask_for_address(ip_address) | ||
790 | 204 | return str(netaddr.IPNetwork("%s/%s" % (ip_address, netmask)).cidr) | ||
791 | 205 | |||
792 | 206 | |||
793 | 192 | def format_ipv6_addr(address): | 207 | def format_ipv6_addr(address): |
794 | 193 | """If address is IPv6, wrap it in '[]' otherwise return None. | 208 | """If address is IPv6, wrap it in '[]' otherwise return None. |
795 | 194 | 209 | ||
796 | @@ -203,7 +218,16 @@ | |||
797 | 203 | 218 | ||
798 | 204 | def get_iface_addr(iface='eth0', inet_type='AF_INET', inc_aliases=False, | 219 | def get_iface_addr(iface='eth0', inet_type='AF_INET', inc_aliases=False, |
799 | 205 | fatal=True, exc_list=None): | 220 | fatal=True, exc_list=None): |
801 | 206 | """Return the assigned IP address for a given interface, if any.""" | 221 | """Return the assigned IP address for a given interface, if any. |
802 | 222 | |||
803 | 223 | :param iface: network interface on which address(es) are expected to | ||
804 | 224 | be found. | ||
805 | 225 | :param inet_type: inet address family | ||
806 | 226 | :param inc_aliases: include alias interfaces in search | ||
807 | 227 | :param fatal: if True, raise exception if address not found | ||
808 | 228 | :param exc_list: list of addresses to ignore | ||
809 | 229 | :return: list of ip addresses | ||
810 | 230 | """ | ||
811 | 207 | # Extract nic if passed /dev/ethX | 231 | # Extract nic if passed /dev/ethX |
812 | 208 | if '/' in iface: | 232 | if '/' in iface: |
813 | 209 | iface = iface.split('/')[-1] | 233 | iface = iface.split('/')[-1] |
814 | @@ -304,6 +328,14 @@ | |||
815 | 304 | We currently only support scope global IPv6 addresses i.e. non-temporary | 328 | We currently only support scope global IPv6 addresses i.e. non-temporary |
816 | 305 | addresses. If no global IPv6 address is found, return the first one found | 329 | addresses. If no global IPv6 address is found, return the first one found |
817 | 306 | in the ipv6 address list. | 330 | in the ipv6 address list. |
818 | 331 | |||
819 | 332 | :param iface: network interface on which ipv6 address(es) are expected to | ||
820 | 333 | be found. | ||
821 | 334 | :param inc_aliases: include alias interfaces in search | ||
822 | 335 | :param fatal: if True, raise exception if address not found | ||
823 | 336 | :param exc_list: list of addresses to ignore | ||
824 | 337 | :param dynamic_only: only recognise dynamic addresses | ||
825 | 338 | :return: list of ipv6 addresses | ||
826 | 307 | """ | 339 | """ |
827 | 308 | addresses = get_iface_addr(iface=iface, inet_type='AF_INET6', | 340 | addresses = get_iface_addr(iface=iface, inet_type='AF_INET6', |
828 | 309 | inc_aliases=inc_aliases, fatal=fatal, | 341 | inc_aliases=inc_aliases, fatal=fatal, |
829 | @@ -325,7 +357,7 @@ | |||
830 | 325 | cmd = ['ip', 'addr', 'show', iface] | 357 | cmd = ['ip', 'addr', 'show', iface] |
831 | 326 | out = subprocess.check_output(cmd).decode('UTF-8') | 358 | out = subprocess.check_output(cmd).decode('UTF-8') |
832 | 327 | if dynamic_only: | 359 | if dynamic_only: |
834 | 328 | key = re.compile("inet6 (.+)/[0-9]+ scope global dynamic.*") | 360 | key = re.compile("inet6 (.+)/[0-9]+ scope global.* dynamic.*") |
835 | 329 | else: | 361 | else: |
836 | 330 | key = re.compile("inet6 (.+)/[0-9]+ scope global.*") | 362 | key = re.compile("inet6 (.+)/[0-9]+ scope global.*") |
837 | 331 | 363 | ||
838 | @@ -377,10 +409,10 @@ | |||
839 | 377 | Returns True if address is a valid IP address. | 409 | Returns True if address is a valid IP address. |
840 | 378 | """ | 410 | """ |
841 | 379 | try: | 411 | try: |
844 | 380 | # Test to see if already an IPv4 address | 412 | # Test to see if already an IPv4/IPv6 address |
845 | 381 | socket.inet_aton(address) | 413 | address = netaddr.IPAddress(address) |
846 | 382 | return True | 414 | return True |
848 | 383 | except socket.error: | 415 | except (netaddr.AddrFormatError, ValueError): |
849 | 384 | return False | 416 | return False |
850 | 385 | 417 | ||
851 | 386 | 418 | ||
852 | @@ -388,7 +420,10 @@ | |||
853 | 388 | try: | 420 | try: |
854 | 389 | import dns.resolver | 421 | import dns.resolver |
855 | 390 | except ImportError: | 422 | except ImportError: |
857 | 391 | apt_install('python-dnspython') | 423 | if six.PY2: |
858 | 424 | apt_install('python-dnspython', fatal=True) | ||
859 | 425 | else: | ||
860 | 426 | apt_install('python3-dnspython', fatal=True) | ||
861 | 392 | import dns.resolver | 427 | import dns.resolver |
862 | 393 | 428 | ||
863 | 394 | if isinstance(address, dns.name.Name): | 429 | if isinstance(address, dns.name.Name): |
864 | @@ -398,7 +433,11 @@ | |||
865 | 398 | else: | 433 | else: |
866 | 399 | return None | 434 | return None |
867 | 400 | 435 | ||
869 | 401 | answers = dns.resolver.query(address, rtype) | 436 | try: |
870 | 437 | answers = dns.resolver.query(address, rtype) | ||
871 | 438 | except dns.resolver.NXDOMAIN: | ||
872 | 439 | return None | ||
873 | 440 | |||
874 | 402 | if answers: | 441 | if answers: |
875 | 403 | return str(answers[0]) | 442 | return str(answers[0]) |
876 | 404 | return None | 443 | return None |
877 | @@ -432,7 +471,10 @@ | |||
878 | 432 | try: | 471 | try: |
879 | 433 | import dns.reversename | 472 | import dns.reversename |
880 | 434 | except ImportError: | 473 | except ImportError: |
882 | 435 | apt_install("python-dnspython") | 474 | if six.PY2: |
883 | 475 | apt_install("python-dnspython", fatal=True) | ||
884 | 476 | else: | ||
885 | 477 | apt_install("python3-dnspython", fatal=True) | ||
886 | 436 | import dns.reversename | 478 | import dns.reversename |
887 | 437 | 479 | ||
888 | 438 | rev = dns.reversename.from_address(address) | 480 | rev = dns.reversename.from_address(address) |
889 | @@ -454,3 +496,18 @@ | |||
890 | 454 | return result | 496 | return result |
891 | 455 | else: | 497 | else: |
892 | 456 | return result.split('.')[0] | 498 | return result.split('.')[0] |
893 | 499 | |||
894 | 500 | |||
895 | 501 | def port_has_listener(address, port): | ||
896 | 502 | """ | ||
897 | 503 | Returns True if the address:port is open and being listened to, | ||
898 | 504 | else False. | ||
899 | 505 | |||
900 | 506 | @param address: an IP address or hostname | ||
901 | 507 | @param port: integer port | ||
902 | 508 | |||
903 | 509 | Note calls 'zc' via a subprocess shell | ||
904 | 510 | """ | ||
905 | 511 | cmd = ['nc', '-z', address, str(port)] | ||
906 | 512 | result = subprocess.call(cmd) | ||
907 | 513 | return not(bool(result)) | ||
908 | 457 | 514 | ||
909 | === modified file 'charmhelpers/contrib/openstack/__init__.py' | |||
910 | --- charmhelpers/contrib/openstack/__init__.py 2015-09-28 20:09:02 +0000 | |||
911 | +++ charmhelpers/contrib/openstack/__init__.py 2017-03-04 02:21:16 +0000 | |||
912 | @@ -1,15 +1,13 @@ | |||
913 | 1 | # Copyright 2014-2015 Canonical Limited. | 1 | # Copyright 2014-2015 Canonical Limited. |
914 | 2 | # | 2 | # |
928 | 3 | # This file is part of charm-helpers. | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
929 | 4 | # | 4 | # you may not use this file except in compliance with the License. |
930 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | 5 | # You may obtain a copy of the License at |
931 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | 6 | # |
932 | 7 | # published by the Free Software Foundation. | 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
933 | 8 | # | 8 | # |
934 | 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 |
935 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
936 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
937 | 12 | # GNU Lesser General Public License for more details. | 12 | # See the License for the specific language governing permissions and |
938 | 13 | # | 13 | # limitations under the License. |
926 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
927 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
939 | 16 | 14 | ||
940 | === modified file 'charmhelpers/contrib/openstack/alternatives.py' | |||
941 | --- charmhelpers/contrib/openstack/alternatives.py 2015-09-28 20:09:02 +0000 | |||
942 | +++ charmhelpers/contrib/openstack/alternatives.py 2017-03-04 02:21:16 +0000 | |||
943 | @@ -1,18 +1,16 @@ | |||
944 | 1 | # Copyright 2014-2015 Canonical Limited. | 1 | # Copyright 2014-2015 Canonical Limited. |
945 | 2 | # | 2 | # |
959 | 3 | # This file is part of charm-helpers. | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
960 | 4 | # | 4 | # you may not use this file except in compliance with the License. |
961 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | 5 | # You may obtain a copy of the License at |
962 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | 6 | # |
963 | 7 | # published by the Free Software Foundation. | 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
964 | 8 | # | 8 | # |
965 | 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 |
966 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
967 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
968 | 12 | # GNU Lesser General Public License for more details. | 12 | # See the License for the specific language governing permissions and |
969 | 13 | # | 13 | # limitations under the License. |
957 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
958 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
970 | 16 | 14 | ||
971 | 17 | ''' Helper for managing alternatives for file conflict resolution ''' | 15 | ''' Helper for managing alternatives for file conflict resolution ''' |
972 | 18 | 16 | ||
973 | 19 | 17 | ||
974 | === modified file 'charmhelpers/contrib/openstack/amulet/__init__.py' | |||
975 | --- charmhelpers/contrib/openstack/amulet/__init__.py 2015-09-28 20:09:02 +0000 | |||
976 | +++ charmhelpers/contrib/openstack/amulet/__init__.py 2017-03-04 02:21:16 +0000 | |||
977 | @@ -1,15 +1,13 @@ | |||
978 | 1 | # Copyright 2014-2015 Canonical Limited. | 1 | # Copyright 2014-2015 Canonical Limited. |
979 | 2 | # | 2 | # |
993 | 3 | # This file is part of charm-helpers. | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
994 | 4 | # | 4 | # you may not use this file except in compliance with the License. |
995 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | 5 | # You may obtain a copy of the License at |
996 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | 6 | # |
997 | 7 | # published by the Free Software Foundation. | 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
998 | 8 | # | 8 | # |
999 | 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 |
1000 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
1001 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
1002 | 12 | # GNU Lesser General Public License for more details. | 12 | # See the License for the specific language governing permissions and |
1003 | 13 | # | 13 | # limitations under the License. |
991 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
992 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
1004 | 16 | 14 | ||
1005 | === modified file 'charmhelpers/contrib/openstack/amulet/deployment.py' | |||
1006 | --- charmhelpers/contrib/openstack/amulet/deployment.py 2015-09-28 20:09:02 +0000 | |||
1007 | +++ charmhelpers/contrib/openstack/amulet/deployment.py 2017-03-04 02:21:16 +0000 | |||
1008 | @@ -1,25 +1,29 @@ | |||
1009 | 1 | # Copyright 2014-2015 Canonical Limited. | 1 | # Copyright 2014-2015 Canonical Limited. |
1010 | 2 | # | 2 | # |
1024 | 3 | # This file is part of charm-helpers. | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
1025 | 4 | # | 4 | # you may not use this file except in compliance with the License. |
1026 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | 5 | # You may obtain a copy of the License at |
1027 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | 6 | # |
1028 | 7 | # published by the Free Software Foundation. | 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
1029 | 8 | # | 8 | # |
1030 | 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 |
1031 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
1032 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
1033 | 12 | # GNU Lesser General Public License for more details. | 12 | # See the License for the specific language governing permissions and |
1034 | 13 | # | 13 | # limitations under the License. |
1022 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
1023 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
1035 | 16 | 14 | ||
1036 | 15 | import logging | ||
1037 | 16 | import re | ||
1038 | 17 | import sys | ||
1039 | 17 | import six | 18 | import six |
1040 | 18 | from collections import OrderedDict | 19 | from collections import OrderedDict |
1041 | 19 | from charmhelpers.contrib.amulet.deployment import ( | 20 | from charmhelpers.contrib.amulet.deployment import ( |
1042 | 20 | AmuletDeployment | 21 | AmuletDeployment |
1043 | 21 | ) | 22 | ) |
1044 | 22 | 23 | ||
1045 | 24 | DEBUG = logging.DEBUG | ||
1046 | 25 | ERROR = logging.ERROR | ||
1047 | 26 | |||
1048 | 23 | 27 | ||
1049 | 24 | class OpenStackAmuletDeployment(AmuletDeployment): | 28 | class OpenStackAmuletDeployment(AmuletDeployment): |
1050 | 25 | """OpenStack amulet deployment. | 29 | """OpenStack amulet deployment. |
1051 | @@ -28,15 +32,31 @@ | |||
1052 | 28 | that is specifically for use by OpenStack charms. | 32 | that is specifically for use by OpenStack charms. |
1053 | 29 | """ | 33 | """ |
1054 | 30 | 34 | ||
1056 | 31 | def __init__(self, series=None, openstack=None, source=None, stable=True): | 35 | def __init__(self, series=None, openstack=None, source=None, |
1057 | 36 | stable=True, log_level=DEBUG): | ||
1058 | 32 | """Initialize the deployment environment.""" | 37 | """Initialize the deployment environment.""" |
1059 | 33 | super(OpenStackAmuletDeployment, self).__init__(series) | 38 | super(OpenStackAmuletDeployment, self).__init__(series) |
1060 | 39 | self.log = self.get_logger(level=log_level) | ||
1061 | 40 | self.log.info('OpenStackAmuletDeployment: init') | ||
1062 | 34 | self.openstack = openstack | 41 | self.openstack = openstack |
1063 | 35 | self.source = source | 42 | self.source = source |
1064 | 36 | self.stable = stable | 43 | self.stable = stable |
1068 | 37 | # Note(coreycb): this needs to be changed when new next branches come | 44 | |
1069 | 38 | # out. | 45 | def get_logger(self, name="deployment-logger", level=logging.DEBUG): |
1070 | 39 | self.current_next = "trusty" | 46 | """Get a logger object that will log to stdout.""" |
1071 | 47 | log = logging | ||
1072 | 48 | logger = log.getLogger(name) | ||
1073 | 49 | fmt = log.Formatter("%(asctime)s %(funcName)s " | ||
1074 | 50 | "%(levelname)s: %(message)s") | ||
1075 | 51 | |||
1076 | 52 | handler = log.StreamHandler(stream=sys.stdout) | ||
1077 | 53 | handler.setLevel(level) | ||
1078 | 54 | handler.setFormatter(fmt) | ||
1079 | 55 | |||
1080 | 56 | logger.addHandler(handler) | ||
1081 | 57 | logger.setLevel(level) | ||
1082 | 58 | |||
1083 | 59 | return logger | ||
1084 | 40 | 60 | ||
1085 | 41 | def _determine_branch_locations(self, other_services): | 61 | def _determine_branch_locations(self, other_services): |
1086 | 42 | """Determine the branch locations for the other services. | 62 | """Determine the branch locations for the other services. |
1087 | @@ -45,43 +65,82 @@ | |||
1088 | 45 | stable or next (dev) branch, and based on this, use the corresonding | 65 | stable or next (dev) branch, and based on this, use the corresonding |
1089 | 46 | stable or next branches for the other_services.""" | 66 | stable or next branches for the other_services.""" |
1090 | 47 | 67 | ||
1103 | 48 | # Charms outside the lp:~openstack-charmers namespace | 68 | self.log.info('OpenStackAmuletDeployment: determine branch locations') |
1104 | 49 | base_charms = ['mysql', 'mongodb', 'nrpe'] | 69 | |
1105 | 50 | 70 | # Charms outside the ~openstack-charmers | |
1106 | 51 | # Force these charms to current series even when using an older series. | 71 | base_charms = { |
1107 | 52 | # ie. Use trusty/nrpe even when series is precise, as the P charm | 72 | 'mysql': ['trusty'], |
1108 | 53 | # does not possess the necessary external master config and hooks. | 73 | 'mongodb': ['trusty'], |
1109 | 54 | force_series_current = ['nrpe'] | 74 | 'nrpe': ['trusty', 'xenial'], |
1110 | 55 | 75 | } | |
1099 | 56 | if self.series in ['precise', 'trusty']: | ||
1100 | 57 | base_series = self.series | ||
1101 | 58 | else: | ||
1102 | 59 | base_series = self.current_next | ||
1111 | 60 | 76 | ||
1112 | 61 | for svc in other_services: | 77 | for svc in other_services: |
1113 | 62 | if svc['name'] in force_series_current: | ||
1114 | 63 | base_series = self.current_next | ||
1115 | 64 | # If a location has been explicitly set, use it | 78 | # If a location has been explicitly set, use it |
1116 | 65 | if svc.get('location'): | 79 | if svc.get('location'): |
1117 | 66 | continue | 80 | continue |
1122 | 67 | if self.stable: | 81 | if svc['name'] in base_charms: |
1123 | 68 | temp = 'lp:charms/{}/{}' | 82 | # NOTE: not all charms have support for all series we |
1124 | 69 | svc['location'] = temp.format(base_series, | 83 | # want/need to test against, so fix to most recent |
1125 | 70 | svc['name']) | 84 | # that each base charm supports |
1126 | 85 | target_series = self.series | ||
1127 | 86 | if self.series not in base_charms[svc['name']]: | ||
1128 | 87 | target_series = base_charms[svc['name']][-1] | ||
1129 | 88 | svc['location'] = 'cs:{}/{}'.format(target_series, | ||
1130 | 89 | svc['name']) | ||
1131 | 90 | elif self.stable: | ||
1132 | 91 | svc['location'] = 'cs:{}/{}'.format(self.series, | ||
1133 | 92 | svc['name']) | ||
1134 | 71 | else: | 93 | else: |
1143 | 72 | if svc['name'] in base_charms: | 94 | svc['location'] = 'cs:~openstack-charmers-next/{}/{}'.format( |
1144 | 73 | temp = 'lp:charms/{}/{}' | 95 | self.series, |
1145 | 74 | svc['location'] = temp.format(base_series, | 96 | svc['name'] |
1146 | 75 | svc['name']) | 97 | ) |
1139 | 76 | else: | ||
1140 | 77 | temp = 'lp:~openstack-charmers/charms/{}/{}/next' | ||
1141 | 78 | svc['location'] = temp.format(self.current_next, | ||
1142 | 79 | svc['name']) | ||
1147 | 80 | 98 | ||
1148 | 81 | return other_services | 99 | return other_services |
1149 | 82 | 100 | ||
1152 | 83 | def _add_services(self, this_service, other_services): | 101 | def _add_services(self, this_service, other_services, use_source=None, |
1153 | 84 | """Add services to the deployment and set openstack-origin/source.""" | 102 | no_origin=None): |
1154 | 103 | """Add services to the deployment and optionally set | ||
1155 | 104 | openstack-origin/source. | ||
1156 | 105 | |||
1157 | 106 | :param this_service dict: Service dictionary describing the service | ||
1158 | 107 | whose amulet tests are being run | ||
1159 | 108 | :param other_services dict: List of service dictionaries describing | ||
1160 | 109 | the services needed to support the target | ||
1161 | 110 | service | ||
1162 | 111 | :param use_source list: List of services which use the 'source' config | ||
1163 | 112 | option rather than 'openstack-origin' | ||
1164 | 113 | :param no_origin list: List of services which do not support setting | ||
1165 | 114 | the Cloud Archive. | ||
1166 | 115 | Service Dict: | ||
1167 | 116 | { | ||
1168 | 117 | 'name': str charm-name, | ||
1169 | 118 | 'units': int number of units, | ||
1170 | 119 | 'constraints': dict of juju constraints, | ||
1171 | 120 | 'location': str location of charm, | ||
1172 | 121 | } | ||
1173 | 122 | eg | ||
1174 | 123 | this_service = { | ||
1175 | 124 | 'name': 'openvswitch-odl', | ||
1176 | 125 | 'constraints': {'mem': '8G'}, | ||
1177 | 126 | } | ||
1178 | 127 | other_services = [ | ||
1179 | 128 | { | ||
1180 | 129 | 'name': 'nova-compute', | ||
1181 | 130 | 'units': 2, | ||
1182 | 131 | 'constraints': {'mem': '4G'}, | ||
1183 | 132 | 'location': cs:~bob/xenial/nova-compute | ||
1184 | 133 | }, | ||
1185 | 134 | { | ||
1186 | 135 | 'name': 'mysql', | ||
1187 | 136 | 'constraints': {'mem': '2G'}, | ||
1188 | 137 | }, | ||
1189 | 138 | {'neutron-api-odl'}] | ||
1190 | 139 | use_source = ['mysql'] | ||
1191 | 140 | no_origin = ['neutron-api-odl'] | ||
1192 | 141 | """ | ||
1193 | 142 | self.log.info('OpenStackAmuletDeployment: adding services') | ||
1194 | 143 | |||
1195 | 85 | other_services = self._determine_branch_locations(other_services) | 144 | other_services = self._determine_branch_locations(other_services) |
1196 | 86 | 145 | ||
1197 | 87 | super(OpenStackAmuletDeployment, self)._add_services(this_service, | 146 | super(OpenStackAmuletDeployment, self)._add_services(this_service, |
1198 | @@ -90,12 +149,22 @@ | |||
1199 | 90 | services = other_services | 149 | services = other_services |
1200 | 91 | services.append(this_service) | 150 | services.append(this_service) |
1201 | 92 | 151 | ||
1202 | 152 | use_source = use_source or [] | ||
1203 | 153 | no_origin = no_origin or [] | ||
1204 | 154 | |||
1205 | 93 | # Charms which should use the source config option | 155 | # Charms which should use the source config option |
1208 | 94 | use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph', | 156 | use_source = list(set( |
1209 | 95 | 'ceph-osd', 'ceph-radosgw'] | 157 | use_source + ['mysql', 'mongodb', 'rabbitmq-server', 'ceph', |
1210 | 158 | 'ceph-osd', 'ceph-radosgw', 'ceph-mon', | ||
1211 | 159 | 'ceph-proxy', 'percona-cluster', 'lxd'])) | ||
1212 | 96 | 160 | ||
1213 | 97 | # Charms which can not use openstack-origin, ie. many subordinates | 161 | # Charms which can not use openstack-origin, ie. many subordinates |
1215 | 98 | no_origin = ['cinder-ceph', 'hacluster', 'neutron-openvswitch', 'nrpe'] | 162 | no_origin = list(set( |
1216 | 163 | no_origin + ['cinder-ceph', 'hacluster', 'neutron-openvswitch', | ||
1217 | 164 | 'nrpe', 'openvswitch-odl', 'neutron-api-odl', | ||
1218 | 165 | 'odl-controller', 'cinder-backup', 'nexentaedge-data', | ||
1219 | 166 | 'nexentaedge-iscsi-gw', 'nexentaedge-swift-gw', | ||
1220 | 167 | 'cinder-nexentaedge', 'nexentaedge-mgmt'])) | ||
1221 | 99 | 168 | ||
1222 | 100 | if self.openstack: | 169 | if self.openstack: |
1223 | 101 | for svc in services: | 170 | for svc in services: |
1224 | @@ -111,9 +180,79 @@ | |||
1225 | 111 | 180 | ||
1226 | 112 | def _configure_services(self, configs): | 181 | def _configure_services(self, configs): |
1227 | 113 | """Configure all of the services.""" | 182 | """Configure all of the services.""" |
1228 | 183 | self.log.info('OpenStackAmuletDeployment: configure services') | ||
1229 | 114 | for service, config in six.iteritems(configs): | 184 | for service, config in six.iteritems(configs): |
1230 | 115 | self.d.configure(service, config) | 185 | self.d.configure(service, config) |
1231 | 116 | 186 | ||
1232 | 187 | def _auto_wait_for_status(self, message=None, exclude_services=None, | ||
1233 | 188 | include_only=None, timeout=1800): | ||
1234 | 189 | """Wait for all units to have a specific extended status, except | ||
1235 | 190 | for any defined as excluded. Unless specified via message, any | ||
1236 | 191 | status containing any case of 'ready' will be considered a match. | ||
1237 | 192 | |||
1238 | 193 | Examples of message usage: | ||
1239 | 194 | |||
1240 | 195 | Wait for all unit status to CONTAIN any case of 'ready' or 'ok': | ||
1241 | 196 | message = re.compile('.*ready.*|.*ok.*', re.IGNORECASE) | ||
1242 | 197 | |||
1243 | 198 | Wait for all units to reach this status (exact match): | ||
1244 | 199 | message = re.compile('^Unit is ready and clustered$') | ||
1245 | 200 | |||
1246 | 201 | Wait for all units to reach any one of these (exact match): | ||
1247 | 202 | message = re.compile('Unit is ready|OK|Ready') | ||
1248 | 203 | |||
1249 | 204 | Wait for at least one unit to reach this status (exact match): | ||
1250 | 205 | message = {'ready'} | ||
1251 | 206 | |||
1252 | 207 | See Amulet's sentry.wait_for_messages() for message usage detail. | ||
1253 | 208 | https://github.com/juju/amulet/blob/master/amulet/sentry.py | ||
1254 | 209 | |||
1255 | 210 | :param message: Expected status match | ||
1256 | 211 | :param exclude_services: List of juju service names to ignore, | ||
1257 | 212 | not to be used in conjuction with include_only. | ||
1258 | 213 | :param include_only: List of juju service names to exclusively check, | ||
1259 | 214 | not to be used in conjuction with exclude_services. | ||
1260 | 215 | :param timeout: Maximum time in seconds to wait for status match | ||
1261 | 216 | :returns: None. Raises if timeout is hit. | ||
1262 | 217 | """ | ||
1263 | 218 | self.log.info('Waiting for extended status on units...') | ||
1264 | 219 | |||
1265 | 220 | all_services = self.d.services.keys() | ||
1266 | 221 | |||
1267 | 222 | if exclude_services and include_only: | ||
1268 | 223 | raise ValueError('exclude_services can not be used ' | ||
1269 | 224 | 'with include_only') | ||
1270 | 225 | |||
1271 | 226 | if message: | ||
1272 | 227 | if isinstance(message, re._pattern_type): | ||
1273 | 228 | match = message.pattern | ||
1274 | 229 | else: | ||
1275 | 230 | match = message | ||
1276 | 231 | |||
1277 | 232 | self.log.debug('Custom extended status wait match: ' | ||
1278 | 233 | '{}'.format(match)) | ||
1279 | 234 | else: | ||
1280 | 235 | self.log.debug('Default extended status wait match: contains ' | ||
1281 | 236 | 'READY (case-insensitive)') | ||
1282 | 237 | message = re.compile('.*ready.*', re.IGNORECASE) | ||
1283 | 238 | |||
1284 | 239 | if exclude_services: | ||
1285 | 240 | self.log.debug('Excluding services from extended status match: ' | ||
1286 | 241 | '{}'.format(exclude_services)) | ||
1287 | 242 | else: | ||
1288 | 243 | exclude_services = [] | ||
1289 | 244 | |||
1290 | 245 | if include_only: | ||
1291 | 246 | services = include_only | ||
1292 | 247 | else: | ||
1293 | 248 | services = list(set(all_services) - set(exclude_services)) | ||
1294 | 249 | |||
1295 | 250 | self.log.debug('Waiting up to {}s for extended status on services: ' | ||
1296 | 251 | '{}'.format(timeout, services)) | ||
1297 | 252 | service_messages = {service: message for service in services} | ||
1298 | 253 | self.d.sentry.wait_for_messages(service_messages, timeout=timeout) | ||
1299 | 254 | self.log.info('OK') | ||
1300 | 255 | |||
1301 | 117 | def _get_openstack_release(self): | 256 | def _get_openstack_release(self): |
1302 | 118 | """Get openstack release. | 257 | """Get openstack release. |
1303 | 119 | 258 | ||
1304 | @@ -121,25 +260,21 @@ | |||
1305 | 121 | release. | 260 | release. |
1306 | 122 | """ | 261 | """ |
1307 | 123 | # Must be ordered by OpenStack release (not by Ubuntu release): | 262 | # Must be ordered by OpenStack release (not by Ubuntu release): |
1313 | 124 | (self.precise_essex, self.precise_folsom, self.precise_grizzly, | 263 | (self.trusty_icehouse, self.trusty_kilo, self.trusty_liberty, |
1314 | 125 | self.precise_havana, self.precise_icehouse, | 264 | self.trusty_mitaka, self.xenial_mitaka, self.xenial_newton, |
1315 | 126 | self.trusty_icehouse, self.trusty_juno, self.utopic_juno, | 265 | self.yakkety_newton, self.xenial_ocata, self.zesty_ocata) = range(9) |
1311 | 127 | self.trusty_kilo, self.vivid_kilo, self.trusty_liberty, | ||
1312 | 128 | self.wily_liberty) = range(12) | ||
1316 | 129 | 266 | ||
1317 | 130 | releases = { | 267 | releases = { |
1318 | 131 | ('precise', None): self.precise_essex, | ||
1319 | 132 | ('precise', 'cloud:precise-folsom'): self.precise_folsom, | ||
1320 | 133 | ('precise', 'cloud:precise-grizzly'): self.precise_grizzly, | ||
1321 | 134 | ('precise', 'cloud:precise-havana'): self.precise_havana, | ||
1322 | 135 | ('precise', 'cloud:precise-icehouse'): self.precise_icehouse, | ||
1323 | 136 | ('trusty', None): self.trusty_icehouse, | 268 | ('trusty', None): self.trusty_icehouse, |
1324 | 137 | ('trusty', 'cloud:trusty-juno'): self.trusty_juno, | ||
1325 | 138 | ('trusty', 'cloud:trusty-kilo'): self.trusty_kilo, | 269 | ('trusty', 'cloud:trusty-kilo'): self.trusty_kilo, |
1326 | 139 | ('trusty', 'cloud:trusty-liberty'): self.trusty_liberty, | 270 | ('trusty', 'cloud:trusty-liberty'): self.trusty_liberty, |
1330 | 140 | ('utopic', None): self.utopic_juno, | 271 | ('trusty', 'cloud:trusty-mitaka'): self.trusty_mitaka, |
1331 | 141 | ('vivid', None): self.vivid_kilo, | 272 | ('xenial', None): self.xenial_mitaka, |
1332 | 142 | ('wily', None): self.wily_liberty} | 273 | ('xenial', 'cloud:xenial-newton'): self.xenial_newton, |
1333 | 274 | ('xenial', 'cloud:xenial-ocata'): self.xenial_ocata, | ||
1334 | 275 | ('yakkety', None): self.yakkety_newton, | ||
1335 | 276 | ('zesty', None): self.zesty_ocata, | ||
1336 | 277 | } | ||
1337 | 143 | return releases[(self.series, self.openstack)] | 278 | return releases[(self.series, self.openstack)] |
1338 | 144 | 279 | ||
1339 | 145 | def _get_openstack_release_string(self): | 280 | def _get_openstack_release_string(self): |
1340 | @@ -148,14 +283,10 @@ | |||
1341 | 148 | Return a string representing the openstack release. | 283 | Return a string representing the openstack release. |
1342 | 149 | """ | 284 | """ |
1343 | 150 | releases = OrderedDict([ | 285 | releases = OrderedDict([ |
1344 | 151 | ('precise', 'essex'), | ||
1345 | 152 | ('quantal', 'folsom'), | ||
1346 | 153 | ('raring', 'grizzly'), | ||
1347 | 154 | ('saucy', 'havana'), | ||
1348 | 155 | ('trusty', 'icehouse'), | 286 | ('trusty', 'icehouse'), |
1352 | 156 | ('utopic', 'juno'), | 287 | ('xenial', 'mitaka'), |
1353 | 157 | ('vivid', 'kilo'), | 288 | ('yakkety', 'newton'), |
1354 | 158 | ('wily', 'liberty'), | 289 | ('zesty', 'ocata'), |
1355 | 159 | ]) | 290 | ]) |
1356 | 160 | if self.openstack: | 291 | if self.openstack: |
1357 | 161 | os_origin = self.openstack.split(':')[1] | 292 | os_origin = self.openstack.split(':')[1] |
1358 | 162 | 293 | ||
1359 | === modified file 'charmhelpers/contrib/openstack/amulet/utils.py' | |||
1360 | --- charmhelpers/contrib/openstack/amulet/utils.py 2015-09-28 20:38:07 +0000 | |||
1361 | +++ charmhelpers/contrib/openstack/amulet/utils.py 2017-03-04 02:21:16 +0000 | |||
1362 | @@ -1,42 +1,51 @@ | |||
1363 | 1 | # Copyright 2014-2015 Canonical Limited. | 1 | # Copyright 2014-2015 Canonical Limited. |
1364 | 2 | # | 2 | # |
1378 | 3 | # This file is part of charm-helpers. | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
1379 | 4 | # | 4 | # you may not use this file except in compliance with the License. |
1380 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | 5 | # You may obtain a copy of the License at |
1381 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | 6 | # |
1382 | 7 | # published by the Free Software Foundation. | 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
1383 | 8 | # | 8 | # |
1384 | 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 |
1385 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
1386 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
1387 | 12 | # GNU Lesser General Public License for more details. | 12 | # See the License for the specific language governing permissions and |
1388 | 13 | # | 13 | # limitations under the License. |
1376 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
1377 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
1389 | 16 | 14 | ||
1390 | 17 | import amulet | 15 | import amulet |
1391 | 18 | import json | 16 | import json |
1392 | 19 | import logging | 17 | import logging |
1393 | 20 | import os | 18 | import os |
1394 | 19 | import re | ||
1395 | 21 | import six | 20 | import six |
1396 | 22 | import time | 21 | import time |
1397 | 23 | import urllib | 22 | import urllib |
1398 | 23 | import urlparse | ||
1399 | 24 | 24 | ||
1400 | 25 | import cinderclient.v1.client as cinder_client | 25 | import cinderclient.v1.client as cinder_client |
1401 | 26 | import glanceclient.v1.client as glance_client | 26 | import glanceclient.v1.client as glance_client |
1402 | 27 | import heatclient.v1.client as heat_client | 27 | import heatclient.v1.client as heat_client |
1403 | 28 | import keystoneclient.v2_0 as keystone_client | 28 | import keystoneclient.v2_0 as keystone_client |
1405 | 29 | import novaclient.v1_1.client as nova_client | 29 | from keystoneclient.auth.identity import v3 as keystone_id_v3 |
1406 | 30 | from keystoneclient import session as keystone_session | ||
1407 | 31 | from keystoneclient.v3 import client as keystone_client_v3 | ||
1408 | 32 | from novaclient import exceptions | ||
1409 | 33 | |||
1410 | 34 | import novaclient.client as nova_client | ||
1411 | 35 | import novaclient | ||
1412 | 30 | import pika | 36 | import pika |
1413 | 31 | import swiftclient | 37 | import swiftclient |
1414 | 32 | 38 | ||
1415 | 33 | from charmhelpers.contrib.amulet.utils import ( | 39 | from charmhelpers.contrib.amulet.utils import ( |
1416 | 34 | AmuletUtils | 40 | AmuletUtils |
1417 | 35 | ) | 41 | ) |
1418 | 42 | from charmhelpers.core.decorators import retry_on_exception | ||
1419 | 36 | 43 | ||
1420 | 37 | DEBUG = logging.DEBUG | 44 | DEBUG = logging.DEBUG |
1421 | 38 | ERROR = logging.ERROR | 45 | ERROR = logging.ERROR |
1422 | 39 | 46 | ||
1423 | 47 | NOVA_CLIENT_VERSION = "2" | ||
1424 | 48 | |||
1425 | 40 | 49 | ||
1426 | 41 | class OpenStackAmuletUtils(AmuletUtils): | 50 | class OpenStackAmuletUtils(AmuletUtils): |
1427 | 42 | """OpenStack amulet utilities. | 51 | """OpenStack amulet utilities. |
1428 | @@ -78,6 +87,56 @@ | |||
1429 | 78 | if not found: | 87 | if not found: |
1430 | 79 | return 'endpoint not found' | 88 | return 'endpoint not found' |
1431 | 80 | 89 | ||
1432 | 90 | def validate_v3_endpoint_data(self, endpoints, admin_port, internal_port, | ||
1433 | 91 | public_port, expected): | ||
1434 | 92 | """Validate keystone v3 endpoint data. | ||
1435 | 93 | |||
1436 | 94 | Validate the v3 endpoint data which has changed from v2. The | ||
1437 | 95 | ports are used to find the matching endpoint. | ||
1438 | 96 | |||
1439 | 97 | The new v3 endpoint data looks like: | ||
1440 | 98 | |||
1441 | 99 | [<Endpoint enabled=True, | ||
1442 | 100 | id=0432655fc2f74d1e9fa17bdaa6f6e60b, | ||
1443 | 101 | interface=admin, | ||
1444 | 102 | links={u'self': u'<RESTful URL of this endpoint>'}, | ||
1445 | 103 | region=RegionOne, | ||
1446 | 104 | region_id=RegionOne, | ||
1447 | 105 | service_id=17f842a0dc084b928e476fafe67e4095, | ||
1448 | 106 | url=http://10.5.6.5:9312>, | ||
1449 | 107 | <Endpoint enabled=True, | ||
1450 | 108 | id=6536cb6cb92f4f41bf22b079935c7707, | ||
1451 | 109 | interface=admin, | ||
1452 | 110 | links={u'self': u'<RESTful url of this endpoint>'}, | ||
1453 | 111 | region=RegionOne, | ||
1454 | 112 | region_id=RegionOne, | ||
1455 | 113 | service_id=72fc8736fb41435e8b3584205bb2cfa3, | ||
1456 | 114 | url=http://10.5.6.6:35357/v3>, | ||
1457 | 115 | ... ] | ||
1458 | 116 | """ | ||
1459 | 117 | self.log.debug('Validating v3 endpoint data...') | ||
1460 | 118 | self.log.debug('actual: {}'.format(repr(endpoints))) | ||
1461 | 119 | found = [] | ||
1462 | 120 | for ep in endpoints: | ||
1463 | 121 | self.log.debug('endpoint: {}'.format(repr(ep))) | ||
1464 | 122 | if ((admin_port in ep.url and ep.interface == 'admin') or | ||
1465 | 123 | (internal_port in ep.url and ep.interface == 'internal') or | ||
1466 | 124 | (public_port in ep.url and ep.interface == 'public')): | ||
1467 | 125 | found.append(ep.interface) | ||
1468 | 126 | # note we ignore the links member. | ||
1469 | 127 | actual = {'id': ep.id, | ||
1470 | 128 | 'region': ep.region, | ||
1471 | 129 | 'region_id': ep.region_id, | ||
1472 | 130 | 'interface': self.not_null, | ||
1473 | 131 | 'url': ep.url, | ||
1474 | 132 | 'service_id': ep.service_id, } | ||
1475 | 133 | ret = self._validate_dict_data(expected, actual) | ||
1476 | 134 | if ret: | ||
1477 | 135 | return 'unexpected endpoint data - {}'.format(ret) | ||
1478 | 136 | |||
1479 | 137 | if len(found) != 3: | ||
1480 | 138 | return 'Unexpected number of endpoints found' | ||
1481 | 139 | |||
1482 | 81 | def validate_svc_catalog_endpoint_data(self, expected, actual): | 140 | def validate_svc_catalog_endpoint_data(self, expected, actual): |
1483 | 82 | """Validate service catalog endpoint data. | 141 | """Validate service catalog endpoint data. |
1484 | 83 | 142 | ||
1485 | @@ -95,6 +154,72 @@ | |||
1486 | 95 | return "endpoint {} does not exist".format(k) | 154 | return "endpoint {} does not exist".format(k) |
1487 | 96 | return ret | 155 | return ret |
1488 | 97 | 156 | ||
1489 | 157 | def validate_v3_svc_catalog_endpoint_data(self, expected, actual): | ||
1490 | 158 | """Validate the keystone v3 catalog endpoint data. | ||
1491 | 159 | |||
1492 | 160 | Validate a list of dictinaries that make up the keystone v3 service | ||
1493 | 161 | catalogue. | ||
1494 | 162 | |||
1495 | 163 | It is in the form of: | ||
1496 | 164 | |||
1497 | 165 | |||
1498 | 166 | {u'identity': [{u'id': u'48346b01c6804b298cdd7349aadb732e', | ||
1499 | 167 | u'interface': u'admin', | ||
1500 | 168 | u'region': u'RegionOne', | ||
1501 | 169 | u'region_id': u'RegionOne', | ||
1502 | 170 | u'url': u'http://10.5.5.224:35357/v3'}, | ||
1503 | 171 | {u'id': u'8414f7352a4b47a69fddd9dbd2aef5cf', | ||
1504 | 172 | u'interface': u'public', | ||
1505 | 173 | u'region': u'RegionOne', | ||
1506 | 174 | u'region_id': u'RegionOne', | ||
1507 | 175 | u'url': u'http://10.5.5.224:5000/v3'}, | ||
1508 | 176 | {u'id': u'd5ca31440cc24ee1bf625e2996fb6a5b', | ||
1509 | 177 | u'interface': u'internal', | ||
1510 | 178 | u'region': u'RegionOne', | ||
1511 | 179 | u'region_id': u'RegionOne', | ||
1512 | 180 | u'url': u'http://10.5.5.224:5000/v3'}], | ||
1513 | 181 | u'key-manager': [{u'id': u'68ebc17df0b045fcb8a8a433ebea9e62', | ||
1514 | 182 | u'interface': u'public', | ||
1515 | 183 | u'region': u'RegionOne', | ||
1516 | 184 | u'region_id': u'RegionOne', | ||
1517 | 185 | u'url': u'http://10.5.5.223:9311'}, | ||
1518 | 186 | {u'id': u'9cdfe2a893c34afd8f504eb218cd2f9d', | ||
1519 | 187 | u'interface': u'internal', | ||
1520 | 188 | u'region': u'RegionOne', | ||
1521 | 189 | u'region_id': u'RegionOne', | ||
1522 | 190 | u'url': u'http://10.5.5.223:9311'}, | ||
1523 | 191 | {u'id': u'f629388955bc407f8b11d8b7ca168086', | ||
1524 | 192 | u'interface': u'admin', | ||
1525 | 193 | u'region': u'RegionOne', | ||
1526 | 194 | u'region_id': u'RegionOne', | ||
1527 | 195 | u'url': u'http://10.5.5.223:9312'}]} | ||
1528 | 196 | |||
1529 | 197 | Note, that an added complication is that the order of admin, public, | ||
1530 | 198 | internal against 'interface' in each region. | ||
1531 | 199 | |||
1532 | 200 | Thus, the function sorts the expected and actual lists using the | ||
1533 | 201 | interface key as a sort key, prior to the comparison. | ||
1534 | 202 | """ | ||
1535 | 203 | self.log.debug('Validating v3 service catalog endpoint data...') | ||
1536 | 204 | self.log.debug('actual: {}'.format(repr(actual))) | ||
1537 | 205 | for k, v in six.iteritems(expected): | ||
1538 | 206 | if k in actual: | ||
1539 | 207 | l_expected = sorted(v, key=lambda x: x['interface']) | ||
1540 | 208 | l_actual = sorted(actual[k], key=lambda x: x['interface']) | ||
1541 | 209 | if len(l_actual) != len(l_expected): | ||
1542 | 210 | return ("endpoint {} has differing number of interfaces " | ||
1543 | 211 | " - expected({}), actual({})" | ||
1544 | 212 | .format(k, len(l_expected), len(l_actual))) | ||
1545 | 213 | for i_expected, i_actual in zip(l_expected, l_actual): | ||
1546 | 214 | self.log.debug("checking interface {}" | ||
1547 | 215 | .format(i_expected['interface'])) | ||
1548 | 216 | ret = self._validate_dict_data(i_expected, i_actual) | ||
1549 | 217 | if ret: | ||
1550 | 218 | return self.endpoint_error(k, ret) | ||
1551 | 219 | else: | ||
1552 | 220 | return "endpoint {} does not exist".format(k) | ||
1553 | 221 | return ret | ||
1554 | 222 | |||
1555 | 98 | def validate_tenant_data(self, expected, actual): | 223 | def validate_tenant_data(self, expected, actual): |
1556 | 99 | """Validate tenant data. | 224 | """Validate tenant data. |
1557 | 100 | 225 | ||
1558 | @@ -138,7 +263,7 @@ | |||
1559 | 138 | return "role {} does not exist".format(e['name']) | 263 | return "role {} does not exist".format(e['name']) |
1560 | 139 | return ret | 264 | return ret |
1561 | 140 | 265 | ||
1563 | 141 | def validate_user_data(self, expected, actual): | 266 | def validate_user_data(self, expected, actual, api_version=None): |
1564 | 142 | """Validate user data. | 267 | """Validate user data. |
1565 | 143 | 268 | ||
1566 | 144 | Validate a list of actual user data vs a list of expected user | 269 | Validate a list of actual user data vs a list of expected user |
1567 | @@ -149,10 +274,15 @@ | |||
1568 | 149 | for e in expected: | 274 | for e in expected: |
1569 | 150 | found = False | 275 | found = False |
1570 | 151 | for act in actual: | 276 | for act in actual: |
1575 | 152 | a = {'enabled': act.enabled, 'name': act.name, | 277 | if e['name'] == act.name: |
1576 | 153 | 'email': act.email, 'tenantId': act.tenantId, | 278 | a = {'enabled': act.enabled, 'name': act.name, |
1577 | 154 | 'id': act.id} | 279 | 'email': act.email, 'id': act.id} |
1578 | 155 | if e['name'] == a['name']: | 280 | if api_version == 3: |
1579 | 281 | a['default_project_id'] = getattr(act, | ||
1580 | 282 | 'default_project_id', | ||
1581 | 283 | 'none') | ||
1582 | 284 | else: | ||
1583 | 285 | a['tenantId'] = act.tenantId | ||
1584 | 156 | found = True | 286 | found = True |
1585 | 157 | ret = self._validate_dict_data(e, a) | 287 | ret = self._validate_dict_data(e, a) |
1586 | 158 | if ret: | 288 | if ret: |
1587 | @@ -176,34 +306,115 @@ | |||
1588 | 176 | self.log.debug('Checking if tenant exists ({})...'.format(tenant)) | 306 | self.log.debug('Checking if tenant exists ({})...'.format(tenant)) |
1589 | 177 | return tenant in [t.name for t in keystone.tenants.list()] | 307 | return tenant in [t.name for t in keystone.tenants.list()] |
1590 | 178 | 308 | ||
1591 | 309 | @retry_on_exception(5, base_delay=10) | ||
1592 | 310 | def keystone_wait_for_propagation(self, sentry_relation_pairs, | ||
1593 | 311 | api_version): | ||
1594 | 312 | """Iterate over list of sentry and relation tuples and verify that | ||
1595 | 313 | api_version has the expected value. | ||
1596 | 314 | |||
1597 | 315 | :param sentry_relation_pairs: list of sentry, relation name tuples used | ||
1598 | 316 | for monitoring propagation of relation | ||
1599 | 317 | data | ||
1600 | 318 | :param api_version: api_version to expect in relation data | ||
1601 | 319 | :returns: None if successful. Raise on error. | ||
1602 | 320 | """ | ||
1603 | 321 | for (sentry, relation_name) in sentry_relation_pairs: | ||
1604 | 322 | rel = sentry.relation('identity-service', | ||
1605 | 323 | relation_name) | ||
1606 | 324 | self.log.debug('keystone relation data: {}'.format(rel)) | ||
1607 | 325 | if rel['api_version'] != str(api_version): | ||
1608 | 326 | raise Exception("api_version not propagated through relation" | ||
1609 | 327 | " data yet ('{}' != '{}')." | ||
1610 | 328 | "".format(rel['api_version'], api_version)) | ||
1611 | 329 | |||
1612 | 330 | def keystone_configure_api_version(self, sentry_relation_pairs, deployment, | ||
1613 | 331 | api_version): | ||
1614 | 332 | """Configure preferred-api-version of keystone in deployment and | ||
1615 | 333 | monitor provided list of relation objects for propagation | ||
1616 | 334 | before returning to caller. | ||
1617 | 335 | |||
1618 | 336 | :param sentry_relation_pairs: list of sentry, relation tuples used for | ||
1619 | 337 | monitoring propagation of relation data | ||
1620 | 338 | :param deployment: deployment to configure | ||
1621 | 339 | :param api_version: value preferred-api-version will be set to | ||
1622 | 340 | :returns: None if successful. Raise on error. | ||
1623 | 341 | """ | ||
1624 | 342 | self.log.debug("Setting keystone preferred-api-version: '{}'" | ||
1625 | 343 | "".format(api_version)) | ||
1626 | 344 | |||
1627 | 345 | config = {'preferred-api-version': api_version} | ||
1628 | 346 | deployment.d.configure('keystone', config) | ||
1629 | 347 | self.keystone_wait_for_propagation(sentry_relation_pairs, api_version) | ||
1630 | 348 | |||
1631 | 179 | def authenticate_cinder_admin(self, keystone_sentry, username, | 349 | def authenticate_cinder_admin(self, keystone_sentry, username, |
1632 | 180 | password, tenant): | 350 | password, tenant): |
1633 | 181 | """Authenticates admin user with cinder.""" | 351 | """Authenticates admin user with cinder.""" |
1634 | 182 | # NOTE(beisner): cinder python client doesn't accept tokens. | 352 | # NOTE(beisner): cinder python client doesn't accept tokens. |
1639 | 183 | service_ip = \ | 353 | keystone_ip = keystone_sentry.info['public-address'] |
1640 | 184 | keystone_sentry.relation('shared-db', | 354 | ept = "http://{}:5000/v2.0".format(keystone_ip.strip().decode('utf-8')) |
1637 | 185 | 'mysql:shared-db')['private-address'] | ||
1638 | 186 | ept = "http://{}:5000/v2.0".format(service_ip.strip().decode('utf-8')) | ||
1641 | 187 | return cinder_client.Client(username, password, tenant, ept) | 355 | return cinder_client.Client(username, password, tenant, ept) |
1642 | 188 | 356 | ||
1643 | 357 | def authenticate_keystone(self, keystone_ip, username, password, | ||
1644 | 358 | api_version=False, admin_port=False, | ||
1645 | 359 | user_domain_name=None, domain_name=None, | ||
1646 | 360 | project_domain_name=None, project_name=None): | ||
1647 | 361 | """Authenticate with Keystone""" | ||
1648 | 362 | self.log.debug('Authenticating with keystone...') | ||
1649 | 363 | port = 5000 | ||
1650 | 364 | if admin_port: | ||
1651 | 365 | port = 35357 | ||
1652 | 366 | base_ep = "http://{}:{}".format(keystone_ip.strip().decode('utf-8'), | ||
1653 | 367 | port) | ||
1654 | 368 | if not api_version or api_version == 2: | ||
1655 | 369 | ep = base_ep + "/v2.0" | ||
1656 | 370 | return keystone_client.Client(username=username, password=password, | ||
1657 | 371 | tenant_name=project_name, | ||
1658 | 372 | auth_url=ep) | ||
1659 | 373 | else: | ||
1660 | 374 | ep = base_ep + "/v3" | ||
1661 | 375 | auth = keystone_id_v3.Password( | ||
1662 | 376 | user_domain_name=user_domain_name, | ||
1663 | 377 | username=username, | ||
1664 | 378 | password=password, | ||
1665 | 379 | domain_name=domain_name, | ||
1666 | 380 | project_domain_name=project_domain_name, | ||
1667 | 381 | project_name=project_name, | ||
1668 | 382 | auth_url=ep | ||
1669 | 383 | ) | ||
1670 | 384 | return keystone_client_v3.Client( | ||
1671 | 385 | session=keystone_session.Session(auth=auth) | ||
1672 | 386 | ) | ||
1673 | 387 | |||
1674 | 189 | def authenticate_keystone_admin(self, keystone_sentry, user, password, | 388 | def authenticate_keystone_admin(self, keystone_sentry, user, password, |
1676 | 190 | tenant): | 389 | tenant=None, api_version=None, |
1677 | 390 | keystone_ip=None): | ||
1678 | 191 | """Authenticates admin user with the keystone admin endpoint.""" | 391 | """Authenticates admin user with the keystone admin endpoint.""" |
1679 | 192 | self.log.debug('Authenticating keystone admin...') | 392 | self.log.debug('Authenticating keystone admin...') |
1686 | 193 | unit = keystone_sentry | 393 | if not keystone_ip: |
1687 | 194 | service_ip = unit.relation('shared-db', | 394 | keystone_ip = keystone_sentry.info['public-address'] |
1688 | 195 | 'mysql:shared-db')['private-address'] | 395 | |
1689 | 196 | ep = "http://{}:35357/v2.0".format(service_ip.strip().decode('utf-8')) | 396 | user_domain_name = None |
1690 | 197 | return keystone_client.Client(username=user, password=password, | 397 | domain_name = None |
1691 | 198 | tenant_name=tenant, auth_url=ep) | 398 | if api_version == 3: |
1692 | 399 | user_domain_name = 'admin_domain' | ||
1693 | 400 | domain_name = user_domain_name | ||
1694 | 401 | |||
1695 | 402 | return self.authenticate_keystone(keystone_ip, user, password, | ||
1696 | 403 | project_name=tenant, | ||
1697 | 404 | api_version=api_version, | ||
1698 | 405 | user_domain_name=user_domain_name, | ||
1699 | 406 | domain_name=domain_name, | ||
1700 | 407 | admin_port=True) | ||
1701 | 199 | 408 | ||
1702 | 200 | def authenticate_keystone_user(self, keystone, user, password, tenant): | 409 | def authenticate_keystone_user(self, keystone, user, password, tenant): |
1703 | 201 | """Authenticates a regular user with the keystone public endpoint.""" | 410 | """Authenticates a regular user with the keystone public endpoint.""" |
1704 | 202 | self.log.debug('Authenticating keystone user ({})...'.format(user)) | 411 | self.log.debug('Authenticating keystone user ({})...'.format(user)) |
1705 | 203 | ep = keystone.service_catalog.url_for(service_type='identity', | 412 | ep = keystone.service_catalog.url_for(service_type='identity', |
1706 | 204 | endpoint_type='publicURL') | 413 | endpoint_type='publicURL') |
1709 | 205 | return keystone_client.Client(username=user, password=password, | 414 | keystone_ip = urlparse.urlparse(ep).hostname |
1710 | 206 | tenant_name=tenant, auth_url=ep) | 415 | |
1711 | 416 | return self.authenticate_keystone(keystone_ip, user, password, | ||
1712 | 417 | project_name=tenant) | ||
1713 | 207 | 418 | ||
1714 | 208 | def authenticate_glance_admin(self, keystone): | 419 | def authenticate_glance_admin(self, keystone): |
1715 | 209 | """Authenticates admin user with glance.""" | 420 | """Authenticates admin user with glance.""" |
1716 | @@ -224,8 +435,14 @@ | |||
1717 | 224 | self.log.debug('Authenticating nova user ({})...'.format(user)) | 435 | self.log.debug('Authenticating nova user ({})...'.format(user)) |
1718 | 225 | ep = keystone.service_catalog.url_for(service_type='identity', | 436 | ep = keystone.service_catalog.url_for(service_type='identity', |
1719 | 226 | endpoint_type='publicURL') | 437 | endpoint_type='publicURL') |
1722 | 227 | return nova_client.Client(username=user, api_key=password, | 438 | if novaclient.__version__[0] >= "7": |
1723 | 228 | project_id=tenant, auth_url=ep) | 439 | return nova_client.Client(NOVA_CLIENT_VERSION, |
1724 | 440 | username=user, password=password, | ||
1725 | 441 | project_name=tenant, auth_url=ep) | ||
1726 | 442 | else: | ||
1727 | 443 | return nova_client.Client(NOVA_CLIENT_VERSION, | ||
1728 | 444 | username=user, api_key=password, | ||
1729 | 445 | project_id=tenant, auth_url=ep) | ||
1730 | 229 | 446 | ||
1731 | 230 | def authenticate_swift_user(self, keystone, user, password, tenant): | 447 | def authenticate_swift_user(self, keystone, user, password, tenant): |
1732 | 231 | """Authenticates a regular user with swift api.""" | 448 | """Authenticates a regular user with swift api.""" |
1733 | @@ -238,6 +455,16 @@ | |||
1734 | 238 | tenant_name=tenant, | 455 | tenant_name=tenant, |
1735 | 239 | auth_version='2.0') | 456 | auth_version='2.0') |
1736 | 240 | 457 | ||
1737 | 458 | def create_flavor(self, nova, name, ram, vcpus, disk, flavorid="auto", | ||
1738 | 459 | ephemeral=0, swap=0, rxtx_factor=1.0, is_public=True): | ||
1739 | 460 | """Create the specified flavor.""" | ||
1740 | 461 | try: | ||
1741 | 462 | nova.flavors.find(name=name) | ||
1742 | 463 | except (exceptions.NotFound, exceptions.NoUniqueMatch): | ||
1743 | 464 | self.log.debug('Creating flavor ({})'.format(name)) | ||
1744 | 465 | nova.flavors.create(name, ram, vcpus, disk, flavorid, | ||
1745 | 466 | ephemeral, swap, rxtx_factor, is_public) | ||
1746 | 467 | |||
1747 | 241 | def create_cirros_image(self, glance, image_name): | 468 | def create_cirros_image(self, glance, image_name): |
1748 | 242 | """Download the latest cirros image and upload it to glance, | 469 | """Download the latest cirros image and upload it to glance, |
1749 | 243 | validate and return a resource pointer. | 470 | validate and return a resource pointer. |
1750 | @@ -604,7 +831,22 @@ | |||
1751 | 604 | '{}'.format(sample_type, samples)) | 831 | '{}'.format(sample_type, samples)) |
1752 | 605 | return None | 832 | return None |
1753 | 606 | 833 | ||
1755 | 607 | # rabbitmq/amqp specific helpers: | 834 | # rabbitmq/amqp specific helpers: |
1756 | 835 | |||
1757 | 836 | def rmq_wait_for_cluster(self, deployment, init_sleep=15, timeout=1200): | ||
1758 | 837 | """Wait for rmq units extended status to show cluster readiness, | ||
1759 | 838 | after an optional initial sleep period. Initial sleep is likely | ||
1760 | 839 | necessary to be effective following a config change, as status | ||
1761 | 840 | message may not instantly update to non-ready.""" | ||
1762 | 841 | |||
1763 | 842 | if init_sleep: | ||
1764 | 843 | time.sleep(init_sleep) | ||
1765 | 844 | |||
1766 | 845 | message = re.compile('^Unit is ready and clustered$') | ||
1767 | 846 | deployment._auto_wait_for_status(message=message, | ||
1768 | 847 | timeout=timeout, | ||
1769 | 848 | include_only=['rabbitmq-server']) | ||
1770 | 849 | |||
1771 | 608 | def add_rmq_test_user(self, sentry_units, | 850 | def add_rmq_test_user(self, sentry_units, |
1772 | 609 | username="testuser1", password="changeme"): | 851 | username="testuser1", password="changeme"): |
1773 | 610 | """Add a test user via the first rmq juju unit, check connection as | 852 | """Add a test user via the first rmq juju unit, check connection as |
1774 | @@ -805,7 +1047,10 @@ | |||
1775 | 805 | if port: | 1047 | if port: |
1776 | 806 | config['ssl_port'] = port | 1048 | config['ssl_port'] = port |
1777 | 807 | 1049 | ||
1779 | 808 | deployment.configure('rabbitmq-server', config) | 1050 | deployment.d.configure('rabbitmq-server', config) |
1780 | 1051 | |||
1781 | 1052 | # Wait for unit status | ||
1782 | 1053 | self.rmq_wait_for_cluster(deployment) | ||
1783 | 809 | 1054 | ||
1784 | 810 | # Confirm | 1055 | # Confirm |
1785 | 811 | tries = 0 | 1056 | tries = 0 |
1786 | @@ -832,7 +1077,10 @@ | |||
1787 | 832 | 1077 | ||
1788 | 833 | # Disable RMQ SSL | 1078 | # Disable RMQ SSL |
1789 | 834 | config = {'ssl': 'off'} | 1079 | config = {'ssl': 'off'} |
1791 | 835 | deployment.configure('rabbitmq-server', config) | 1080 | deployment.d.configure('rabbitmq-server', config) |
1792 | 1081 | |||
1793 | 1082 | # Wait for unit status | ||
1794 | 1083 | self.rmq_wait_for_cluster(deployment) | ||
1795 | 836 | 1084 | ||
1796 | 837 | # Confirm | 1085 | # Confirm |
1797 | 838 | tries = 0 | 1086 | tries = 0 |
1798 | @@ -881,7 +1129,8 @@ | |||
1799 | 881 | retry_delay=5, | 1129 | retry_delay=5, |
1800 | 882 | socket_timeout=1) | 1130 | socket_timeout=1) |
1801 | 883 | connection = pika.BlockingConnection(parameters) | 1131 | connection = pika.BlockingConnection(parameters) |
1803 | 884 | assert connection.server_properties['product'] == 'RabbitMQ' | 1132 | assert connection.is_open is True |
1804 | 1133 | assert connection.is_closing is False | ||
1805 | 885 | self.log.debug('Connect OK') | 1134 | self.log.debug('Connect OK') |
1806 | 886 | return connection | 1135 | return connection |
1807 | 887 | except Exception as e: | 1136 | except Exception as e: |
1808 | @@ -961,3 +1210,70 @@ | |||
1809 | 961 | else: | 1210 | else: |
1810 | 962 | msg = 'No message retrieved.' | 1211 | msg = 'No message retrieved.' |
1811 | 963 | amulet.raise_status(amulet.FAIL, msg) | 1212 | amulet.raise_status(amulet.FAIL, msg) |
1812 | 1213 | |||
1813 | 1214 | def validate_memcache(self, sentry_unit, conf, os_release, | ||
1814 | 1215 | earliest_release=5, section='keystone_authtoken', | ||
1815 | 1216 | check_kvs=None): | ||
1816 | 1217 | """Check Memcache is running and is configured to be used | ||
1817 | 1218 | |||
1818 | 1219 | Example call from Amulet test: | ||
1819 | 1220 | |||
1820 | 1221 | def test_110_memcache(self): | ||
1821 | 1222 | u.validate_memcache(self.neutron_api_sentry, | ||
1822 | 1223 | '/etc/neutron/neutron.conf', | ||
1823 | 1224 | self._get_openstack_release()) | ||
1824 | 1225 | |||
1825 | 1226 | :param sentry_unit: sentry unit | ||
1826 | 1227 | :param conf: OpenStack config file to check memcache settings | ||
1827 | 1228 | :param os_release: Current OpenStack release int code | ||
1828 | 1229 | :param earliest_release: Earliest Openstack release to check int code | ||
1829 | 1230 | :param section: OpenStack config file section to check | ||
1830 | 1231 | :param check_kvs: Dict of settings to check in config file | ||
1831 | 1232 | :returns: None | ||
1832 | 1233 | """ | ||
1833 | 1234 | if os_release < earliest_release: | ||
1834 | 1235 | self.log.debug('Skipping memcache checks for deployment. {} <' | ||
1835 | 1236 | 'mitaka'.format(os_release)) | ||
1836 | 1237 | return | ||
1837 | 1238 | _kvs = check_kvs or {'memcached_servers': 'inet6:[::1]:11211'} | ||
1838 | 1239 | self.log.debug('Checking memcached is running') | ||
1839 | 1240 | ret = self.validate_services_by_name({sentry_unit: ['memcached']}) | ||
1840 | 1241 | if ret: | ||
1841 | 1242 | amulet.raise_status(amulet.FAIL, msg='Memcache running check' | ||
1842 | 1243 | 'failed {}'.format(ret)) | ||
1843 | 1244 | else: | ||
1844 | 1245 | self.log.debug('OK') | ||
1845 | 1246 | self.log.debug('Checking memcache url is configured in {}'.format( | ||
1846 | 1247 | conf)) | ||
1847 | 1248 | if self.validate_config_data(sentry_unit, conf, section, _kvs): | ||
1848 | 1249 | message = "Memcache config error in: {}".format(conf) | ||
1849 | 1250 | amulet.raise_status(amulet.FAIL, msg=message) | ||
1850 | 1251 | else: | ||
1851 | 1252 | self.log.debug('OK') | ||
1852 | 1253 | self.log.debug('Checking memcache configuration in ' | ||
1853 | 1254 | '/etc/memcached.conf') | ||
1854 | 1255 | contents = self.file_contents_safe(sentry_unit, '/etc/memcached.conf', | ||
1855 | 1256 | fatal=True) | ||
1856 | 1257 | ubuntu_release, _ = self.run_cmd_unit(sentry_unit, 'lsb_release -cs') | ||
1857 | 1258 | if ubuntu_release <= 'trusty': | ||
1858 | 1259 | memcache_listen_addr = 'ip6-localhost' | ||
1859 | 1260 | else: | ||
1860 | 1261 | memcache_listen_addr = '::1' | ||
1861 | 1262 | expected = { | ||
1862 | 1263 | '-p': '11211', | ||
1863 | 1264 | '-l': memcache_listen_addr} | ||
1864 | 1265 | found = [] | ||
1865 | 1266 | for key, value in expected.items(): | ||
1866 | 1267 | for line in contents.split('\n'): | ||
1867 | 1268 | if line.startswith(key): | ||
1868 | 1269 | self.log.debug('Checking {} is set to {}'.format( | ||
1869 | 1270 | key, | ||
1870 | 1271 | value)) | ||
1871 | 1272 | assert value == line.split()[-1] | ||
1872 | 1273 | self.log.debug(line.split()[-1]) | ||
1873 | 1274 | found.append(key) | ||
1874 | 1275 | if sorted(found) == sorted(expected.keys()): | ||
1875 | 1276 | self.log.debug('OK') | ||
1876 | 1277 | else: | ||
1877 | 1278 | message = "Memcache config error in: /etc/memcached.conf" | ||
1878 | 1279 | amulet.raise_status(amulet.FAIL, msg=message) | ||
1879 | 964 | 1280 | ||
1880 | === modified file 'charmhelpers/contrib/openstack/context.py' | |||
1881 | --- charmhelpers/contrib/openstack/context.py 2015-09-28 20:09:02 +0000 | |||
1882 | +++ charmhelpers/contrib/openstack/context.py 2017-03-04 02:21:16 +0000 | |||
1883 | @@ -1,29 +1,27 @@ | |||
1884 | 1 | # Copyright 2014-2015 Canonical Limited. | 1 | # Copyright 2014-2015 Canonical Limited. |
1885 | 2 | # | 2 | # |
1899 | 3 | # This file is part of charm-helpers. | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
1900 | 4 | # | 4 | # you may not use this file except in compliance with the License. |
1901 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | 5 | # You may obtain a copy of the License at |
1902 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | 6 | # |
1903 | 7 | # published by the Free Software Foundation. | 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
1904 | 8 | # | 8 | # |
1905 | 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 |
1906 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
1907 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
1908 | 12 | # GNU Lesser General Public License for more details. | 12 | # See the License for the specific language governing permissions and |
1909 | 13 | # | 13 | # limitations under the License. |
1897 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
1898 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
1910 | 16 | 14 | ||
1911 | 17 | import glob | 15 | import glob |
1912 | 18 | import json | 16 | import json |
1913 | 17 | import math | ||
1914 | 19 | import os | 18 | import os |
1915 | 20 | import re | 19 | import re |
1916 | 21 | import time | 20 | import time |
1917 | 22 | from base64 import b64decode | 21 | from base64 import b64decode |
1919 | 23 | from subprocess import check_call | 22 | from subprocess import check_call, CalledProcessError |
1920 | 24 | 23 | ||
1921 | 25 | import six | 24 | import six |
1922 | 26 | import yaml | ||
1923 | 27 | 25 | ||
1924 | 28 | from charmhelpers.fetch import ( | 26 | from charmhelpers.fetch import ( |
1925 | 29 | apt_install, | 27 | apt_install, |
1926 | @@ -45,10 +43,12 @@ | |||
1927 | 45 | INFO, | 43 | INFO, |
1928 | 46 | WARNING, | 44 | WARNING, |
1929 | 47 | ERROR, | 45 | ERROR, |
1930 | 46 | status_set, | ||
1931 | 48 | ) | 47 | ) |
1932 | 49 | 48 | ||
1933 | 50 | from charmhelpers.core.sysctl import create as sysctl_create | 49 | from charmhelpers.core.sysctl import create as sysctl_create |
1934 | 51 | from charmhelpers.core.strutils import bool_from_string | 50 | from charmhelpers.core.strutils import bool_from_string |
1935 | 51 | from charmhelpers.contrib.openstack.exceptions import OSContextError | ||
1936 | 52 | 52 | ||
1937 | 53 | from charmhelpers.core.host import ( | 53 | from charmhelpers.core.host import ( |
1938 | 54 | get_bond_master, | 54 | get_bond_master, |
1939 | @@ -57,6 +57,8 @@ | |||
1940 | 57 | get_nic_hwaddr, | 57 | get_nic_hwaddr, |
1941 | 58 | mkdir, | 58 | mkdir, |
1942 | 59 | write_file, | 59 | write_file, |
1943 | 60 | pwgen, | ||
1944 | 61 | lsb_release, | ||
1945 | 60 | ) | 62 | ) |
1946 | 61 | from charmhelpers.contrib.hahelpers.cluster import ( | 63 | from charmhelpers.contrib.hahelpers.cluster import ( |
1947 | 62 | determine_apache_port, | 64 | determine_apache_port, |
1948 | @@ -86,15 +88,28 @@ | |||
1949 | 86 | is_address_in_network, | 88 | is_address_in_network, |
1950 | 87 | is_bridge_member, | 89 | is_bridge_member, |
1951 | 88 | ) | 90 | ) |
1953 | 89 | from charmhelpers.contrib.openstack.utils import get_host_ip | 91 | from charmhelpers.contrib.openstack.utils import ( |
1954 | 92 | config_flags_parser, | ||
1955 | 93 | get_host_ip, | ||
1956 | 94 | git_determine_usr_bin, | ||
1957 | 95 | git_determine_python_path, | ||
1958 | 96 | enable_memcache, | ||
1959 | 97 | ) | ||
1960 | 98 | from charmhelpers.core.unitdata import kv | ||
1961 | 99 | |||
1962 | 100 | try: | ||
1963 | 101 | import psutil | ||
1964 | 102 | except ImportError: | ||
1965 | 103 | if six.PY2: | ||
1966 | 104 | apt_install('python-psutil', fatal=True) | ||
1967 | 105 | else: | ||
1968 | 106 | apt_install('python3-psutil', fatal=True) | ||
1969 | 107 | import psutil | ||
1970 | 108 | |||
1971 | 90 | CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt' | 109 | CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt' |
1972 | 91 | ADDRESS_TYPES = ['admin', 'internal', 'public'] | 110 | ADDRESS_TYPES = ['admin', 'internal', 'public'] |
1973 | 92 | 111 | ||
1974 | 93 | 112 | ||
1975 | 94 | class OSContextError(Exception): | ||
1976 | 95 | pass | ||
1977 | 96 | |||
1978 | 97 | |||
1979 | 98 | def ensure_packages(packages): | 113 | def ensure_packages(packages): |
1980 | 99 | """Install but do not upgrade required plugin packages.""" | 114 | """Install but do not upgrade required plugin packages.""" |
1981 | 100 | required = filter_installed_packages(packages) | 115 | required = filter_installed_packages(packages) |
1982 | @@ -115,83 +130,6 @@ | |||
1983 | 115 | return True | 130 | return True |
1984 | 116 | 131 | ||
1985 | 117 | 132 | ||
1986 | 118 | def config_flags_parser(config_flags): | ||
1987 | 119 | """Parses config flags string into dict. | ||
1988 | 120 | |||
1989 | 121 | This parsing method supports a few different formats for the config | ||
1990 | 122 | flag values to be parsed: | ||
1991 | 123 | |||
1992 | 124 | 1. A string in the simple format of key=value pairs, with the possibility | ||
1993 | 125 | of specifying multiple key value pairs within the same string. For | ||
1994 | 126 | example, a string in the format of 'key1=value1, key2=value2' will | ||
1995 | 127 | return a dict of: | ||
1996 | 128 | |||
1997 | 129 | {'key1': 'value1', | ||
1998 | 130 | 'key2': 'value2'}. | ||
1999 | 131 | |||
2000 | 132 | 2. A string in the above format, but supporting a comma-delimited list | ||
2001 | 133 | of values for the same key. For example, a string in the format of | ||
2002 | 134 | 'key1=value1, key2=value3,value4,value5' will return a dict of: | ||
2003 | 135 | |||
2004 | 136 | {'key1', 'value1', | ||
2005 | 137 | 'key2', 'value2,value3,value4'} | ||
2006 | 138 | |||
2007 | 139 | 3. A string containing a colon character (:) prior to an equal | ||
2008 | 140 | character (=) will be treated as yaml and parsed as such. This can be | ||
2009 | 141 | used to specify more complex key value pairs. For example, | ||
2010 | 142 | a string in the format of 'key1: subkey1=value1, subkey2=value2' will | ||
2011 | 143 | return a dict of: | ||
2012 | 144 | |||
2013 | 145 | {'key1', 'subkey1=value1, subkey2=value2'} | ||
2014 | 146 | |||
2015 | 147 | The provided config_flags string may be a list of comma-separated values | ||
2016 | 148 | which themselves may be comma-separated list of values. | ||
2017 | 149 | """ | ||
2018 | 150 | # If we find a colon before an equals sign then treat it as yaml. | ||
2019 | 151 | # Note: limit it to finding the colon first since this indicates assignment | ||
2020 | 152 | # for inline yaml. | ||
2021 | 153 | colon = config_flags.find(':') | ||
2022 | 154 | equals = config_flags.find('=') | ||
2023 | 155 | if colon > 0: | ||
2024 | 156 | if colon < equals or equals < 0: | ||
2025 | 157 | return yaml.safe_load(config_flags) | ||
2026 | 158 | |||
2027 | 159 | if config_flags.find('==') >= 0: | ||
2028 | 160 | log("config_flags is not in expected format (key=value)", level=ERROR) | ||
2029 | 161 | raise OSContextError | ||
2030 | 162 | |||
2031 | 163 | # strip the following from each value. | ||
2032 | 164 | post_strippers = ' ,' | ||
2033 | 165 | # we strip any leading/trailing '=' or ' ' from the string then | ||
2034 | 166 | # split on '='. | ||
2035 | 167 | split = config_flags.strip(' =').split('=') | ||
2036 | 168 | limit = len(split) | ||
2037 | 169 | flags = {} | ||
2038 | 170 | for i in range(0, limit - 1): | ||
2039 | 171 | current = split[i] | ||
2040 | 172 | next = split[i + 1] | ||
2041 | 173 | vindex = next.rfind(',') | ||
2042 | 174 | if (i == limit - 2) or (vindex < 0): | ||
2043 | 175 | value = next | ||
2044 | 176 | else: | ||
2045 | 177 | value = next[:vindex] | ||
2046 | 178 | |||
2047 | 179 | if i == 0: | ||
2048 | 180 | key = current | ||
2049 | 181 | else: | ||
2050 | 182 | # if this not the first entry, expect an embedded key. | ||
2051 | 183 | index = current.rfind(',') | ||
2052 | 184 | if index < 0: | ||
2053 | 185 | log("Invalid config value(s) at index %s" % (i), level=ERROR) | ||
2054 | 186 | raise OSContextError | ||
2055 | 187 | key = current[index + 1:] | ||
2056 | 188 | |||
2057 | 189 | # Add to collection. | ||
2058 | 190 | flags[key.strip(post_strippers)] = value.rstrip(post_strippers) | ||
2059 | 191 | |||
2060 | 192 | return flags | ||
2061 | 193 | |||
2062 | 194 | |||
2063 | 195 | class OSContextGenerator(object): | 133 | class OSContextGenerator(object): |
2064 | 196 | """Base class for all context generators.""" | 134 | """Base class for all context generators.""" |
2065 | 197 | interfaces = [] | 135 | interfaces = [] |
2066 | @@ -401,6 +339,7 @@ | |||
2067 | 401 | auth_host = format_ipv6_addr(auth_host) or auth_host | 339 | auth_host = format_ipv6_addr(auth_host) or auth_host |
2068 | 402 | svc_protocol = rdata.get('service_protocol') or 'http' | 340 | svc_protocol = rdata.get('service_protocol') or 'http' |
2069 | 403 | auth_protocol = rdata.get('auth_protocol') or 'http' | 341 | auth_protocol = rdata.get('auth_protocol') or 'http' |
2070 | 342 | api_version = rdata.get('api_version') or '2.0' | ||
2071 | 404 | ctxt.update({'service_port': rdata.get('service_port'), | 343 | ctxt.update({'service_port': rdata.get('service_port'), |
2072 | 405 | 'service_host': serv_host, | 344 | 'service_host': serv_host, |
2073 | 406 | 'auth_host': auth_host, | 345 | 'auth_host': auth_host, |
2074 | @@ -409,7 +348,12 @@ | |||
2075 | 409 | 'admin_user': rdata.get('service_username'), | 348 | 'admin_user': rdata.get('service_username'), |
2076 | 410 | 'admin_password': rdata.get('service_password'), | 349 | 'admin_password': rdata.get('service_password'), |
2077 | 411 | 'service_protocol': svc_protocol, | 350 | 'service_protocol': svc_protocol, |
2079 | 412 | 'auth_protocol': auth_protocol}) | 351 | 'auth_protocol': auth_protocol, |
2080 | 352 | 'api_version': api_version}) | ||
2081 | 353 | |||
2082 | 354 | if float(api_version) > 2: | ||
2083 | 355 | ctxt.update({'admin_domain_name': | ||
2084 | 356 | rdata.get('service_domain')}) | ||
2085 | 413 | 357 | ||
2086 | 414 | if self.context_complete(ctxt): | 358 | if self.context_complete(ctxt): |
2087 | 415 | # NOTE(jamespage) this is required for >= icehouse | 359 | # NOTE(jamespage) this is required for >= icehouse |
2088 | @@ -451,16 +395,20 @@ | |||
2089 | 451 | for rid in relation_ids(self.rel_name): | 395 | for rid in relation_ids(self.rel_name): |
2090 | 452 | ha_vip_only = False | 396 | ha_vip_only = False |
2091 | 453 | self.related = True | 397 | self.related = True |
2092 | 398 | transport_hosts = None | ||
2093 | 399 | rabbitmq_port = '5672' | ||
2094 | 454 | for unit in related_units(rid): | 400 | for unit in related_units(rid): |
2095 | 455 | if relation_get('clustered', rid=rid, unit=unit): | 401 | if relation_get('clustered', rid=rid, unit=unit): |
2096 | 456 | ctxt['clustered'] = True | 402 | ctxt['clustered'] = True |
2097 | 457 | vip = relation_get('vip', rid=rid, unit=unit) | 403 | vip = relation_get('vip', rid=rid, unit=unit) |
2098 | 458 | vip = format_ipv6_addr(vip) or vip | 404 | vip = format_ipv6_addr(vip) or vip |
2099 | 459 | ctxt['rabbitmq_host'] = vip | 405 | ctxt['rabbitmq_host'] = vip |
2100 | 406 | transport_hosts = [vip] | ||
2101 | 460 | else: | 407 | else: |
2102 | 461 | host = relation_get('private-address', rid=rid, unit=unit) | 408 | host = relation_get('private-address', rid=rid, unit=unit) |
2103 | 462 | host = format_ipv6_addr(host) or host | 409 | host = format_ipv6_addr(host) or host |
2104 | 463 | ctxt['rabbitmq_host'] = host | 410 | ctxt['rabbitmq_host'] = host |
2105 | 411 | transport_hosts = [host] | ||
2106 | 464 | 412 | ||
2107 | 465 | ctxt.update({ | 413 | ctxt.update({ |
2108 | 466 | 'rabbitmq_user': username, | 414 | 'rabbitmq_user': username, |
2109 | @@ -472,6 +420,7 @@ | |||
2110 | 472 | ssl_port = relation_get('ssl_port', rid=rid, unit=unit) | 420 | ssl_port = relation_get('ssl_port', rid=rid, unit=unit) |
2111 | 473 | if ssl_port: | 421 | if ssl_port: |
2112 | 474 | ctxt['rabbit_ssl_port'] = ssl_port | 422 | ctxt['rabbit_ssl_port'] = ssl_port |
2113 | 423 | rabbitmq_port = ssl_port | ||
2114 | 475 | 424 | ||
2115 | 476 | ssl_ca = relation_get('ssl_ca', rid=rid, unit=unit) | 425 | ssl_ca = relation_get('ssl_ca', rid=rid, unit=unit) |
2116 | 477 | if ssl_ca: | 426 | if ssl_ca: |
2117 | @@ -509,6 +458,20 @@ | |||
2118 | 509 | rabbitmq_hosts.append(host) | 458 | rabbitmq_hosts.append(host) |
2119 | 510 | 459 | ||
2120 | 511 | ctxt['rabbitmq_hosts'] = ','.join(sorted(rabbitmq_hosts)) | 460 | ctxt['rabbitmq_hosts'] = ','.join(sorted(rabbitmq_hosts)) |
2121 | 461 | transport_hosts = rabbitmq_hosts | ||
2122 | 462 | |||
2123 | 463 | if transport_hosts: | ||
2124 | 464 | transport_url_hosts = '' | ||
2125 | 465 | for host in transport_hosts: | ||
2126 | 466 | if transport_url_hosts: | ||
2127 | 467 | format_string = ",{}:{}@{}:{}" | ||
2128 | 468 | else: | ||
2129 | 469 | format_string = "{}:{}@{}:{}" | ||
2130 | 470 | transport_url_hosts += format_string.format( | ||
2131 | 471 | ctxt['rabbitmq_user'], ctxt['rabbitmq_password'], | ||
2132 | 472 | host, rabbitmq_port) | ||
2133 | 473 | ctxt['transport_url'] = "rabbit://{}/{}".format( | ||
2134 | 474 | transport_url_hosts, vhost) | ||
2135 | 512 | 475 | ||
2136 | 513 | oslo_messaging_flags = conf.get('oslo-messaging-flags', None) | 476 | oslo_messaging_flags = conf.get('oslo-messaging-flags', None) |
2137 | 514 | if oslo_messaging_flags: | 477 | if oslo_messaging_flags: |
2138 | @@ -540,13 +503,16 @@ | |||
2139 | 540 | ctxt['auth'] = relation_get('auth', rid=rid, unit=unit) | 503 | ctxt['auth'] = relation_get('auth', rid=rid, unit=unit) |
2140 | 541 | if not ctxt.get('key'): | 504 | if not ctxt.get('key'): |
2141 | 542 | ctxt['key'] = relation_get('key', rid=rid, unit=unit) | 505 | ctxt['key'] = relation_get('key', rid=rid, unit=unit) |
2143 | 543 | ceph_pub_addr = relation_get('ceph-public-address', rid=rid, | 506 | |
2144 | 507 | ceph_addrs = relation_get('ceph-public-address', rid=rid, | ||
2145 | 508 | unit=unit) | ||
2146 | 509 | if ceph_addrs: | ||
2147 | 510 | for addr in ceph_addrs.split(' '): | ||
2148 | 511 | mon_hosts.append(format_ipv6_addr(addr) or addr) | ||
2149 | 512 | else: | ||
2150 | 513 | priv_addr = relation_get('private-address', rid=rid, | ||
2151 | 544 | unit=unit) | 514 | unit=unit) |
2157 | 545 | unit_priv_addr = relation_get('private-address', rid=rid, | 515 | mon_hosts.append(format_ipv6_addr(priv_addr) or priv_addr) |
2153 | 546 | unit=unit) | ||
2154 | 547 | ceph_addr = ceph_pub_addr or unit_priv_addr | ||
2155 | 548 | ceph_addr = format_ipv6_addr(ceph_addr) or ceph_addr | ||
2156 | 549 | mon_hosts.append(ceph_addr) | ||
2158 | 550 | 516 | ||
2159 | 551 | ctxt['mon_hosts'] = ' '.join(sorted(mon_hosts)) | 517 | ctxt['mon_hosts'] = ' '.join(sorted(mon_hosts)) |
2160 | 552 | 518 | ||
2161 | @@ -626,15 +592,28 @@ | |||
2162 | 626 | if config('haproxy-client-timeout'): | 592 | if config('haproxy-client-timeout'): |
2163 | 627 | ctxt['haproxy_client_timeout'] = config('haproxy-client-timeout') | 593 | ctxt['haproxy_client_timeout'] = config('haproxy-client-timeout') |
2164 | 628 | 594 | ||
2165 | 595 | if config('haproxy-queue-timeout'): | ||
2166 | 596 | ctxt['haproxy_queue_timeout'] = config('haproxy-queue-timeout') | ||
2167 | 597 | |||
2168 | 598 | if config('haproxy-connect-timeout'): | ||
2169 | 599 | ctxt['haproxy_connect_timeout'] = config('haproxy-connect-timeout') | ||
2170 | 600 | |||
2171 | 629 | if config('prefer-ipv6'): | 601 | if config('prefer-ipv6'): |
2172 | 630 | ctxt['ipv6'] = True | 602 | ctxt['ipv6'] = True |
2173 | 631 | ctxt['local_host'] = 'ip6-localhost' | 603 | ctxt['local_host'] = 'ip6-localhost' |
2174 | 632 | ctxt['haproxy_host'] = '::' | 604 | ctxt['haproxy_host'] = '::' |
2175 | 633 | ctxt['stat_port'] = ':::8888' | ||
2176 | 634 | else: | 605 | else: |
2177 | 635 | ctxt['local_host'] = '127.0.0.1' | 606 | ctxt['local_host'] = '127.0.0.1' |
2178 | 636 | ctxt['haproxy_host'] = '0.0.0.0' | 607 | ctxt['haproxy_host'] = '0.0.0.0' |
2180 | 637 | ctxt['stat_port'] = ':8888' | 608 | |
2181 | 609 | ctxt['stat_port'] = '8888' | ||
2182 | 610 | |||
2183 | 611 | db = kv() | ||
2184 | 612 | ctxt['stat_password'] = db.get('stat-password') | ||
2185 | 613 | if not ctxt['stat_password']: | ||
2186 | 614 | ctxt['stat_password'] = db.set('stat-password', | ||
2187 | 615 | pwgen(32)) | ||
2188 | 616 | db.flush() | ||
2189 | 638 | 617 | ||
2190 | 639 | for frontend in cluster_hosts: | 618 | for frontend in cluster_hosts: |
2191 | 640 | if (len(cluster_hosts[frontend]['backends']) > 1 or | 619 | if (len(cluster_hosts[frontend]['backends']) > 1 or |
2192 | @@ -698,7 +677,7 @@ | |||
2193 | 698 | service_namespace = None | 677 | service_namespace = None |
2194 | 699 | 678 | ||
2195 | 700 | def enable_modules(self): | 679 | def enable_modules(self): |
2197 | 701 | cmd = ['a2enmod', 'ssl', 'proxy', 'proxy_http'] | 680 | cmd = ['a2enmod', 'ssl', 'proxy', 'proxy_http', 'headers'] |
2198 | 702 | check_call(cmd) | 681 | check_call(cmd) |
2199 | 703 | 682 | ||
2200 | 704 | def configure_cert(self, cn=None): | 683 | def configure_cert(self, cn=None): |
2201 | @@ -952,6 +931,19 @@ | |||
2202 | 952 | 'config': config} | 931 | 'config': config} |
2203 | 953 | return ovs_ctxt | 932 | return ovs_ctxt |
2204 | 954 | 933 | ||
2205 | 934 | def midonet_ctxt(self): | ||
2206 | 935 | driver = neutron_plugin_attribute(self.plugin, 'driver', | ||
2207 | 936 | self.network_manager) | ||
2208 | 937 | midonet_config = neutron_plugin_attribute(self.plugin, 'config', | ||
2209 | 938 | self.network_manager) | ||
2210 | 939 | mido_ctxt = {'core_plugin': driver, | ||
2211 | 940 | 'neutron_plugin': 'midonet', | ||
2212 | 941 | 'neutron_security_groups': self.neutron_security_groups, | ||
2213 | 942 | 'local_ip': unit_private_ip(), | ||
2214 | 943 | 'config': midonet_config} | ||
2215 | 944 | |||
2216 | 945 | return mido_ctxt | ||
2217 | 946 | |||
2218 | 955 | def __call__(self): | 947 | def __call__(self): |
2219 | 956 | if self.network_manager not in ['quantum', 'neutron']: | 948 | if self.network_manager not in ['quantum', 'neutron']: |
2220 | 957 | return {} | 949 | return {} |
2221 | @@ -973,6 +965,8 @@ | |||
2222 | 973 | ctxt.update(self.nuage_ctxt()) | 965 | ctxt.update(self.nuage_ctxt()) |
2223 | 974 | elif self.plugin == 'plumgrid': | 966 | elif self.plugin == 'plumgrid': |
2224 | 975 | ctxt.update(self.pg_ctxt()) | 967 | ctxt.update(self.pg_ctxt()) |
2225 | 968 | elif self.plugin == 'midonet': | ||
2226 | 969 | ctxt.update(self.midonet_ctxt()) | ||
2227 | 976 | 970 | ||
2228 | 977 | alchemy_flags = config('neutron-alchemy-flags') | 971 | alchemy_flags = config('neutron-alchemy-flags') |
2229 | 978 | if alchemy_flags: | 972 | if alchemy_flags: |
2230 | @@ -1073,6 +1067,20 @@ | |||
2231 | 1073 | config_flags_parser(config_flags)} | 1067 | config_flags_parser(config_flags)} |
2232 | 1074 | 1068 | ||
2233 | 1075 | 1069 | ||
2234 | 1070 | class LibvirtConfigFlagsContext(OSContextGenerator): | ||
2235 | 1071 | """ | ||
2236 | 1072 | This context provides support for extending | ||
2237 | 1073 | the libvirt section through user-defined flags. | ||
2238 | 1074 | """ | ||
2239 | 1075 | def __call__(self): | ||
2240 | 1076 | ctxt = {} | ||
2241 | 1077 | libvirt_flags = config('libvirt-flags') | ||
2242 | 1078 | if libvirt_flags: | ||
2243 | 1079 | ctxt['libvirt_flags'] = config_flags_parser( | ||
2244 | 1080 | libvirt_flags) | ||
2245 | 1081 | return ctxt | ||
2246 | 1082 | |||
2247 | 1083 | |||
2248 | 1076 | class SubordinateConfigContext(OSContextGenerator): | 1084 | class SubordinateConfigContext(OSContextGenerator): |
2249 | 1077 | 1085 | ||
2250 | 1078 | """ | 1086 | """ |
2251 | @@ -1105,7 +1113,7 @@ | |||
2252 | 1105 | 1113 | ||
2253 | 1106 | ctxt = { | 1114 | ctxt = { |
2254 | 1107 | ... other context ... | 1115 | ... other context ... |
2256 | 1108 | 'subordinate_config': { | 1116 | 'subordinate_configuration': { |
2257 | 1109 | 'DEFAULT': { | 1117 | 'DEFAULT': { |
2258 | 1110 | 'key1': 'value1', | 1118 | 'key1': 'value1', |
2259 | 1111 | }, | 1119 | }, |
2260 | @@ -1146,22 +1154,23 @@ | |||
2261 | 1146 | try: | 1154 | try: |
2262 | 1147 | sub_config = json.loads(sub_config) | 1155 | sub_config = json.loads(sub_config) |
2263 | 1148 | except: | 1156 | except: |
2266 | 1149 | log('Could not parse JSON from subordinate_config ' | 1157 | log('Could not parse JSON from ' |
2267 | 1150 | 'setting from %s' % rid, level=ERROR) | 1158 | 'subordinate_configuration setting from %s' |
2268 | 1159 | % rid, level=ERROR) | ||
2269 | 1151 | continue | 1160 | continue |
2270 | 1152 | 1161 | ||
2271 | 1153 | for service in self.services: | 1162 | for service in self.services: |
2272 | 1154 | if service not in sub_config: | 1163 | if service not in sub_config: |
2276 | 1155 | log('Found subordinate_config on %s but it contained' | 1164 | log('Found subordinate_configuration on %s but it ' |
2277 | 1156 | 'nothing for %s service' % (rid, service), | 1165 | 'contained nothing for %s service' |
2278 | 1157 | level=INFO) | 1166 | % (rid, service), level=INFO) |
2279 | 1158 | continue | 1167 | continue |
2280 | 1159 | 1168 | ||
2281 | 1160 | sub_config = sub_config[service] | 1169 | sub_config = sub_config[service] |
2282 | 1161 | if self.config_file not in sub_config: | 1170 | if self.config_file not in sub_config: |
2286 | 1162 | log('Found subordinate_config on %s but it contained' | 1171 | log('Found subordinate_configuration on %s but it ' |
2287 | 1163 | 'nothing for %s' % (rid, self.config_file), | 1172 | 'contained nothing for %s' |
2288 | 1164 | level=INFO) | 1173 | % (rid, self.config_file), level=INFO) |
2289 | 1165 | continue | 1174 | continue |
2290 | 1166 | 1175 | ||
2291 | 1167 | sub_config = sub_config[self.config_file] | 1176 | sub_config = sub_config[self.config_file] |
2292 | @@ -1212,17 +1221,55 @@ | |||
2293 | 1212 | 1221 | ||
2294 | 1213 | @property | 1222 | @property |
2295 | 1214 | def num_cpus(self): | 1223 | def num_cpus(self): |
2303 | 1215 | try: | 1224 | # NOTE: use cpu_count if present (16.04 support) |
2304 | 1216 | from psutil import NUM_CPUS | 1225 | if hasattr(psutil, 'cpu_count'): |
2305 | 1217 | except ImportError: | 1226 | return psutil.cpu_count() |
2306 | 1218 | apt_install('python-psutil', fatal=True) | 1227 | else: |
2307 | 1219 | from psutil import NUM_CPUS | 1228 | return psutil.NUM_CPUS |
2301 | 1220 | |||
2302 | 1221 | return NUM_CPUS | ||
2308 | 1222 | 1229 | ||
2309 | 1223 | def __call__(self): | 1230 | def __call__(self): |
2310 | 1224 | multiplier = config('worker-multiplier') or 0 | 1231 | multiplier = config('worker-multiplier') or 0 |
2312 | 1225 | ctxt = {"workers": self.num_cpus * multiplier} | 1232 | count = int(self.num_cpus * multiplier) |
2313 | 1233 | if multiplier > 0 and count == 0: | ||
2314 | 1234 | count = 1 | ||
2315 | 1235 | ctxt = {"workers": count} | ||
2316 | 1236 | return ctxt | ||
2317 | 1237 | |||
2318 | 1238 | |||
2319 | 1239 | class WSGIWorkerConfigContext(WorkerConfigContext): | ||
2320 | 1240 | |||
2321 | 1241 | def __init__(self, name=None, script=None, admin_script=None, | ||
2322 | 1242 | public_script=None, process_weight=1.00, | ||
2323 | 1243 | admin_process_weight=0.75, public_process_weight=0.25): | ||
2324 | 1244 | self.service_name = name | ||
2325 | 1245 | self.user = name | ||
2326 | 1246 | self.group = name | ||
2327 | 1247 | self.script = script | ||
2328 | 1248 | self.admin_script = admin_script | ||
2329 | 1249 | self.public_script = public_script | ||
2330 | 1250 | self.process_weight = process_weight | ||
2331 | 1251 | self.admin_process_weight = admin_process_weight | ||
2332 | 1252 | self.public_process_weight = public_process_weight | ||
2333 | 1253 | |||
2334 | 1254 | def __call__(self): | ||
2335 | 1255 | multiplier = config('worker-multiplier') or 1 | ||
2336 | 1256 | total_processes = self.num_cpus * multiplier | ||
2337 | 1257 | ctxt = { | ||
2338 | 1258 | "service_name": self.service_name, | ||
2339 | 1259 | "user": self.user, | ||
2340 | 1260 | "group": self.group, | ||
2341 | 1261 | "script": self.script, | ||
2342 | 1262 | "admin_script": self.admin_script, | ||
2343 | 1263 | "public_script": self.public_script, | ||
2344 | 1264 | "processes": int(math.ceil(self.process_weight * total_processes)), | ||
2345 | 1265 | "admin_processes": int(math.ceil(self.admin_process_weight * | ||
2346 | 1266 | total_processes)), | ||
2347 | 1267 | "public_processes": int(math.ceil(self.public_process_weight * | ||
2348 | 1268 | total_processes)), | ||
2349 | 1269 | "threads": 1, | ||
2350 | 1270 | "usr_bin": git_determine_usr_bin(), | ||
2351 | 1271 | "python_path": git_determine_python_path(), | ||
2352 | 1272 | } | ||
2353 | 1226 | return ctxt | 1273 | return ctxt |
2354 | 1227 | 1274 | ||
2355 | 1228 | 1275 | ||
2356 | @@ -1364,7 +1411,7 @@ | |||
2357 | 1364 | normalized.update({port: port for port in resolved | 1411 | normalized.update({port: port for port in resolved |
2358 | 1365 | if port in ports}) | 1412 | if port in ports}) |
2359 | 1366 | if resolved: | 1413 | if resolved: |
2361 | 1367 | return {bridge: normalized[port] for port, bridge in | 1414 | return {normalized[port]: bridge for port, bridge in |
2362 | 1368 | six.iteritems(portmap) if port in normalized.keys()} | 1415 | six.iteritems(portmap) if port in normalized.keys()} |
2363 | 1369 | 1416 | ||
2364 | 1370 | return None | 1417 | return None |
2365 | @@ -1375,8 +1422,8 @@ | |||
2366 | 1375 | def __call__(self): | 1422 | def __call__(self): |
2367 | 1376 | ctxt = {} | 1423 | ctxt = {} |
2368 | 1377 | mappings = super(PhyNICMTUContext, self).__call__() | 1424 | mappings = super(PhyNICMTUContext, self).__call__() |
2371 | 1378 | if mappings and mappings.values(): | 1425 | if mappings and mappings.keys(): |
2372 | 1379 | ports = mappings.values() | 1426 | ports = sorted(mappings.keys()) |
2373 | 1380 | napi_settings = NeutronAPIContext()() | 1427 | napi_settings = NeutronAPIContext()() |
2374 | 1381 | mtu = napi_settings.get('network_device_mtu') | 1428 | mtu = napi_settings.get('network_device_mtu') |
2375 | 1382 | all_ports = set() | 1429 | all_ports = set() |
2376 | @@ -1421,7 +1468,146 @@ | |||
2377 | 1421 | rdata.get('service_protocol') or 'http', | 1468 | rdata.get('service_protocol') or 'http', |
2378 | 1422 | 'auth_protocol': | 1469 | 'auth_protocol': |
2379 | 1423 | rdata.get('auth_protocol') or 'http', | 1470 | rdata.get('auth_protocol') or 'http', |
2380 | 1471 | 'api_version': | ||
2381 | 1472 | rdata.get('api_version') or '2.0', | ||
2382 | 1424 | } | 1473 | } |
2383 | 1425 | if self.context_complete(ctxt): | 1474 | if self.context_complete(ctxt): |
2384 | 1426 | return ctxt | 1475 | return ctxt |
2385 | 1427 | return {} | 1476 | return {} |
2386 | 1477 | |||
2387 | 1478 | |||
2388 | 1479 | class InternalEndpointContext(OSContextGenerator): | ||
2389 | 1480 | """Internal endpoint context. | ||
2390 | 1481 | |||
2391 | 1482 | This context provides the endpoint type used for communication between | ||
2392 | 1483 | services e.g. between Nova and Cinder internally. Openstack uses Public | ||
2393 | 1484 | endpoints by default so this allows admins to optionally use internal | ||
2394 | 1485 | endpoints. | ||
2395 | 1486 | """ | ||
2396 | 1487 | def __call__(self): | ||
2397 | 1488 | return {'use_internal_endpoints': config('use-internal-endpoints')} | ||
2398 | 1489 | |||
2399 | 1490 | |||
2400 | 1491 | class AppArmorContext(OSContextGenerator): | ||
2401 | 1492 | """Base class for apparmor contexts.""" | ||
2402 | 1493 | |||
2403 | 1494 | def __init__(self, profile_name=None): | ||
2404 | 1495 | self._ctxt = None | ||
2405 | 1496 | self.aa_profile = profile_name | ||
2406 | 1497 | self.aa_utils_packages = ['apparmor-utils'] | ||
2407 | 1498 | |||
2408 | 1499 | @property | ||
2409 | 1500 | def ctxt(self): | ||
2410 | 1501 | if self._ctxt is not None: | ||
2411 | 1502 | return self._ctxt | ||
2412 | 1503 | self._ctxt = self._determine_ctxt() | ||
2413 | 1504 | return self._ctxt | ||
2414 | 1505 | |||
2415 | 1506 | def _determine_ctxt(self): | ||
2416 | 1507 | """ | ||
2417 | 1508 | Validate aa-profile-mode settings is disable, enforce, or complain. | ||
2418 | 1509 | |||
2419 | 1510 | :return ctxt: Dictionary of the apparmor profile or None | ||
2420 | 1511 | """ | ||
2421 | 1512 | if config('aa-profile-mode') in ['disable', 'enforce', 'complain']: | ||
2422 | 1513 | ctxt = {'aa_profile_mode': config('aa-profile-mode'), | ||
2423 | 1514 | 'ubuntu_release': lsb_release()['DISTRIB_RELEASE']} | ||
2424 | 1515 | if self.aa_profile: | ||
2425 | 1516 | ctxt['aa_profile'] = self.aa_profile | ||
2426 | 1517 | else: | ||
2427 | 1518 | ctxt = None | ||
2428 | 1519 | return ctxt | ||
2429 | 1520 | |||
2430 | 1521 | def __call__(self): | ||
2431 | 1522 | return self.ctxt | ||
2432 | 1523 | |||
2433 | 1524 | def install_aa_utils(self): | ||
2434 | 1525 | """ | ||
2435 | 1526 | Install packages required for apparmor configuration. | ||
2436 | 1527 | """ | ||
2437 | 1528 | log("Installing apparmor utils.") | ||
2438 | 1529 | ensure_packages(self.aa_utils_packages) | ||
2439 | 1530 | |||
2440 | 1531 | def manually_disable_aa_profile(self): | ||
2441 | 1532 | """ | ||
2442 | 1533 | Manually disable an apparmor profile. | ||
2443 | 1534 | |||
2444 | 1535 | If aa-profile-mode is set to disabled (default) this is required as the | ||
2445 | 1536 | template has been written but apparmor is yet unaware of the profile | ||
2446 | 1537 | and aa-disable aa-profile fails. Without this the profile would kick | ||
2447 | 1538 | into enforce mode on the next service restart. | ||
2448 | 1539 | |||
2449 | 1540 | """ | ||
2450 | 1541 | profile_path = '/etc/apparmor.d' | ||
2451 | 1542 | disable_path = '/etc/apparmor.d/disable' | ||
2452 | 1543 | if not os.path.lexists(os.path.join(disable_path, self.aa_profile)): | ||
2453 | 1544 | os.symlink(os.path.join(profile_path, self.aa_profile), | ||
2454 | 1545 | os.path.join(disable_path, self.aa_profile)) | ||
2455 | 1546 | |||
2456 | 1547 | def setup_aa_profile(self): | ||
2457 | 1548 | """ | ||
2458 | 1549 | Setup an apparmor profile. | ||
2459 | 1550 | The ctxt dictionary will contain the apparmor profile mode and | ||
2460 | 1551 | the apparmor profile name. | ||
2461 | 1552 | Makes calls out to aa-disable, aa-complain, or aa-enforce to setup | ||
2462 | 1553 | the apparmor profile. | ||
2463 | 1554 | """ | ||
2464 | 1555 | self() | ||
2465 | 1556 | if not self.ctxt: | ||
2466 | 1557 | log("Not enabling apparmor Profile") | ||
2467 | 1558 | return | ||
2468 | 1559 | self.install_aa_utils() | ||
2469 | 1560 | cmd = ['aa-{}'.format(self.ctxt['aa_profile_mode'])] | ||
2470 | 1561 | cmd.append(self.ctxt['aa_profile']) | ||
2471 | 1562 | log("Setting up the apparmor profile for {} in {} mode." | ||
2472 | 1563 | "".format(self.ctxt['aa_profile'], self.ctxt['aa_profile_mode'])) | ||
2473 | 1564 | try: | ||
2474 | 1565 | check_call(cmd) | ||
2475 | 1566 | except CalledProcessError as e: | ||
2476 | 1567 | # If aa-profile-mode is set to disabled (default) manual | ||
2477 | 1568 | # disabling is required as the template has been written but | ||
2478 | 1569 | # apparmor is yet unaware of the profile and aa-disable aa-profile | ||
2479 | 1570 | # fails. If aa-disable learns to read profile files first this can | ||
2480 | 1571 | # be removed. | ||
2481 | 1572 | if self.ctxt['aa_profile_mode'] == 'disable': | ||
2482 | 1573 | log("Manually disabling the apparmor profile for {}." | ||
2483 | 1574 | "".format(self.ctxt['aa_profile'])) | ||
2484 | 1575 | self.manually_disable_aa_profile() | ||
2485 | 1576 | return | ||
2486 | 1577 | status_set('blocked', "Apparmor profile {} failed to be set to {}." | ||
2487 | 1578 | "".format(self.ctxt['aa_profile'], | ||
2488 | 1579 | self.ctxt['aa_profile_mode'])) | ||
2489 | 1580 | raise e | ||
2490 | 1581 | |||
2491 | 1582 | |||
2492 | 1583 | class MemcacheContext(OSContextGenerator): | ||
2493 | 1584 | """Memcache context | ||
2494 | 1585 | |||
2495 | 1586 | This context provides options for configuring a local memcache client and | ||
2496 | 1587 | server | ||
2497 | 1588 | """ | ||
2498 | 1589 | |||
2499 | 1590 | def __init__(self, package=None): | ||
2500 | 1591 | """ | ||
2501 | 1592 | @param package: Package to examine to extrapolate OpenStack release. | ||
2502 | 1593 | Used when charms have no openstack-origin config | ||
2503 | 1594 | option (ie subordinates) | ||
2504 | 1595 | """ | ||
2505 | 1596 | self.package = package | ||
2506 | 1597 | |||
2507 | 1598 | def __call__(self): | ||
2508 | 1599 | ctxt = {} | ||
2509 | 1600 | ctxt['use_memcache'] = enable_memcache(package=self.package) | ||
2510 | 1601 | if ctxt['use_memcache']: | ||
2511 | 1602 | # Trusty version of memcached does not support ::1 as a listen | ||
2512 | 1603 | # address so use host file entry instead | ||
2513 | 1604 | if lsb_release()['DISTRIB_CODENAME'].lower() > 'trusty': | ||
2514 | 1605 | ctxt['memcache_server'] = '::1' | ||
2515 | 1606 | else: | ||
2516 | 1607 | ctxt['memcache_server'] = 'ip6-localhost' | ||
2517 | 1608 | ctxt['memcache_server_formatted'] = '[::1]' | ||
2518 | 1609 | ctxt['memcache_port'] = '11211' | ||
2519 | 1610 | ctxt['memcache_url'] = 'inet6:{}:{}'.format( | ||
2520 | 1611 | ctxt['memcache_server_formatted'], | ||
2521 | 1612 | ctxt['memcache_port']) | ||
2522 | 1613 | return ctxt | ||
2523 | 1428 | 1614 | ||
2524 | === added file 'charmhelpers/contrib/openstack/exceptions.py' | |||
2525 | --- charmhelpers/contrib/openstack/exceptions.py 1970-01-01 00:00:00 +0000 | |||
2526 | +++ charmhelpers/contrib/openstack/exceptions.py 2017-03-04 02:21:16 +0000 | |||
2527 | @@ -0,0 +1,21 @@ | |||
2528 | 1 | # Copyright 2016 Canonical Ltd | ||
2529 | 2 | # | ||
2530 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
2531 | 4 | # you may not use this file except in compliance with the License. | ||
2532 | 5 | # You may obtain a copy of the License at | ||
2533 | 6 | # | ||
2534 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
2535 | 8 | # | ||
2536 | 9 | # Unless required by applicable law or agreed to in writing, software | ||
2537 | 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
2538 | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
2539 | 12 | # See the License for the specific language governing permissions and | ||
2540 | 13 | # limitations under the License. | ||
2541 | 14 | |||
2542 | 15 | |||
2543 | 16 | class OSContextError(Exception): | ||
2544 | 17 | """Raised when an error occurs during context generation. | ||
2545 | 18 | |||
2546 | 19 | This exception is principally used in contrib.openstack.context | ||
2547 | 20 | """ | ||
2548 | 21 | pass | ||
2549 | 0 | 22 | ||
2550 | === modified file 'charmhelpers/contrib/openstack/files/__init__.py' | |||
2551 | --- charmhelpers/contrib/openstack/files/__init__.py 2015-09-28 20:09:02 +0000 | |||
2552 | +++ charmhelpers/contrib/openstack/files/__init__.py 2017-03-04 02:21:16 +0000 | |||
2553 | @@ -1,18 +1,16 @@ | |||
2554 | 1 | # Copyright 2014-2015 Canonical Limited. | 1 | # Copyright 2014-2015 Canonical Limited. |
2555 | 2 | # | 2 | # |
2569 | 3 | # This file is part of charm-helpers. | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
2570 | 4 | # | 4 | # you may not use this file except in compliance with the License. |
2571 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | 5 | # You may obtain a copy of the License at |
2572 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | 6 | # |
2573 | 7 | # published by the Free Software Foundation. | 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
2574 | 8 | # | 8 | # |
2575 | 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 |
2576 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
2577 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
2578 | 12 | # GNU Lesser General Public License for more details. | 12 | # See the License for the specific language governing permissions and |
2579 | 13 | # | 13 | # limitations under the License. |
2567 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
2568 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
2580 | 16 | 14 | ||
2581 | 17 | # dummy __init__.py to fool syncer into thinking this is a syncable python | 15 | # dummy __init__.py to fool syncer into thinking this is a syncable python |
2582 | 18 | # module | 16 | # module |
2583 | 19 | 17 | ||
2584 | === modified file 'charmhelpers/contrib/openstack/files/check_haproxy.sh' | |||
2585 | --- charmhelpers/contrib/openstack/files/check_haproxy.sh 2015-09-28 20:09:02 +0000 | |||
2586 | +++ charmhelpers/contrib/openstack/files/check_haproxy.sh 2017-03-04 02:21:16 +0000 | |||
2587 | @@ -9,15 +9,17 @@ | |||
2588 | 9 | CRITICAL=0 | 9 | CRITICAL=0 |
2589 | 10 | NOTACTIVE='' | 10 | NOTACTIVE='' |
2590 | 11 | LOGFILE=/var/log/nagios/check_haproxy.log | 11 | LOGFILE=/var/log/nagios/check_haproxy.log |
2592 | 12 | AUTH=$(grep -r "stats auth" /etc/haproxy | head -1 | awk '{print $4}') | 12 | AUTH=$(grep -r "stats auth" /etc/haproxy | awk 'NR=1{print $4}') |
2593 | 13 | 13 | ||
2595 | 14 | for appserver in $(grep ' server' /etc/haproxy/haproxy.cfg | awk '{print $2'}); | 14 | typeset -i N_INSTANCES=0 |
2596 | 15 | for appserver in $(awk '/^\s+server/{print $2}' /etc/haproxy/haproxy.cfg) | ||
2597 | 15 | do | 16 | do |
2599 | 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 |
2600 | 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') | ||
2601 | 17 | if [ $? != 0 ]; then | 19 | if [ $? != 0 ]; then |
2602 | 18 | date >> $LOGFILE | 20 | date >> $LOGFILE |
2603 | 19 | echo $output >> $LOGFILE | 21 | echo $output >> $LOGFILE |
2605 | 20 | /usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 -v | grep $appserver >> $LOGFILE 2>&1 | 22 | /usr/lib/nagios/plugins/check_http -a ${AUTH} -I 127.0.0.1 -p 8888 -u '/;csv' -v | grep ",${appserver}," >> $LOGFILE 2>&1 |
2606 | 21 | CRITICAL=1 | 23 | CRITICAL=1 |
2607 | 22 | NOTACTIVE="${NOTACTIVE} $appserver" | 24 | NOTACTIVE="${NOTACTIVE} $appserver" |
2608 | 23 | fi | 25 | fi |
2609 | @@ -28,5 +30,5 @@ | |||
2610 | 28 | exit 2 | 30 | exit 2 |
2611 | 29 | fi | 31 | fi |
2612 | 30 | 32 | ||
2614 | 31 | echo "OK: All haproxy instances looking good" | 33 | echo "OK: All haproxy instances ($N_INSTANCES) looking good" |
2615 | 32 | exit 0 | 34 | exit 0 |
2616 | 33 | 35 | ||
2617 | === added directory 'charmhelpers/contrib/openstack/ha' | |||
2618 | === added file 'charmhelpers/contrib/openstack/ha/__init__.py' | |||
2619 | --- charmhelpers/contrib/openstack/ha/__init__.py 1970-01-01 00:00:00 +0000 | |||
2620 | +++ charmhelpers/contrib/openstack/ha/__init__.py 2017-03-04 02:21:16 +0000 | |||
2621 | @@ -0,0 +1,13 @@ | |||
2622 | 1 | # Copyright 2016 Canonical Ltd | ||
2623 | 2 | # | ||
2624 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
2625 | 4 | # you may not use this file except in compliance with the License. | ||
2626 | 5 | # You may obtain a copy of the License at | ||
2627 | 6 | # | ||
2628 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
2629 | 8 | # | ||
2630 | 9 | # Unless required by applicable law or agreed to in writing, software | ||
2631 | 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
2632 | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
2633 | 12 | # See the License for the specific language governing permissions and | ||
2634 | 13 | # limitations under the License. | ||
2635 | 0 | 14 | ||
2636 | === added file 'charmhelpers/contrib/openstack/ha/utils.py' | |||
2637 | --- charmhelpers/contrib/openstack/ha/utils.py 1970-01-01 00:00:00 +0000 | |||
2638 | +++ charmhelpers/contrib/openstack/ha/utils.py 2017-03-04 02:21:16 +0000 | |||
2639 | @@ -0,0 +1,139 @@ | |||
2640 | 1 | # Copyright 2014-2016 Canonical Limited. | ||
2641 | 2 | # | ||
2642 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
2643 | 4 | # you may not use this file except in compliance with the License. | ||
2644 | 5 | # You may obtain a copy of the License at | ||
2645 | 6 | # | ||
2646 | 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
2647 | 8 | # | ||
2648 | 9 | # Unless required by applicable law or agreed to in writing, software | ||
2649 | 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
2650 | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
2651 | 12 | # See the License for the specific language governing permissions and | ||
2652 | 13 | # limitations under the License. | ||
2653 | 14 | |||
2654 | 15 | # | ||
2655 | 16 | # Copyright 2016 Canonical Ltd. | ||
2656 | 17 | # | ||
2657 | 18 | # Authors: | ||
2658 | 19 | # Openstack Charmers < | ||
2659 | 20 | # | ||
2660 | 21 | |||
2661 | 22 | """ | ||
2662 | 23 | Helpers for high availability. | ||
2663 | 24 | """ | ||
2664 | 25 | |||
2665 | 26 | import re | ||
2666 | 27 | |||
2667 | 28 | from charmhelpers.core.hookenv import ( | ||
2668 | 29 | log, | ||
2669 | 30 | relation_set, | ||
2670 | 31 | charm_name, | ||
2671 | 32 | config, | ||
2672 | 33 | status_set, | ||
2673 | 34 | DEBUG, | ||
2674 | 35 | ) | ||
2675 | 36 | |||
2676 | 37 | from charmhelpers.core.host import ( | ||
2677 | 38 | lsb_release | ||
2678 | 39 | ) | ||
2679 | 40 | |||
2680 | 41 | from charmhelpers.contrib.openstack.ip import ( | ||
2681 | 42 | resolve_address, | ||
2682 | 43 | ) | ||
2683 | 44 | |||
2684 | 45 | |||
2685 | 46 | class DNSHAException(Exception): | ||
2686 | 47 | """Raised when an error occurs setting up DNS HA | ||
2687 | 48 | """ | ||
2688 | 49 | |||
2689 | 50 | pass | ||
2690 | 51 | |||
2691 | 52 | |||
2692 | 53 | def update_dns_ha_resource_params(resources, resource_params, | ||
2693 | 54 | relation_id=None, | ||
2694 | 55 | crm_ocf='ocf:maas:dns'): | ||
2695 | 56 | """ Check for os-*-hostname settings and update resource dictionaries for | ||
2696 | 57 | the HA relation. | ||
2697 | 58 | |||
2698 | 59 | @param resources: Pointer to dictionary of resources. | ||
2699 | 60 | Usually instantiated in ha_joined(). | ||
2700 | 61 | @param resource_params: Pointer to dictionary of resource parameters. | ||
2701 | 62 | Usually instantiated in ha_joined() | ||
2702 | 63 | @param relation_id: Relation ID of the ha relation | ||
2703 | 64 | @param crm_ocf: Corosync Open Cluster Framework resource agent to use for | ||
2704 | 65 | DNS HA | ||
2705 | 66 | """ | ||
2706 | 67 | |||
2707 | 68 | # Validate the charm environment for DNS HA | ||
2708 | 69 | assert_charm_supports_dns_ha() | ||
2709 | 70 | |||
2710 | 71 | settings = ['os-admin-hostname', 'os-internal-hostname', | ||
2711 | 72 | 'os-public-hostname', 'os-access-hostname'] | ||
2712 | 73 | |||
2713 | 74 | # Check which DNS settings are set and update dictionaries | ||
2714 | 75 | hostname_group = [] | ||
2715 | 76 | for setting in settings: | ||
2716 | 77 | hostname = config(setting) | ||
2717 | 78 | if hostname is None: | ||
2718 | 79 | log('DNS HA: Hostname setting {} is None. Ignoring.' | ||
2719 | 80 | ''.format(setting), | ||
2720 | 81 | DEBUG) | ||
2721 | 82 | continue | ||
2722 | 83 | m = re.search('os-(.+?)-hostname', setting) | ||
2723 | 84 | if m: | ||
2724 | 85 | networkspace = m.group(1) | ||
2725 | 86 | else: | ||
2726 | 87 | msg = ('Unexpected DNS hostname setting: {}. ' | ||
2727 | 88 | 'Cannot determine network space name' | ||
2728 | 89 | ''.format(setting)) | ||
2729 | 90 | status_set('blocked', msg) | ||
2730 | 91 | raise DNSHAException(msg) | ||
2731 | 92 | |||
2732 | 93 | hostname_key = 'res_{}_{}_hostname'.format(charm_name(), networkspace) | ||
2733 | 94 | if hostname_key in hostname_group: | ||
2734 | 95 | log('DNS HA: Resource {}: {} already exists in ' | ||
2735 | 96 | 'hostname group - skipping'.format(hostname_key, hostname), | ||
2736 | 97 | DEBUG) | ||
2737 | 98 | continue | ||
2738 | 99 | |||
2739 | 100 | hostname_group.append(hostname_key) | ||
2740 | 101 | resources[hostname_key] = crm_ocf | ||
2741 | 102 | resource_params[hostname_key] = ( | ||
2742 | 103 | 'params fqdn="{}" ip_address="{}" ' | ||
2743 | 104 | ''.format(hostname, resolve_address(endpoint_type=networkspace, | ||
2744 | 105 | override=False))) | ||
2745 | 106 | |||
2746 | 107 | if len(hostname_group) >= 1: | ||
2747 | 108 | log('DNS HA: Hostname group is set with {} as members. ' | ||
2748 | 109 | 'Informing the ha relation'.format(' '.join(hostname_group)), | ||
2749 | 110 | DEBUG) | ||
2750 | 111 | relation_set(relation_id=relation_id, groups={ | ||
2751 | 112 | 'grp_{}_hostnames'.format(charm_name()): ' '.join(hostname_group)}) | ||
2752 | 113 | else: | ||
2753 | 114 | msg = 'DNS HA: Hostname group has no members.' | ||
2754 | 115 | status_set('blocked', msg) | ||
2755 | 116 | raise DNSHAException(msg) | ||
2756 | 117 | |||
2757 | 118 | |||
2758 | 119 | def assert_charm_supports_dns_ha(): | ||
2759 | 120 | """Validate prerequisites for DNS HA | ||
2760 | 121 | The MAAS client is only available on Xenial or greater | ||
2761 | 122 | """ | ||
2762 | 123 | if lsb_release().get('DISTRIB_RELEASE') < '16.04': | ||
2763 | 124 | msg = ('DNS HA is only supported on 16.04 and greater ' | ||
2764 | 125 | 'versions of Ubuntu.') | ||
2765 | 126 | status_set('blocked', msg) | ||
2766 | 127 | raise DNSHAException(msg) | ||
2767 | 128 | return True | ||
2768 | 129 | |||
2769 | 130 | |||
2770 | 131 | def expect_ha(): | ||
2771 | 132 | """ Determine if the unit expects to be in HA | ||
2772 | 133 | |||
2773 | 134 | Check for VIP or dns-ha settings which indicate the unit should expect to | ||
2774 | 135 | be related to hacluster. | ||
2775 | 136 | |||
2776 | 137 | @returns boolean | ||
2777 | 138 | """ | ||
2778 | 139 | return config('vip') or config('dns-ha') | ||
2779 | 0 | 140 | ||
2780 | === modified file 'charmhelpers/contrib/openstack/ip.py' | |||
2781 | --- charmhelpers/contrib/openstack/ip.py 2015-09-28 20:09:02 +0000 | |||
2782 | +++ charmhelpers/contrib/openstack/ip.py 2017-03-04 02:21:16 +0000 | |||
2783 | @@ -1,52 +1,62 @@ | |||
2784 | 1 | # Copyright 2014-2015 Canonical Limited. | 1 | # Copyright 2014-2015 Canonical Limited. |
2785 | 2 | # | 2 | # |
2799 | 3 | # This file is part of charm-helpers. | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
2800 | 4 | # | 4 | # you may not use this file except in compliance with the License. |
2801 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | 5 | # You may obtain a copy of the License at |
2802 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | 6 | # |
2803 | 7 | # published by the Free Software Foundation. | 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
2804 | 8 | # | 8 | # |
2805 | 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 |
2806 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
2807 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
2808 | 12 | # GNU Lesser General Public License for more details. | 12 | # See the License for the specific language governing permissions and |
2809 | 13 | # | 13 | # limitations under the License. |
2797 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
2798 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
2810 | 16 | 14 | ||
2811 | 17 | from charmhelpers.core.hookenv import ( | 15 | from charmhelpers.core.hookenv import ( |
2812 | 18 | config, | 16 | config, |
2813 | 19 | unit_get, | 17 | unit_get, |
2814 | 20 | service_name, | 18 | service_name, |
2815 | 19 | network_get_primary_address, | ||
2816 | 21 | ) | 20 | ) |
2817 | 22 | from charmhelpers.contrib.network.ip import ( | 21 | from charmhelpers.contrib.network.ip import ( |
2818 | 23 | get_address_in_network, | 22 | get_address_in_network, |
2819 | 24 | is_address_in_network, | 23 | is_address_in_network, |
2820 | 25 | is_ipv6, | 24 | is_ipv6, |
2821 | 26 | get_ipv6_addr, | 25 | get_ipv6_addr, |
2822 | 26 | resolve_network_cidr, | ||
2823 | 27 | ) | 27 | ) |
2824 | 28 | from charmhelpers.contrib.hahelpers.cluster import is_clustered | 28 | from charmhelpers.contrib.hahelpers.cluster import is_clustered |
2825 | 29 | 29 | ||
2826 | 30 | PUBLIC = 'public' | 30 | PUBLIC = 'public' |
2827 | 31 | INTERNAL = 'int' | 31 | INTERNAL = 'int' |
2828 | 32 | ADMIN = 'admin' | 32 | ADMIN = 'admin' |
2829 | 33 | ACCESS = 'access' | ||
2830 | 33 | 34 | ||
2831 | 34 | ADDRESS_MAP = { | 35 | ADDRESS_MAP = { |
2832 | 35 | PUBLIC: { | 36 | PUBLIC: { |
2833 | 37 | 'binding': 'public', | ||
2834 | 36 | 'config': 'os-public-network', | 38 | 'config': 'os-public-network', |
2835 | 37 | 'fallback': 'public-address', | 39 | 'fallback': 'public-address', |
2836 | 38 | 'override': 'os-public-hostname', | 40 | 'override': 'os-public-hostname', |
2837 | 39 | }, | 41 | }, |
2838 | 40 | INTERNAL: { | 42 | INTERNAL: { |
2839 | 43 | 'binding': 'internal', | ||
2840 | 41 | 'config': 'os-internal-network', | 44 | 'config': 'os-internal-network', |
2841 | 42 | 'fallback': 'private-address', | 45 | 'fallback': 'private-address', |
2842 | 43 | 'override': 'os-internal-hostname', | 46 | 'override': 'os-internal-hostname', |
2843 | 44 | }, | 47 | }, |
2844 | 45 | ADMIN: { | 48 | ADMIN: { |
2845 | 49 | 'binding': 'admin', | ||
2846 | 46 | 'config': 'os-admin-network', | 50 | 'config': 'os-admin-network', |
2847 | 47 | 'fallback': 'private-address', | 51 | 'fallback': 'private-address', |
2848 | 48 | 'override': 'os-admin-hostname', | 52 | 'override': 'os-admin-hostname', |
2850 | 49 | } | 53 | }, |
2851 | 54 | ACCESS: { | ||
2852 | 55 | 'binding': 'access', | ||
2853 | 56 | 'config': 'access-network', | ||
2854 | 57 | 'fallback': 'private-address', | ||
2855 | 58 | 'override': 'os-access-hostname', | ||
2856 | 59 | }, | ||
2857 | 50 | } | 60 | } |
2858 | 51 | 61 | ||
2859 | 52 | 62 | ||
2860 | @@ -103,20 +113,23 @@ | |||
2861 | 103 | return addr_override.format(service_name=service_name()) | 113 | return addr_override.format(service_name=service_name()) |
2862 | 104 | 114 | ||
2863 | 105 | 115 | ||
2865 | 106 | def resolve_address(endpoint_type=PUBLIC): | 116 | def resolve_address(endpoint_type=PUBLIC, override=True): |
2866 | 107 | """Return unit address depending on net config. | 117 | """Return unit address depending on net config. |
2867 | 108 | 118 | ||
2868 | 109 | If unit is clustered with vip(s) and has net splits defined, return vip on | 119 | If unit is clustered with vip(s) and has net splits defined, return vip on |
2869 | 110 | correct network. If clustered with no nets defined, return primary vip. | 120 | correct network. If clustered with no nets defined, return primary vip. |
2870 | 111 | 121 | ||
2871 | 112 | If not clustered, return unit address ensuring address is on configured net | 122 | If not clustered, return unit address ensuring address is on configured net |
2873 | 113 | split if one is configured. | 123 | split if one is configured, or a Juju 2.0 extra-binding has been used. |
2874 | 114 | 124 | ||
2875 | 115 | :param endpoint_type: Network endpoing type | 125 | :param endpoint_type: Network endpoing type |
2876 | 126 | :param override: Accept hostname overrides or not | ||
2877 | 116 | """ | 127 | """ |
2881 | 117 | resolved_address = _get_address_override(endpoint_type) | 128 | resolved_address = None |
2882 | 118 | if resolved_address: | 129 | if override: |
2883 | 119 | return resolved_address | 130 | resolved_address = _get_address_override(endpoint_type) |
2884 | 131 | if resolved_address: | ||
2885 | 132 | return resolved_address | ||
2886 | 120 | 133 | ||
2887 | 121 | vips = config('vip') | 134 | vips = config('vip') |
2888 | 122 | if vips: | 135 | if vips: |
2889 | @@ -125,23 +138,45 @@ | |||
2890 | 125 | net_type = ADDRESS_MAP[endpoint_type]['config'] | 138 | net_type = ADDRESS_MAP[endpoint_type]['config'] |
2891 | 126 | net_addr = config(net_type) | 139 | net_addr = config(net_type) |
2892 | 127 | net_fallback = ADDRESS_MAP[endpoint_type]['fallback'] | 140 | net_fallback = ADDRESS_MAP[endpoint_type]['fallback'] |
2893 | 141 | binding = ADDRESS_MAP[endpoint_type]['binding'] | ||
2894 | 128 | clustered = is_clustered() | 142 | clustered = is_clustered() |
2900 | 129 | if clustered: | 143 | |
2901 | 130 | if not net_addr: | 144 | if clustered and vips: |
2902 | 131 | # If no net-splits defined, we expect a single vip | 145 | if net_addr: |
2898 | 132 | resolved_address = vips[0] | ||
2899 | 133 | else: | ||
2903 | 134 | for vip in vips: | 146 | for vip in vips: |
2904 | 135 | if is_address_in_network(net_addr, vip): | 147 | if is_address_in_network(net_addr, vip): |
2905 | 136 | resolved_address = vip | 148 | resolved_address = vip |
2906 | 137 | break | 149 | break |
2907 | 150 | else: | ||
2908 | 151 | # NOTE: endeavour to check vips against network space | ||
2909 | 152 | # bindings | ||
2910 | 153 | try: | ||
2911 | 154 | bound_cidr = resolve_network_cidr( | ||
2912 | 155 | network_get_primary_address(binding) | ||
2913 | 156 | ) | ||
2914 | 157 | for vip in vips: | ||
2915 | 158 | if is_address_in_network(bound_cidr, vip): | ||
2916 | 159 | resolved_address = vip | ||
2917 | 160 | break | ||
2918 | 161 | except NotImplementedError: | ||
2919 | 162 | # If no net-splits configured and no support for extra | ||
2920 | 163 | # bindings/network spaces so we expect a single vip | ||
2921 | 164 | resolved_address = vips[0] | ||
2922 | 138 | else: | 165 | else: |
2923 | 139 | if config('prefer-ipv6'): | 166 | if config('prefer-ipv6'): |
2924 | 140 | fallback_addr = get_ipv6_addr(exc_list=vips)[0] | 167 | fallback_addr = get_ipv6_addr(exc_list=vips)[0] |
2925 | 141 | else: | 168 | else: |
2926 | 142 | fallback_addr = unit_get(net_fallback) | 169 | fallback_addr = unit_get(net_fallback) |
2927 | 143 | 170 | ||
2929 | 144 | resolved_address = get_address_in_network(net_addr, fallback_addr) | 171 | if net_addr: |
2930 | 172 | resolved_address = get_address_in_network(net_addr, fallback_addr) | ||
2931 | 173 | else: | ||
2932 | 174 | # NOTE: only try to use extra bindings if legacy network | ||
2933 | 175 | # configuration is not in use | ||
2934 | 176 | try: | ||
2935 | 177 | resolved_address = network_get_primary_address(binding) | ||
2936 | 178 | except NotImplementedError: | ||
2937 | 179 | resolved_address = fallback_addr | ||
2938 | 145 | 180 | ||
2939 | 146 | if resolved_address is None: | 181 | if resolved_address is None: |
2940 | 147 | raise ValueError("Unable to resolve a suitable IP address based on " | 182 | raise ValueError("Unable to resolve a suitable IP address based on " |
2941 | 148 | 183 | ||
2942 | === added file 'charmhelpers/contrib/openstack/keystone.py' | |||
2943 | --- charmhelpers/contrib/openstack/keystone.py 1970-01-01 00:00:00 +0000 | |||
2944 | +++ charmhelpers/contrib/openstack/keystone.py 2017-03-04 02:21:16 +0000 | |||
2945 | @@ -0,0 +1,178 @@ | |||
2946 | 1 | #!/usr/bin/python | ||
2947 | 2 | # | ||
2948 | 3 | # Copyright 2017 Canonical Ltd | ||
2949 | 4 | # | ||
2950 | 5 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
2951 | 6 | # you may not use this file except in compliance with the License. | ||
2952 | 7 | # You may obtain a copy of the License at | ||
2953 | 8 | # | ||
2954 | 9 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
2955 | 10 | # | ||
2956 | 11 | # Unless required by applicable law or agreed to in writing, software | ||
2957 | 12 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
2958 | 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
2959 | 14 | # See the License for the specific language governing permissions and | ||
2960 | 15 | # limitations under the License. | ||
2961 | 16 | |||
2962 | 17 | import six | ||
2963 | 18 | from charmhelpers.fetch import apt_install | ||
2964 | 19 | from charmhelpers.contrib.openstack.context import IdentityServiceContext | ||
2965 | 20 | from charmhelpers.core.hookenv import ( | ||
2966 | 21 | log, | ||
2967 | 22 | ERROR, | ||
2968 | 23 | ) | ||
2969 | 24 | |||
2970 | 25 | |||
2971 | 26 | def get_api_suffix(api_version): | ||
2972 | 27 | """Return the formatted api suffix for the given version | ||
2973 | 28 | @param api_version: version of the keystone endpoint | ||
2974 | 29 | @returns the api suffix formatted according to the given api | ||
2975 | 30 | version | ||
2976 | 31 | """ | ||
2977 | 32 | return 'v2.0' if api_version in (2, "2.0") else 'v3' | ||
2978 | 33 | |||
2979 | 34 | |||
2980 | 35 | def format_endpoint(schema, addr, port, api_version): | ||
2981 | 36 | """Return a formatted keystone endpoint | ||
2982 | 37 | @param schema: http or https | ||
2983 | 38 | @param addr: ipv4/ipv6 host of the keystone service | ||
2984 | 39 | @param port: port of the keystone service | ||
2985 | 40 | @param api_version: 2 or 3 | ||
2986 | 41 | @returns a fully formatted keystone endpoint | ||
2987 | 42 | """ | ||
2988 | 43 | return '{}://{}:{}/{}/'.format(schema, addr, port, | ||
2989 | 44 | get_api_suffix(api_version)) | ||
2990 | 45 | |||
2991 | 46 | |||
2992 | 47 | def get_keystone_manager(endpoint, api_version, **kwargs): | ||
2993 | 48 | """Return a keystonemanager for the correct API version | ||
2994 | 49 | |||
2995 | 50 | @param endpoint: the keystone endpoint to point client at | ||
2996 | 51 | @param api_version: version of the keystone api the client should use | ||
2997 | 52 | @param kwargs: token or username/tenant/password information | ||
2998 | 53 | @returns keystonemanager class used for interrogating keystone | ||
2999 | 54 | """ | ||
3000 | 55 | if api_version == 2: | ||
3001 | 56 | return KeystoneManager2(endpoint, **kwargs) | ||
3002 | 57 | if api_version == 3: | ||
3003 | 58 | return KeystoneManager3(endpoint, **kwargs) | ||
3004 | 59 | raise ValueError('No manager found for api version {}'.format(api_version)) | ||
3005 | 60 | |||
3006 | 61 | |||
3007 | 62 | def get_keystone_manager_from_identity_service_context(): | ||
3008 | 63 | """Return a keystonmanager generated from a | ||
3009 | 64 | instance of charmhelpers.contrib.openstack.context.IdentityServiceContext | ||
3010 | 65 | @returns keystonamenager instance | ||
3011 | 66 | """ | ||
3012 | 67 | context = IdentityServiceContext()() | ||
3013 | 68 | if not context: | ||
3014 | 69 | msg = "Identity service context cannot be generated" | ||
3015 | 70 | log(msg, level=ERROR) | ||
3016 | 71 | raise ValueError(msg) | ||
3017 | 72 | |||
3018 | 73 | endpoint = format_endpoint(context['service_protocol'], | ||
3019 | 74 | context['service_host'], | ||
3020 | 75 | context['service_port'], | ||
3021 | 76 | context['api_version']) | ||
3022 | 77 | |||
3023 | 78 | if context['api_version'] in (2, "2.0"): | ||
3024 | 79 | api_version = 2 | ||
3025 | 80 | else: | ||
3026 | 81 | api_version = 3 | ||
3027 | 82 | |||
3028 | 83 | return get_keystone_manager(endpoint, api_version, | ||
3029 | 84 | username=context['admin_user'], | ||
3030 | 85 | password=context['admin_password'], | ||
3031 | 86 | tenant_name=context['admin_tenant_name']) | ||
3032 | 87 | |||
3033 | 88 | |||
3034 | 89 | class KeystoneManager(object): | ||
3035 | 90 | |||
3036 | 91 | def resolve_service_id(self, service_name=None, service_type=None): | ||
3037 | 92 | """Find the service_id of a given service""" | ||
3038 | 93 | services = [s._info for s in self.api.services.list()] | ||
3039 | 94 | |||
3040 | 95 | service_name = service_name.lower() | ||
3041 | 96 | for s in services: | ||
3042 | 97 | name = s['name'].lower() | ||
3043 | 98 | if service_type and service_name: | ||
3044 | 99 | if (service_name == name and service_type == s['type']): | ||
3045 | 100 | return s['id'] | ||
3046 | 101 | elif service_name and service_name == name: | ||
3047 | 102 | return s['id'] | ||
3048 | 103 | elif service_type and service_type == s['type']: | ||
3049 | 104 | return s['id'] | ||
3050 | 105 | return None | ||
3051 | 106 | |||
3052 | 107 | def service_exists(self, service_name=None, service_type=None): | ||
3053 | 108 | """Determine if the given service exists on the service list""" | ||
3054 | 109 | return self.resolve_service_id(service_name, service_type) is not None | ||
3055 | 110 | |||
3056 | 111 | |||
3057 | 112 | class KeystoneManager2(KeystoneManager): | ||
3058 | 113 | |||
3059 | 114 | def __init__(self, endpoint, **kwargs): | ||
3060 | 115 | try: | ||
3061 | 116 | from keystoneclient.v2_0 import client | ||
3062 | 117 | from keystoneclient.auth.identity import v2 | ||
3063 | 118 | from keystoneclient import session | ||
3064 | 119 | except ImportError: | ||
3065 | 120 | if six.PY2: | ||
3066 | 121 | apt_install(["python-keystoneclient"], fatal=True) | ||
3067 | 122 | else: | ||
3068 | 123 | apt_install(["python3-keystoneclient"], fatal=True) | ||
3069 | 124 | |||
3070 | 125 | from keystoneclient.v2_0 import client | ||
3071 | 126 | from keystoneclient.auth.identity import v2 | ||
3072 | 127 | from keystoneclient import session | ||
3073 | 128 | |||
3074 | 129 | self.api_version = 2 | ||
3075 | 130 | |||
3076 | 131 | token = kwargs.get("token", None) | ||
3077 | 132 | if token: | ||
3078 | 133 | api = client.Client(endpoint=endpoint, token=token) | ||
3079 | 134 | else: | ||
3080 | 135 | auth = v2.Password(username=kwargs.get("username"), | ||
3081 | 136 | password=kwargs.get("password"), | ||
3082 | 137 | tenant_name=kwargs.get("tenant_name"), | ||
3083 | 138 | auth_url=endpoint) | ||
3084 | 139 | sess = session.Session(auth=auth) | ||
3085 | 140 | api = client.Client(session=sess) | ||
3086 | 141 | |||
3087 | 142 | self.api = api | ||
3088 | 143 | |||
3089 | 144 | |||
3090 | 145 | class KeystoneManager3(KeystoneManager): | ||
3091 | 146 | |||
3092 | 147 | def __init__(self, endpoint, **kwargs): | ||
3093 | 148 | try: | ||
3094 | 149 | from keystoneclient.v3 import client | ||
3095 | 150 | from keystoneclient.auth import token_endpoint | ||
3096 | 151 | from keystoneclient import session | ||
3097 | 152 | from keystoneclient.auth.identity import v3 | ||
3098 | 153 | except ImportError: | ||
3099 | 154 | if six.PY2: | ||
3100 | 155 | apt_install(["python-keystoneclient"], fatal=True) | ||
3101 | 156 | else: | ||
3102 | 157 | apt_install(["python3-keystoneclient"], fatal=True) | ||
3103 | 158 | |||
3104 | 159 | from keystoneclient.v3 import client | ||
3105 | 160 | from keystoneclient.auth import token_endpoint | ||
3106 | 161 | from keystoneclient import session | ||
3107 | 162 | from keystoneclient.auth.identity import v3 | ||
3108 | 163 | |||
3109 | 164 | self.api_version = 3 | ||
3110 | 165 | |||
3111 | 166 | token = kwargs.get("token", None) | ||
3112 | 167 | if token: | ||
3113 | 168 | auth = token_endpoint.Token(endpoint=endpoint, | ||
3114 | 169 | token=token) | ||
3115 | 170 | sess = session.Session(auth=auth) | ||
3116 | 171 | else: | ||
3117 | 172 | auth = v3.Password(auth_url=endpoint, | ||
3118 | 173 | user_id=kwargs.get("username"), | ||
3119 | 174 | password=kwargs.get("password"), | ||
3120 | 175 | project_id=kwargs.get("tenant_name")) | ||
3121 | 176 | sess = session.Session(auth=auth) | ||
3122 | 177 | |||
3123 | 178 | self.api = client.Client(session=sess) | ||
3124 | 0 | 179 | ||
3125 | === modified file 'charmhelpers/contrib/openstack/neutron.py' | |||
3126 | --- charmhelpers/contrib/openstack/neutron.py 2015-09-28 20:09:02 +0000 | |||
3127 | +++ charmhelpers/contrib/openstack/neutron.py 2017-03-04 02:21:16 +0000 | |||
3128 | @@ -1,18 +1,16 @@ | |||
3129 | 1 | # Copyright 2014-2015 Canonical Limited. | 1 | # Copyright 2014-2015 Canonical Limited. |
3130 | 2 | # | 2 | # |
3144 | 3 | # This file is part of charm-helpers. | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
3145 | 4 | # | 4 | # you may not use this file except in compliance with the License. |
3146 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | 5 | # You may obtain a copy of the License at |
3147 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | 6 | # |
3148 | 7 | # published by the Free Software Foundation. | 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
3149 | 8 | # | 8 | # |
3150 | 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 |
3151 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
3152 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
3153 | 12 | # GNU Lesser General Public License for more details. | 12 | # See the License for the specific language governing permissions and |
3154 | 13 | # | 13 | # limitations under the License. |
3142 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
3143 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
3155 | 16 | 14 | ||
3156 | 17 | # Various utilies for dealing with Neutron and the renaming from Quantum. | 15 | # Various utilies for dealing with Neutron and the renaming from Quantum. |
3157 | 18 | 16 | ||
3158 | @@ -34,6 +32,7 @@ | |||
3159 | 34 | kver = check_output(['uname', '-r']).decode('UTF-8').strip() | 32 | kver = check_output(['uname', '-r']).decode('UTF-8').strip() |
3160 | 35 | return 'linux-headers-%s' % kver | 33 | return 'linux-headers-%s' % kver |
3161 | 36 | 34 | ||
3162 | 35 | |||
3163 | 37 | QUANTUM_CONF_DIR = '/etc/quantum' | 36 | QUANTUM_CONF_DIR = '/etc/quantum' |
3164 | 38 | 37 | ||
3165 | 39 | 38 | ||
3166 | @@ -50,7 +49,7 @@ | |||
3167 | 50 | if kernel_version() >= (3, 13): | 49 | if kernel_version() >= (3, 13): |
3168 | 51 | return [] | 50 | return [] |
3169 | 52 | else: | 51 | else: |
3171 | 53 | return ['openvswitch-datapath-dkms'] | 52 | return [headers_package(), 'openvswitch-datapath-dkms'] |
3172 | 54 | 53 | ||
3173 | 55 | 54 | ||
3174 | 56 | # legacy | 55 | # legacy |
3175 | @@ -70,7 +69,7 @@ | |||
3176 | 70 | relation_prefix='neutron', | 69 | relation_prefix='neutron', |
3177 | 71 | ssl_dir=QUANTUM_CONF_DIR)], | 70 | ssl_dir=QUANTUM_CONF_DIR)], |
3178 | 72 | 'services': ['quantum-plugin-openvswitch-agent'], | 71 | 'services': ['quantum-plugin-openvswitch-agent'], |
3180 | 73 | 'packages': [[headers_package()] + determine_dkms_package(), | 72 | 'packages': [determine_dkms_package(), |
3181 | 74 | ['quantum-plugin-openvswitch-agent']], | 73 | ['quantum-plugin-openvswitch-agent']], |
3182 | 75 | 'server_packages': ['quantum-server', | 74 | 'server_packages': ['quantum-server', |
3183 | 76 | 'quantum-plugin-openvswitch'], | 75 | 'quantum-plugin-openvswitch'], |
3184 | @@ -93,6 +92,7 @@ | |||
3185 | 93 | } | 92 | } |
3186 | 94 | } | 93 | } |
3187 | 95 | 94 | ||
3188 | 95 | |||
3189 | 96 | NEUTRON_CONF_DIR = '/etc/neutron' | 96 | NEUTRON_CONF_DIR = '/etc/neutron' |
3190 | 97 | 97 | ||
3191 | 98 | 98 | ||
3192 | @@ -111,7 +111,7 @@ | |||
3193 | 111 | relation_prefix='neutron', | 111 | relation_prefix='neutron', |
3194 | 112 | ssl_dir=NEUTRON_CONF_DIR)], | 112 | ssl_dir=NEUTRON_CONF_DIR)], |
3195 | 113 | 'services': ['neutron-plugin-openvswitch-agent'], | 113 | 'services': ['neutron-plugin-openvswitch-agent'], |
3197 | 114 | 'packages': [[headers_package()] + determine_dkms_package(), | 114 | 'packages': [determine_dkms_package(), |
3198 | 115 | ['neutron-plugin-openvswitch-agent']], | 115 | ['neutron-plugin-openvswitch-agent']], |
3199 | 116 | 'server_packages': ['neutron-server', | 116 | 'server_packages': ['neutron-server', |
3200 | 117 | 'neutron-plugin-openvswitch'], | 117 | 'neutron-plugin-openvswitch'], |
3201 | @@ -155,7 +155,7 @@ | |||
3202 | 155 | relation_prefix='neutron', | 155 | relation_prefix='neutron', |
3203 | 156 | ssl_dir=NEUTRON_CONF_DIR)], | 156 | ssl_dir=NEUTRON_CONF_DIR)], |
3204 | 157 | 'services': [], | 157 | 'services': [], |
3206 | 158 | 'packages': [[headers_package()] + determine_dkms_package(), | 158 | 'packages': [determine_dkms_package(), |
3207 | 159 | ['neutron-plugin-cisco']], | 159 | ['neutron-plugin-cisco']], |
3208 | 160 | 'server_packages': ['neutron-server', | 160 | 'server_packages': ['neutron-server', |
3209 | 161 | 'neutron-plugin-cisco'], | 161 | 'neutron-plugin-cisco'], |
3210 | @@ -174,7 +174,7 @@ | |||
3211 | 174 | 'neutron-dhcp-agent', | 174 | 'neutron-dhcp-agent', |
3212 | 175 | 'nova-api-metadata', | 175 | 'nova-api-metadata', |
3213 | 176 | 'etcd'], | 176 | 'etcd'], |
3215 | 177 | 'packages': [[headers_package()] + determine_dkms_package(), | 177 | 'packages': [determine_dkms_package(), |
3216 | 178 | ['calico-compute', | 178 | ['calico-compute', |
3217 | 179 | 'bird', | 179 | 'bird', |
3218 | 180 | 'neutron-dhcp-agent', | 180 | 'neutron-dhcp-agent', |
3219 | @@ -204,11 +204,25 @@ | |||
3220 | 204 | database=config('database'), | 204 | database=config('database'), |
3221 | 205 | ssl_dir=NEUTRON_CONF_DIR)], | 205 | ssl_dir=NEUTRON_CONF_DIR)], |
3222 | 206 | 'services': [], | 206 | 'services': [], |
3225 | 207 | 'packages': [['plumgrid-lxc'], | 207 | 'packages': ['plumgrid-lxc', |
3226 | 208 | ['iovisor-dkms']], | 208 | 'iovisor-dkms'], |
3227 | 209 | 'server_packages': ['neutron-server', | 209 | 'server_packages': ['neutron-server', |
3228 | 210 | 'neutron-plugin-plumgrid'], | 210 | 'neutron-plugin-plumgrid'], |
3229 | 211 | 'server_services': ['neutron-server'] | 211 | 'server_services': ['neutron-server'] |
3230 | 212 | }, | ||
3231 | 213 | 'midonet': { | ||
3232 | 214 | 'config': '/etc/neutron/plugins/midonet/midonet.ini', | ||
3233 | 215 | 'driver': 'midonet.neutron.plugin.MidonetPluginV2', | ||
3234 | 216 | 'contexts': [ | ||
3235 | 217 | context.SharedDBContext(user=config('neutron-database-user'), | ||
3236 | 218 | database=config('neutron-database'), | ||
3237 | 219 | relation_prefix='neutron', | ||
3238 | 220 | ssl_dir=NEUTRON_CONF_DIR)], | ||
3239 | 221 | 'services': [], | ||
3240 | 222 | 'packages': [determine_dkms_package()], | ||
3241 | 223 | 'server_packages': ['neutron-server', | ||
3242 | 224 | 'python-neutron-plugin-midonet'], | ||
3243 | 225 | 'server_services': ['neutron-server'] | ||
3244 | 212 | } | 226 | } |
3245 | 213 | } | 227 | } |
3246 | 214 | if release >= 'icehouse': | 228 | if release >= 'icehouse': |
3247 | @@ -219,6 +233,26 @@ | |||
3248 | 219 | 'neutron-plugin-ml2'] | 233 | 'neutron-plugin-ml2'] |
3249 | 220 | # NOTE: patch in vmware renames nvp->nsx for icehouse onwards | 234 | # NOTE: patch in vmware renames nvp->nsx for icehouse onwards |
3250 | 221 | plugins['nvp'] = plugins['nsx'] | 235 | plugins['nvp'] = plugins['nsx'] |
3251 | 236 | if release >= 'kilo': | ||
3252 | 237 | plugins['midonet']['driver'] = ( | ||
3253 | 238 | 'neutron.plugins.midonet.plugin.MidonetPluginV2') | ||
3254 | 239 | if release >= 'liberty': | ||
3255 | 240 | plugins['midonet']['driver'] = ( | ||
3256 | 241 | 'midonet.neutron.plugin_v1.MidonetPluginV2') | ||
3257 | 242 | plugins['midonet']['server_packages'].remove( | ||
3258 | 243 | 'python-neutron-plugin-midonet') | ||
3259 | 244 | plugins['midonet']['server_packages'].append( | ||
3260 | 245 | 'python-networking-midonet') | ||
3261 | 246 | plugins['plumgrid']['driver'] = ( | ||
3262 | 247 | 'networking_plumgrid.neutron.plugins.plugin.NeutronPluginPLUMgridV2') | ||
3263 | 248 | plugins['plumgrid']['server_packages'].remove( | ||
3264 | 249 | 'neutron-plugin-plumgrid') | ||
3265 | 250 | if release >= 'mitaka': | ||
3266 | 251 | plugins['nsx']['server_packages'].remove('neutron-plugin-vmware') | ||
3267 | 252 | plugins['nsx']['server_packages'].append('python-vmware-nsx') | ||
3268 | 253 | plugins['nsx']['config'] = '/etc/neutron/nsx.ini' | ||
3269 | 254 | plugins['vsp']['driver'] = ( | ||
3270 | 255 | 'nuage_neutron.plugins.nuage.plugin.NuagePlugin') | ||
3271 | 222 | return plugins | 256 | return plugins |
3272 | 223 | 257 | ||
3273 | 224 | 258 | ||
3274 | @@ -310,10 +344,10 @@ | |||
3275 | 310 | def parse_data_port_mappings(mappings, default_bridge='br-data'): | 344 | def parse_data_port_mappings(mappings, default_bridge='br-data'): |
3276 | 311 | """Parse data port mappings. | 345 | """Parse data port mappings. |
3277 | 312 | 346 | ||
3279 | 313 | Mappings must be a space-delimited list of port:bridge mappings. | 347 | Mappings must be a space-delimited list of bridge:port. |
3280 | 314 | 348 | ||
3283 | 315 | Returns dict of the form {port:bridge} where port may be an mac address or | 349 | Returns dict of the form {port:bridge} where ports may be mac addresses or |
3284 | 316 | interface name. | 350 | interface names. |
3285 | 317 | """ | 351 | """ |
3286 | 318 | 352 | ||
3287 | 319 | # NOTE(dosaboy): we use rvalue for key to allow multiple values to be | 353 | # NOTE(dosaboy): we use rvalue for key to allow multiple values to be |
3288 | 320 | 354 | ||
3289 | === modified file 'charmhelpers/contrib/openstack/templates/__init__.py' | |||
3290 | --- charmhelpers/contrib/openstack/templates/__init__.py 2015-09-28 20:09:02 +0000 | |||
3291 | +++ charmhelpers/contrib/openstack/templates/__init__.py 2017-03-04 02:21:16 +0000 | |||
3292 | @@ -1,18 +1,16 @@ | |||
3293 | 1 | # Copyright 2014-2015 Canonical Limited. | 1 | # Copyright 2014-2015 Canonical Limited. |
3294 | 2 | # | 2 | # |
3308 | 3 | # This file is part of charm-helpers. | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
3309 | 4 | # | 4 | # you may not use this file except in compliance with the License. |
3310 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | 5 | # You may obtain a copy of the License at |
3311 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | 6 | # |
3312 | 7 | # published by the Free Software Foundation. | 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
3313 | 8 | # | 8 | # |
3314 | 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 |
3315 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
3316 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
3317 | 12 | # GNU Lesser General Public License for more details. | 12 | # See the License for the specific language governing permissions and |
3318 | 13 | # | 13 | # limitations under the License. |
3306 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
3307 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
3319 | 16 | 14 | ||
3320 | 17 | # dummy __init__.py to fool syncer into thinking this is a syncable python | 15 | # dummy __init__.py to fool syncer into thinking this is a syncable python |
3321 | 18 | # module | 16 | # module |
3322 | 19 | 17 | ||
3323 | === modified file 'charmhelpers/contrib/openstack/templates/haproxy.cfg' | |||
3324 | --- charmhelpers/contrib/openstack/templates/haproxy.cfg 2015-09-28 20:09:02 +0000 | |||
3325 | +++ charmhelpers/contrib/openstack/templates/haproxy.cfg 2017-03-04 02:21:16 +0000 | |||
3326 | @@ -12,27 +12,35 @@ | |||
3327 | 12 | option tcplog | 12 | option tcplog |
3328 | 13 | option dontlognull | 13 | option dontlognull |
3329 | 14 | retries 3 | 14 | retries 3 |
3333 | 15 | timeout queue 1000 | 15 | {%- if haproxy_queue_timeout %} |
3334 | 16 | timeout connect 1000 | 16 | timeout queue {{ haproxy_queue_timeout }} |
3335 | 17 | {% if haproxy_client_timeout -%} | 17 | {%- else %} |
3336 | 18 | timeout queue 5000 | ||
3337 | 19 | {%- endif %} | ||
3338 | 20 | {%- if haproxy_connect_timeout %} | ||
3339 | 21 | timeout connect {{ haproxy_connect_timeout }} | ||
3340 | 22 | {%- else %} | ||
3341 | 23 | timeout connect 5000 | ||
3342 | 24 | {%- endif %} | ||
3343 | 25 | {%- if haproxy_client_timeout %} | ||
3344 | 18 | timeout client {{ haproxy_client_timeout }} | 26 | timeout client {{ haproxy_client_timeout }} |
3346 | 19 | {% else -%} | 27 | {%- else %} |
3347 | 20 | timeout client 30000 | 28 | timeout client 30000 |
3351 | 21 | {% endif -%} | 29 | {%- endif %} |
3352 | 22 | 30 | {%- if haproxy_server_timeout %} | |
3350 | 23 | {% if haproxy_server_timeout -%} | ||
3353 | 24 | timeout server {{ haproxy_server_timeout }} | 31 | timeout server {{ haproxy_server_timeout }} |
3355 | 25 | {% else -%} | 32 | {%- else %} |
3356 | 26 | timeout server 30000 | 33 | timeout server 30000 |
3358 | 27 | {% endif -%} | 34 | {%- endif %} |
3359 | 28 | 35 | ||
3361 | 29 | listen stats {{ stat_port }} | 36 | listen stats |
3362 | 37 | bind {{ local_host }}:{{ stat_port }} | ||
3363 | 30 | mode http | 38 | mode http |
3364 | 31 | stats enable | 39 | stats enable |
3365 | 32 | stats hide-version | 40 | stats hide-version |
3366 | 33 | stats realm Haproxy\ Statistics | 41 | stats realm Haproxy\ Statistics |
3367 | 34 | stats uri / | 42 | stats uri / |
3369 | 35 | stats auth admin:password | 43 | stats auth admin:{{ stat_password }} |
3370 | 36 | 44 | ||
3371 | 37 | {% if frontends -%} | 45 | {% if frontends -%} |
3372 | 38 | {% for service, ports in service_ports.items() -%} | 46 | {% for service, ports in service_ports.items() -%} |
3373 | 39 | 47 | ||
3374 | === added file 'charmhelpers/contrib/openstack/templates/memcached.conf' | |||
3375 | --- charmhelpers/contrib/openstack/templates/memcached.conf 1970-01-01 00:00:00 +0000 | |||
3376 | +++ charmhelpers/contrib/openstack/templates/memcached.conf 2017-03-04 02:21:16 +0000 | |||
3377 | @@ -0,0 +1,53 @@ | |||
3378 | 1 | ############################################################################### | ||
3379 | 2 | # [ WARNING ] | ||
3380 | 3 | # memcached configuration file maintained by Juju | ||
3381 | 4 | # local changes may be overwritten. | ||
3382 | 5 | ############################################################################### | ||
3383 | 6 | |||
3384 | 7 | # memcached default config file | ||
3385 | 8 | # 2003 - Jay Bonci <jaybonci@debian.org> | ||
3386 | 9 | # This configuration file is read by the start-memcached script provided as | ||
3387 | 10 | # part of the Debian GNU/Linux distribution. | ||
3388 | 11 | |||
3389 | 12 | # Run memcached as a daemon. This command is implied, and is not needed for the | ||
3390 | 13 | # daemon to run. See the README.Debian that comes with this package for more | ||
3391 | 14 | # information. | ||
3392 | 15 | -d | ||
3393 | 16 | |||
3394 | 17 | # Log memcached's output to /var/log/memcached | ||
3395 | 18 | logfile /var/log/memcached.log | ||
3396 | 19 | |||
3397 | 20 | # Be verbose | ||
3398 | 21 | # -v | ||
3399 | 22 | |||
3400 | 23 | # Be even more verbose (print client commands as well) | ||
3401 | 24 | # -vv | ||
3402 | 25 | |||
3403 | 26 | # Start with a cap of 64 megs of memory. It's reasonable, and the daemon default | ||
3404 | 27 | # Note that the daemon will grow to this size, but does not start out holding this much | ||
3405 | 28 | # memory | ||
3406 | 29 | -m 64 | ||
3407 | 30 | |||
3408 | 31 | # Default connection port is 11211 | ||
3409 | 32 | -p {{ memcache_port }} | ||
3410 | 33 | |||
3411 | 34 | # Run the daemon as root. The start-memcached will default to running as root if no | ||
3412 | 35 | # -u command is present in this config file | ||
3413 | 36 | -u memcache | ||
3414 | 37 | |||
3415 | 38 | # Specify which IP address to listen on. The default is to listen on all IP addresses | ||
3416 | 39 | # This parameter is one of the only security measures that memcached has, so make sure | ||
3417 | 40 | # it's listening on a firewalled interface. | ||
3418 | 41 | -l {{ memcache_server }} | ||
3419 | 42 | |||
3420 | 43 | # Limit the number of simultaneous incoming connections. The daemon default is 1024 | ||
3421 | 44 | # -c 1024 | ||
3422 | 45 | |||
3423 | 46 | # Lock down all paged memory. Consult with the README and homepage before you do this | ||
3424 | 47 | # -k | ||
3425 | 48 | |||
3426 | 49 | # Return error when memory is exhausted (rather than removing items) | ||
3427 | 50 | # -M | ||
3428 | 51 | |||
3429 | 52 | # Maximize core file limit | ||
3430 | 53 | # -r | ||
3431 | 0 | 54 | ||
3432 | === modified file 'charmhelpers/contrib/openstack/templates/openstack_https_frontend' | |||
3433 | --- charmhelpers/contrib/openstack/templates/openstack_https_frontend 2015-09-28 20:09:02 +0000 | |||
3434 | +++ charmhelpers/contrib/openstack/templates/openstack_https_frontend 2017-03-04 02:21:16 +0000 | |||
3435 | @@ -6,11 +6,16 @@ | |||
3436 | 6 | <VirtualHost {{ address }}:{{ ext }}> | 6 | <VirtualHost {{ address }}:{{ ext }}> |
3437 | 7 | ServerName {{ endpoint }} | 7 | ServerName {{ endpoint }} |
3438 | 8 | SSLEngine on | 8 | SSLEngine on |
3439 | 9 | SSLProtocol +TLSv1 +TLSv1.1 +TLSv1.2 | ||
3440 | 10 | SSLCipherSuite HIGH:!RC4:!MD5:!aNULL:!eNULL:!EXP:!LOW:!MEDIUM | ||
3441 | 9 | SSLCertificateFile /etc/apache2/ssl/{{ namespace }}/cert_{{ endpoint }} | 11 | SSLCertificateFile /etc/apache2/ssl/{{ namespace }}/cert_{{ endpoint }} |
3442 | 12 | # See LP 1484489 - this is to support <= 2.4.7 and >= 2.4.8 | ||
3443 | 13 | SSLCertificateChainFile /etc/apache2/ssl/{{ namespace }}/cert_{{ endpoint }} | ||
3444 | 10 | SSLCertificateKeyFile /etc/apache2/ssl/{{ namespace }}/key_{{ endpoint }} | 14 | SSLCertificateKeyFile /etc/apache2/ssl/{{ namespace }}/key_{{ endpoint }} |
3445 | 11 | ProxyPass / http://localhost:{{ int }}/ | 15 | ProxyPass / http://localhost:{{ int }}/ |
3446 | 12 | ProxyPassReverse / http://localhost:{{ int }}/ | 16 | ProxyPassReverse / http://localhost:{{ int }}/ |
3447 | 13 | ProxyPreserveHost on | 17 | ProxyPreserveHost on |
3448 | 18 | RequestHeader set X-Forwarded-Proto "https" | ||
3449 | 14 | </VirtualHost> | 19 | </VirtualHost> |
3450 | 15 | {% endfor -%} | 20 | {% endfor -%} |
3451 | 16 | <Proxy *> | 21 | <Proxy *> |
3452 | 17 | 22 | ||
3453 | === modified file 'charmhelpers/contrib/openstack/templates/openstack_https_frontend.conf' | |||
3454 | --- charmhelpers/contrib/openstack/templates/openstack_https_frontend.conf 2015-09-28 20:09:02 +0000 | |||
3455 | +++ charmhelpers/contrib/openstack/templates/openstack_https_frontend.conf 2017-03-04 02:21:16 +0000 | |||
3456 | @@ -6,11 +6,16 @@ | |||
3457 | 6 | <VirtualHost {{ address }}:{{ ext }}> | 6 | <VirtualHost {{ address }}:{{ ext }}> |
3458 | 7 | ServerName {{ endpoint }} | 7 | ServerName {{ endpoint }} |
3459 | 8 | SSLEngine on | 8 | SSLEngine on |
3460 | 9 | SSLProtocol +TLSv1 +TLSv1.1 +TLSv1.2 | ||
3461 | 10 | SSLCipherSuite HIGH:!RC4:!MD5:!aNULL:!eNULL:!EXP:!LOW:!MEDIUM | ||
3462 | 9 | SSLCertificateFile /etc/apache2/ssl/{{ namespace }}/cert_{{ endpoint }} | 11 | SSLCertificateFile /etc/apache2/ssl/{{ namespace }}/cert_{{ endpoint }} |
3463 | 12 | # See LP 1484489 - this is to support <= 2.4.7 and >= 2.4.8 | ||
3464 | 13 | SSLCertificateChainFile /etc/apache2/ssl/{{ namespace }}/cert_{{ endpoint }} | ||
3465 | 10 | SSLCertificateKeyFile /etc/apache2/ssl/{{ namespace }}/key_{{ endpoint }} | 14 | SSLCertificateKeyFile /etc/apache2/ssl/{{ namespace }}/key_{{ endpoint }} |
3466 | 11 | ProxyPass / http://localhost:{{ int }}/ | 15 | ProxyPass / http://localhost:{{ int }}/ |
3467 | 12 | ProxyPassReverse / http://localhost:{{ int }}/ | 16 | ProxyPassReverse / http://localhost:{{ int }}/ |
3468 | 13 | ProxyPreserveHost on | 17 | ProxyPreserveHost on |
3469 | 18 | RequestHeader set X-Forwarded-Proto "https" | ||
3470 | 14 | </VirtualHost> | 19 | </VirtualHost> |
3471 | 15 | {% endfor -%} | 20 | {% endfor -%} |
3472 | 16 | <Proxy *> | 21 | <Proxy *> |
3473 | 17 | 22 | ||
3474 | === modified file 'charmhelpers/contrib/openstack/templates/section-keystone-authtoken' | |||
3475 | --- charmhelpers/contrib/openstack/templates/section-keystone-authtoken 2015-09-28 20:09:02 +0000 | |||
3476 | +++ charmhelpers/contrib/openstack/templates/section-keystone-authtoken 2017-03-04 02:21:16 +0000 | |||
3477 | @@ -1,9 +1,12 @@ | |||
3478 | 1 | {% if auth_host -%} | 1 | {% if auth_host -%} |
3479 | 2 | [keystone_authtoken] | 2 | [keystone_authtoken] |
3485 | 3 | identity_uri = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}/{{ auth_admin_prefix }} | 3 | auth_uri = {{ service_protocol }}://{{ service_host }}:{{ service_port }} |
3486 | 4 | auth_uri = {{ service_protocol }}://{{ service_host }}:{{ service_port }}/{{ service_admin_prefix }} | 4 | auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }} |
3487 | 5 | admin_tenant_name = {{ admin_tenant_name }} | 5 | auth_plugin = password |
3488 | 6 | admin_user = {{ admin_user }} | 6 | project_domain_id = default |
3489 | 7 | admin_password = {{ admin_password }} | 7 | user_domain_id = default |
3490 | 8 | project_name = {{ admin_tenant_name }} | ||
3491 | 9 | username = {{ admin_user }} | ||
3492 | 10 | password = {{ admin_password }} | ||
3493 | 8 | signing_dir = {{ signing_dir }} | 11 | signing_dir = {{ signing_dir }} |
3494 | 9 | {% endif -%} | 12 | {% endif -%} |
3495 | 10 | 13 | ||
3496 | === added file 'charmhelpers/contrib/openstack/templates/section-keystone-authtoken-legacy' | |||
3497 | --- charmhelpers/contrib/openstack/templates/section-keystone-authtoken-legacy 1970-01-01 00:00:00 +0000 | |||
3498 | +++ charmhelpers/contrib/openstack/templates/section-keystone-authtoken-legacy 2017-03-04 02:21:16 +0000 | |||
3499 | @@ -0,0 +1,10 @@ | |||
3500 | 1 | {% if auth_host -%} | ||
3501 | 2 | [keystone_authtoken] | ||
3502 | 3 | # Juno specific config (Bug #1557223) | ||
3503 | 4 | auth_uri = {{ service_protocol }}://{{ service_host }}:{{ service_port }}/{{ service_admin_prefix }} | ||
3504 | 5 | identity_uri = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }} | ||
3505 | 6 | admin_tenant_name = {{ admin_tenant_name }} | ||
3506 | 7 | admin_user = {{ admin_user }} | ||
3507 | 8 | admin_password = {{ admin_password }} | ||
3508 | 9 | signing_dir = {{ signing_dir }} | ||
3509 | 10 | {% endif -%} | ||
3510 | 0 | 11 | ||
3511 | === added file 'charmhelpers/contrib/openstack/templates/section-keystone-authtoken-mitaka' | |||
3512 | --- charmhelpers/contrib/openstack/templates/section-keystone-authtoken-mitaka 1970-01-01 00:00:00 +0000 | |||
3513 | +++ charmhelpers/contrib/openstack/templates/section-keystone-authtoken-mitaka 2017-03-04 02:21:16 +0000 | |||
3514 | @@ -0,0 +1,20 @@ | |||
3515 | 1 | {% if auth_host -%} | ||
3516 | 2 | [keystone_authtoken] | ||
3517 | 3 | auth_uri = {{ service_protocol }}://{{ service_host }}:{{ service_port }} | ||
3518 | 4 | auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }} | ||
3519 | 5 | auth_type = password | ||
3520 | 6 | {% if api_version == "3" -%} | ||
3521 | 7 | project_domain_name = {{ admin_domain_name }} | ||
3522 | 8 | user_domain_name = {{ admin_domain_name }} | ||
3523 | 9 | {% else -%} | ||
3524 | 10 | project_domain_name = default | ||
3525 | 11 | user_domain_name = default | ||
3526 | 12 | {% endif -%} | ||
3527 | 13 | project_name = {{ admin_tenant_name }} | ||
3528 | 14 | username = {{ admin_user }} | ||
3529 | 15 | password = {{ admin_password }} | ||
3530 | 16 | signing_dir = {{ signing_dir }} | ||
3531 | 17 | {% if use_memcache == true %} | ||
3532 | 18 | memcached_servers = {{ memcache_url }} | ||
3533 | 19 | {% endif -%} | ||
3534 | 20 | {% endif -%} | ||
3535 | 0 | 21 | ||
3536 | === added file 'charmhelpers/contrib/openstack/templates/wsgi-openstack-api.conf' | |||
3537 | --- charmhelpers/contrib/openstack/templates/wsgi-openstack-api.conf 1970-01-01 00:00:00 +0000 | |||
3538 | +++ charmhelpers/contrib/openstack/templates/wsgi-openstack-api.conf 2017-03-04 02:21:16 +0000 | |||
3539 | @@ -0,0 +1,100 @@ | |||
3540 | 1 | # Configuration file maintained by Juju. Local changes may be overwritten. | ||
3541 | 2 | |||
3542 | 3 | {% if port -%} | ||
3543 | 4 | Listen {{ port }} | ||
3544 | 5 | {% endif -%} | ||
3545 | 6 | |||
3546 | 7 | {% if admin_port -%} | ||
3547 | 8 | Listen {{ admin_port }} | ||
3548 | 9 | {% endif -%} | ||
3549 | 10 | |||
3550 | 11 | {% if public_port -%} | ||
3551 | 12 | Listen {{ public_port }} | ||
3552 | 13 | {% endif -%} | ||
3553 | 14 | |||
3554 | 15 | {% if port -%} | ||
3555 | 16 | <VirtualHost *:{{ port }}> | ||
3556 | 17 | WSGIDaemonProcess {{ service_name }} processes={{ processes }} threads={{ threads }} user={{ service_name }} group={{ service_name }} \ | ||
3557 | 18 | {% if python_path -%} | ||
3558 | 19 | python-path={{ python_path }} \ | ||
3559 | 20 | {% endif -%} | ||
3560 | 21 | display-name=%{GROUP} | ||
3561 | 22 | WSGIProcessGroup {{ service_name }} | ||
3562 | 23 | WSGIScriptAlias / {{ script }} | ||
3563 | 24 | WSGIApplicationGroup %{GLOBAL} | ||
3564 | 25 | WSGIPassAuthorization On | ||
3565 | 26 | <IfVersion >= 2.4> | ||
3566 | 27 | ErrorLogFormat "%{cu}t %M" | ||
3567 | 28 | </IfVersion> | ||
3568 | 29 | ErrorLog /var/log/apache2/{{ service_name }}_error.log | ||
3569 | 30 | CustomLog /var/log/apache2/{{ service_name }}_access.log combined | ||
3570 | 31 | |||
3571 | 32 | <Directory {{ usr_bin }}> | ||
3572 | 33 | <IfVersion >= 2.4> | ||
3573 | 34 | Require all granted | ||
3574 | 35 | </IfVersion> | ||
3575 | 36 | <IfVersion < 2.4> | ||
3576 | 37 | Order allow,deny | ||
3577 | 38 | Allow from all | ||
3578 | 39 | </IfVersion> | ||
3579 | 40 | </Directory> | ||
3580 | 41 | </VirtualHost> | ||
3581 | 42 | {% endif -%} | ||
3582 | 43 | |||
3583 | 44 | {% if admin_port -%} | ||
3584 | 45 | <VirtualHost *:{{ admin_port }}> | ||
3585 | 46 | WSGIDaemonProcess {{ service_name }}-admin processes={{ admin_processes }} threads={{ threads }} user={{ service_name }} group={{ service_name }} \ | ||
3586 | 47 | {% if python_path -%} | ||
3587 | 48 | python-path={{ python_path }} \ | ||
3588 | 49 | {% endif -%} | ||
3589 | 50 | display-name=%{GROUP} | ||
3590 | 51 | WSGIProcessGroup {{ service_name }}-admin | ||
3591 | 52 | WSGIScriptAlias / {{ admin_script }} | ||
3592 | 53 | WSGIApplicationGroup %{GLOBAL} | ||
3593 | 54 | WSGIPassAuthorization On | ||
3594 | 55 | <IfVersion >= 2.4> | ||
3595 | 56 | ErrorLogFormat "%{cu}t %M" | ||
3596 | 57 | </IfVersion> | ||
3597 | 58 | ErrorLog /var/log/apache2/{{ service_name }}_error.log | ||
3598 | 59 | CustomLog /var/log/apache2/{{ service_name }}_access.log combined | ||
3599 | 60 | |||
3600 | 61 | <Directory {{ usr_bin }}> | ||
3601 | 62 | <IfVersion >= 2.4> | ||
3602 | 63 | Require all granted | ||
3603 | 64 | </IfVersion> | ||
3604 | 65 | <IfVersion < 2.4> | ||
3605 | 66 | Order allow,deny | ||
3606 | 67 | Allow from all | ||
3607 | 68 | </IfVersion> | ||
3608 | 69 | </Directory> | ||
3609 | 70 | </VirtualHost> | ||
3610 | 71 | {% endif -%} | ||
3611 | 72 | |||
3612 | 73 | {% if public_port -%} | ||
3613 | 74 | <VirtualHost *:{{ public_port }}> | ||
3614 | 75 | WSGIDaemonProcess {{ service_name }}-public processes={{ public_processes }} threads={{ threads }} user={{ service_name }} group={{ service_name }} \ | ||
3615 | 76 | {% if python_path -%} | ||
3616 | 77 | python-path={{ python_path }} \ | ||
3617 | 78 | {% endif -%} | ||
3618 | 79 | display-name=%{GROUP} | ||
3619 | 80 | WSGIProcessGroup {{ service_name }}-public | ||
3620 | 81 | WSGIScriptAlias / {{ public_script }} | ||
3621 | 82 | WSGIApplicationGroup %{GLOBAL} | ||
3622 | 83 | WSGIPassAuthorization On | ||
3623 | 84 | <IfVersion >= 2.4> | ||
3624 | 85 | ErrorLogFormat "%{cu}t %M" | ||
3625 | 86 | </IfVersion> | ||
3626 | 87 | ErrorLog /var/log/apache2/{{ service_name }}_error.log | ||
3627 | 88 | CustomLog /var/log/apache2/{{ service_name }}_access.log combined | ||
3628 | 89 | |||
3629 | 90 | <Directory {{ usr_bin }}> | ||
3630 | 91 | <IfVersion >= 2.4> | ||
3631 | 92 | Require all granted | ||
3632 | 93 | </IfVersion> | ||
3633 | 94 | <IfVersion < 2.4> | ||
3634 | 95 | Order allow,deny | ||
3635 | 96 | Allow from all | ||
3636 | 97 | </IfVersion> | ||
3637 | 98 | </Directory> | ||
3638 | 99 | </VirtualHost> | ||
3639 | 100 | {% endif -%} | ||
3640 | 0 | 101 | ||
3641 | === modified file 'charmhelpers/contrib/openstack/templating.py' | |||
3642 | --- charmhelpers/contrib/openstack/templating.py 2015-09-28 20:09:02 +0000 | |||
3643 | +++ charmhelpers/contrib/openstack/templating.py 2017-03-04 02:21:16 +0000 | |||
3644 | @@ -1,18 +1,16 @@ | |||
3645 | 1 | # Copyright 2014-2015 Canonical Limited. | 1 | # Copyright 2014-2015 Canonical Limited. |
3646 | 2 | # | 2 | # |
3660 | 3 | # This file is part of charm-helpers. | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
3661 | 4 | # | 4 | # you may not use this file except in compliance with the License. |
3662 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | 5 | # You may obtain a copy of the License at |
3663 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | 6 | # |
3664 | 7 | # published by the Free Software Foundation. | 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
3665 | 8 | # | 8 | # |
3666 | 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 |
3667 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
3668 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
3669 | 12 | # GNU Lesser General Public License for more details. | 12 | # See the License for the specific language governing permissions and |
3670 | 13 | # | 13 | # limitations under the License. |
3658 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
3659 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
3671 | 16 | 14 | ||
3672 | 17 | import os | 15 | import os |
3673 | 18 | 16 | ||
3674 | @@ -30,7 +28,10 @@ | |||
3675 | 30 | from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions | 28 | from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions |
3676 | 31 | except ImportError: | 29 | except ImportError: |
3677 | 32 | apt_update(fatal=True) | 30 | apt_update(fatal=True) |
3679 | 33 | apt_install('python-jinja2', fatal=True) | 31 | if six.PY2: |
3680 | 32 | apt_install('python-jinja2', fatal=True) | ||
3681 | 33 | else: | ||
3682 | 34 | apt_install('python3-jinja2', fatal=True) | ||
3683 | 34 | from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions | 35 | from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions |
3684 | 35 | 36 | ||
3685 | 36 | 37 | ||
3686 | @@ -209,7 +210,10 @@ | |||
3687 | 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. |
3688 | 210 | # jinja2 shouldn't get touched until the module is reloaded on next | 211 | # jinja2 shouldn't get touched until the module is reloaded on next |
3689 | 211 | # hook execution, with proper jinja2 bits successfully imported. | 212 | # hook execution, with proper jinja2 bits successfully imported. |
3691 | 212 | apt_install('python-jinja2') | 213 | if six.PY2: |
3692 | 214 | apt_install('python-jinja2') | ||
3693 | 215 | else: | ||
3694 | 216 | apt_install('python3-jinja2') | ||
3695 | 213 | 217 | ||
3696 | 214 | def register(self, config_file, contexts): | 218 | def register(self, config_file, contexts): |
3697 | 215 | """ | 219 | """ |
3698 | 216 | 220 | ||
3699 | === modified file 'charmhelpers/contrib/openstack/utils.py' | |||
3700 | --- charmhelpers/contrib/openstack/utils.py 2015-09-28 20:09:02 +0000 | |||
3701 | +++ charmhelpers/contrib/openstack/utils.py 2017-03-04 02:21:16 +0000 | |||
3702 | @@ -1,18 +1,16 @@ | |||
3703 | 1 | # Copyright 2014-2015 Canonical Limited. | 1 | # Copyright 2014-2015 Canonical Limited. |
3704 | 2 | # | 2 | # |
3718 | 3 | # This file is part of charm-helpers. | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
3719 | 4 | # | 4 | # you may not use this file except in compliance with the License. |
3720 | 5 | # charm-helpers is free software: you can redistribute it and/or modify | 5 | # You may obtain a copy of the License at |
3721 | 6 | # it under the terms of the GNU Lesser General Public License version 3 as | 6 | # |
3722 | 7 | # published by the Free Software Foundation. | 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
3723 | 8 | # | 8 | # |
3724 | 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 |
3725 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
3726 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
3727 | 12 | # GNU Lesser General Public License for more details. | 12 | # See the License for the specific language governing permissions and |
3728 | 13 | # | 13 | # limitations under the License. |
3716 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
3717 | 15 | # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. | ||
3729 | 16 | 14 | ||
3730 | 17 | # Common python helper functions used for OpenStack charms. | 15 | # Common python helper functions used for OpenStack charms. |
3731 | 18 | from collections import OrderedDict | 16 | from collections import OrderedDict |
3732 | @@ -23,9 +21,14 @@ | |||
3733 | 23 | import os | 21 | import os |
3734 | 24 | import sys | 22 | import sys |
3735 | 25 | import re | 23 | import re |
3736 | 24 | import itertools | ||
3737 | 25 | import functools | ||
3738 | 26 | import shutil | ||
3739 | 26 | 27 | ||
3740 | 27 | import six | 28 | import six |
3741 | 29 | import tempfile | ||
3742 | 28 | import traceback | 30 | import traceback |
3743 | 31 | import uuid | ||
3744 | 29 | import yaml | 32 | import yaml |
3745 | 30 | 33 | ||
3746 | 31 | from charmhelpers.contrib.network import ip | 34 | from charmhelpers.contrib.network import ip |
3747 | @@ -40,11 +43,16 @@ | |||
3748 | 40 | config, | 43 | config, |
3749 | 41 | log as juju_log, | 44 | log as juju_log, |
3750 | 42 | charm_dir, | 45 | charm_dir, |
3751 | 46 | DEBUG, | ||
3752 | 43 | INFO, | 47 | INFO, |
3753 | 48 | ERROR, | ||
3754 | 49 | related_units, | ||
3755 | 44 | relation_ids, | 50 | relation_ids, |
3756 | 45 | relation_set, | 51 | relation_set, |
3757 | 52 | service_name, | ||
3758 | 46 | status_set, | 53 | status_set, |
3760 | 47 | hook_name | 54 | hook_name, |
3761 | 55 | application_version_set, | ||
3762 | 48 | ) | 56 | ) |
3763 | 49 | 57 | ||
3764 | 50 | from charmhelpers.contrib.storage.linux.lvm import ( | 58 | from charmhelpers.contrib.storage.linux.lvm import ( |
3765 | @@ -56,6 +64,7 @@ | |||
3766 | 56 | from charmhelpers.contrib.network.ip import ( | 64 | from charmhelpers.contrib.network.ip import ( |
3767 | 57 | get_ipv6_addr, | 65 | get_ipv6_addr, |
3768 | 58 | is_ipv6, | 66 | is_ipv6, |
3769 | 67 | port_has_listener, | ||
3770 | 59 | ) | 68 | ) |
3771 | 60 | 69 | ||
3772 | 61 | from charmhelpers.contrib.python.packages import ( | 70 | from charmhelpers.contrib.python.packages import ( |
3773 | @@ -63,10 +72,24 @@ | |||
3774 | 63 | pip_install, | 72 | pip_install, |
3775 | 64 | ) | 73 | ) |
3776 | 65 | 74 | ||
3779 | 66 | from charmhelpers.core.host import lsb_release, mounts, umount | 75 | from charmhelpers.core.host import ( |
3780 | 67 | from charmhelpers.fetch import apt_install, apt_cache, install_remote | 76 | lsb_release, |
3781 | 77 | mounts, | ||
3782 | 78 | umount, | ||
3783 | 79 | service_running, | ||
3784 | 80 | service_pause, | ||
3785 | 81 | service_resume, | ||
3786 | 82 | restart_on_change_helper, | ||
3787 | 83 | ) | ||
3788 | 84 | from charmhelpers.fetch import ( | ||
3789 | 85 | apt_install, | ||
3790 | 86 | apt_cache, | ||
3791 | 87 | install_remote, | ||
3792 | 88 | get_upstream_version | ||
3793 | 89 | ) | ||
3794 | 68 | from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk | 90 | from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk |
3795 | 69 | from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device | 91 | from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device |
3796 | 92 | from charmhelpers.contrib.openstack.exceptions import OSContextError | ||
3797 | 70 | 93 | ||
3798 | 71 | CLOUD_ARCHIVE_URL = "http://ubuntu-cloud.archive.canonical.com/ubuntu" | 94 | CLOUD_ARCHIVE_URL = "http://ubuntu-cloud.archive.canonical.com/ubuntu" |
3799 | 72 | CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA' | 95 | CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA' |
3800 | @@ -84,6 +107,9 @@ | |||
3801 | 84 | ('utopic', 'juno'), | 107 | ('utopic', 'juno'), |
3802 | 85 | ('vivid', 'kilo'), | 108 | ('vivid', 'kilo'), |
3803 | 86 | ('wily', 'liberty'), | 109 | ('wily', 'liberty'), |
3804 | 110 | ('xenial', 'mitaka'), | ||
3805 | 111 | ('yakkety', 'newton'), | ||
3806 | 112 | ('zesty', 'ocata'), | ||
3807 | 87 | ]) | 113 | ]) |
3808 | 88 | 114 | ||
3809 | 89 | 115 | ||
3810 | @@ -97,63 +123,118 @@ | |||
3811 | 97 | ('2014.2', 'juno'), | 123 | ('2014.2', 'juno'), |
3812 | 98 | ('2015.1', 'kilo'), | 124 | ('2015.1', 'kilo'), |
3813 | 99 | ('2015.2', 'liberty'), | 125 | ('2015.2', 'liberty'), |
3814 | 126 | ('2016.1', 'mitaka'), | ||
3815 | 127 | ('2016.2', 'newton'), | ||
3816 | 128 | ('2017.1', 'ocata'), | ||
3817 | 100 | ]) | 129 | ]) |
3818 | 101 | 130 | ||
3820 | 102 | # The ugly duckling | 131 | # The ugly duckling - must list releases oldest to newest |
3821 | 103 | SWIFT_CODENAMES = OrderedDict([ | 132 | SWIFT_CODENAMES = OrderedDict([ |
3842 | 104 | ('1.4.3', 'diablo'), | 133 | ('diablo', |
3843 | 105 | ('1.4.8', 'essex'), | 134 | ['1.4.3']), |
3844 | 106 | ('1.7.4', 'folsom'), | 135 | ('essex', |
3845 | 107 | ('1.8.0', 'grizzly'), | 136 | ['1.4.8']), |
3846 | 108 | ('1.7.7', 'grizzly'), | 137 | ('folsom', |
3847 | 109 | ('1.7.6', 'grizzly'), | 138 | ['1.7.4']), |
3848 | 110 | ('1.10.0', 'havana'), | 139 | ('grizzly', |
3849 | 111 | ('1.9.1', 'havana'), | 140 | ['1.7.6', '1.7.7', '1.8.0']), |
3850 | 112 | ('1.9.0', 'havana'), | 141 | ('havana', |
3851 | 113 | ('1.13.1', 'icehouse'), | 142 | ['1.9.0', '1.9.1', '1.10.0']), |
3852 | 114 | ('1.13.0', 'icehouse'), | 143 | ('icehouse', |
3853 | 115 | ('1.12.0', 'icehouse'), | 144 | ['1.11.0', '1.12.0', '1.13.0', '1.13.1']), |
3854 | 116 | ('1.11.0', 'icehouse'), | 145 | ('juno', |
3855 | 117 | ('2.0.0', 'juno'), | 146 | ['2.0.0', '2.1.0', '2.2.0']), |
3856 | 118 | ('2.1.0', 'juno'), | 147 | ('kilo', |
3857 | 119 | ('2.2.0', 'juno'), | 148 | ['2.2.1', '2.2.2']), |
3858 | 120 | ('2.2.1', 'kilo'), | 149 | ('liberty', |
3859 | 121 | ('2.2.2', 'kilo'), | 150 | ['2.3.0', '2.4.0', '2.5.0']), |
3860 | 122 | ('2.3.0', 'liberty'), | 151 | ('mitaka', |
3861 | 123 | ('2.4.0', 'liberty'), | 152 | ['2.5.0', '2.6.0', '2.7.0']), |
3862 | 153 | ('newton', | ||
3863 | 154 | ['2.8.0', '2.9.0', '2.10.0']), | ||
3864 | 155 | ('ocata', | ||
3865 | 156 | ['2.11.0', '2.12.0', '2.13.0']), | ||
3866 | 124 | ]) | 157 | ]) |
3867 | 125 | 158 | ||
3868 | 126 | # >= Liberty version->codename mapping | 159 | # >= Liberty version->codename mapping |
3869 | 127 | PACKAGE_CODENAMES = { | 160 | PACKAGE_CODENAMES = { |
3870 | 128 | 'nova-common': OrderedDict([ | 161 | 'nova-common': OrderedDict([ |
3872 | 129 | ('12.0.0', 'liberty'), | 162 | ('12', 'liberty'), |
3873 | 163 | ('13', 'mitaka'), | ||
3874 | 164 | ('14', 'newton'), | ||
3875 | 165 | ('15', 'ocata'), | ||
3876 | 130 | ]), | 166 | ]), |
3877 | 131 | 'neutron-common': OrderedDict([ | 167 | 'neutron-common': OrderedDict([ |
3879 | 132 | ('7.0.0', 'liberty'), | 168 | ('7', 'liberty'), |
3880 | 169 | ('8', 'mitaka'), | ||
3881 | 170 | ('9', 'newton'), | ||
3882 | 171 | ('10', 'ocata'), | ||
3883 | 133 | ]), | 172 | ]), |
3884 | 134 | 'cinder-common': OrderedDict([ | 173 | 'cinder-common': OrderedDict([ |
3886 | 135 | ('7.0.0', 'liberty'), | 174 | ('7', 'liberty'), |
3887 | 175 | ('8', 'mitaka'), | ||
3888 | 176 | ('9', 'newton'), | ||
3889 | 177 | ('10', 'ocata'), | ||
3890 | 136 | ]), | 178 | ]), |
3891 | 137 | 'keystone': OrderedDict([ | 179 | 'keystone': OrderedDict([ |
3893 | 138 | ('8.0.0', 'liberty'), | 180 | ('8', 'liberty'), |
3894 | 181 | ('9', 'mitaka'), | ||
3895 | 182 | ('10', 'newton'), | ||
3896 | 183 | ('11', 'ocata'), | ||
3897 | 139 | ]), | 184 | ]), |
3898 | 140 | 'horizon-common': OrderedDict([ | 185 | 'horizon-common': OrderedDict([ |
3900 | 141 | ('8.0.0', 'liberty'), | 186 | ('8', 'liberty'), |
3901 | 187 | ('9', 'mitaka'), | ||
3902 | 188 | ('10', 'newton'), | ||
3903 | 189 | ('11', 'ocata'), | ||
3904 | 142 | ]), | 190 | ]), |
3905 | 143 | 'ceilometer-common': OrderedDict([ | 191 | 'ceilometer-common': OrderedDict([ |
3907 | 144 | ('5.0.0', 'liberty'), | 192 | ('5', 'liberty'), |
3908 | 193 | ('6', 'mitaka'), | ||
3909 | 194 | ('7', 'newton'), | ||
3910 | 195 | ('8', 'ocata'), | ||
3911 | 145 | ]), | 196 | ]), |
3912 | 146 | 'heat-common': OrderedDict([ | 197 | 'heat-common': OrderedDict([ |
3914 | 147 | ('5.0.0', 'liberty'), | 198 | ('5', 'liberty'), |
3915 | 199 | ('6', 'mitaka'), | ||
3916 | 200 | ('7', 'newton'), | ||
3917 | 201 | ('8', 'ocata'), | ||
3918 | 148 | ]), | 202 | ]), |
3919 | 149 | 'glance-common': OrderedDict([ | 203 | 'glance-common': OrderedDict([ |
3921 | 150 | ('11.0.0', 'liberty'), | 204 | ('11', 'liberty'), |
3922 | 205 | ('12', 'mitaka'), | ||
3923 | 206 | ('13', 'newton'), | ||
3924 | 207 | ('14', 'ocata'), | ||
3925 | 151 | ]), | 208 | ]), |
3926 | 152 | 'openstack-dashboard': OrderedDict([ | 209 | 'openstack-dashboard': OrderedDict([ |
3928 | 153 | ('8.0.0', 'liberty'), | 210 | ('8', 'liberty'), |
3929 | 211 | ('9', 'mitaka'), | ||
3930 | 212 | ('10', 'newton'), | ||
3931 | 213 | ('11', 'ocata'), | ||
3932 | 154 | ]), | 214 | ]), |
3933 | 155 | } | 215 | } |
3934 | 156 | 216 | ||
3935 | 217 | GIT_DEFAULT_REPOS = { | ||
3936 | 218 | 'requirements': 'git://github.com/openstack/requirements', | ||
3937 | 219 | 'cinder': 'git://github.com/openstack/cinder', | ||
3938 | 220 | 'glance': 'git://github.com/openstack/glance', | ||
3939 | 221 | 'horizon': 'git://github.com/openstack/horizon', | ||
3940 | 222 | 'keystone': 'git://github.com/openstack/keystone', | ||
3941 | 223 | 'networking-hyperv': 'git://github.com/openstack/networking-hyperv', | ||
3942 | 224 | 'neutron': 'git://github.com/openstack/neutron', | ||
3943 | 225 | 'neutron-fwaas': 'git://github.com/openstack/neutron-fwaas', | ||
3944 | 226 | 'neutron-lbaas': 'git://github.com/openstack/neutron-lbaas', | ||
3945 | 227 | 'neutron-vpnaas': 'git://github.com/openstack/neutron-vpnaas', | ||
3946 | 228 | 'nova': 'git://github.com/openstack/nova', | ||
3947 | 229 | } | ||
3948 | 230 | |||
3949 | 231 | GIT_DEFAULT_BRANCHES = { | ||
3950 | 232 | 'liberty': 'stable/liberty', | ||
3951 | 233 | 'mitaka': 'stable/mitaka', | ||
3952 | 234 | 'newton': 'stable/newton', | ||
3953 | 235 | 'master': 'master', | ||
3954 | 236 | } | ||
3955 | 237 | |||
3956 | 157 | DEFAULT_LOOPBACK_SIZE = '5G' | 238 | DEFAULT_LOOPBACK_SIZE = '5G' |
3957 | 158 | 239 | ||
3958 | 159 | 240 | ||
3959 | @@ -213,6 +294,44 @@ | |||
3960 | 213 | error_out(e) | 294 | error_out(e) |
3961 | 214 | 295 | ||
3962 | 215 | 296 | ||
3963 | 297 | def get_os_version_codename_swift(codename): | ||
3964 | 298 | '''Determine OpenStack version number of swift from codename.''' | ||
3965 | 299 | for k, v in six.iteritems(SWIFT_CODENAMES): | ||
3966 | 300 | if k == codename: | ||
3967 | 301 | return v[-1] | ||
3968 | 302 | e = 'Could not derive swift version for '\ | ||
3969 | 303 | 'codename: %s' % codename | ||
3970 | 304 | error_out(e) | ||
3971 | 305 | |||
3972 | 306 | |||
3973 | 307 | def get_swift_codename(version): | ||
3974 | 308 | '''Determine OpenStack codename that corresponds to swift version.''' | ||
3975 | 309 | codenames = [k for k, v in six.iteritems(SWIFT_CODENAMES) if version in v] | ||
3976 | 310 | |||
3977 | 311 | if len(codenames) > 1: | ||
3978 | 312 | # If more than one release codename contains this version we determine | ||
3979 | 313 | # the actual codename based on the highest available install source. | ||
3980 | 314 | for codename in reversed(codenames): | ||
3981 | 315 | releases = UBUNTU_OPENSTACK_RELEASE | ||
3982 | 316 | release = [k for k, v in six.iteritems(releases) if codename in v] | ||
3983 | 317 | ret = subprocess.check_output(['apt-cache', 'policy', 'swift']) | ||
3984 | 318 | if codename in ret or release[0] in ret: | ||
3985 | 319 | return codename | ||
3986 | 320 | elif len(codenames) == 1: | ||
3987 | 321 | return codenames[0] | ||
3988 | 322 | |||
3989 | 323 | # NOTE: fallback - attempt to match with just major.minor version | ||
3990 | 324 | match = re.match('^(\d+)\.(\d+)', version) | ||
3991 | 325 | if match: | ||
3992 | 326 | major_minor_version = match.group(0) | ||
3993 | 327 | for codename, versions in six.iteritems(SWIFT_CODENAMES): | ||
3994 | 328 | for release_version in versions: | ||
3995 | 329 | if release_version.startswith(major_minor_version): | ||
3996 | 330 | return codename | ||
3997 | 331 | |||
3998 | 332 | return None | ||
3999 | 333 | |||
4000 | 334 | |||
4001 | 216 | def get_os_codename_package(package, fatal=True): | 335 | def get_os_codename_package(package, fatal=True): |
4002 | 217 | '''Derive OpenStack release codename from an installed package.''' | 336 | '''Derive OpenStack release codename from an installed package.''' |
4003 | 218 | import apt_pkg as apt | 337 | import apt_pkg as apt |
4004 | @@ -237,25 +356,30 @@ | |||
4005 | 237 | error_out(e) | 356 | error_out(e) |
4006 | 238 | 357 | ||
4007 | 239 | vers = apt.upstream_version(pkg.current_ver.ver_str) | 358 | vers = apt.upstream_version(pkg.current_ver.ver_str) |
4009 | 240 | match = re.match('^(\d+)\.(\d+)\.(\d+)', vers) | 359 | if 'swift' in pkg.name: |
4010 | 360 | # Fully x.y.z match for swift versions | ||
4011 | 361 | match = re.match('^(\d+)\.(\d+)\.(\d+)', vers) | ||
4012 | 362 | else: | ||
4013 | 363 | # x.y match only for 20XX.X | ||
4014 | 364 | # and ignore patch level for other packages | ||
4015 | 365 | match = re.match('^(\d+)\.(\d+)', vers) | ||
4016 | 366 | |||
4017 | 241 | if match: | 367 | if match: |
4018 | 242 | vers = match.group(0) | 368 | vers = match.group(0) |
4019 | 243 | 369 | ||
4020 | 370 | # Generate a major version number for newer semantic | ||
4021 | 371 | # versions of openstack projects | ||
4022 | 372 | major_vers = vers.split('.')[0] | ||
4023 | 244 | # >= Liberty independent project versions | 373 | # >= Liberty independent project versions |
4024 | 245 | if (package in PACKAGE_CODENAMES and | 374 | if (package in PACKAGE_CODENAMES and |
4027 | 246 | vers in PACKAGE_CODENAMES[package]): | 375 | major_vers in PACKAGE_CODENAMES[package]): |
4028 | 247 | return PACKAGE_CODENAMES[package][vers] | 376 | return PACKAGE_CODENAMES[package][major_vers] |
4029 | 248 | else: | 377 | else: |
4030 | 249 | # < Liberty co-ordinated project versions | 378 | # < Liberty co-ordinated project versions |
4031 | 250 | try: | 379 | try: |
4032 | 251 | if 'swift' in pkg.name: | 380 | if 'swift' in pkg.name: |
4038 | 252 | swift_vers = vers[:5] | 381 | return get_swift_codename(vers) |
4034 | 253 | if swift_vers not in SWIFT_CODENAMES: | ||
4035 | 254 | # Deal with 1.10.0 upward | ||
4036 | 255 | swift_vers = vers[:6] | ||
4037 | 256 | return SWIFT_CODENAMES[swift_vers] | ||
4039 | 257 | else: | 382 | else: |
4040 | 258 | vers = vers[:6] | ||
4041 | 259 | return OPENSTACK_CODENAMES[vers] | 383 | return OPENSTACK_CODENAMES[vers] |
4042 | 260 | except KeyError: | 384 | except KeyError: |
4043 | 261 | if not fatal: | 385 | if not fatal: |
4044 | @@ -273,12 +397,14 @@ | |||
4045 | 273 | 397 | ||
4046 | 274 | if 'swift' in pkg: | 398 | if 'swift' in pkg: |
4047 | 275 | vers_map = SWIFT_CODENAMES | 399 | vers_map = SWIFT_CODENAMES |
4048 | 400 | for cname, version in six.iteritems(vers_map): | ||
4049 | 401 | if cname == codename: | ||
4050 | 402 | return version[-1] | ||
4051 | 276 | else: | 403 | else: |
4052 | 277 | vers_map = OPENSTACK_CODENAMES | 404 | vers_map = OPENSTACK_CODENAMES |
4057 | 278 | 405 | for version, cname in six.iteritems(vers_map): | |
4058 | 279 | for version, cname in six.iteritems(vers_map): | 406 | if cname == codename: |
4059 | 280 | if cname == codename: | 407 | return version |
4056 | 281 | return version | ||
4060 | 282 | # e = "Could not determine OpenStack version for package: %s" % pkg | 408 | # e = "Could not determine OpenStack version for package: %s" % pkg |
4061 | 283 | # error_out(e) | 409 | # error_out(e) |
4062 | 284 | 410 | ||
4063 | @@ -286,29 +412,72 @@ | |||
4064 | 286 | os_rel = None | 412 | os_rel = None |
4065 | 287 | 413 | ||
4066 | 288 | 414 | ||
4068 | 289 | def os_release(package, base='essex'): | 415 | def reset_os_release(): |
4069 | 416 | '''Unset the cached os_release version''' | ||
4070 | 417 | global os_rel | ||
4071 | 418 | os_rel = None | ||
4072 | 419 | |||
4073 | 420 | |||
4074 | 421 | def os_release(package, base='essex', reset_cache=False): | ||
4075 | 290 | ''' | 422 | ''' |
4076 | 291 | Returns OpenStack release codename from a cached global. | 423 | Returns OpenStack release codename from a cached global. |
4077 | 424 | |||
4078 | 425 | If reset_cache then unset the cached os_release version and return the | ||
4079 | 426 | freshly determined version. | ||
4080 | 427 | |||
4081 | 292 | If the codename can not be determined from either an installed package or | 428 | If the codename can not be determined from either an installed package or |
4082 | 293 | the installation source, the earliest release supported by the charm should | 429 | the installation source, the earliest release supported by the charm should |
4083 | 294 | be returned. | 430 | be returned. |
4084 | 295 | ''' | 431 | ''' |
4085 | 296 | global os_rel | 432 | global os_rel |
4086 | 433 | if reset_cache: | ||
4087 | 434 | reset_os_release() | ||
4088 | 297 | if os_rel: | 435 | if os_rel: |
4089 | 298 | return os_rel | 436 | return os_rel |
4091 | 299 | os_rel = (get_os_codename_package(package, fatal=False) or | 437 | os_rel = (git_os_codename_install_source(config('openstack-origin-git')) or |
4092 | 438 | get_os_codename_package(package, fatal=False) or | ||
4093 | 300 | get_os_codename_install_source(config('openstack-origin')) or | 439 | get_os_codename_install_source(config('openstack-origin')) or |
4094 | 301 | base) | 440 | base) |
4095 | 302 | return os_rel | 441 | return os_rel |
4096 | 303 | 442 | ||
4097 | 304 | 443 | ||
4098 | 305 | def import_key(keyid): | 444 | def import_key(keyid): |
4105 | 306 | cmd = "apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 " \ | 445 | key = keyid.strip() |
4106 | 307 | "--recv-keys %s" % keyid | 446 | if (key.startswith('-----BEGIN PGP PUBLIC KEY BLOCK-----') and |
4107 | 308 | try: | 447 | key.endswith('-----END PGP PUBLIC KEY BLOCK-----')): |
4108 | 309 | subprocess.check_call(cmd.split(' ')) | 448 | juju_log("PGP key found (looks like ASCII Armor format)", level=DEBUG) |
4109 | 310 | except subprocess.CalledProcessError: | 449 | juju_log("Importing ASCII Armor PGP key", level=DEBUG) |
4110 | 311 | error_out("Error importing repo key %s" % keyid) | 450 | with tempfile.NamedTemporaryFile() as keyfile: |
4111 | 451 | with open(keyfile.name, 'w') as fd: | ||
4112 | 452 | fd.write(key) | ||
4113 | 453 | fd.write("\n") | ||
4114 | 454 | |||
4115 | 455 | cmd = ['apt-key', 'add', keyfile.name] | ||
4116 | 456 | try: | ||
4117 | 457 | subprocess.check_call(cmd) | ||
4118 | 458 | except subprocess.CalledProcessError: | ||
4119 | 459 | error_out("Error importing PGP key '%s'" % key) | ||
4120 | 460 | else: | ||
4121 | 461 | juju_log("PGP key found (looks like Radix64 format)", level=DEBUG) | ||
4122 | 462 | juju_log("Importing PGP key from keyserver", level=DEBUG) | ||
4123 | 463 | cmd = ['apt-key', 'adv', '--keyserver', | ||
4124 | 464 | 'hkp://keyserver.ubuntu.com:80', '--recv-keys', key] | ||
4125 | 465 | try: | ||
4126 | 466 | subprocess.check_call(cmd) | ||
4127 | 467 | except subprocess.CalledProcessError: | ||
4128 | 468 | error_out("Error importing PGP key '%s'" % key) | ||
4129 | 469 | |||
4130 | 470 | |||
4131 | 471 | def get_source_and_pgp_key(input): | ||
4132 | 472 | """Look for a pgp key ID or ascii-armor key in the given input.""" | ||
4133 | 473 | index = input.strip() | ||
4134 | 474 | index = input.rfind('|') | ||
4135 | 475 | if index < 0: | ||
4136 | 476 | return input, None | ||
4137 | 477 | |||
4138 | 478 | key = input[index + 1:].strip('|') | ||
4139 | 479 | source = input[:index] | ||
4140 | 480 | return source, key | ||
4141 | 312 | 481 | ||
4142 | 313 | 482 | ||
4143 | 314 | def configure_installation_source(rel): | 483 | def configure_installation_source(rel): |
4144 | @@ -320,16 +489,16 @@ | |||
4145 | 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: |
4146 | 321 | f.write(DISTRO_PROPOSED % ubuntu_rel) | 490 | f.write(DISTRO_PROPOSED % ubuntu_rel) |
4147 | 322 | elif rel[:4] == "ppa:": | 491 | elif rel[:4] == "ppa:": |
4149 | 323 | src = rel | 492 | src, key = get_source_and_pgp_key(rel) |
4150 | 493 | if key: | ||
4151 | 494 | import_key(key) | ||
4152 | 495 | |||
4153 | 324 | subprocess.check_call(["add-apt-repository", "-y", src]) | 496 | subprocess.check_call(["add-apt-repository", "-y", src]) |
4154 | 325 | elif rel[:3] == "deb": | 497 | elif rel[:3] == "deb": |
4159 | 326 | l = len(rel.split('|')) | 498 | src, key = get_source_and_pgp_key(rel) |
4160 | 327 | if l == 2: | 499 | if key: |
4157 | 328 | src, key = rel.split('|') | ||
4158 | 329 | juju_log("Importing PPA key from keyserver for %s" % src) | ||
4161 | 330 | import_key(key) | 500 | import_key(key) |
4164 | 331 | elif l == 1: | 501 | |
4163 | 332 | src = rel | ||
4165 | 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: |
4166 | 334 | f.write(src) | 503 | f.write(src) |
4167 | 335 | elif rel[:6] == 'cloud:': | 504 | elif rel[:6] == 'cloud:': |
4168 | @@ -374,6 +543,15 @@ | |||
4169 | 374 | 'liberty': 'trusty-updates/liberty', | 543 | 'liberty': 'trusty-updates/liberty', |
4170 | 375 | 'liberty/updates': 'trusty-updates/liberty', | 544 | 'liberty/updates': 'trusty-updates/liberty', |
4171 | 376 | 'liberty/proposed': 'trusty-proposed/liberty', | 545 | 'liberty/proposed': 'trusty-proposed/liberty', |
4172 | 546 | 'mitaka': 'trusty-updates/mitaka', | ||
4173 | 547 | 'mitaka/updates': 'trusty-updates/mitaka', | ||
4174 | 548 | 'mitaka/proposed': 'trusty-proposed/mitaka', | ||
4175 | 549 | 'newton': 'xenial-updates/newton', | ||
4176 | 550 | 'newton/updates': 'xenial-updates/newton', | ||
4177 | 551 | 'newton/proposed': 'xenial-proposed/newton', | ||
4178 | 552 | 'ocata': 'xenial-updates/ocata', | ||
4179 | 553 | 'ocata/updates': 'xenial-updates/ocata', | ||
4180 | 554 | 'ocata/proposed': 'xenial-proposed/ocata', | ||
4181 | 377 | } | 555 | } |
4182 | 378 | 556 | ||
4183 | 379 | try: | 557 | try: |
4184 | @@ -441,11 +619,16 @@ | |||
4185 | 441 | cur_vers = get_os_version_package(package) | 619 | cur_vers = get_os_version_package(package) |
4186 | 442 | if "swift" in package: | 620 | if "swift" in package: |
4187 | 443 | codename = get_os_codename_install_source(src) | 621 | codename = get_os_codename_install_source(src) |
4189 | 444 | available_vers = get_os_version_codename(codename, SWIFT_CODENAMES) | 622 | avail_vers = get_os_version_codename_swift(codename) |
4190 | 445 | else: | 623 | else: |
4192 | 446 | available_vers = get_os_version_install_source(src) | 624 | avail_vers = get_os_version_install_source(src) |
4193 | 447 | apt.init() | 625 | apt.init() |
4195 | 448 | return apt.version_compare(available_vers, cur_vers) == 1 | 626 | if "swift" in package: |
4196 | 627 | major_cur_vers = cur_vers.split('.', 1)[0] | ||
4197 | 628 | major_avail_vers = avail_vers.split('.', 1)[0] | ||
4198 | 629 | major_diff = apt.version_compare(major_avail_vers, major_cur_vers) | ||
4199 | 630 | return avail_vers > cur_vers and (major_diff == 1 or major_diff == 0) | ||
4200 | 631 | return apt.version_compare(avail_vers, cur_vers) == 1 | ||
4201 | 449 | 632 | ||
4202 | 450 | 633 | ||
4203 | 451 | def ensure_block_device(block_device): | 634 | def ensure_block_device(block_device): |
4204 | @@ -502,6 +685,7 @@ | |||
4205 | 502 | else: | 685 | else: |
4206 | 503 | zap_disk(block_device) | 686 | zap_disk(block_device) |
4207 | 504 | 687 | ||
4208 | 688 | |||
4209 | 505 | is_ip = ip.is_ip | 689 | is_ip = ip.is_ip |
4210 | 506 | ns_query = ip.ns_query | 690 | ns_query = ip.ns_query |
4211 | 507 | get_host_ip = ip.get_host_ip | 691 | get_host_ip = ip.get_host_ip |
4212 | @@ -561,7 +745,86 @@ | |||
4213 | 561 | return config('openstack-origin-git') is not None | 745 | return config('openstack-origin-git') is not None |
4214 | 562 | 746 | ||
4215 | 563 | 747 | ||
4217 | 564 | requirements_dir = None | 748 | def git_os_codename_install_source(projects_yaml): |
4218 | 749 | """ | ||
4219 | 750 | Returns OpenStack codename of release being installed from source. | ||
4220 | 751 | """ | ||
4221 | 752 | if git_install_requested(): | ||
4222 | 753 | projects = _git_yaml_load(projects_yaml) | ||
4223 | 754 | |||
4224 | 755 | if projects in GIT_DEFAULT_BRANCHES.keys(): | ||
4225 | 756 | if projects == 'master': | ||
4226 | 757 | return 'ocata' | ||
4227 | 758 | return projects | ||
4228 | 759 | |||
4229 | 760 | if 'release' in projects: | ||
4230 | 761 | if projects['release'] == 'master': | ||
4231 | 762 | return 'ocata' | ||
4232 | 763 | return projects['release'] | ||
4233 | 764 | |||
4234 | 765 | return None | ||
4235 | 766 | |||
4236 | 767 | |||
4237 | 768 | def git_default_repos(projects_yaml): | ||
4238 | 769 | """ | ||
4239 | 770 | Returns default repos if a default openstack-origin-git value is specified. | ||
4240 | 771 | """ | ||
4241 | 772 | service = service_name() | ||
4242 | 773 | core_project = service | ||
4243 | 774 | |||
4244 | 775 | for default, branch in GIT_DEFAULT_BRANCHES.iteritems(): | ||
4245 | 776 | if projects_yaml == default: | ||
4246 | 777 | |||
4247 | 778 | # add the requirements repo first | ||
4248 | 779 | repo = { | ||
4249 | 780 | 'name': 'requirements', | ||
4250 | 781 | 'repository': GIT_DEFAULT_REPOS['requirements'], | ||
4251 | 782 | 'branch': branch, | ||
4252 | 783 | } | ||
4253 | 784 | repos = [repo] | ||
4254 | 785 | |||
4255 | 786 | # neutron-* and nova-* charms require some additional repos | ||
4256 | 787 | if service in ['neutron-api', 'neutron-gateway', | ||
4257 | 788 | 'neutron-openvswitch']: | ||
4258 | 789 | core_project = 'neutron' | ||
4259 | 790 | if service == 'neutron-api': | ||
4260 | 791 | repo = { | ||
4261 | 792 | 'name': 'networking-hyperv', | ||
4262 | 793 | 'repository': GIT_DEFAULT_REPOS['networking-hyperv'], | ||
4263 | 794 | 'branch': branch, | ||
4264 | 795 | } | ||
4265 | 796 | repos.append(repo) | ||
4266 | 797 | for project in ['neutron-fwaas', 'neutron-lbaas', | ||
4267 | 798 | 'neutron-vpnaas', 'nova']: | ||
4268 | 799 | repo = { | ||
4269 | 800 | 'name': project, | ||
4270 | 801 | 'repository': GIT_DEFAULT_REPOS[project], | ||
4271 | 802 | 'branch': branch, | ||
4272 | 803 | } | ||
4273 | 804 | repos.append(repo) | ||
4274 | 805 | |||
4275 | 806 | elif service in ['nova-cloud-controller', 'nova-compute']: | ||
4276 | 807 | core_project = 'nova' | ||
4277 | 808 | repo = { | ||
4278 | 809 | 'name': 'neutron', | ||
4279 | 810 | 'repository': GIT_DEFAULT_REPOS['neutron'], | ||
4280 | 811 | 'branch': branch, | ||
4281 | 812 | } | ||
4282 | 813 | repos.append(repo) | ||
4283 | 814 | elif service == 'openstack-dashboard': | ||
4284 | 815 | core_project = 'horizon' | ||
4285 | 816 | |||
4286 | 817 | # finally add the current service's core project repo | ||
4287 | 818 | repo = { | ||
4288 | 819 | 'name': core_project, | ||
4289 | 820 | 'repository': GIT_DEFAULT_REPOS[core_project], | ||
4290 | 821 | 'branch': branch, | ||
4291 | 822 | } | ||
4292 | 823 | repos.append(repo) | ||
4293 | 824 | |||
4294 | 825 | return yaml.dump(dict(repositories=repos, release=default)) | ||
4295 | 826 | |||
4296 | 827 | return projects_yaml | ||
4297 | 565 | 828 | ||
4298 | 566 | 829 | ||
4299 | 567 | def _git_yaml_load(projects_yaml): | 830 | def _git_yaml_load(projects_yaml): |
4300 | @@ -574,7 +837,10 @@ | |||
4301 | 574 | return yaml.load(projects_yaml) | 837 | return yaml.load(projects_yaml) |
4302 | 575 | 838 | ||
4303 | 576 | 839 | ||
4305 | 577 | def git_clone_and_install(projects_yaml, core_project, depth=1): | 840 | requirements_dir = None |
4306 | 841 | |||
4307 | 842 | |||
4308 | 843 | def git_clone_and_install(projects_yaml, core_project): | ||
4309 | 578 | """ | 844 | """ |
4310 | 579 | Clone/install all specified OpenStack repositories. | 845 | Clone/install all specified OpenStack repositories. |
4311 | 580 | 846 | ||
4312 | @@ -621,18 +887,31 @@ | |||
4313 | 621 | pip_install(p, upgrade=True, proxy=http_proxy, | 887 | pip_install(p, upgrade=True, proxy=http_proxy, |
4314 | 622 | venv=os.path.join(parent_dir, 'venv')) | 888 | venv=os.path.join(parent_dir, 'venv')) |
4315 | 623 | 889 | ||
4316 | 890 | constraints = None | ||
4317 | 624 | for p in projects['repositories']: | 891 | for p in projects['repositories']: |
4318 | 625 | repo = p['repository'] | 892 | repo = p['repository'] |
4319 | 626 | branch = p['branch'] | 893 | branch = p['branch'] |
4320 | 894 | depth = '1' | ||
4321 | 895 | if 'depth' in p.keys(): | ||
4322 | 896 | depth = p['depth'] | ||
4323 | 627 | if p['name'] == 'requirements': | 897 | if p['name'] == 'requirements': |
4324 | 628 | repo_dir = _git_clone_and_install_single(repo, branch, depth, | 898 | repo_dir = _git_clone_and_install_single(repo, branch, depth, |
4325 | 629 | parent_dir, http_proxy, | 899 | parent_dir, http_proxy, |
4326 | 630 | update_requirements=False) | 900 | update_requirements=False) |
4327 | 631 | requirements_dir = repo_dir | 901 | requirements_dir = repo_dir |
4328 | 902 | constraints = os.path.join(repo_dir, "upper-constraints.txt") | ||
4329 | 903 | # upper-constraints didn't exist until after icehouse | ||
4330 | 904 | if not os.path.isfile(constraints): | ||
4331 | 905 | constraints = None | ||
4332 | 906 | # use constraints unless project yaml sets use_constraints to false | ||
4333 | 907 | if 'use_constraints' in projects.keys(): | ||
4334 | 908 | if not projects['use_constraints']: | ||
4335 | 909 | constraints = None | ||
4336 | 632 | else: | 910 | else: |
4337 | 633 | repo_dir = _git_clone_and_install_single(repo, branch, depth, | 911 | repo_dir = _git_clone_and_install_single(repo, branch, depth, |
4338 | 634 | parent_dir, http_proxy, | 912 | parent_dir, http_proxy, |
4340 | 635 | update_requirements=True) | 913 | update_requirements=True, |
4341 | 914 | constraints=constraints) | ||
4342 | 636 | 915 | ||
4343 | 637 | os.environ = old_environ | 916 | os.environ = old_environ |
4344 | 638 | 917 | ||
4345 | @@ -654,6 +933,8 @@ | |||
4346 | 654 | if projects['repositories'][-1]['name'] != core_project: | 933 | if projects['repositories'][-1]['name'] != core_project: |
4347 | 655 | error_out('{} git repo must be specified last'.format(core_project)) | 934 | error_out('{} git repo must be specified last'.format(core_project)) |
4348 | 656 | 935 | ||
4349 | 936 | _git_ensure_key_exists('release', projects) | ||
4350 | 937 | |||
4351 | 657 | 938 | ||
4352 | 658 | def _git_ensure_key_exists(key, keys): | 939 | def _git_ensure_key_exists(key, keys): |
4353 | 659 | """ | 940 | """ |
4354 | @@ -664,23 +945,18 @@ | |||
4355 | 664 | 945 | ||
4356 | 665 | 946 | ||
4357 | 666 | def _git_clone_and_install_single(repo, branch, depth, parent_dir, http_proxy, | 947 | def _git_clone_and_install_single(repo, branch, depth, parent_dir, http_proxy, |
4359 | 667 | update_requirements): | 948 | update_requirements, constraints=None): |
4360 | 668 | """ | 949 | """ |
4361 | 669 | Clone and install a single git repository. | 950 | Clone and install a single git repository. |
4362 | 670 | """ | 951 | """ |
4363 | 671 | dest_dir = os.path.join(parent_dir, os.path.basename(repo)) | ||
4364 | 672 | |||
4365 | 673 | if not os.path.exists(parent_dir): | 952 | if not os.path.exists(parent_dir): |
4366 | 674 | juju_log('Directory already exists at {}. ' | 953 | juju_log('Directory already exists at {}. ' |
4367 | 675 | 'No need to create directory.'.format(parent_dir)) | 954 | 'No need to create directory.'.format(parent_dir)) |
4368 | 676 | os.mkdir(parent_dir) | 955 | os.mkdir(parent_dir) |
4369 | 677 | 956 | ||
4376 | 678 | if not os.path.exists(dest_dir): | 957 | juju_log('Cloning git repo: {}, branch: {}'.format(repo, branch)) |
4377 | 679 | juju_log('Cloning git repo: {}, branch: {}'.format(repo, branch)) | 958 | repo_dir = install_remote( |
4378 | 680 | repo_dir = install_remote(repo, dest=parent_dir, branch=branch, | 959 | repo, dest=parent_dir, branch=branch, depth=depth) |
4373 | 681 | depth=depth) | ||
4374 | 682 | else: | ||
4375 | 683 | repo_dir = dest_dir | ||
4379 | 684 | 960 | ||
4380 | 685 | venv = os.path.join(parent_dir, 'venv') | 961 | venv = os.path.join(parent_dir, 'venv') |
4381 | 686 | 962 | ||
4382 | @@ -692,9 +968,10 @@ | |||
4383 | 692 | 968 | ||
4384 | 693 | juju_log('Installing git repo from dir: {}'.format(repo_dir)) | 969 | juju_log('Installing git repo from dir: {}'.format(repo_dir)) |
4385 | 694 | if http_proxy: | 970 | if http_proxy: |
4387 | 695 | pip_install(repo_dir, proxy=http_proxy, venv=venv) | 971 | pip_install(repo_dir, proxy=http_proxy, venv=venv, |
4388 | 972 | constraints=constraints) | ||
4389 | 696 | else: | 973 | else: |
4391 | 697 | pip_install(repo_dir, venv=venv) | 974 | pip_install(repo_dir, venv=venv, constraints=constraints) |
4392 | 698 | 975 | ||
4393 | 699 | return repo_dir | 976 | return repo_dir |
4394 | 700 | 977 | ||
4395 | @@ -763,6 +1040,114 @@ | |||
4396 | 763 | return None | 1040 | return None |
4397 | 764 | 1041 | ||
4398 | 765 | 1042 | ||
4399 | 1043 | def git_generate_systemd_init_files(templates_dir): | ||
4400 | 1044 | """ | ||
4401 | 1045 | Generate systemd init files. | ||
4402 | 1046 | |||
4403 | 1047 | Generates and installs systemd init units and script files based on the | ||
4404 | 1048 | *.init.in files contained in the templates_dir directory. | ||
4405 | 1049 | |||
4406 | 1050 | This code is based on the openstack-pkg-tools package and its init | ||
4407 | 1051 | script generation, which is used by the OpenStack packages. | ||
4408 | 1052 | """ | ||
4409 | 1053 | for f in os.listdir(templates_dir): | ||
4410 | 1054 | # Create the init script and systemd unit file from the template | ||
4411 | 1055 | if f.endswith(".init.in"): | ||
4412 | 1056 | init_in_file = f | ||
4413 | 1057 | init_file = f[:-8] | ||
4414 | 1058 | service_file = "{}.service".format(init_file) | ||
4415 | 1059 | |||
4416 | 1060 | init_in_source = os.path.join(templates_dir, init_in_file) | ||
4417 | 1061 | init_source = os.path.join(templates_dir, init_file) | ||
4418 | 1062 | service_source = os.path.join(templates_dir, service_file) | ||
4419 | 1063 | |||
4420 | 1064 | init_dest = os.path.join('/etc/init.d', init_file) | ||
4421 | 1065 | service_dest = os.path.join('/lib/systemd/system', service_file) | ||
4422 | 1066 | |||
4423 | 1067 | shutil.copyfile(init_in_source, init_source) | ||
4424 | 1068 | with open(init_source, 'a') as outfile: | ||
4425 | 1069 | template = '/usr/share/openstack-pkg-tools/init-script-template' | ||
4426 | 1070 | with open(template) as infile: | ||
4427 | 1071 | outfile.write('\n\n{}'.format(infile.read())) | ||
4428 | 1072 | |||
4429 | 1073 | cmd = ['pkgos-gen-systemd-unit', init_in_source] | ||
4430 | 1074 | subprocess.check_call(cmd) | ||
4431 | 1075 | |||
4432 | 1076 | if os.path.exists(init_dest): | ||
4433 | 1077 | os.remove(init_dest) | ||
4434 | 1078 | if os.path.exists(service_dest): | ||
4435 | 1079 | os.remove(service_dest) | ||
4436 | 1080 | shutil.copyfile(init_source, init_dest) | ||
4437 | 1081 | shutil.copyfile(service_source, service_dest) | ||
4438 | 1082 | os.chmod(init_dest, 0o755) | ||
4439 | 1083 | |||
4440 | 1084 | for f in os.listdir(templates_dir): | ||
4441 | 1085 | # If there's a service.in file, use it instead of the generated one | ||
4442 | 1086 | if f.endswith(".service.in"): | ||
4443 | 1087 | service_in_file = f | ||
4444 | 1088 | service_file = f[:-3] | ||
4445 | 1089 | |||
4446 | 1090 | service_in_source = os.path.join(templates_dir, service_in_file) | ||
4447 | 1091 | service_source = os.path.join(templates_dir, service_file) | ||
4448 | 1092 | service_dest = os.path.join('/lib/systemd/system', service_file) | ||
4449 | 1093 | |||
4450 | 1094 | shutil.copyfile(service_in_source, service_source) | ||
4451 | 1095 | |||
4452 | 1096 | if os.path.exists(service_dest): | ||
4453 | 1097 | os.remove(service_dest) | ||
4454 | 1098 | shutil.copyfile(service_source, service_dest) | ||
4455 | 1099 | |||
4456 | 1100 | for f in os.listdir(templates_dir): | ||
4457 | 1101 | # Generate the systemd unit if there's no existing .service.in | ||
4458 | 1102 | if f.endswith(".init.in"): | ||
4459 | 1103 | init_in_file = f | ||
4460 | 1104 | init_file = f[:-8] | ||
4461 | 1105 | service_in_file = "{}.service.in".format(init_file) | ||
4462 | 1106 | service_file = "{}.service".format(init_file) | ||
4463 | 1107 | |||
4464 | 1108 | init_in_source = os.path.join(templates_dir, init_in_file) | ||
4465 | 1109 | service_in_source = os.path.join(templates_dir, service_in_file) | ||
4466 | 1110 | service_source = os.path.join(templates_dir, service_file) | ||
4467 | 1111 | service_dest = os.path.join('/lib/systemd/system', service_file) | ||
4468 | 1112 | |||
4469 | 1113 | if not os.path.exists(service_in_source): | ||
4470 | 1114 | cmd = ['pkgos-gen-systemd-unit', init_in_source] | ||
4471 | 1115 | subprocess.check_call(cmd) | ||
4472 | 1116 | |||
4473 | 1117 | if os.path.exists(service_dest): | ||
4474 | 1118 | os.remove(service_dest) | ||
4475 | 1119 | shutil.copyfile(service_source, service_dest) | ||
4476 | 1120 | |||
4477 | 1121 | |||
4478 | 1122 | def git_determine_usr_bin(): | ||
4479 | 1123 | """Return the /usr/bin path for Apache2 config. | ||
4480 | 1124 | |||
4481 | 1125 | The /usr/bin path will be located in the virtualenv if the charm | ||
4482 | 1126 | is configured to deploy from source. | ||
4483 | 1127 | """ | ||
4484 | 1128 | if git_install_requested(): | ||
4485 | 1129 | projects_yaml = config('openstack-origin-git') | ||
4486 | 1130 | projects_yaml = git_default_repos(projects_yaml) | ||
4487 | 1131 | return os.path.join(git_pip_venv_dir(projects_yaml), 'bin') | ||
4488 | 1132 | else: | ||
4489 | 1133 | return '/usr/bin' | ||
4490 | 1134 | |||
4491 | 1135 | |||
4492 | 1136 | def git_determine_python_path(): | ||
4493 | 1137 | """Return the python-path for Apache2 config. | ||
4494 | 1138 | |||
4495 | 1139 | Returns 'None' unless the charm is configured to deploy from source, | ||
4496 | 1140 | in which case the path of the virtualenv's site-packages is returned. | ||
4497 | 1141 | """ | ||
4498 | 1142 | if git_install_requested(): | ||
4499 | 1143 | projects_yaml = config('openstack-origin-git') | ||
4500 | 1144 | projects_yaml = git_default_repos(projects_yaml) | ||
4501 | 1145 | return os.path.join(git_pip_venv_dir(projects_yaml), | ||
4502 | 1146 | 'lib/python2.7/site-packages') | ||
4503 | 1147 | else: | ||
4504 | 1148 | return None | ||
4505 | 1149 | |||
4506 | 1150 | |||
4507 | 766 | def os_workload_status(configs, required_interfaces, charm_func=None): | 1151 | def os_workload_status(configs, required_interfaces, charm_func=None): |
4508 | 767 | """ | 1152 | """ |
4509 | 768 | Decorator to set workload status based on complete contexts | 1153 | Decorator to set workload status based on complete contexts |
4510 | @@ -779,56 +1164,155 @@ | |||
4511 | 779 | return wrap | 1164 | return wrap |
4512 | 780 | 1165 | ||
4513 | 781 | 1166 | ||
4526 | 782 | def set_os_workload_status(configs, required_interfaces, charm_func=None): | 1167 | def set_os_workload_status(configs, required_interfaces, charm_func=None, |
4527 | 783 | """ | 1168 | services=None, ports=None): |
4528 | 784 | Set workload status based on complete contexts. | 1169 | """Set the state of the workload status for the charm. |
4529 | 785 | status-set missing or incomplete contexts | 1170 | |
4530 | 786 | and juju-log details of missing required data. | 1171 | This calls _determine_os_workload_status() to get the new state, message |
4531 | 787 | charm_func is a charm specific function to run checking | 1172 | and sets the status using status_set() |
4532 | 788 | for charm specific requirements such as a VIP setting. | 1173 | |
4533 | 789 | """ | 1174 | @param configs: a templating.OSConfigRenderer() object |
4534 | 790 | incomplete_rel_data = incomplete_relation_data(configs, required_interfaces) | 1175 | @param required_interfaces: {generic: [specific, specific2, ...]} |
4535 | 791 | state = 'active' | 1176 | @param charm_func: a callable function that returns state, message. The |
4536 | 792 | missing_relations = [] | 1177 | signature is charm_func(configs) -> (state, message) |
4537 | 793 | incomplete_relations = [] | 1178 | @param services: list of strings OR dictionary specifying services/ports |
4538 | 1179 | @param ports: OPTIONAL list of port numbers. | ||
4539 | 1180 | @returns state, message: the new workload status, user message | ||
4540 | 1181 | """ | ||
4541 | 1182 | state, message = _determine_os_workload_status( | ||
4542 | 1183 | configs, required_interfaces, charm_func, services, ports) | ||
4543 | 1184 | status_set(state, message) | ||
4544 | 1185 | |||
4545 | 1186 | |||
4546 | 1187 | def _determine_os_workload_status( | ||
4547 | 1188 | configs, required_interfaces, charm_func=None, | ||
4548 | 1189 | services=None, ports=None): | ||
4549 | 1190 | """Determine the state of the workload status for the charm. | ||
4550 | 1191 | |||
4551 | 1192 | This function returns the new workload status for the charm based | ||
4552 | 1193 | on the state of the interfaces, the paused state and whether the | ||
4553 | 1194 | services are actually running and any specified ports are open. | ||
4554 | 1195 | |||
4555 | 1196 | This checks: | ||
4556 | 1197 | |||
4557 | 1198 | 1. if the unit should be paused, that it is actually paused. If so the | ||
4558 | 1199 | state is 'maintenance' + message, else 'broken'. | ||
4559 | 1200 | 2. that the interfaces/relations are complete. If they are not then | ||
4560 | 1201 | it sets the state to either 'broken' or 'waiting' and an appropriate | ||
4561 | 1202 | message. | ||
4562 | 1203 | 3. If all the relation data is set, then it checks that the actual | ||
4563 | 1204 | services really are running. If not it sets the state to 'broken'. | ||
4564 | 1205 | |||
4565 | 1206 | If everything is okay then the state returns 'active'. | ||
4566 | 1207 | |||
4567 | 1208 | @param configs: a templating.OSConfigRenderer() object | ||
4568 | 1209 | @param required_interfaces: {generic: [specific, specific2, ...]} | ||
4569 | 1210 | @param charm_func: a callable function that returns state, message. The | ||
4570 | 1211 | signature is charm_func(configs) -> (state, message) | ||
4571 | 1212 | @param services: list of strings OR dictionary specifying services/ports | ||
4572 | 1213 | @param ports: OPTIONAL list of port numbers. | ||
4573 | 1214 | @returns state, message: the new workload status, user message | ||
4574 | 1215 | """ | ||
4575 | 1216 | state, message = _ows_check_if_paused(services, ports) | ||
4576 | 1217 | |||
4577 | 1218 | if state is None: | ||
4578 | 1219 | state, message = _ows_check_generic_interfaces( | ||
4579 | 1220 | configs, required_interfaces) | ||
4580 | 1221 | |||
4581 | 1222 | if state != 'maintenance' and charm_func: | ||
4582 | 1223 | # _ows_check_charm_func() may modify the state, message | ||
4583 | 1224 | state, message = _ows_check_charm_func( | ||
4584 | 1225 | state, message, lambda: charm_func(configs)) | ||
4585 | 1226 | |||
4586 | 1227 | if state is None: | ||
4587 | 1228 | state, message = _ows_check_services_running(services, ports) | ||
4588 | 1229 | |||
4589 | 1230 | if state is None: | ||
4590 | 1231 | state = 'active' | ||
4591 | 1232 | message = "Unit is ready" | ||
4592 | 1233 | juju_log(message, 'INFO') | ||
4593 | 1234 | |||
4594 | 1235 | return state, message | ||
4595 | 1236 | |||
4596 | 1237 | |||
4597 | 1238 | def _ows_check_if_paused(services=None, ports=None): | ||
4598 | 1239 | """Check if the unit is supposed to be paused, and if so check that the | ||
4599 | 1240 | services/ports (if passed) are actually stopped/not being listened to. | ||
4600 | 1241 | |||
4601 | 1242 | if the unit isn't supposed to be paused, just return None, None | ||
4602 | 1243 | |||
4603 | 1244 | @param services: OPTIONAL services spec or list of service names. | ||
4604 | 1245 | @param ports: OPTIONAL list of port numbers. | ||
4605 | 1246 | @returns state, message or None, None | ||
4606 | 1247 | """ | ||
4607 | 1248 | if is_unit_paused_set(): | ||
4608 | 1249 | state, message = check_actually_paused(services=services, | ||
4609 | 1250 | ports=ports) | ||
4610 | 1251 | if state is None: | ||
4611 | 1252 | # we're paused okay, so set maintenance and return | ||
4612 | 1253 | state = "maintenance" | ||
4613 | 1254 | message = "Paused. Use 'resume' action to resume normal service." | ||
4614 | 1255 | return state, message | ||
4615 | 1256 | return None, None | ||
4616 | 1257 | |||
4617 | 1258 | |||
4618 | 1259 | def _ows_check_generic_interfaces(configs, required_interfaces): | ||
4619 | 1260 | """Check the complete contexts to determine the workload status. | ||
4620 | 1261 | |||
4621 | 1262 | - Checks for missing or incomplete contexts | ||
4622 | 1263 | - juju log details of missing required data. | ||
4623 | 1264 | - determines the correct workload status | ||
4624 | 1265 | - creates an appropriate message for status_set(...) | ||
4625 | 1266 | |||
4626 | 1267 | if there are no problems then the function returns None, None | ||
4627 | 1268 | |||
4628 | 1269 | @param configs: a templating.OSConfigRenderer() object | ||
4629 | 1270 | @params required_interfaces: {generic_interface: [specific_interface], } | ||
4630 | 1271 | @returns state, message or None, None | ||
4631 | 1272 | """ | ||
4632 | 1273 | incomplete_rel_data = incomplete_relation_data(configs, | ||
4633 | 1274 | required_interfaces) | ||
4634 | 1275 | state = None | ||
4635 | 794 | message = None | 1276 | message = None |
4638 | 795 | charm_state = None | 1277 | missing_relations = set() |
4639 | 796 | charm_message = None | 1278 | incomplete_relations = set() |
4640 | 797 | 1279 | ||
4642 | 798 | for generic_interface in incomplete_rel_data.keys(): | 1280 | for generic_interface, relations_states in incomplete_rel_data.items(): |
4643 | 799 | related_interface = None | 1281 | related_interface = None |
4644 | 800 | missing_data = {} | 1282 | missing_data = {} |
4645 | 801 | # Related or not? | 1283 | # Related or not? |
4648 | 802 | for interface in incomplete_rel_data[generic_interface]: | 1284 | for interface, relation_state in relations_states.items(): |
4649 | 803 | if incomplete_rel_data[generic_interface][interface].get('related'): | 1285 | if relation_state.get('related'): |
4650 | 804 | related_interface = interface | 1286 | related_interface = interface |
4653 | 805 | missing_data = incomplete_rel_data[generic_interface][interface].get('missing_data') | 1287 | missing_data = relation_state.get('missing_data') |
4654 | 806 | # No relation ID for the generic_interface | 1288 | break |
4655 | 1289 | # No relation ID for the generic_interface? | ||
4656 | 807 | if not related_interface: | 1290 | if not related_interface: |
4657 | 808 | juju_log("{} relation is missing and must be related for " | 1291 | juju_log("{} relation is missing and must be related for " |
4658 | 809 | "functionality. ".format(generic_interface), 'WARN') | 1292 | "functionality. ".format(generic_interface), 'WARN') |
4659 | 810 | state = 'blocked' | 1293 | state = 'blocked' |
4662 | 811 | if generic_interface not in missing_relations: | 1294 | missing_relations.add(generic_interface) |
4661 | 812 | missing_relations.append(generic_interface) | ||
4663 | 813 | else: | 1295 | else: |
4665 | 814 | # Relation ID exists but no related unit | 1296 | # Relation ID eists but no related unit |
4666 | 815 | if not missing_data: | 1297 | if not missing_data: |
4670 | 816 | # Edge case relation ID exists but departing | 1298 | # Edge case - relation ID exists but departings |
4671 | 817 | if ('departed' in hook_name() or 'broken' in hook_name()) \ | 1299 | _hook_name = hook_name() |
4672 | 818 | and related_interface in hook_name(): | 1300 | if (('departed' in _hook_name or 'broken' in _hook_name) and |
4673 | 1301 | related_interface in _hook_name): | ||
4674 | 819 | state = 'blocked' | 1302 | state = 'blocked' |
4677 | 820 | if generic_interface not in missing_relations: | 1303 | missing_relations.add(generic_interface) |
4676 | 821 | missing_relations.append(generic_interface) | ||
4678 | 822 | juju_log("{} relation's interface, {}, " | 1304 | juju_log("{} relation's interface, {}, " |
4679 | 823 | "relationship is departed or broken " | 1305 | "relationship is departed or broken " |
4680 | 824 | "and is required for functionality." | 1306 | "and is required for functionality." |
4682 | 825 | "".format(generic_interface, related_interface), "WARN") | 1307 | "".format(generic_interface, related_interface), |
4683 | 1308 | "WARN") | ||
4684 | 826 | # Normal case relation ID exists but no related unit | 1309 | # Normal case relation ID exists but no related unit |
4685 | 827 | # (joining) | 1310 | # (joining) |
4686 | 828 | else: | 1311 | else: |
4690 | 829 | juju_log("{} relations's interface, {}, is related but has " | 1312 | juju_log("{} relations's interface, {}, is related but has" |
4691 | 830 | "no units in the relation." | 1313 | " no units in the relation." |
4692 | 831 | "".format(generic_interface, related_interface), "INFO") | 1314 | "".format(generic_interface, related_interface), |
4693 | 1315 | "INFO") | ||
4694 | 832 | # Related unit exists and data missing on the relation | 1316 | # Related unit exists and data missing on the relation |
4695 | 833 | else: | 1317 | else: |
4696 | 834 | juju_log("{} relation's interface, {}, is related awaiting " | 1318 | juju_log("{} relation's interface, {}, is related awaiting " |
4697 | @@ -837,9 +1321,8 @@ | |||
4698 | 837 | ", ".join(missing_data)), "INFO") | 1321 | ", ".join(missing_data)), "INFO") |
4699 | 838 | if state != 'blocked': | 1322 | if state != 'blocked': |
4700 | 839 | state = 'waiting' | 1323 | state = 'waiting' |
4704 | 840 | if generic_interface not in incomplete_relations \ | 1324 | if generic_interface not in missing_relations: |
4705 | 841 | and generic_interface not in missing_relations: | 1325 | incomplete_relations.add(generic_interface) |
4703 | 842 | incomplete_relations.append(generic_interface) | ||
4706 | 843 | 1326 | ||
4707 | 844 | if missing_relations: | 1327 | if missing_relations: |
4708 | 845 | message = "Missing relations: {}".format(", ".join(missing_relations)) | 1328 | message = "Missing relations: {}".format(", ".join(missing_relations)) |
4709 | @@ -852,22 +1335,175 @@ | |||
4710 | 852 | "".format(", ".join(incomplete_relations)) | 1335 | "".format(", ".join(incomplete_relations)) |
4711 | 853 | state = 'waiting' | 1336 | state = 'waiting' |
4712 | 854 | 1337 | ||
4716 | 855 | # Run charm specific checks | 1338 | return state, message |
4717 | 856 | if charm_func: | 1339 | |
4718 | 857 | charm_state, charm_message = charm_func(configs) | 1340 | |
4719 | 1341 | def _ows_check_charm_func(state, message, charm_func_with_configs): | ||
4720 | 1342 | """Run a custom check function for the charm to see if it wants to | ||
4721 | 1343 | change the state. This is only run if not in 'maintenance' and | ||
4722 | 1344 | tests to see if the new state is more important that the previous | ||
4723 | 1345 | one determined by the interfaces/relations check. | ||
4724 | 1346 | |||
4725 | 1347 | @param state: the previously determined state so far. | ||
4726 | 1348 | @param message: the user orientated message so far. | ||
4727 | 1349 | @param charm_func: a callable function that returns state, message | ||
4728 | 1350 | @returns state, message strings. | ||
4729 | 1351 | """ | ||
4730 | 1352 | if charm_func_with_configs: | ||
4731 | 1353 | charm_state, charm_message = charm_func_with_configs() | ||
4732 | 858 | if charm_state != 'active' and charm_state != 'unknown': | 1354 | if charm_state != 'active' and charm_state != 'unknown': |
4733 | 859 | state = workload_state_compare(state, charm_state) | 1355 | state = workload_state_compare(state, charm_state) |
4734 | 860 | if message: | 1356 | if message: |
4736 | 861 | message = "{} {}".format(message, charm_message) | 1357 | charm_message = charm_message.replace("Incomplete relations: ", |
4737 | 1358 | "") | ||
4738 | 1359 | message = "{}, {}".format(message, charm_message) | ||
4739 | 862 | else: | 1360 | else: |
4740 | 863 | message = charm_message | 1361 | message = charm_message |
4748 | 864 | 1362 | return state, message | |
4749 | 865 | # Set to active if all requirements have been met | 1363 | |
4750 | 866 | if state == 'active': | 1364 | |
4751 | 867 | message = "Unit is ready" | 1365 | def _ows_check_services_running(services, ports): |
4752 | 868 | juju_log(message, "INFO") | 1366 | """Check that the services that should be running are actually running |
4753 | 869 | 1367 | and that any ports specified are being listened to. | |
4754 | 870 | status_set(state, message) | 1368 | |
4755 | 1369 | @param services: list of strings OR dictionary specifying services/ports | ||
4756 | 1370 | @param ports: list of ports | ||
4757 | 1371 | @returns state, message: strings or None, None | ||
4758 | 1372 | """ | ||
4759 | 1373 | messages = [] | ||
4760 | 1374 | state = None | ||
4761 | 1375 | if services is not None: | ||
4762 | 1376 | services = _extract_services_list_helper(services) | ||
4763 | 1377 | services_running, running = _check_running_services(services) | ||
4764 | 1378 | if not all(running): | ||
4765 | 1379 | messages.append( | ||
4766 | 1380 | "Services not running that should be: {}" | ||
4767 | 1381 | .format(", ".join(_filter_tuples(services_running, False)))) | ||
4768 | 1382 | state = 'blocked' | ||
4769 | 1383 | # also verify that the ports that should be open are open | ||
4770 | 1384 | # NB, that ServiceManager objects only OPTIONALLY have ports | ||
4771 | 1385 | map_not_open, ports_open = ( | ||
4772 | 1386 | _check_listening_on_services_ports(services)) | ||
4773 | 1387 | if not all(ports_open): | ||
4774 | 1388 | # find which service has missing ports. They are in service | ||
4775 | 1389 | # order which makes it a bit easier. | ||
4776 | 1390 | message_parts = {service: ", ".join([str(v) for v in open_ports]) | ||
4777 | 1391 | for service, open_ports in map_not_open.items()} | ||
4778 | 1392 | message = ", ".join( | ||
4779 | 1393 | ["{}: [{}]".format(s, sp) for s, sp in message_parts.items()]) | ||
4780 | 1394 | messages.append( | ||
4781 | 1395 | "Services with ports not open that should be: {}" | ||
4782 | 1396 | .format(message)) | ||
4783 | 1397 | state = 'blocked' | ||
4784 | 1398 | |||
4785 | 1399 | if ports is not None: | ||
4786 | 1400 | # and we can also check ports which we don't know the service for | ||
4787 | 1401 | ports_open, ports_open_bools = _check_listening_on_ports_list(ports) | ||
4788 | 1402 | if not all(ports_open_bools): | ||
4789 | 1403 | messages.append( | ||
4790 | 1404 | "Ports which should be open, but are not: {}" | ||
4791 | 1405 | .format(", ".join([str(p) for p, v in ports_open | ||
4792 | 1406 | if not v]))) | ||
4793 | 1407 | state = 'blocked' | ||
4794 | 1408 | |||
4795 | 1409 | if state is not None: | ||
4796 | 1410 | message = "; ".join(messages) | ||
4797 | 1411 | return state, message | ||
4798 | 1412 | |||
4799 | 1413 | return None, None | ||
4800 | 1414 | |||
4801 | 1415 | |||
4802 | 1416 | def _extract_services_list_helper(services): | ||
4803 | 1417 | """Extract a OrderedDict of {service: [ports]} of the supplied services | ||
4804 | 1418 | for use by the other functions. | ||
4805 | 1419 | |||
4806 | 1420 | The services object can either be: | ||
4807 | 1421 | - None : no services were passed (an empty dict is returned) | ||
4808 | 1422 | - a list of strings | ||
4809 | 1423 | - A dictionary (optionally OrderedDict) {service_name: {'service': ..}} | ||
4810 | 1424 | - An array of [{'service': service_name, ...}, ...] | ||
4811 | 1425 | |||
4812 | 1426 | @param services: see above | ||
4813 | 1427 | @returns OrderedDict(service: [ports], ...) | ||
4814 | 1428 | """ | ||
4815 | 1429 | if services is None: | ||
4816 | 1430 | return {} | ||
4817 | 1431 | if isinstance(services, dict): | ||
4818 | 1432 | services = services.values() | ||
4819 | 1433 | # either extract the list of services from the dictionary, or if | ||
4820 | 1434 | # it is a simple string, use that. i.e. works with mixed lists. | ||
4821 | 1435 | _s = OrderedDict() | ||
4822 | 1436 | for s in services: | ||
4823 | 1437 | if isinstance(s, dict) and 'service' in s: | ||
4824 | 1438 | _s[s['service']] = s.get('ports', []) | ||
4825 | 1439 | if isinstance(s, str): | ||
4826 | 1440 | _s[s] = [] | ||
4827 | 1441 | return _s | ||
4828 | 1442 | |||
4829 | 1443 | |||
4830 | 1444 | def _check_running_services(services): | ||
4831 | 1445 | """Check that the services dict provided is actually running and provide | ||
4832 | 1446 | a list of (service, boolean) tuples for each service. | ||
4833 | 1447 | |||
4834 | 1448 | Returns both a zipped list of (service, boolean) and a list of booleans | ||
4835 | 1449 | in the same order as the services. | ||
4836 | 1450 | |||
4837 | 1451 | @param services: OrderedDict of strings: [ports], one for each service to | ||
4838 | 1452 | check. | ||
4839 | 1453 | @returns [(service, boolean), ...], : results for checks | ||
4840 | 1454 | [boolean] : just the result of the service checks | ||
4841 | 1455 | """ | ||
4842 | 1456 | services_running = [service_running(s) for s in services] | ||
4843 | 1457 | return list(zip(services, services_running)), services_running | ||
4844 | 1458 | |||
4845 | 1459 | |||
4846 | 1460 | def _check_listening_on_services_ports(services, test=False): | ||
4847 | 1461 | """Check that the unit is actually listening (has the port open) on the | ||
4848 | 1462 | ports that the service specifies are open. If test is True then the | ||
4849 | 1463 | function returns the services with ports that are open rather than | ||
4850 | 1464 | closed. | ||
4851 | 1465 | |||
4852 | 1466 | Returns an OrderedDict of service: ports and a list of booleans | ||
4853 | 1467 | |||
4854 | 1468 | @param services: OrderedDict(service: [port, ...], ...) | ||
4855 | 1469 | @param test: default=False, if False, test for closed, otherwise open. | ||
4856 | 1470 | @returns OrderedDict(service: [port-not-open, ...]...), [boolean] | ||
4857 | 1471 | """ | ||
4858 | 1472 | test = not(not(test)) # ensure test is True or False | ||
4859 | 1473 | all_ports = list(itertools.chain(*services.values())) | ||
4860 | 1474 | ports_states = [port_has_listener('0.0.0.0', p) for p in all_ports] | ||
4861 | 1475 | map_ports = OrderedDict() | ||
4862 | 1476 | matched_ports = [p for p, opened in zip(all_ports, ports_states) | ||
4863 | 1477 | if opened == test] # essentially opened xor test | ||
4864 | 1478 | for service, ports in services.items(): | ||
4865 | 1479 | set_ports = set(ports).intersection(matched_ports) | ||
4866 | 1480 | if set_ports: | ||
4867 | 1481 | map_ports[service] = set_ports | ||
4868 | 1482 | return map_ports, ports_states | ||
4869 | 1483 | |||
4870 | 1484 | |||
4871 | 1485 | def _check_listening_on_ports_list(ports): | ||
4872 | 1486 | """Check that the ports list given are being listened to | ||
4873 | 1487 | |||
4874 | 1488 | Returns a list of ports being listened to and a list of the | ||
4875 | 1489 | booleans. | ||
4876 | 1490 | |||
4877 | 1491 | @param ports: LIST or port numbers. | ||
4878 | 1492 | @returns [(port_num, boolean), ...], [boolean] | ||
4879 | 1493 | """ | ||
4880 | 1494 | ports_open = [port_has_listener('0.0.0.0', p) for p in ports] | ||
4881 | 1495 | return zip(ports, ports_open), ports_open | ||
4882 | 1496 | |||
4883 | 1497 | |||
4884 | 1498 | def _filter_tuples(services_states, state): | ||
4885 | 1499 | """Return a simple list from a list of tuples according to the condition | ||
4886 | 1500 | |||
4887 | 1501 | @param services_states: LIST of (string, boolean): service and running | ||
4888 | 1502 | state. | ||
4889 | 1503 | @param state: Boolean to match the tuple against. | ||
4890 | 1504 | @returns [LIST of strings] that matched the tuple RHS. | ||
4891 | 1505 | """ | ||
4892 | 1506 | return [s for s, b in services_states if b == state] | ||
4893 | 871 | 1507 | ||
4894 | 872 | 1508 | ||
4895 | 873 | def workload_state_compare(current_workload_state, workload_state): | 1509 | def workload_state_compare(current_workload_state, workload_state): |
4896 | @@ -892,8 +1528,7 @@ | |||
4897 | 892 | 1528 | ||
4898 | 893 | 1529 | ||
4899 | 894 | def incomplete_relation_data(configs, required_interfaces): | 1530 | def incomplete_relation_data(configs, required_interfaces): |
4902 | 895 | """ | 1531 | """Check complete contexts against required_interfaces |
4901 | 896 | Check complete contexts against required_interfaces | ||
4903 | 897 | Return dictionary of incomplete relation data. | 1532 | Return dictionary of incomplete relation data. |
4904 | 898 | 1533 | ||
4905 | 899 | configs is an OSConfigRenderer object with configs registered | 1534 | configs is an OSConfigRenderer object with configs registered |
4906 | @@ -918,19 +1553,13 @@ | |||
4907 | 918 | 'shared-db': {'related': True}}} | 1553 | 'shared-db': {'related': True}}} |
4908 | 919 | """ | 1554 | """ |
4909 | 920 | complete_ctxts = configs.complete_contexts() | 1555 | complete_ctxts = configs.complete_contexts() |
4923 | 921 | incomplete_relations = [] | 1556 | incomplete_relations = [ |
4924 | 922 | for svc_type in required_interfaces.keys(): | 1557 | svc_type |
4925 | 923 | # Avoid duplicates | 1558 | for svc_type, interfaces in required_interfaces.items() |
4926 | 924 | found_ctxt = False | 1559 | if not set(interfaces).intersection(complete_ctxts)] |
4927 | 925 | for interface in required_interfaces[svc_type]: | 1560 | return { |
4928 | 926 | if interface in complete_ctxts: | 1561 | i: configs.get_incomplete_context_data(required_interfaces[i]) |
4929 | 927 | found_ctxt = True | 1562 | for i in incomplete_relations} |
4917 | 928 | if not found_ctxt: | ||
4918 | 929 | incomplete_relations.append(svc_type) | ||
4919 | 930 | incomplete_context_data = {} | ||
4920 | 931 | for i in incomplete_relations: | ||
4921 | 932 | incomplete_context_data[i] = configs.get_incomplete_context_data(required_interfaces[i]) | ||
4922 | 933 | return incomplete_context_data | ||
4930 | 934 | 1563 | ||
4931 | 935 | 1564 | ||
4932 | 936 | def do_action_openstack_upgrade(package, upgrade_callback, configs): | 1565 | def do_action_openstack_upgrade(package, upgrade_callback, configs): |
4933 | @@ -975,3 +1604,386 @@ | |||
4934 | 975 | action_set({'outcome': 'no upgrade available.'}) | 1604 | action_set({'outcome': 'no upgrade available.'}) |
4935 | 976 | 1605 | ||
4936 | 977 | return ret | 1606 | return ret |
4937 | 1607 | |||
4938 | 1608 | |||
4939 | 1609 | def remote_restart(rel_name, remote_service=None): | ||
4940 | 1610 | trigger = { | ||
4941 | 1611 | 'restart-trigger': str(uuid.uuid4()), | ||
4942 | 1612 | } | ||
4943 | 1613 | if remote_service: | ||
4944 | 1614 | trigger['remote-service'] = remote_service | ||
4945 | 1615 | for rid in relation_ids(rel_name): | ||
4946 | 1616 | # This subordinate can be related to two seperate services using | ||
4947 | 1617 | # different subordinate relations so only issue the restart if | ||
4948 | 1618 | # the principle is conencted down the relation we think it is | ||
4949 | 1619 | if related_units(relid=rid): | ||
4950 | 1620 | relation_set(relation_id=rid, | ||
4951 | 1621 | relation_settings=trigger, | ||
4952 | 1622 | ) | ||
4953 | 1623 | |||
4954 | 1624 | |||
4955 | 1625 | def check_actually_paused(services=None, ports=None): | ||
4956 | 1626 | """Check that services listed in the services object and and ports | ||
4957 | 1627 | are actually closed (not listened to), to verify that the unit is | ||
4958 | 1628 | properly paused. | ||
4959 | 1629 | |||
4960 | 1630 | @param services: See _extract_services_list_helper | ||
4961 | 1631 | @returns status, : string for status (None if okay) | ||
4962 | 1632 | message : string for problem for status_set | ||
4963 | 1633 | """ | ||
4964 | 1634 | state = None | ||
4965 | 1635 | message = None | ||
4966 | 1636 | messages = [] | ||
4967 | 1637 | if services is not None: | ||
4968 | 1638 | services = _extract_services_list_helper(services) | ||
4969 | 1639 | services_running, services_states = _check_running_services(services) | ||
4970 | 1640 | if any(services_states): | ||
4971 | 1641 | # there shouldn't be any running so this is a problem | ||
4972 | 1642 | messages.append("these services running: {}" | ||
4973 | 1643 | .format(", ".join( | ||
4974 | 1644 | _filter_tuples(services_running, True)))) | ||
4975 | 1645 | state = "blocked" | ||
4976 | 1646 | ports_open, ports_open_bools = ( | ||
4977 | 1647 | _check_listening_on_services_ports(services, True)) | ||
4978 | 1648 | if any(ports_open_bools): | ||
4979 | 1649 | message_parts = {service: ", ".join([str(v) for v in open_ports]) | ||
4980 | 1650 | for service, open_ports in ports_open.items()} | ||
4981 | 1651 | message = ", ".join( | ||
4982 | 1652 | ["{}: [{}]".format(s, sp) for s, sp in message_parts.items()]) | ||
4983 | 1653 | messages.append( | ||
4984 | 1654 | "these service:ports are open: {}".format(message)) | ||
4985 | 1655 | state = 'blocked' | ||
4986 | 1656 | if ports is not None: | ||
4987 | 1657 | ports_open, bools = _check_listening_on_ports_list(ports) | ||
4988 | 1658 | if any(bools): | ||
4989 | 1659 | messages.append( | ||
4990 | 1660 | "these ports which should be closed, but are open: {}" | ||
4991 | 1661 | .format(", ".join([str(p) for p, v in ports_open if v]))) | ||
4992 | 1662 | state = 'blocked' | ||
4993 | 1663 | if messages: | ||
4994 | 1664 | message = ("Services should be paused but {}" | ||
4995 | 1665 | .format(", ".join(messages))) | ||
4996 | 1666 | return state, message | ||
4997 | 1667 | |||
4998 | 1668 | |||
4999 | 1669 | def set_unit_paused(): | ||
5000 | 1670 | """Set the unit to a paused state in the local kv() store. |
The diff has been truncated for viewing.