blob: ffda7d1d76b37aa2bfce82cd2c6602c20aab9f9d [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 """
469 cell_uuid = __salt__['cmd.shell'](
470 "nova-manage cell_v2 list_cells 2>/dev/null | "
471 "awk '/%s/ {print $4}'" % name, runas=runas)
472 result = {'name': name, 'changes': {}, 'result': False}
473 if not cell_uuid:
474 result['comment'] = (
475 'Failed to map all instances in cell {0}, it does not exist'
476 .format(name))
477 return result
478 start_time = time.time()
479 while True:
480 rc = __salt__['cmd.retcode']('nova-manage cell_v2 map_instances '
481 '--cell_uuid %s' % cell_uuid, runas=runas)
482 if rc == 0 or time.time() - start_time > timeout:
483 break
484 if rc != 0:
485 result['comment'] = (
486 'Failed to map all instances in cell {0} in {1} seconds'
487 .format(name, timeout))
488 return result
489 result['comment'] = 'All instances mapped in cell {0}'.format(name)
490 result['result'] = True
491 return result
492
493
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300494def _db_version_update(db, version, human_readable_resource_name):
495 existing_version = __salt__['cmd.shell'](
496 'nova-manage %s version 2>/dev/null' % db)
497 try:
498 existing_version = int(existing_version)
499 version = int(version)
500 except Exception as e:
501 ret = _update_failed(existing_version,
502 human_readable_resource_name)
503 ret['comment'] += ('\nCan not convert existing or requested version '
504 'to integer, exception: %s' % e)
505 LOG.error(ret['comment'])
506 return ret
507 if existing_version < version:
508 try:
509 __salt__['cmd.shell'](
510 'nova-manage %s sync --version %s' % (db, version))
511 ret = _updated(existing_version, human_readable_resource_name,
512 {db: '%s sync --version %s' % (db, version)})
513 except Exception as e:
514 ret = _update_failed(existing_version,
515 human_readable_resource_name)
516 ret['comment'] += '\nException: %s' % e
517 return ret
518 return _no_change(existing_version, human_readable_resource_name)
519
520
521def api_db_version_present(name=None, version="20"):
522 """Ensures that specific api_db version is present"""
523 return _db_version_update('api_db', version, 'Nova API database version')
524
525
526def db_version_present(name=None, version="334"):
527 """Ensures that specific db version is present"""
528 return _db_version_update('db', version, 'Nova database version')
529
530
531def online_data_migrations_present(name=None, api_db_version="20",
532 db_version="334"):
533 """Runs online_data_migrations if databases are of specific versions"""
534 ret = {'name': 'online_data_migrations', 'changes': {}, 'result': False,
535 'comment': 'Current nova api_db version != {0} or nova db version '
536 '!= {1}.'.format(api_db_version, db_version)}
537 cur_api_db_version = __salt__['cmd.shell'](
538 'nova-manage api_db version 2>/dev/null')
539 cur_db_version = __salt__['cmd.shell'](
540 'nova-manage db version 2>/dev/null')
541 try:
542 cur_api_db_version = int(cur_api_db_version)
543 cur_db_version = int(cur_db_version)
544 api_db_version = int(api_db_version)
545 db_version = int(db_version)
546 except Exception as e:
547 LOG.error(ret['comment'])
548 ret['comment'] = ('\nCan not convert existing or requested database '
549 'versions to integer, exception: %s' % e)
550 return ret
551 if cur_api_db_version == api_db_version and cur_db_version == db_version:
552 try:
553 __salt__['cmd.shell']('nova-manage db online_data_migrations')
554 ret['result'] = True
555 ret['comment'] = ('nova-manage db online_data_migrations was '
556 'executed successfuly')
557 ret['changes']['online_data_migrations'] = (
558 'online_data_migrations run on nova api_db version {0} and '
559 'nova db version {1}'.format(api_db_version, db_version))
560 except Exception as e:
561 ret['comment'] = (
562 'Failed to execute online_data_migrations on nova api_db '
563 'version %s and nova db version %s, exception: %s' % (
564 api_db_version, db_version, e))
565 return ret
566
567
Oleh Hryhorov5cfb9d32018-09-11 16:55:24 +0000568@_error_handler
569def service_enabled(name, cloud_name, binary="nova-compute"):
570 """Ensures that the service is enabled on the host
571
572 :param name: name of a host where service is running
573 :param service: name of the service have to be run
574 """
575 changes = {}
576
577 services = _call_nova_salt_module('services_list', name)(
Oleksandr Shyshkoc74c4772018-11-29 15:17:34 +0000578 name, binary=binary, cloud_name=cloud_name)
Oleh Hryhorov5cfb9d32018-09-11 16:55:24 +0000579 enabled_service = [s for s in services if s['binary'] == binary
580 and s['status'] == 'enabled' and s['host'] == name]
581 if len(enabled_service) > 0:
582 ret = _no_change(name, 'Compute services')
583 else:
584 changes = _call_nova_salt_module('services_update', name)(
585 name, binary, 'enable', cloud_name=cloud_name)
586 ret = _updated(name, 'Compute services', changes)
587
588 return ret
589
590@_error_handler
591def service_disabled(name, cloud_name, binary="nova-compute", disabled_reason=None):
592 """Ensures that the service is disabled on the host
593
594 :param name: name of a host where service is running
595 :param service: name of the service have to be disabled
596 """
597
598 changes = {}
599 kwargs = {}
600
601 if disabled_reason is not None:
602 kwargs['disabled_reason'] = disabled_reason
603
604 services = _call_nova_salt_module('services_list', name)(
Oleksandr Shyshkoc74c4772018-11-29 15:17:34 +0000605 name, binary=binary, cloud_name=cloud_name)
Oleh Hryhorov5cfb9d32018-09-11 16:55:24 +0000606 disabled_service = [s for s in services if s['binary'] == binary
607 and s['status'] == 'disabled' and s['host'] == name]
608 if len(disabled_service) > 0:
609 ret = _no_change(name, 'Compute services')
610 else:
611 changes = _call_nova_salt_module('services_update', name)(
612 name, binary, 'disable', cloud_name=cloud_name, **kwargs)
613 ret = _updated(name, 'Compute services', changes)
614
615 return ret
616
617
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300618def _find_failed(name, resource):
619 return {
620 'name': name, 'changes': {}, 'result': False,
621 'comment': 'Failed to find {0}s with name {1}'.format(resource, name)}
622
623
624def _created(name, resource, changes):
625 return {
626 'name': name, 'changes': changes, 'result': True,
627 'comment': '{0} {1} created'.format(resource, name)}
628
629
630def _create_failed(name, resource):
631 return {
632 'name': name, 'changes': {}, 'result': False,
633 'comment': '{0} {1} creation failed'.format(resource, name)}
634
635
636def _no_change(name, resource):
637 return {
638 'name': name, 'changes': {}, 'result': True,
639 'comment': '{0} {1} already is in the desired state'.format(
640 resource, name)}
641
642
643def _updated(name, resource, changes):
644 return {
645 'name': name, 'changes': changes, 'result': True,
646 'comment': '{0} {1} was updated'.format(resource, name)}
647
648
649def _update_failed(name, resource):
650 return {
651 'name': name, 'changes': {}, 'result': False,
652 'comment': '{0} {1} update failed'.format(resource, name)}
653
654
655def _deleted(name, resource):
656 return {
657 'name': name, 'changes': {}, 'result': True,
658 'comment': '{0} {1} deleted'.format(resource, name)}
659
660
661def _delete_failed(name, resource):
662 return {
663 'name': name, 'changes': {}, 'result': False,
664 'comment': '{0} {1} deletion failed'.format(resource, name)}
665
666
667def _non_existent(name, resource):
668 return {
669 'name': name, 'changes': {}, 'result': True,
670 'comment': '{0} {1} does not exist'.format(resource, name)}