blob: ad8a4a5d3dda58f54076663d165ae49643788acc [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,
77 extra_specs=None):
78 """Ensures that the flavor exists"""
79 extra_specs = extra_specs or {}
80 # There is no way to query flavors by name
81 flavors = _call_nova_salt_module('flavor_list', name)(
82 detail=True, cloud_name=cloud_name)
83 flavor = [flavor for flavor in flavors if flavor['name'] == name]
84 # Flavor names are unique, there is either 1 or 0 with requested name
85 if flavor:
86 flavor = flavor[0]
87 current_extra_specs = _call_nova_salt_module(
88 'flavor_get_extra_specs', name)(
89 flavor['id'], cloud_name=cloud_name)
90 to_delete = set(current_extra_specs) - set(extra_specs)
91 to_add = set(extra_specs) - set(current_extra_specs)
92 for spec in to_delete:
93 _call_nova_salt_module('flavor_delete_extra_spec', name)(
94 flavor['id'], spec, cloud_name=cloud_name)
95 _call_nova_salt_module('flavor_add_extra_specs', name)(
96 flavor['id'], cloud_name=cloud_name, **extra_specs)
97 if to_delete or to_add:
98 ret = _updated(name, 'Flavor', extra_specs)
99 else:
100 ret = _no_change(name, 'Flavor')
101 else:
102 flavor = _call_nova_salt_module('flavor_create', name)(
103 name, vcpus, ram, disk, id=flavor_id, cloud_name=cloud_name)
104 _call_nova_salt_module('flavor_add_extra_specs', name)(
105 flavor['id'], cloud_name=cloud_name, **extra_specs)
106 flavor['extra_specs'] = extra_specs
107 ret = _created(name, 'Flavor', flavor)
108 return ret
109
110
111@_error_handler
112def flavor_absent(name, cloud_name):
113 """Ensure flavor is absent"""
114 # There is no way to query flavors by name
115 flavors = _call_nova_salt_module('flavor_list', name)(
116 detail=True, cloud_name=cloud_name)
117 flavor = [flavor for flavor in flavors if flavor['name'] == name]
118 # Flavor names are unique, there is either 1 or 0 with requested name
119 if flavor:
120 _call_nova_salt_module('flavor_delete', name)(
121 flavor[0]['id'], cloud_name=cloud_name)
122 return _deleted(name, 'Flavor')
123 return _non_existent(name, 'Flavor')
124
125
126def _get_keystone_project_id_by_name(project_name, cloud_name):
127 if not KEYSTONE_LOADED:
128 LOG.error("Keystone module not found, can not look up project ID "
129 "by name")
130 return None
131 project = __salt__['keystonev3.project_get_details'](
132 project_name, cloud_name=cloud_name)
133 if not project:
134 return None
135 return project['project']['id']
136
137
138@_error_handler
139def quota_present(name, cloud_name, **kwargs):
140 """Ensures that the nova quota exists
141
142 :param name: project name to ensure quota for.
143 """
144 project_name = name
145 project_id = _get_keystone_project_id_by_name(project_name, cloud_name)
146 changes = {}
147 if not project_id:
148 ret = _update_failed(project_name, 'Project quota')
149 ret['comment'] += ('\nCould not retrieve keystone project %s' %
150 project_name)
151 return ret
152 quota = _call_nova_salt_module('quota_list', project_name)(
153 project_id, cloud_name=cloud_name)
154 for key, value in kwargs.items():
155 if quota.get(key) != value:
156 changes[key] = value
157 if changes:
158 _call_nova_salt_module('quota_update', project_name)(
159 project_id, cloud_name=cloud_name, **changes)
160 return _updated(project_name, 'Project quota', changes)
161 else:
162 return _no_change(project_name, 'Project quota')
163
164
165@_error_handler
166def quota_absent(name, cloud_name):
167 """Ensures that the nova quota set to default
168
169 :param name: project name to reset quota for.
170 """
171 project_name = name
172 project_id = _get_keystone_project_id_by_name(project_name, cloud_name)
173 if not project_id:
174 ret = _delete_failed(project_name, 'Project quota')
175 ret['comment'] += ('\nCould not retrieve keystone project %s' %
176 project_name)
177 return ret
178 _call_nova_salt_module('quota_delete', name)(
179 project_id, cloud_name=cloud_name)
180 return _deleted(name, 'Project quota')
181
182
183@_error_handler
184def aggregate_present(name, cloud_name, availability_zone_name=None,
185 hosts=None, metadata=None):
186 """Ensures that the nova aggregate exists"""
187 aggregates = _call_nova_salt_module('aggregate_list', name)(
188 cloud_name=cloud_name)
189 aggregate_exists = [agg for agg in aggregates
190 if agg['name'] == name]
191 metadata = metadata or {}
192 hosts = hosts or []
193 if availability_zone_name:
194 metadata.update(availability_zone=availability_zone_name)
195 if not aggregate_exists:
196 aggregate = _call_nova_salt_module('aggregate_create', name)(
197 name, availability_zone_name, cloud_name=cloud_name)
198 if metadata:
199 _call_nova_salt_module('aggregate_set_metadata', name)(
200 cloud_name=cloud_name, **metadata)
201 aggregate['metadata'] = metadata
202 for host in hosts or []:
203 _call_nova_salt_module('aggregate_add_host', name)(
204 name, host, cloud_name=cloud_name)
205 aggregate['hosts'] = hosts
206 return _created(name, 'Host aggregate', aggregate)
207 else:
208 aggregate = aggregate_exists[0]
209 changes = {}
210 existing_meta = set(aggregate['metadata'].items())
211 requested_meta = set(metadata.items())
212 if existing_meta - requested_meta or requested_meta - existing_meta:
213 _call_nova_salt_module('aggregate_set_metadata', name)(
214 name, cloud_name=cloud_name, **metadata)
215 changes['metadata'] = metadata
216 hosts_to_add = set(hosts) - set(aggregate['hosts'])
217 hosts_to_remove = set(aggregate['hosts']) - set(hosts)
218 if hosts_to_remove or hosts_to_add:
219 for host in hosts_to_add:
220 _call_nova_salt_module('aggregate_add_host', name)(
221 name, host, cloud_name=cloud_name)
222 for host in hosts_to_remove:
223 _call_nova_salt_module('aggregate_remove_host', name)(
224 name, host, cloud_name=cloud_name)
225 changes['hosts'] = hosts
226 if changes:
227 return _updated(name, 'Host aggregate', changes)
228 else:
229 return _no_change(name, 'Host aggregate')
230
231
232@_error_handler
233def aggregate_absent(name, cloud_name):
234 """Ensure aggregate is absent"""
235 existing_aggregates = _call_nova_salt_module('aggregate_list', name)(
236 cloud_name=cloud_name)
237 matching_aggs = [agg for agg in existing_aggregates
238 if agg['name'] == name]
239 if matching_aggs:
240 _call_nova_salt_module('aggregate_delete', name)(
241 name, cloud_name=cloud_name)
242 return _deleted(name, 'Host Aggregate')
243 return _non_existent(name, 'Host Aggregate')
244
245
246@_error_handler
247def keypair_present(name, cloud_name, public_key_file=None, public_key=None):
248 """Ensures that the Nova key-pair exists"""
249 existing_keypairs = _call_nova_salt_module('keypair_list', name)(
250 cloud_name=cloud_name)
251 matching_kps = [kp for kp in existing_keypairs
252 if kp['keypair']['name'] == name]
253 if public_key_file and not public_key:
254 with salt.utils.fopen(public_key_file, 'r') as f:
255 public_key = f.read()
256 if not public_key:
257 ret = _create_failed(name, 'Keypair')
258 ret['comment'] += '\nPlease specify public key for keypair creation.'
259 return ret
260 if matching_kps:
261 # Keypair names are unique, there is either 1 or 0 with requested name
262 kp = matching_kps[0]['keypair']
263 if kp['public_key'] != public_key:
264 _call_nova_salt_module('keypair_delete', name)(
265 name, cloud_name=cloud_name)
266 else:
267 return _no_change(name, 'Keypair')
268 res = _call_nova_salt_module('keypair_create', name)(
269 name, cloud_name=cloud_name, public_key=public_key)
270 return _created(name, 'Keypair', res)
271
272
273@_error_handler
274def keypair_absent(name, cloud_name):
275 """Ensure keypair is absent"""
276 existing_keypairs = _call_nova_salt_module('keypair_list', name)(
277 cloud_name=cloud_name)
278 matching_kps = [kp for kp in existing_keypairs
279 if kp['keypair']['name'] == name]
280 if matching_kps:
281 _call_nova_salt_module('keypair_delete', name)(
282 name, cloud_name=cloud_name)
283 return _deleted(name, 'Keypair')
284 return _non_existent(name, 'Keypair')
285
286
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200287def _urlencode(dictionary):
288 return '&'.join([
289 '='.join((urllib.parse.quote(key), urllib.parse.quote(str(value))))
290 for key, value in dictionary.items()])
291
292
293def _cmd_raising_helper(command_string, test=False, runas='nova'):
294 if test:
295 LOG.info('This is a test run of the following command: "%s"' %
296 command_string)
297 return ''
298 result = __salt__['cmd.run_all'](command_string, python_shell=True,
299 runas=runas)
300 if result.get('retcode', 0) != 0:
301 raise CommandExecutionError(
302 "Command '%(cmd)s' returned error code %(code)s." %
303 {'cmd': command_string, 'code': result.get('retcode')})
304 return result['stdout']
305
306
307def cell_present(
308 name, db_name=None, db_user=None, db_password=None, db_address=None,
309 messaging_hosts=None, messaging_user=None, messaging_password=None,
310 messaging_virtual_host=None, db_engine='mysql',
311 messaging_engine='rabbit', messaging_query_params=None,
312 db_query_params=None, runas='nova', test=False):
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300313 """Ensure nova cell is present
314
315 For newly created cells this state also runs discover_hosts and
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200316 map_instances.
317
318 :param name: name of the cell to be present.
319 :param db_engine: cell database engine.
320 :param db_name: name of the cell database.
321 :param db_user: username for the cell database.
322 :param db_password: password for the cell database.
323 :param db_address: cell database host. If not provided, other db parameters
324 passed to this function are ignored, create/update cell commands will
325 be using connection strings from the config files.
326 :param messaging_engine: cell messaging engine.
327 :param messaging_hosts: a list of dictionaries of messaging hosts of the
328 cell, containing host and optional port keys. port key defaults
329 to 5672. If not provided, other messaging parameters passed to this
330 function are ignored, create/update cell commands will be using
331 connection strings from the config files.
332 :param messaging_user: username for cell messaging hosts.
333 :param messaging_password: password for cell messaging hosts.
334 :param messaging_virtual_host: cell messaging vhost.
335 :param messaging_query_params: dictionary of query params to append to
336 transport URL.
337 :param db_query_params: dictionary of query params to append to database
338 connection URL. charset=utf8 will always be present in query params.
339 :param runas: username to run the shell commands under.
340 :param test: if this is a test run, actual commands changing state won't
341 be run. False by default.
342 """
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300343 cell_info = __salt__['cmd.shell'](
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200344 "nova-manage cell_v2 list_cells --verbose 2>/dev/null | "
345 "awk '/%s/ {print $4,$6,$8}'" % name, runas=runas).split()
346 if messaging_hosts:
347 transport_url = '%s://' % messaging_engine
348 for h in messaging_hosts:
349 if 'host' not in h:
350 ret = _create_failed(name, 'Nova cell')
351 ret['comment'] = (
352 'messaging_hosts parameter of cell_present call should be '
353 'a list of dicts containing host and optionally port keys')
354 return ret
355 transport_url = (
356 '%(transport_url)s%(user)s:%(password)s@%(host)s:%(port)s,' %
357 {'transport_url': transport_url, 'user': messaging_user,
358 'password': messaging_password, 'host': h['host'],
359 'port': h.get('port', 5672)})
360 transport_url = '%(transport_url)s/%(messaging_virtual_host)s' % {
361 'transport_url': transport_url.rstrip(','),
362 'messaging_virtual_host': messaging_virtual_host}
363 if messaging_query_params:
364 transport_url = '%(transport_url)s?%(query)s' % {
365 'transport_url': transport_url,
366 'query': _urlencode(messaging_query_params)}
367 else:
368 transport_url = None
369 if db_address:
370 db_connection = (
371 '%(db_engine)s+pymysql://%(db_user)s:%(db_password)s@'
372 '%(db_address)s/%(db_name)s' % {
373 'db_engine': db_engine, 'db_user': db_user,
374 'db_password': db_password, 'db_address': db_address,
375 'db_name': db_name})
376 if not db_query_params:
377 db_query_params = {}
378 db_query_params['charset'] = 'utf8'
379 db_connection = '%(db_connection)s?%(query)s' % {
380 'db_connection': db_connection,
381 'query': _urlencode(db_query_params)}
382 else:
383 db_connection = None
384 changes = {}
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300385 # There should be at least 1 component printed to cell_info
386 if len(cell_info) >= 1:
387 cell_info = dict(zip_longest(
388 ('cell_uuid', 'existing_transport_url', 'existing_db_connection'),
389 cell_info))
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200390 command_string = (
391 '--transport-url \'%(transport_url)s\' '
392 if transport_url else '' +
393 '--database_connection \'%(db_connection)s\''
394 if db_connection else '')
395 if cell_info['existing_transport_url'] != transport_url:
396 changes['transport_url'] = transport_url
397 if cell_info['existing_db_connection'] != db_connection:
398 changes['db_connection'] = db_connection
399 if not changes:
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300400 return _no_change(name, 'Nova cell')
401 try:
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200402 _cmd_raising_helper(
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300403 ('nova-manage cell_v2 update_cell --cell_uuid %s %s' % (
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200404 cell_info['cell_uuid'], command_string)) % {
405 'transport_url': transport_url,
406 'db_connection': db_connection}, test=test, runas=runas)
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300407 LOG.warning("Updating the transport_url or database_connection "
408 "fields on a running system will NOT result in all "
409 "nodes immediately using the new values. Use caution "
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200410 "when changing these values. You need to restart all "
411 "nova services on all controllers after this action.")
412 ret = _updated(name, 'Nova cell', changes)
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300413 except Exception as e:
414 ret = _update_failed(name, 'Nova cell')
415 ret['comment'] += '\nException: %s' % e
416 return ret
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200417 changes = {'transport_url': transport_url, 'db_connection': db_connection,
418 'name': name}
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300419 try:
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200420 _cmd_raising_helper((
421 'nova-manage cell_v2 create_cell --name %(name)s ' +
422 ('--transport-url \'%(transport_url)s\' ' if transport_url else '')
423 + ('--database_connection \'%(db_connection)s\' '
424 if db_connection else '') + '--verbose ') % changes,
425 test=test, runas=runas)
426 ret = _created(name, 'Nova cell', changes)
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300427 except Exception as e:
428 ret = _create_failed(name, 'Nova cell')
429 ret['comment'] += '\nException: %s' % e
430 return ret
431
432
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200433def cell_absent(name, force=False, runas='nova', test=False):
434 """Ensure cell is absent
435
436 :param name: name of the cell to delete.
437 :param force: force cell deletion even if it contains host mappings
438 (host entries will be removed as well).
439 :param runas: username to run the shell commands under.
440 :param test: if this is a test run, actual commands changing state won't
441 be run. False by default.
442 """
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300443 cell_uuid = __salt__['cmd.shell'](
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200444 "nova-manage cell_v2 list_cells 2>/dev/null | awk '/%s/ {print $4}'" %
445 name, runas=runas)
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300446 if not cell_uuid:
447 return _non_existent(name, 'Nova cell')
448 try:
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200449 # Note that if the cell contains any hosts, you need to pass force
450 # parameter for the deletion to succeed. Cell that has any instance
451 # mappings can not be deleted even with force.
452 _cmd_raising_helper(
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300453 'nova-manage cell_v2 delete_cell --cell_uuid %s %s' % (
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200454 cell_uuid, '--force' if force else ''), test=test, runas=runas)
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300455 ret = _deleted(name, 'Nova cell')
456 except Exception as e:
457 ret = _delete_failed(name, 'Nova cell')
458 ret['comment'] += '\nException: %s' % e
459 return ret
460
461
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200462def instances_mapped_to_cell(name, timeout=60, runas='nova'):
463 """Ensure that all instances in the cell are mapped
464
465 :param name: cell name.
466 :param timeout: amount of time in seconds mapping process should finish in.
467 :param runas: username to run the shell commands under.
468 """
Vladyslav Drok8a7631f2019-02-06 15:34:31 +0100469 test = __opts__.get('test', False)
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200470 cell_uuid = __salt__['cmd.shell'](
471 "nova-manage cell_v2 list_cells 2>/dev/null | "
472 "awk '/%s/ {print $4}'" % name, runas=runas)
473 result = {'name': name, 'changes': {}, 'result': False}
474 if not cell_uuid:
475 result['comment'] = (
476 'Failed to map all instances in cell {0}, it does not exist'
477 .format(name))
478 return result
479 start_time = time.time()
Vladyslav Drok8a7631f2019-02-06 15:34:31 +0100480 if not test:
481 while True:
482 rc = __salt__['cmd.retcode'](
483 'nova-manage cell_v2 map_instances --cell_uuid %s' % cell_uuid,
484 runas=runas)
485 if rc == 0 or time.time() - start_time > timeout:
486 break
487 if rc != 0:
488 result['comment'] = (
489 'Failed to map all instances in cell {0} in {1} seconds'
490 .format(name, timeout))
491 return result
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200492 result['comment'] = 'All instances mapped in cell {0}'.format(name)
Vladyslav Drok8a7631f2019-02-06 15:34:31 +0100493 if test:
494 result['comment'] = 'TEST: {}'.format(result['comment'])
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200495 result['result'] = True
496 return result
497
498
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300499def _db_version_update(db, version, human_readable_resource_name):
500 existing_version = __salt__['cmd.shell'](
501 'nova-manage %s version 2>/dev/null' % db)
502 try:
503 existing_version = int(existing_version)
504 version = int(version)
505 except Exception as e:
506 ret = _update_failed(existing_version,
507 human_readable_resource_name)
508 ret['comment'] += ('\nCan not convert existing or requested version '
509 'to integer, exception: %s' % e)
510 LOG.error(ret['comment'])
511 return ret
512 if existing_version < version:
513 try:
514 __salt__['cmd.shell'](
515 'nova-manage %s sync --version %s' % (db, version))
516 ret = _updated(existing_version, human_readable_resource_name,
517 {db: '%s sync --version %s' % (db, version)})
518 except Exception as e:
519 ret = _update_failed(existing_version,
520 human_readable_resource_name)
521 ret['comment'] += '\nException: %s' % e
522 return ret
523 return _no_change(existing_version, human_readable_resource_name)
524
525
526def api_db_version_present(name=None, version="20"):
527 """Ensures that specific api_db version is present"""
528 return _db_version_update('api_db', version, 'Nova API database version')
529
530
531def db_version_present(name=None, version="334"):
532 """Ensures that specific db version is present"""
533 return _db_version_update('db', version, 'Nova database version')
534
535
536def online_data_migrations_present(name=None, api_db_version="20",
537 db_version="334"):
538 """Runs online_data_migrations if databases are of specific versions"""
539 ret = {'name': 'online_data_migrations', 'changes': {}, 'result': False,
540 'comment': 'Current nova api_db version != {0} or nova db version '
541 '!= {1}.'.format(api_db_version, db_version)}
542 cur_api_db_version = __salt__['cmd.shell'](
543 'nova-manage api_db version 2>/dev/null')
544 cur_db_version = __salt__['cmd.shell'](
545 'nova-manage db version 2>/dev/null')
546 try:
547 cur_api_db_version = int(cur_api_db_version)
548 cur_db_version = int(cur_db_version)
549 api_db_version = int(api_db_version)
550 db_version = int(db_version)
551 except Exception as e:
552 LOG.error(ret['comment'])
553 ret['comment'] = ('\nCan not convert existing or requested database '
554 'versions to integer, exception: %s' % e)
555 return ret
556 if cur_api_db_version == api_db_version and cur_db_version == db_version:
557 try:
558 __salt__['cmd.shell']('nova-manage db online_data_migrations')
559 ret['result'] = True
560 ret['comment'] = ('nova-manage db online_data_migrations was '
561 'executed successfuly')
562 ret['changes']['online_data_migrations'] = (
563 'online_data_migrations run on nova api_db version {0} and '
564 'nova db version {1}'.format(api_db_version, db_version))
565 except Exception as e:
566 ret['comment'] = (
567 'Failed to execute online_data_migrations on nova api_db '
568 'version %s and nova db version %s, exception: %s' % (
569 api_db_version, db_version, e))
570 return ret
571
572
Oleh Hryhorov5cfb9d32018-09-11 16:55:24 +0000573@_error_handler
574def service_enabled(name, cloud_name, binary="nova-compute"):
575 """Ensures that the service is enabled on the host
576
577 :param name: name of a host where service is running
578 :param service: name of the service have to be run
579 """
580 changes = {}
581
582 services = _call_nova_salt_module('services_list', name)(
Oleksandr Shyshkoc74c4772018-11-29 15:17:34 +0000583 name, binary=binary, cloud_name=cloud_name)
Oleh Hryhorov5cfb9d32018-09-11 16:55:24 +0000584 enabled_service = [s for s in services if s['binary'] == binary
585 and s['status'] == 'enabled' and s['host'] == name]
586 if len(enabled_service) > 0:
587 ret = _no_change(name, 'Compute services')
588 else:
589 changes = _call_nova_salt_module('services_update', name)(
590 name, binary, 'enable', cloud_name=cloud_name)
591 ret = _updated(name, 'Compute services', changes)
592
593 return ret
594
595@_error_handler
596def service_disabled(name, cloud_name, binary="nova-compute", disabled_reason=None):
597 """Ensures that the service is disabled on the host
598
599 :param name: name of a host where service is running
600 :param service: name of the service have to be disabled
601 """
602
603 changes = {}
604 kwargs = {}
605
606 if disabled_reason is not None:
607 kwargs['disabled_reason'] = disabled_reason
608
609 services = _call_nova_salt_module('services_list', name)(
Oleksandr Shyshkoc74c4772018-11-29 15:17:34 +0000610 name, binary=binary, cloud_name=cloud_name)
Oleh Hryhorov5cfb9d32018-09-11 16:55:24 +0000611 disabled_service = [s for s in services if s['binary'] == binary
612 and s['status'] == 'disabled' and s['host'] == name]
613 if len(disabled_service) > 0:
614 ret = _no_change(name, 'Compute services')
615 else:
616 changes = _call_nova_salt_module('services_update', name)(
617 name, binary, 'disable', cloud_name=cloud_name, **kwargs)
618 ret = _updated(name, 'Compute services', changes)
619
620 return ret
621
622
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300623def _find_failed(name, resource):
624 return {
625 'name': name, 'changes': {}, 'result': False,
626 'comment': 'Failed to find {0}s with name {1}'.format(resource, name)}
627
628
629def _created(name, resource, changes):
630 return {
631 'name': name, 'changes': changes, 'result': True,
632 'comment': '{0} {1} created'.format(resource, name)}
633
634
635def _create_failed(name, resource):
636 return {
637 'name': name, 'changes': {}, 'result': False,
638 'comment': '{0} {1} creation failed'.format(resource, name)}
639
640
641def _no_change(name, resource):
642 return {
643 'name': name, 'changes': {}, 'result': True,
644 'comment': '{0} {1} already is in the desired state'.format(
645 resource, name)}
646
647
648def _updated(name, resource, changes):
649 return {
650 'name': name, 'changes': changes, 'result': True,
651 'comment': '{0} {1} was updated'.format(resource, name)}
652
653
654def _update_failed(name, resource):
655 return {
656 'name': name, 'changes': {}, 'result': False,
657 'comment': '{0} {1} update failed'.format(resource, name)}
658
659
660def _deleted(name, resource):
661 return {
662 'name': name, 'changes': {}, 'result': True,
663 'comment': '{0} {1} deleted'.format(resource, name)}
664
665
666def _delete_failed(name, resource):
667 return {
668 'name': name, 'changes': {}, 'result': False,
669 'comment': '{0} {1} deletion failed'.format(resource, name)}
670
671
672def _non_existent(name, resource):
673 return {
674 'name': name, 'changes': {}, 'result': True,
675 'comment': '{0} {1} does not exist'.format(resource, name)}