blob: 0d8d76d4d00086c3b2f83a4c4a039c5c40904db2 [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())
Dzmitry Stremkouski8babb812020-02-16 17:02:44 +0100212 if hosts and (existing_meta - requested_meta or requested_meta - existing_meta):
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300213 _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)
Dzmitry Stremkouski8babb812020-02-16 17:02:44 +0100218 if hosts and (hosts_to_remove or hosts_to_add):
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300219 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 Drok13c1fef2019-12-10 12:01:19 +0100462def instances_mapped_to_cell(name, max_count=None, timeout=60, runas='nova'):
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200463 """Ensure that all instances in the cell are mapped
464
465 :param name: cell name.
Vladyslav Drok13c1fef2019-12-10 12:01:19 +0100466 :param max_count: how many instances to map in one iteration. If there are
467 lots of instances present in the cell database, consider setting higher
468 value. By default nova will run in batches of 50.
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200469 :param timeout: amount of time in seconds mapping process should finish in.
470 :param runas: username to run the shell commands under.
471 """
Vladyslav Drok8a7631f2019-02-06 15:34:31 +0100472 test = __opts__.get('test', False)
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200473 cell_uuid = __salt__['cmd.shell'](
474 "nova-manage cell_v2 list_cells 2>/dev/null | "
475 "awk '/%s/ {print $4}'" % name, runas=runas)
476 result = {'name': name, 'changes': {}, 'result': False}
477 if not cell_uuid:
478 result['comment'] = (
479 'Failed to map all instances in cell {0}, it does not exist'
480 .format(name))
481 return result
Vladyslav Drok13c1fef2019-12-10 12:01:19 +0100482 command = 'nova-manage cell_v2 map_instances --cell_uuid %s' % cell_uuid
483 if max_count:
484 command += ' --max-count %s' % max_count
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200485 start_time = time.time()
Vladyslav Drok8a7631f2019-02-06 15:34:31 +0100486 if not test:
487 while True:
Vladyslav Drok13c1fef2019-12-10 12:01:19 +0100488 rc = __salt__['cmd.retcode'](command, runas=runas)
Vladyslav Drok8a7631f2019-02-06 15:34:31 +0100489 if rc == 0 or time.time() - start_time > timeout:
490 break
491 if rc != 0:
492 result['comment'] = (
493 'Failed to map all instances in cell {0} in {1} seconds'
494 .format(name, timeout))
495 return result
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200496 result['comment'] = 'All instances mapped in cell {0}'.format(name)
Vladyslav Drok8a7631f2019-02-06 15:34:31 +0100497 if test:
498 result['comment'] = 'TEST: {}'.format(result['comment'])
Vladyslav Drokd182bf12019-01-11 14:02:38 +0200499 result['result'] = True
500 return result
501
502
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300503def _db_version_update(db, version, human_readable_resource_name):
504 existing_version = __salt__['cmd.shell'](
505 'nova-manage %s version 2>/dev/null' % db)
506 try:
507 existing_version = int(existing_version)
508 version = int(version)
509 except Exception as e:
510 ret = _update_failed(existing_version,
511 human_readable_resource_name)
512 ret['comment'] += ('\nCan not convert existing or requested version '
513 'to integer, exception: %s' % e)
514 LOG.error(ret['comment'])
515 return ret
516 if existing_version < version:
517 try:
518 __salt__['cmd.shell'](
519 'nova-manage %s sync --version %s' % (db, version))
520 ret = _updated(existing_version, human_readable_resource_name,
521 {db: '%s sync --version %s' % (db, version)})
522 except Exception as e:
523 ret = _update_failed(existing_version,
524 human_readable_resource_name)
525 ret['comment'] += '\nException: %s' % e
526 return ret
527 return _no_change(existing_version, human_readable_resource_name)
528
529
530def api_db_version_present(name=None, version="20"):
531 """Ensures that specific api_db version is present"""
532 return _db_version_update('api_db', version, 'Nova API database version')
533
534
535def db_version_present(name=None, version="334"):
536 """Ensures that specific db version is present"""
537 return _db_version_update('db', version, 'Nova database version')
538
539
540def online_data_migrations_present(name=None, api_db_version="20",
541 db_version="334"):
542 """Runs online_data_migrations if databases are of specific versions"""
543 ret = {'name': 'online_data_migrations', 'changes': {}, 'result': False,
544 'comment': 'Current nova api_db version != {0} or nova db version '
545 '!= {1}.'.format(api_db_version, db_version)}
546 cur_api_db_version = __salt__['cmd.shell'](
547 'nova-manage api_db version 2>/dev/null')
548 cur_db_version = __salt__['cmd.shell'](
549 'nova-manage db version 2>/dev/null')
550 try:
551 cur_api_db_version = int(cur_api_db_version)
552 cur_db_version = int(cur_db_version)
553 api_db_version = int(api_db_version)
554 db_version = int(db_version)
555 except Exception as e:
556 LOG.error(ret['comment'])
557 ret['comment'] = ('\nCan not convert existing or requested database '
558 'versions to integer, exception: %s' % e)
559 return ret
560 if cur_api_db_version == api_db_version and cur_db_version == db_version:
561 try:
562 __salt__['cmd.shell']('nova-manage db online_data_migrations')
563 ret['result'] = True
564 ret['comment'] = ('nova-manage db online_data_migrations was '
565 'executed successfuly')
566 ret['changes']['online_data_migrations'] = (
567 'online_data_migrations run on nova api_db version {0} and '
568 'nova db version {1}'.format(api_db_version, db_version))
569 except Exception as e:
570 ret['comment'] = (
571 'Failed to execute online_data_migrations on nova api_db '
572 'version %s and nova db version %s, exception: %s' % (
573 api_db_version, db_version, e))
574 return ret
575
576
Oleh Hryhorov5cfb9d32018-09-11 16:55:24 +0000577@_error_handler
578def service_enabled(name, cloud_name, binary="nova-compute"):
579 """Ensures that the service is enabled on the host
580
581 :param name: name of a host where service is running
582 :param service: name of the service have to be run
583 """
584 changes = {}
585
586 services = _call_nova_salt_module('services_list', name)(
Oleksandr Shyshkoc74c4772018-11-29 15:17:34 +0000587 name, binary=binary, cloud_name=cloud_name)
Oleh Hryhorov5cfb9d32018-09-11 16:55:24 +0000588 enabled_service = [s for s in services if s['binary'] == binary
589 and s['status'] == 'enabled' and s['host'] == name]
590 if len(enabled_service) > 0:
591 ret = _no_change(name, 'Compute services')
592 else:
593 changes = _call_nova_salt_module('services_update', name)(
594 name, binary, 'enable', cloud_name=cloud_name)
595 ret = _updated(name, 'Compute services', changes)
596
597 return ret
598
599@_error_handler
600def service_disabled(name, cloud_name, binary="nova-compute", disabled_reason=None):
601 """Ensures that the service is disabled on the host
602
603 :param name: name of a host where service is running
604 :param service: name of the service have to be disabled
605 """
606
607 changes = {}
608 kwargs = {}
609
610 if disabled_reason is not None:
611 kwargs['disabled_reason'] = disabled_reason
612
613 services = _call_nova_salt_module('services_list', name)(
Oleksandr Shyshkoc74c4772018-11-29 15:17:34 +0000614 name, binary=binary, cloud_name=cloud_name)
Oleh Hryhorov5cfb9d32018-09-11 16:55:24 +0000615 disabled_service = [s for s in services if s['binary'] == binary
616 and s['status'] == 'disabled' and s['host'] == name]
617 if len(disabled_service) > 0:
618 ret = _no_change(name, 'Compute services')
619 else:
620 changes = _call_nova_salt_module('services_update', name)(
621 name, binary, 'disable', cloud_name=cloud_name, **kwargs)
622 ret = _updated(name, 'Compute services', changes)
623
624 return ret
625
626
Vladyslav Drokcb8d0fb2018-06-27 19:28:14 +0300627def _find_failed(name, resource):
628 return {
629 'name': name, 'changes': {}, 'result': False,
630 'comment': 'Failed to find {0}s with name {1}'.format(resource, name)}
631
632
633def _created(name, resource, changes):
634 return {
635 'name': name, 'changes': changes, 'result': True,
636 'comment': '{0} {1} created'.format(resource, name)}
637
638
639def _create_failed(name, resource):
640 return {
641 'name': name, 'changes': {}, 'result': False,
642 'comment': '{0} {1} creation failed'.format(resource, name)}
643
644
645def _no_change(name, resource):
646 return {
647 'name': name, 'changes': {}, 'result': True,
648 'comment': '{0} {1} already is in the desired state'.format(
649 resource, name)}
650
651
652def _updated(name, resource, changes):
653 return {
654 'name': name, 'changes': changes, 'result': True,
655 'comment': '{0} {1} was updated'.format(resource, name)}
656
657
658def _update_failed(name, resource):
659 return {
660 'name': name, 'changes': {}, 'result': False,
661 'comment': '{0} {1} update failed'.format(resource, name)}
662
663
664def _deleted(name, resource):
665 return {
666 'name': name, 'changes': {}, 'result': True,
667 'comment': '{0} {1} deleted'.format(resource, name)}
668
669
670def _delete_failed(name, resource):
671 return {
672 'name': name, 'changes': {}, 'result': False,
673 'comment': '{0} {1} deletion failed'.format(resource, name)}
674
675
676def _non_existent(name, resource):
677 return {
678 'name': name, 'changes': {}, 'result': True,
679 'comment': '{0} {1} does not exist'.format(resource, name)}