blob: dbc517030ac949eb187d56e91b10823d38e1fc5d [file] [log] [blame]
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +03001# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13import logging
Oleh Hryhorov5cfb9d32018-09-11 16:55:24 +000014import time
Vladyslav Drokd182bf12019-01-11 14:02:38 +020015
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +030016import salt
Oleh Hryhorov5cfb9d32018-09-11 16:55:24 +000017from salt.exceptions import CommandExecutionError
Vladyslav Drokd182bf12019-01-11 14:02:38 +020018import six
19from six.moves import urllib
20from six.moves import zip_longest
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +030021
22LOG = logging.getLogger(__name__)
23
24KEYSTONE_LOADED = False
25
26
27def __virtual__():
28 """Only load if the nova module is in __salt__"""
29 if 'keystonev3.project_get_details' in __salt__:
30 global KEYSTONE_LOADED
31 KEYSTONE_LOADED = True
32 return 'novav21'
33
34
35class SaltModuleCallException(Exception):
36
37 def __init__(self, result_dict, *args, **kwargs):
38 super(SaltModuleCallException, self).__init__(*args, **kwargs)
39 self.result_dict = result_dict
40
41
42def _get_failure_function_mapping():
43 return {
44 'create': _create_failed,
45 'update': _update_failed,
46 'find': _find_failed,
47 'delete': _delete_failed,
48 }
49
50
51def _call_nova_salt_module(call_string, name, module_name='novav21'):
52 def inner(*args, **kwargs):
53 func = __salt__['%s.%s' % (module_name, call_string)]
54 result = func(*args, **kwargs)
55 if not result['result']:
56 ret = _get_failure_function_mapping()[func._action_type](
57 name, func._resource_human_readable_name)
58 ret['comment'] += '\nStatus code: %s\n%s' % (result['status_code'],
59 result['comment'])
60 raise SaltModuleCallException(ret)
61 return result['body'].get(func._body_response_key)
62 return inner
63
64
65def _error_handler(fun):
66 @six.wraps(fun)
67 def inner(*args, **kwargs):
68 try:
69 return fun(*args, **kwargs)
70 except SaltModuleCallException as e:
71 return e.result_dict
72 return inner
73
74
75@_error_handler
76def flavor_present(name, cloud_name, vcpus=1, ram=256, disk=0, flavor_id=None,
Dzmitry Stremkouskid37f2c82020-09-11 13:13:58 +020077 extra_specs=None, is_public=False):
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +030078 """Ensures that the flavor exists"""
79 extra_specs = extra_specs or {}
Dzmitry Stremkouskid37f2c82020-09-11 13:13:58 +020080 # There is no way to query flavors by name. And we always list both
81 # public and private flavors
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +030082 flavors = _call_nova_salt_module('flavor_list', name)(
Dzmitry Stremkouskid37f2c82020-09-11 13:13:58 +020083 detail=True, is_public=None, cloud_name=cloud_name)
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +030084 flavor = [flavor for flavor in flavors if flavor['name'] == name]
85 # Flavor names are unique, there is either 1 or 0 with requested name
Dzmitry Stremkouskid37f2c82020-09-11 13:13:58 +020086 # TODO: check all the vcpus, ram etc. and delete the existing flavor if
87 # something does not match, as it is impossible to update exising flavor
88 # apart from its extra specs
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +030089 if flavor:
90 flavor = flavor[0]
91 current_extra_specs = _call_nova_salt_module(
92 'flavor_get_extra_specs', name)(
93 flavor['id'], cloud_name=cloud_name)
94 to_delete = set(current_extra_specs) - set(extra_specs)
95 to_add = set(extra_specs) - set(current_extra_specs)
96 for spec in to_delete:
97 _call_nova_salt_module('flavor_delete_extra_spec', name)(
98 flavor['id'], spec, cloud_name=cloud_name)
99 _call_nova_salt_module('flavor_add_extra_specs', name)(
100 flavor['id'], cloud_name=cloud_name, **extra_specs)
101 if to_delete or to_add:
102 ret = _updated(name, 'Flavor', extra_specs)
103 else:
104 ret = _no_change(name, 'Flavor')
105 else:
106 flavor = _call_nova_salt_module('flavor_create', name)(
Dzmitry Stremkouskid37f2c82020-09-11 13:13:58 +0200107 name, vcpus, ram, disk, id=flavor_id, cloud_name=cloud_name,
108 **{"os-flavor-access:is_public": is_public})
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300109 _call_nova_salt_module('flavor_add_extra_specs', name)(
110 flavor['id'], cloud_name=cloud_name, **extra_specs)
111 flavor['extra_specs'] = extra_specs
112 ret = _created(name, 'Flavor', flavor)
113 return ret
114
115
116@_error_handler
117def flavor_absent(name, cloud_name):
118 """Ensure flavor is absent"""
119 # There is no way to query flavors by name
120 flavors = _call_nova_salt_module('flavor_list', name)(
121 detail=True, cloud_name=cloud_name)
122 flavor = [flavor for flavor in flavors if flavor['name'] == name]
123 # Flavor names are unique, there is either 1 or 0 with requested name
124 if flavor:
125 _call_nova_salt_module('flavor_delete', name)(
126 flavor[0]['id'], cloud_name=cloud_name)
127 return _deleted(name, 'Flavor')
128 return _non_existent(name, 'Flavor')
129
130
131def _get_keystone_project_id_by_name(project_name, cloud_name):
132 if not KEYSTONE_LOADED:
133 LOG.error("Keystone module not found, can not look up project ID "
134 "by name")
135 return None
136 project = __salt__['keystonev3.project_get_details'](
137 project_name, cloud_name=cloud_name)
138 if not project:
139 return None
140 return project['project']['id']
141
142
143@_error_handler
144def quota_present(name, cloud_name, **kwargs):
145 """Ensures that the nova quota exists
146
147 :param name: project name to ensure quota for.
148 """
149 project_name = name
150 project_id = _get_keystone_project_id_by_name(project_name, cloud_name)
151 changes = {}
152 if not project_id:
153 ret = _update_failed(project_name, 'Project quota')
154 ret['comment'] += ('\nCould not retrieve keystone project %s' %
155 project_name)
156 return ret
157 quota = _call_nova_salt_module('quota_list', project_name)(
158 project_id, cloud_name=cloud_name)
159 for key, value in kwargs.items():
160 if quota.get(key) != value:
161 changes[key] = value
162 if changes:
163 _call_nova_salt_module('quota_update', project_name)(
164 project_id, cloud_name=cloud_name, **changes)
165 return _updated(project_name, 'Project quota', changes)
166 else:
167 return _no_change(project_name, 'Project quota')
168
169
170@_error_handler
171def quota_absent(name, cloud_name):
172 """Ensures that the nova quota set to default
173
174 :param name: project name to reset quota for.
175 """
176 project_name = name
177 project_id = _get_keystone_project_id_by_name(project_name, cloud_name)
178 if not project_id:
179 ret = _delete_failed(project_name, 'Project quota')
180 ret['comment'] += ('\nCould not retrieve keystone project %s' %
181 project_name)
182 return ret
183 _call_nova_salt_module('quota_delete', name)(
184 project_id, cloud_name=cloud_name)
185 return _deleted(name, 'Project quota')
186
187
188@_error_handler
189def aggregate_present(name, cloud_name, availability_zone_name=None,
190 hosts=None, metadata=None):
191 """Ensures that the nova aggregate exists"""
192 aggregates = _call_nova_salt_module('aggregate_list', name)(
193 cloud_name=cloud_name)
194 aggregate_exists = [agg for agg in aggregates
195 if agg['name'] == name]
196 metadata = metadata or {}
197 hosts = hosts or []
198 if availability_zone_name:
199 metadata.update(availability_zone=availability_zone_name)
200 if not aggregate_exists:
201 aggregate = _call_nova_salt_module('aggregate_create', name)(
202 name, availability_zone_name, cloud_name=cloud_name)
203 if metadata:
204 _call_nova_salt_module('aggregate_set_metadata', name)(
Dzmitry Stremkouskif81957c2020-04-15 18:40:47 +0200205 name, cloud_name=cloud_name, **metadata)
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300206 aggregate['metadata'] = metadata
207 for host in hosts or []:
208 _call_nova_salt_module('aggregate_add_host', name)(
209 name, host, cloud_name=cloud_name)
210 aggregate['hosts'] = hosts
211 return _created(name, 'Host aggregate', aggregate)
212 else:
213 aggregate = aggregate_exists[0]
214 changes = {}
215 existing_meta = set(aggregate['metadata'].items())
216 requested_meta = set(metadata.items())
Dzmitry Stremkouskif81957c2020-04-15 18:40:47 +0200217 if existing_meta - requested_meta or requested_meta - existing_meta:
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300218 _call_nova_salt_module('aggregate_set_metadata', name)(
219 name, cloud_name=cloud_name, **metadata)
220 changes['metadata'] = metadata
221 hosts_to_add = set(hosts) - set(aggregate['hosts'])
222 hosts_to_remove = set(aggregate['hosts']) - set(hosts)
Dzmitry Stremkouski8babb812020-02-16 17:02:44 +0100223 if hosts and (hosts_to_remove or hosts_to_add):
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300224 for host in hosts_to_add:
225 _call_nova_salt_module('aggregate_add_host', name)(
226 name, host, cloud_name=cloud_name)
227 for host in hosts_to_remove:
228 _call_nova_salt_module('aggregate_remove_host', name)(
229 name, host, cloud_name=cloud_name)
230 changes['hosts'] = hosts
231 if changes:
232 return _updated(name, 'Host aggregate', changes)
233 else:
234 return _no_change(name, 'Host aggregate')
235
236
237@_error_handler
238def aggregate_absent(name, cloud_name):
239 """Ensure aggregate is absent"""
240 existing_aggregates = _call_nova_salt_module('aggregate_list', name)(
241 cloud_name=cloud_name)
242 matching_aggs = [agg for agg in existing_aggregates
243 if agg['name'] == name]
244 if matching_aggs:
245 _call_nova_salt_module('aggregate_delete', name)(
246 name, cloud_name=cloud_name)
247 return _deleted(name, 'Host Aggregate')
248 return _non_existent(name, 'Host Aggregate')
249
250
251@_error_handler
252def keypair_present(name, cloud_name, public_key_file=None, public_key=None):
253 """Ensures that the Nova key-pair exists"""
254 existing_keypairs = _call_nova_salt_module('keypair_list', name)(
255 cloud_name=cloud_name)
256 matching_kps = [kp for kp in existing_keypairs
257 if kp['keypair']['name'] == name]
258 if public_key_file and not public_key:
259 with salt.utils.fopen(public_key_file, 'r') as f:
260 public_key = f.read()
261 if not public_key:
262 ret = _create_failed(name, 'Keypair')
263 ret['comment'] += '\nPlease specify public key for keypair creation.'
264 return ret
265 if matching_kps:
266 # Keypair names are unique, there is either 1 or 0 with requested name
267 kp = matching_kps[0]['keypair']
268 if kp['public_key'] != public_key:
269 _call_nova_salt_module('keypair_delete', name)(
270 name, cloud_name=cloud_name)
271 else:
272 return _no_change(name, 'Keypair')
273 res = _call_nova_salt_module('keypair_create', name)(
274 name, cloud_name=cloud_name, public_key=public_key)
275 return _created(name, 'Keypair', res)
276
277
278@_error_handler
279def keypair_absent(name, cloud_name):
280 """Ensure keypair is absent"""
281 existing_keypairs = _call_nova_salt_module('keypair_list', name)(
282 cloud_name=cloud_name)
283 matching_kps = [kp for kp in existing_keypairs
284 if kp['keypair']['name'] == name]
285 if matching_kps:
286 _call_nova_salt_module('keypair_delete', name)(
287 name, cloud_name=cloud_name)
288 return _deleted(name, 'Keypair')
289 return _non_existent(name, 'Keypair')
290
291
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200292def _urlencode(dictionary):
293 return '&'.join([
294 '='.join((urllib.parse.quote(key), urllib.parse.quote(str(value))))
295 for key, value in dictionary.items()])
296
297
298def _cmd_raising_helper(command_string, test=False, runas='nova'):
299 if test:
300 LOG.info('This is a test run of the following command: "%s"' %
301 command_string)
302 return ''
303 result = __salt__['cmd.run_all'](command_string, python_shell=True,
304 runas=runas)
305 if result.get('retcode', 0) != 0:
306 raise CommandExecutionError(
307 "Command '%(cmd)s' returned error code %(code)s." %
308 {'cmd': command_string, 'code': result.get('retcode')})
309 return result['stdout']
310
311
312def cell_present(
313 name, db_name=None, db_user=None, db_password=None, db_address=None,
314 messaging_hosts=None, messaging_user=None, messaging_password=None,
315 messaging_virtual_host=None, db_engine='mysql',
316 messaging_engine='rabbit', messaging_query_params=None,
317 db_query_params=None, runas='nova', test=False):
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300318 """Ensure nova cell is present
319
320 For newly created cells this state also runs discover_hosts and
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200321 map_instances.
322
323 :param name: name of the cell to be present.
324 :param db_engine: cell database engine.
325 :param db_name: name of the cell database.
326 :param db_user: username for the cell database.
327 :param db_password: password for the cell database.
328 :param db_address: cell database host. If not provided, other db parameters
329 passed to this function are ignored, create/update cell commands will
330 be using connection strings from the config files.
331 :param messaging_engine: cell messaging engine.
332 :param messaging_hosts: a list of dictionaries of messaging hosts of the
333 cell, containing host and optional port keys. port key defaults
334 to 5672. If not provided, other messaging parameters passed to this
335 function are ignored, create/update cell commands will be using
336 connection strings from the config files.
337 :param messaging_user: username for cell messaging hosts.
338 :param messaging_password: password for cell messaging hosts.
339 :param messaging_virtual_host: cell messaging vhost.
340 :param messaging_query_params: dictionary of query params to append to
341 transport URL.
342 :param db_query_params: dictionary of query params to append to database
343 connection URL. charset=utf8 will always be present in query params.
344 :param runas: username to run the shell commands under.
345 :param test: if this is a test run, actual commands changing state won't
346 be run. False by default.
347 """
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300348 cell_info = __salt__['cmd.shell'](
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200349 "nova-manage cell_v2 list_cells --verbose 2>/dev/null | "
350 "awk '/%s/ {print $4,$6,$8}'" % name, runas=runas).split()
351 if messaging_hosts:
352 transport_url = '%s://' % messaging_engine
353 for h in messaging_hosts:
354 if 'host' not in h:
355 ret = _create_failed(name, 'Nova cell')
356 ret['comment'] = (
357 'messaging_hosts parameter of cell_present call should be '
358 'a list of dicts containing host and optionally port keys')
359 return ret
360 transport_url = (
361 '%(transport_url)s%(user)s:%(password)s@%(host)s:%(port)s,' %
362 {'transport_url': transport_url, 'user': messaging_user,
363 'password': messaging_password, 'host': h['host'],
364 'port': h.get('port', 5672)})
365 transport_url = '%(transport_url)s/%(messaging_virtual_host)s' % {
366 'transport_url': transport_url.rstrip(','),
367 'messaging_virtual_host': messaging_virtual_host}
368 if messaging_query_params:
369 transport_url = '%(transport_url)s?%(query)s' % {
370 'transport_url': transport_url,
371 'query': _urlencode(messaging_query_params)}
372 else:
373 transport_url = None
374 if db_address:
375 db_connection = (
376 '%(db_engine)s+pymysql://%(db_user)s:%(db_password)s@'
377 '%(db_address)s/%(db_name)s' % {
378 'db_engine': db_engine, 'db_user': db_user,
379 'db_password': db_password, 'db_address': db_address,
380 'db_name': db_name})
381 if not db_query_params:
382 db_query_params = {}
383 db_query_params['charset'] = 'utf8'
384 db_connection = '%(db_connection)s?%(query)s' % {
385 'db_connection': db_connection,
386 'query': _urlencode(db_query_params)}
387 else:
388 db_connection = None
389 changes = {}
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300390 # There should be at least 1 component printed to cell_info
391 if len(cell_info) >= 1:
392 cell_info = dict(zip_longest(
393 ('cell_uuid', 'existing_transport_url', 'existing_db_connection'),
394 cell_info))
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200395 command_string = (
396 '--transport-url \'%(transport_url)s\' '
397 if transport_url else '' +
398 '--database_connection \'%(db_connection)s\''
399 if db_connection else '')
400 if cell_info['existing_transport_url'] != transport_url:
401 changes['transport_url'] = transport_url
402 if cell_info['existing_db_connection'] != db_connection:
403 changes['db_connection'] = db_connection
404 if not changes:
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300405 return _no_change(name, 'Nova cell')
406 try:
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200407 _cmd_raising_helper(
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300408 ('nova-manage cell_v2 update_cell --cell_uuid %s %s' % (
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200409 cell_info['cell_uuid'], command_string)) % {
410 'transport_url': transport_url,
411 'db_connection': db_connection}, test=test, runas=runas)
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300412 LOG.warning("Updating the transport_url or database_connection "
413 "fields on a running system will NOT result in all "
414 "nodes immediately using the new values. Use caution "
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200415 "when changing these values. You need to restart all "
416 "nova services on all controllers after this action.")
417 ret = _updated(name, 'Nova cell', changes)
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300418 except Exception as e:
419 ret = _update_failed(name, 'Nova cell')
420 ret['comment'] += '\nException: %s' % e
421 return ret
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200422 changes = {'transport_url': transport_url, 'db_connection': db_connection,
423 'name': name}
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300424 try:
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200425 _cmd_raising_helper((
426 'nova-manage cell_v2 create_cell --name %(name)s ' +
427 ('--transport-url \'%(transport_url)s\' ' if transport_url else '')
428 + ('--database_connection \'%(db_connection)s\' '
429 if db_connection else '') + '--verbose ') % changes,
430 test=test, runas=runas)
431 ret = _created(name, 'Nova cell', changes)
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300432 except Exception as e:
433 ret = _create_failed(name, 'Nova cell')
434 ret['comment'] += '\nException: %s' % e
435 return ret
436
437
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200438def cell_absent(name, force=False, runas='nova', test=False):
439 """Ensure cell is absent
440
441 :param name: name of the cell to delete.
442 :param force: force cell deletion even if it contains host mappings
443 (host entries will be removed as well).
444 :param runas: username to run the shell commands under.
445 :param test: if this is a test run, actual commands changing state won't
446 be run. False by default.
447 """
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300448 cell_uuid = __salt__['cmd.shell'](
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200449 "nova-manage cell_v2 list_cells 2>/dev/null | awk '/%s/ {print $4}'" %
450 name, runas=runas)
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300451 if not cell_uuid:
452 return _non_existent(name, 'Nova cell')
453 try:
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200454 # Note that if the cell contains any hosts, you need to pass force
455 # parameter for the deletion to succeed. Cell that has any instance
456 # mappings can not be deleted even with force.
457 _cmd_raising_helper(
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300458 'nova-manage cell_v2 delete_cell --cell_uuid %s %s' % (
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200459 cell_uuid, '--force' if force else ''), test=test, runas=runas)
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300460 ret = _deleted(name, 'Nova cell')
461 except Exception as e:
462 ret = _delete_failed(name, 'Nova cell')
463 ret['comment'] += '\nException: %s' % e
464 return ret
465
466
Vladyslav Drok13c1fef2019-12-10 12:01:19 +0100467def instances_mapped_to_cell(name, max_count=None, timeout=60, runas='nova'):
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200468 """Ensure that all instances in the cell are mapped
469
470 :param name: cell name.
Vladyslav Drok13c1fef2019-12-10 12:01:19 +0100471 :param max_count: how many instances to map in one iteration. If there are
472 lots of instances present in the cell database, consider setting higher
473 value. By default nova will run in batches of 50.
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200474 :param timeout: amount of time in seconds mapping process should finish in.
475 :param runas: username to run the shell commands under.
476 """
Vladyslav Drok8a7631f2019-02-06 15:34:31 +0100477 test = __opts__.get('test', False)
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200478 cell_uuid = __salt__['cmd.shell'](
479 "nova-manage cell_v2 list_cells 2>/dev/null | "
480 "awk '/%s/ {print $4}'" % name, runas=runas)
481 result = {'name': name, 'changes': {}, 'result': False}
482 if not cell_uuid:
483 result['comment'] = (
484 'Failed to map all instances in cell {0}, it does not exist'
485 .format(name))
486 return result
Vladyslav Drok13c1fef2019-12-10 12:01:19 +0100487 command = 'nova-manage cell_v2 map_instances --cell_uuid %s' % cell_uuid
488 if max_count:
489 command += ' --max-count %s' % max_count
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200490 start_time = time.time()
Vladyslav Drok8a7631f2019-02-06 15:34:31 +0100491 if not test:
492 while True:
Vladyslav Drok13c1fef2019-12-10 12:01:19 +0100493 rc = __salt__['cmd.retcode'](command, runas=runas)
Vladyslav Drok8a7631f2019-02-06 15:34:31 +0100494 if rc == 0 or time.time() - start_time > timeout:
495 break
496 if rc != 0:
497 result['comment'] = (
498 'Failed to map all instances in cell {0} in {1} seconds'
499 .format(name, timeout))
500 return result
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200501 result['comment'] = 'All instances mapped in cell {0}'.format(name)
Vladyslav Drok8a7631f2019-02-06 15:34:31 +0100502 if test:
503 result['comment'] = 'TEST: {}'.format(result['comment'])
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200504 result['result'] = True
505 return result
506
507
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300508def _db_version_update(db, version, human_readable_resource_name):
509 existing_version = __salt__['cmd.shell'](
510 'nova-manage %s version 2>/dev/null' % db)
511 try:
512 existing_version = int(existing_version)
513 version = int(version)
514 except Exception as e:
515 ret = _update_failed(existing_version,
516 human_readable_resource_name)
517 ret['comment'] += ('\nCan not convert existing or requested version '
518 'to integer, exception: %s' % e)
519 LOG.error(ret['comment'])
520 return ret
521 if existing_version < version:
522 try:
523 __salt__['cmd.shell'](
524 'nova-manage %s sync --version %s' % (db, version))
525 ret = _updated(existing_version, human_readable_resource_name,
526 {db: '%s sync --version %s' % (db, version)})
527 except Exception as e:
528 ret = _update_failed(existing_version,
529 human_readable_resource_name)
530 ret['comment'] += '\nException: %s' % e
531 return ret
532 return _no_change(existing_version, human_readable_resource_name)
533
534
535def api_db_version_present(name=None, version="20"):
536 """Ensures that specific api_db version is present"""
537 return _db_version_update('api_db', version, 'Nova API database version')
538
539
540def db_version_present(name=None, version="334"):
541 """Ensures that specific db version is present"""
542 return _db_version_update('db', version, 'Nova database version')
543
544
545def online_data_migrations_present(name=None, api_db_version="20",
546 db_version="334"):
547 """Runs online_data_migrations if databases are of specific versions"""
548 ret = {'name': 'online_data_migrations', 'changes': {}, 'result': False,
549 'comment': 'Current nova api_db version != {0} or nova db version '
550 '!= {1}.'.format(api_db_version, db_version)}
551 cur_api_db_version = __salt__['cmd.shell'](
552 'nova-manage api_db version 2>/dev/null')
553 cur_db_version = __salt__['cmd.shell'](
554 'nova-manage db version 2>/dev/null')
555 try:
556 cur_api_db_version = int(cur_api_db_version)
557 cur_db_version = int(cur_db_version)
558 api_db_version = int(api_db_version)
559 db_version = int(db_version)
560 except Exception as e:
561 LOG.error(ret['comment'])
562 ret['comment'] = ('\nCan not convert existing or requested database '
563 'versions to integer, exception: %s' % e)
564 return ret
565 if cur_api_db_version == api_db_version and cur_db_version == db_version:
566 try:
567 __salt__['cmd.shell']('nova-manage db online_data_migrations')
568 ret['result'] = True
569 ret['comment'] = ('nova-manage db online_data_migrations was '
570 'executed successfuly')
571 ret['changes']['online_data_migrations'] = (
572 'online_data_migrations run on nova api_db version {0} and '
573 'nova db version {1}'.format(api_db_version, db_version))
574 except Exception as e:
575 ret['comment'] = (
576 'Failed to execute online_data_migrations on nova api_db '
577 'version %s and nova db version %s, exception: %s' % (
578 api_db_version, db_version, e))
579 return ret
580
581
Oleh Hryhorov5cfb9d32018-09-11 16:55:24 +0000582@_error_handler
583def service_enabled(name, cloud_name, binary="nova-compute"):
584 """Ensures that the service is enabled on the host
585
586 :param name: name of a host where service is running
587 :param service: name of the service have to be run
588 """
589 changes = {}
590
591 services = _call_nova_salt_module('services_list', name)(
Oleksandr Shyshkoc74c4772018-11-29 15:17:34 +0000592 name, binary=binary, cloud_name=cloud_name)
Oleh Hryhorov5cfb9d32018-09-11 16:55:24 +0000593 enabled_service = [s for s in services if s['binary'] == binary
594 and s['status'] == 'enabled' and s['host'] == name]
595 if len(enabled_service) > 0:
596 ret = _no_change(name, 'Compute services')
597 else:
598 changes = _call_nova_salt_module('services_update', name)(
599 name, binary, 'enable', cloud_name=cloud_name)
600 ret = _updated(name, 'Compute services', changes)
601
602 return ret
603
604@_error_handler
605def service_disabled(name, cloud_name, binary="nova-compute", disabled_reason=None):
606 """Ensures that the service is disabled on the host
607
608 :param name: name of a host where service is running
609 :param service: name of the service have to be disabled
610 """
611
612 changes = {}
613 kwargs = {}
614
615 if disabled_reason is not None:
616 kwargs['disabled_reason'] = disabled_reason
617
618 services = _call_nova_salt_module('services_list', name)(
Oleksandr Shyshkoc74c4772018-11-29 15:17:34 +0000619 name, binary=binary, cloud_name=cloud_name)
Oleh Hryhorov5cfb9d32018-09-11 16:55:24 +0000620 disabled_service = [s for s in services if s['binary'] == binary
621 and s['status'] == 'disabled' and s['host'] == name]
622 if len(disabled_service) > 0:
623 ret = _no_change(name, 'Compute services')
624 else:
625 changes = _call_nova_salt_module('services_update', name)(
626 name, binary, 'disable', cloud_name=cloud_name, **kwargs)
627 ret = _updated(name, 'Compute services', changes)
628
629 return ret
630
631
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300632def _find_failed(name, resource):
633 return {
634 'name': name, 'changes': {}, 'result': False,
635 'comment': 'Failed to find {0}s with name {1}'.format(resource, name)}
636
637
638def _created(name, resource, changes):
639 return {
640 'name': name, 'changes': changes, 'result': True,
641 'comment': '{0} {1} created'.format(resource, name)}
642
643
644def _create_failed(name, resource):
645 return {
646 'name': name, 'changes': {}, 'result': False,
647 'comment': '{0} {1} creation failed'.format(resource, name)}
648
649
650def _no_change(name, resource):
651 return {
652 'name': name, 'changes': {}, 'result': True,
653 'comment': '{0} {1} already is in the desired state'.format(
654 resource, name)}
655
656
657def _updated(name, resource, changes):
658 return {
659 'name': name, 'changes': changes, 'result': True,
660 'comment': '{0} {1} was updated'.format(resource, name)}
661
662
663def _update_failed(name, resource):
664 return {
665 'name': name, 'changes': {}, 'result': False,
666 'comment': '{0} {1} update failed'.format(resource, name)}
667
668
669def _deleted(name, resource):
670 return {
671 'name': name, 'changes': {}, 'result': True,
672 'comment': '{0} {1} deleted'.format(resource, name)}
673
674
675def _delete_failed(name, resource):
676 return {
677 'name': name, 'changes': {}, 'result': False,
678 'comment': '{0} {1} deletion failed'.format(resource, name)}
679
680
681def _non_existent(name, resource):
682 return {
683 'name': name, 'changes': {}, 'result': True,
684 'comment': '{0} {1} does not exist'.format(resource, name)}