blob: 0c1ffe5d83295b6c8f10aa995996575ca719c01b [file] [log] [blame]
Jiri Broulika2c79292017-02-05 21:01:38 +01001# -*- coding: utf-8 -*-
Adam Tenglere8afccc2017-06-27 17:57:21 +00002from __future__ import absolute_import, with_statement
Jiri Broulika2c79292017-02-05 21:01:38 +01003from pprint import pprint
4
5# Import python libs
6import logging
7
Jiri Broulika2c79292017-02-05 21:01:38 +01008# Get logging started
9log = logging.getLogger(__name__)
10
11# Function alias to not shadow built-ins
12__func_alias__ = {
13 'list_': 'list'
14}
15
16# Define the module's virtual name
17__virtualname__ = 'novang'
18
19
20def __virtual__():
21 '''
22 Only load this module if nova
23 is installed on this minion.
24 '''
Adam Tenglere8afccc2017-06-27 17:57:21 +000025 if check_nova():
Jiri Broulika2c79292017-02-05 21:01:38 +010026 return __virtualname__
27 return (False, 'The nova execution module failed to load: '
28 'only available if nova is installed.')
29
30
31__opts__ = {}
32
33
Adam Tenglere8afccc2017-06-27 17:57:21 +000034def _authng(profile=None, tenant_name=None):
Jiri Broulika2c79292017-02-05 21:01:38 +010035 '''
36 Set up nova credentials
37 '''
38 if profile:
39 credentials = __salt__['config.option'](profile)
40 user = credentials['keystone.user']
41 password = credentials['keystone.password']
Richard Felkl55d1f572017-02-15 16:41:53 +010042 if tenant_name:
43 tenant = tenant_name
44 else:
45 tenant = credentials['keystone.tenant']
Jiri Broulika2c79292017-02-05 21:01:38 +010046 auth_url = credentials['keystone.auth_url']
47 region_name = credentials.get('keystone.region_name', None)
48 api_key = credentials.get('keystone.api_key', None)
49 os_auth_system = credentials.get('keystone.os_auth_system', None)
Adam Tenglere8afccc2017-06-27 17:57:21 +000050 use_keystoneauth = credentials.get('keystone.use_keystoneauth', False)
51 verify = credentials.get('keystone.verify', True)
Jiri Broulika2c79292017-02-05 21:01:38 +010052 else:
53 user = __salt__['config.option']('keystone.user')
54 password = __salt__['config.option']('keystone.password')
55 tenant = __salt__['config.option']('keystone.tenant')
56 auth_url = __salt__['config.option']('keystone.auth_url')
57 region_name = __salt__['config.option']('keystone.region_name')
58 api_key = __salt__['config.option']('keystone.api_key')
59 os_auth_system = __salt__['config.option']('keystone.os_auth_system')
Adam Tenglere8afccc2017-06-27 17:57:21 +000060 use_keystoneauth = __salt__['config.option']('keystone.use_keystoneauth', False)
61 verify = __salt__['config.option']('keystone.verify', True)
Jiri Broulika2c79292017-02-05 21:01:38 +010062 kwargs = {
63 'username': user,
64 'password': password,
65 'api_key': api_key,
66 'project_id': tenant,
67 'auth_url': auth_url,
68 'region_name': region_name,
Adam Tenglere8afccc2017-06-27 17:57:21 +000069 'os_auth_plugin': os_auth_system,
70 'use_keystoneauth': use_keystoneauth,
71 'verify': verify
Jiri Broulika2c79292017-02-05 21:01:38 +010072 }
Adam Tenglere8afccc2017-06-27 17:57:21 +000073 return SaltNova(**kwargs)
Jiri Broulika2c79292017-02-05 21:01:38 +010074
75
Jiri Broulik70d9e3f2017-02-15 18:37:13 +010076def server_list(profile=None, tenant_name=None):
77 '''
78 Return list of active servers
79 CLI Example:
80 .. code-block:: bash
81 salt '*' nova.server_list
82 '''
Adam Tenglere8afccc2017-06-27 17:57:21 +000083 conn = _authng(profile, tenant_name)
Jiri Broulik70d9e3f2017-02-15 18:37:13 +010084 return conn.server_list()
85
86
87def server_get(name, tenant_name=None, profile=None):
88 '''
89 Return information about a server
90 '''
91 items = server_list(profile, tenant_name)
92 instance_id = None
93 for key, value in items.iteritems():
94 if key == name:
95 instance_id = value['id']
96 return instance_id
97
98
Jiri Broulika2c79292017-02-05 21:01:38 +010099def get_connection_args(profile=None):
100 '''
101 Set up profile credentials
102 '''
103 if profile:
104 credentials = __salt__['config.option'](profile)
105 user = credentials['keystone.user']
106 password = credentials['keystone.password']
107 tenant = credentials['keystone.tenant']
108 auth_url = credentials['keystone.auth_url']
109
110 kwargs = {
111 'username': user,
112 'password': password,
113 'tenant': tenant,
114 'auth_url': auth_url
115 }
116 return kwargs
117
118
119def quota_list(tenant_name, profile=None):
120 '''
121 list quotas of a tenant
122 '''
123 connection_args = get_connection_args(profile)
124 tenant = __salt__['keystone.tenant_get'](name=tenant_name, profile=profile, **connection_args)
125 tenant_id = tenant[tenant_name]['id']
Adam Tenglere8afccc2017-06-27 17:57:21 +0000126 conn = _authng(profile)
Jiri Broulika2c79292017-02-05 21:01:38 +0100127 nt_ks = conn.compute_conn
128 item = nt_ks.quotas.get(tenant_id).__dict__
129 return item
130
131
132def quota_get(name, tenant_name, profile=None, quota_value=None):
133 '''
134 get specific quota value of a tenant
135 '''
136 item = quota_list(tenant_name, profile)
137 quota_value = item[name]
138 return quota_value
139
140
141def quota_update(tenant_name, profile=None, **quota_argument):
142 '''
143 update quota of specified tenant
144 '''
145 connection_args = get_connection_args(profile)
146 tenant = __salt__['keystone.tenant_get'](name=tenant_name, profile=profile, **connection_args)
147 tenant_id = tenant[tenant_name]['id']
Adam Tenglere8afccc2017-06-27 17:57:21 +0000148 conn = _authng(profile)
Jiri Broulika2c79292017-02-05 21:01:38 +0100149 nt_ks = conn.compute_conn
150 item = nt_ks.quotas.update(tenant_id, **quota_argument)
151 return item
Jiri Broulik70d9e3f2017-02-15 18:37:13 +0100152
153
Richard Felkl55d1f572017-02-15 16:41:53 +0100154def server_list(profile=None, tenant_name=None):
155 '''
156 Return list of active servers
157 CLI Example:
158 .. code-block:: bash
159 salt '*' nova.server_list
160 '''
Adam Tenglere8afccc2017-06-27 17:57:21 +0000161 conn = _authng(profile, tenant_name)
Richard Felkl55d1f572017-02-15 16:41:53 +0100162 return conn.server_list()
Jiri Broulika2c79292017-02-05 21:01:38 +0100163
Jiri Broulik70d9e3f2017-02-15 18:37:13 +0100164
Richard Felkl55d1f572017-02-15 16:41:53 +0100165def secgroup_list(profile=None, tenant_name=None):
166 '''
167 Return a list of available security groups (nova items-list)
168 CLI Example:
169 .. code-block:: bash
170 salt '*' nova.secgroup_list
171 '''
Adam Tenglere8afccc2017-06-27 17:57:21 +0000172 conn = _authng(profile, tenant_name)
Richard Felkl55d1f572017-02-15 16:41:53 +0100173 return conn.secgroup_list()
Jiri Broulika2c79292017-02-05 21:01:38 +0100174
Jiri Broulik70d9e3f2017-02-15 18:37:13 +0100175
Richard Felkl55d1f572017-02-15 16:41:53 +0100176def boot(name, flavor_id=0, image_id=0, profile=None, tenant_name=None, timeout=300, **kwargs):
177 '''
178 Boot (create) a new instance
179 name
180 Name of the new instance (must be first)
181 flavor_id
182 Unique integer ID for the flavor
183 image_id
184 Unique integer ID for the image
185 timeout
186 How long to wait, after creating the instance, for the provider to
187 return information about it (default 300 seconds).
188 .. versionadded:: 2014.1.0
189 CLI Example:
190 .. code-block:: bash
191 salt '*' nova.boot myinstance flavor_id=4596 image_id=2
192 The flavor_id and image_id are obtained from nova.flavor_list and
193 nova.image_list
194 .. code-block:: bash
195 salt '*' nova.flavor_list
196 salt '*' nova.image_list
197 '''
198 #kwargs = {'nics': nics}
Adam Tenglere8afccc2017-06-27 17:57:21 +0000199 conn = _authng(profile, tenant_name)
Richard Felkl55d1f572017-02-15 16:41:53 +0100200 return conn.boot(name, flavor_id, image_id, timeout, **kwargs)
Jiri Broulik70d9e3f2017-02-15 18:37:13 +0100201
202
Richard Felkl55d1f572017-02-15 16:41:53 +0100203def network_show(name, profile=None):
Adam Tenglere8afccc2017-06-27 17:57:21 +0000204 conn = _authng(profile)
Richard Felkl55d1f572017-02-15 16:41:53 +0100205 return conn.network_show(name)
Jiri Broulik70d9e3f2017-02-15 18:37:13 +0100206
207
208def availability_zone_list(profile=None):
209 '''
210 list existing availability zones
211 '''
212 connection_args = get_connection_args(profile)
Adam Tenglere8afccc2017-06-27 17:57:21 +0000213 conn = _authng(profile)
Jiri Broulik70d9e3f2017-02-15 18:37:13 +0100214 nt_ks = conn.compute_conn
215 ret = nt_ks.aggregates.list()
216 return ret
217
218
219def availability_zone_get(name, profile=None):
220 '''
221 list existing availability zones
222 '''
223 connection_args = get_connection_args(profile)
Adam Tenglere8afccc2017-06-27 17:57:21 +0000224 conn = _authng(profile)
Jiri Broulik70d9e3f2017-02-15 18:37:13 +0100225 nt_ks = conn.compute_conn
226 zone_exists=False
227 items = availability_zone_list(profile)
228 for p in items:
229 item = nt_ks.aggregates.get(p).__getattr__('name')
230 if item == name:
231 zone_exists = True
232 return zone_exists
233
234
235def availability_zone_create(name, availability_zone, profile=None):
236 '''
237 create availability zone
238 '''
239 connection_args = get_connection_args(profile)
Adam Tenglere8afccc2017-06-27 17:57:21 +0000240 conn = _authng(profile)
Jiri Broulik70d9e3f2017-02-15 18:37:13 +0100241 nt_ks = conn.compute_conn
242 item = nt_ks.aggregates.create(name, availability_zone)
243 ret = {
244 'Id': item.__getattr__('id'),
245 'Aggregate Name': item.__getattr__('name'),
246 'Availability Zone': item.__getattr__('availability_zone'),
247 }
248 return ret
Damian Szeluga5dca0f02017-04-13 17:27:15 +0200249
250def aggregate_list(profile=None):
251 '''
252 list existing aggregates
253 '''
254 connection_args = get_connection_args(profile)
Adam Tenglere8afccc2017-06-27 17:57:21 +0000255 conn = _authng(profile)
Damian Szeluga5dca0f02017-04-13 17:27:15 +0200256 nt_ks = conn.compute_conn
257 ret = nt_ks.aggregates.list()
258 return ret
259
260
261def aggregate_get(name, profile=None):
262 '''
263 list existing aggregates
264 '''
265 connection_args = get_connection_args(profile)
Adam Tenglere8afccc2017-06-27 17:57:21 +0000266 conn = _authng(profile)
Damian Szeluga5dca0f02017-04-13 17:27:15 +0200267 nt_ks = conn.compute_conn
268 aggregate_exists=False
269 items = aggregate_list(profile)
270 for p in items:
271 item = nt_ks.aggregates.get(p).__getattr__('name')
272 if item == name:
273 aggregate_exists = True
274 return aggregate_exists
275
276
277def aggregate_create(name, aggregate, profile=None):
278 '''
279 create aggregate
280 '''
281 connection_args = get_connection_args(profile)
Adam Tenglere8afccc2017-06-27 17:57:21 +0000282 conn = _authng(profile)
Damian Szeluga5dca0f02017-04-13 17:27:15 +0200283 nt_ks = conn.compute_conn
284 item = nt_ks.aggregates.create(name, aggregate)
285 ret = {
286 'Id': item.__getattr__('id'),
287 'Aggregate Name': item.__getattr__('name'),
288 }
289 return ret
Adam Tenglere8afccc2017-06-27 17:57:21 +0000290
291#
292# Moved from salt.utils.openstack.nova until this works in upstream
293#
294
295'''
296Nova class
297'''
298
299# Import Python libs
300import inspect
301import time
302
303from distutils.version import LooseVersion as _LooseVersion
304
305# Import third party libs
306import salt.ext.six as six
307HAS_NOVA = False
308# pylint: disable=import-error
309try:
310 import novaclient
311 from novaclient import client
312 from novaclient.shell import OpenStackComputeShell
313 import novaclient.utils
314 import novaclient.exceptions
315 import novaclient.extension
316 import novaclient.base
317 HAS_NOVA = True
318except ImportError:
319 pass
320
321OCATA = True
322try:
323 import novaclient.auth_plugin
324 OCATA = False
325except ImportError:
326 pass
327
328HAS_KEYSTONEAUTH = False
329try:
330 import keystoneauth1.loading
331 import keystoneauth1.session
332 HAS_KEYSTONEAUTH = True
333except ImportError:
334 pass
335# pylint: enable=import-error
336
337# Import salt libs
338import salt.utils
339from salt.exceptions import SaltCloudSystemExit
340
341# Version added to novaclient.client.Client function
342NOVACLIENT_MINVER = '2.6.1'
343
344# dict for block_device_mapping_v2
345CLIENT_BDM2_KEYS = {
346 'id': 'uuid',
347 'source': 'source_type',
348 'dest': 'destination_type',
349 'bus': 'disk_bus',
350 'device': 'device_name',
351 'size': 'volume_size',
352 'format': 'guest_format',
353 'bootindex': 'boot_index',
354 'type': 'device_type',
355 'shutdown': 'delete_on_termination',
356}
357
358
359def check_nova():
360 if HAS_NOVA:
361 novaclient_ver = _LooseVersion(novaclient.__version__)
362 min_ver = _LooseVersion(NOVACLIENT_MINVER)
363 if novaclient_ver >= min_ver:
364 return HAS_NOVA
365 log.debug('Newer novaclient version required. Minimum: {0}'.format(NOVACLIENT_MINVER))
366 return False
367
368
369# kwargs has to be an object instead of a dictionary for the __post_parse_arg__
370class KwargsStruct(object):
371 def __init__(self, **entries):
372 self.__dict__.update(entries)
373
374
375def _parse_block_device_mapping_v2(block_device=None, boot_volume=None, snapshot=None, ephemeral=None, swap=None):
376 bdm = []
377 if block_device is None:
378 block_device = []
379 if ephemeral is None:
380 ephemeral = []
381
382 if boot_volume is not None:
383 bdm_dict = {'uuid': boot_volume, 'source_type': 'volume',
384 'destination_type': 'volume', 'boot_index': 0,
385 'delete_on_termination': False}
386 bdm.append(bdm_dict)
387
388 if snapshot is not None:
389 bdm_dict = {'uuid': snapshot, 'source_type': 'snapshot',
390 'destination_type': 'volume', 'boot_index': 0,
391 'delete_on_termination': False}
392 bdm.append(bdm_dict)
393
394 for device_spec in block_device:
395 bdm_dict = {}
396
397 for key, value in six.iteritems(device_spec):
398 bdm_dict[CLIENT_BDM2_KEYS[key]] = value
399
400 # Convert the delete_on_termination to a boolean or set it to true by
401 # default for local block devices when not specified.
402 if 'delete_on_termination' in bdm_dict:
403 action = bdm_dict['delete_on_termination']
404 bdm_dict['delete_on_termination'] = (action == 'remove')
405 elif bdm_dict.get('destination_type') == 'local':
406 bdm_dict['delete_on_termination'] = True
407
408 bdm.append(bdm_dict)
409
410 for ephemeral_spec in ephemeral:
411 bdm_dict = {'source_type': 'blank', 'destination_type': 'local',
412 'boot_index': -1, 'delete_on_termination': True}
413 if 'size' in ephemeral_spec:
414 bdm_dict['volume_size'] = ephemeral_spec['size']
415 if 'format' in ephemeral_spec:
416 bdm_dict['guest_format'] = ephemeral_spec['format']
417
418 bdm.append(bdm_dict)
419
420 if swap is not None:
421 bdm_dict = {'source_type': 'blank', 'destination_type': 'local',
422 'boot_index': -1, 'delete_on_termination': True,
423 'guest_format': 'swap', 'volume_size': swap}
424 bdm.append(bdm_dict)
425
426 return bdm
427
428
429class NovaServer(object):
430 def __init__(self, name, server, password=None):
431 '''
432 Make output look like libcloud output for consistency
433 '''
434 self.name = name
435 self.id = server['id']
436 self.image = server.get('image', {}).get('id', 'Boot From Volume')
437 self.size = server['flavor']['id']
438 self.state = server['state']
439 self._uuid = None
440 self.extra = {
441 'metadata': server['metadata'],
442 'access_ip': server['accessIPv4']
443 }
444
445 self.addresses = server.get('addresses', {})
446 self.public_ips, self.private_ips = [], []
447 for network in self.addresses.values():
448 for addr in network:
449 if salt.utils.cloud.is_public_ip(addr['addr']):
450 self.public_ips.append(addr['addr'])
451 else:
452 self.private_ips.append(addr['addr'])
453
454 if password:
455 self.extra['password'] = password
456
457 def __str__(self):
458 return self.__dict__
459
460
461def get_entry(dict_, key, value, raise_error=True):
462 for entry in dict_:
463 if entry[key] == value:
464 return entry
465 if raise_error is True:
466 raise SaltCloudSystemExit('Unable to find {0} in {1}.'.format(key, dict_))
467 return {}
468
469
470def get_entry_multi(dict_, pairs, raise_error=True):
471 for entry in dict_:
472 if all([entry[key] == value for key, value in pairs]):
473 return entry
474 if raise_error is True:
475 raise SaltCloudSystemExit('Unable to find {0} in {1}.'.format(pairs, dict_))
476 return {}
477
478
479def sanatize_novaclient(kwargs):
480 variables = (
481 'username', 'api_key', 'project_id', 'auth_url', 'insecure',
482 'timeout', 'proxy_tenant_id', 'proxy_token', 'region_name',
483 'endpoint_type', 'extensions', 'service_type', 'service_name',
484 'volume_service_name', 'timings', 'bypass_url', 'os_cache',
485 'no_cache', 'http_log_debug', 'auth_system', 'auth_plugin',
486 'auth_token', 'cacert', 'tenant_id'
487 )
488 ret = {}
489 for var in kwargs:
490 if var in variables:
491 ret[var] = kwargs[var]
492
493 return ret
494
495
496# Function alias to not shadow built-ins
497class SaltNova(object):
498 '''
499 Class for all novaclient functions
500 '''
501 extensions = []
502
503 def __init__(
504 self,
505 username,
506 project_id,
507 auth_url,
508 region_name=None,
509 password=None,
510 os_auth_plugin=None,
511 use_keystoneauth=False,
512 verify=True,
513 **kwargs
514 ):
515 '''
516 Set up nova credentials
517 '''
518 if all([use_keystoneauth, HAS_KEYSTONEAUTH]):
519 self._new_init(username=username,
520 project_id=project_id,
521 auth_url=auth_url,
522 region_name=region_name,
523 password=password,
524 os_auth_plugin=os_auth_plugin,
525 verify=verify,
526 **kwargs)
527 else:
528 self._old_init(username=username,
529 project_id=project_id,
530 auth_url=auth_url,
531 region_name=region_name,
532 password=password,
533 os_auth_plugin=os_auth_plugin,
534 verify=verify,
535 **kwargs)
536
537 def _new_init(self, username, project_id, auth_url, region_name, password, os_auth_plugin, auth=None, verify=True, **kwargs):
538 if auth is None:
539 auth = {}
540
541 loader = keystoneauth1.loading.get_plugin_loader(os_auth_plugin or 'password')
542
543 self.client_kwargs = kwargs.copy()
544 self.kwargs = auth.copy()
545 if not self.extensions:
546 if hasattr(OpenStackComputeShell, '_discover_extensions'):
547 self.extensions = OpenStackComputeShell()._discover_extensions('2.0')
548 else:
549 self.extensions = client.discover_extensions('2.0')
550 for extension in self.extensions:
551 extension.run_hooks('__pre_parse_args__')
552 self.client_kwargs['extensions'] = self.extensions
553
554 self.kwargs['username'] = username
555 self.kwargs['project_name'] = project_id
556 self.kwargs['auth_url'] = auth_url
557 self.kwargs['password'] = password
558 if auth_url.endswith('3'):
559 self.kwargs['user_domain_name'] = kwargs.get('user_domain_name', 'default')
560 self.kwargs['project_domain_name'] = kwargs.get('project_domain_name', 'default')
561
562 self.client_kwargs['region_name'] = region_name
563 self.client_kwargs['service_type'] = 'compute'
564
565 if hasattr(self, 'extensions'):
566 # needs an object, not a dictionary
567 self.kwargstruct = KwargsStruct(**self.client_kwargs)
568 for extension in self.extensions:
569 extension.run_hooks('__post_parse_args__', self.kwargstruct)
570 self.client_kwargs = self.kwargstruct.__dict__
571
572 # Requires novaclient version >= 2.6.1
573 self.version = str(kwargs.get('version', 2))
574
575 self.client_kwargs = sanatize_novaclient(self.client_kwargs)
576 options = loader.load_from_options(**self.kwargs)
577 self.session = keystoneauth1.session.Session(auth=options, verify=verify)
578 conn = client.Client(version=self.version, session=self.session, **self.client_kwargs)
579 self.kwargs['auth_token'] = conn.client.session.get_token()
580 if conn.client.get_endpoint(service_type='identity').endswith('v3'):
581 self.catalog = conn.client.session.get('/auth/catalog', endpoint_filter={'service_type': 'identity'}).json().get('catalog', [])
582 self._v3_setup(region_name)
583 else:
584 if OCATA:
585 msg = 'Method service_catalog is no longer present in python-novaclient >= 7.0.0'
586 raise Exception(msg)
587 self.catalog = conn.client.service_catalog.catalog['access']['serviceCatalog']
588 self._v2_setup(region_name)
589
590 def _old_init(self, username, project_id, auth_url, region_name, password, os_auth_plugin, **kwargs):
591 self.kwargs = kwargs.copy()
592 if not self.extensions:
593 if hasattr(OpenStackComputeShell, '_discover_extensions'):
594 self.extensions = OpenStackComputeShell()._discover_extensions('2.0')
595 else:
596 self.extensions = client.discover_extensions('2.0')
597 for extension in self.extensions:
598 extension.run_hooks('__pre_parse_args__')
599 self.kwargs['extensions'] = self.extensions
600
601 self.kwargs['username'] = username
602 self.kwargs['project_id'] = project_id
603 self.kwargs['auth_url'] = auth_url
604 self.kwargs['region_name'] = region_name
605 self.kwargs['service_type'] = 'compute'
606
607 # used in novaclient extensions to see if they are rackspace or not, to know if it needs to load
608 # the hooks for that extension or not. This is cleaned up by sanatize_novaclient
609 self.kwargs['os_auth_url'] = auth_url
610
611 if os_auth_plugin is not None:
612 if OCATA:
613 msg = 'Module auth_plugin is no longer present in python-novaclient >= 7.0.0'
614 raise Exception(msg)
615 else:
616 novaclient.auth_plugin.discover_auth_systems()
617 auth_plugin = novaclient.auth_plugin.load_plugin(os_auth_plugin)
618 self.kwargs['auth_plugin'] = auth_plugin
619 self.kwargs['auth_system'] = os_auth_plugin
620
621 if not self.kwargs.get('api_key', None):
622 self.kwargs['api_key'] = password
623
624 # This has to be run before sanatize_novaclient before extra variables are cleaned out.
625 if hasattr(self, 'extensions'):
626 # needs an object, not a dictionary
627 self.kwargstruct = KwargsStruct(**self.kwargs)
628 for extension in self.extensions:
629 extension.run_hooks('__post_parse_args__', self.kwargstruct)
630 self.kwargs = self.kwargstruct.__dict__
631
632 self.kwargs = sanatize_novaclient(self.kwargs)
633
634 # Requires novaclient version >= 2.6.1
635 self.kwargs['version'] = str(kwargs.get('version', 2))
636
637 conn = client.Client(**self.kwargs)
638 try:
639 conn.client.authenticate()
640 except novaclient.exceptions.AmbiguousEndpoints:
641 raise SaltCloudSystemExit(
642 "Nova provider requires a 'region_name' to be specified"
643 )
644
645 self.kwargs['auth_token'] = conn.client.auth_token
646
647 # There is currently no way to get service catalog in the expected format with Ocata compatible
648 # python-novaclient in _old_init, because there is no session
649 if OCATA:
650 msg = 'Method service_catalog is no longer present in python-novaclient >= 7.0.0'
651 raise Exception(msg)
652
653 self.catalog = conn.client.service_catalog.catalog['access']['serviceCatalog']
654
655 self._v2_setup(region_name)
656
657 def _v3_setup(self, region_name):
658 if region_name is not None:
659 servers_endpoints = get_entry(self.catalog, 'type', 'compute')['endpoints']
660 self.kwargs['bypass_url'] = get_entry_multi(
661 servers_endpoints,
662 [('region', region_name), ('interface', 'public')]
663 )['url']
664
665 if hasattr(self, 'session'):
666 self.compute_conn = client.Client(version=self.version, session=self.session, **self.client_kwargs)
667 else:
668 self.compute_conn = client.Client(**self.kwargs)
669
670 volume_endpoints = get_entry(self.catalog, 'type', 'volume', raise_error=False).get('endpoints', {})
671 if volume_endpoints:
672 if region_name is not None:
673 self.kwargs['bypass_url'] = get_entry_multi(
674 volume_endpoints,
675 [('region', region_name), ('interface', 'public')]
676 )['url']
677
678 self.volume_conn = client.Client(version=self.version, session=self.session, **self.client_kwargs)
679 if hasattr(self, 'extensions'):
680 self.expand_extensions()
681 else:
682 self.volume_conn = None
683
684 def _v2_setup(self, region_name):
685 if region_name is not None:
686 servers_endpoints = get_entry(self.catalog, 'type', 'compute')['endpoints']
687 self.kwargs['bypass_url'] = get_entry(
688 servers_endpoints,
689 'region',
690 region_name
691 )['publicURL']
692
693 if hasattr(self, 'session'):
694 self.compute_conn = client.Client(version=self.version, session=self.session, **self.client_kwargs)
695 else:
696 self.compute_conn = client.Client(**self.kwargs)
697
698 volume_endpoints = get_entry(self.catalog, 'type', 'volume', raise_error=False).get('endpoints', {})
699 if volume_endpoints:
700 if region_name is not None:
701 self.kwargs['bypass_url'] = get_entry(
702 volume_endpoints,
703 'region',
704 region_name
705 )['publicURL']
706
707 self.volume_conn = client.Client(**self.kwargs)
708 if hasattr(self, 'extensions'):
709 self.expand_extensions()
710 else:
711 self.volume_conn = None
712
713 def expand_extensions(self):
714 for connection in (self.compute_conn, self.volume_conn):
715 if connection is None:
716 continue
717 for extension in self.extensions:
718 for attr in extension.module.__dict__:
719 if not inspect.isclass(getattr(extension.module, attr)):
720 continue
721 for key, value in six.iteritems(connection.__dict__):
722 if not isinstance(value, novaclient.base.Manager):
723 continue
724 if value.__class__.__name__ == attr:
725 setattr(connection, key, extension.manager_class(connection))
726
727 def get_catalog(self):
728 '''
729 Return service catalog
730 '''
731 return self.catalog
732
733 def server_show_libcloud(self, uuid):
734 '''
735 Make output look like libcloud output for consistency
736 '''
737 server_info = self.server_show(uuid)
738 server = next(six.itervalues(server_info))
739 server_name = next(six.iterkeys(server_info))
740 if not hasattr(self, 'password'):
741 self.password = None
742 ret = NovaServer(server_name, server, self.password)
743
744 return ret
745
746 def boot(self, name, flavor_id=0, image_id=0, timeout=300, **kwargs):
747 '''
748 Boot a cloud server.
749 '''
750 nt_ks = self.compute_conn
751 kwargs['name'] = name
752 kwargs['flavor'] = flavor_id
753 kwargs['image'] = image_id or None
754 ephemeral = kwargs.pop('ephemeral', [])
755 block_device = kwargs.pop('block_device', [])
756 boot_volume = kwargs.pop('boot_volume', None)
757 snapshot = kwargs.pop('snapshot', None)
758 swap = kwargs.pop('swap', None)
759 kwargs['block_device_mapping_v2'] = _parse_block_device_mapping_v2(
760 block_device=block_device, boot_volume=boot_volume, snapshot=snapshot,
761 ephemeral=ephemeral, swap=swap
762 )
763 response = nt_ks.servers.create(**kwargs)
764 self.uuid = response.id
765 self.password = getattr(response, 'adminPass', None)
766
767 start = time.time()
768 trycount = 0
769 while True:
770 trycount += 1
771 try:
772 return self.server_show_libcloud(self.uuid)
773 except Exception as exc:
774 log.debug(
775 'Server information not yet available: {0}'.format(exc)
776 )
777 time.sleep(1)
778 if time.time() - start > timeout:
779 log.error('Timed out after {0} seconds '
780 'while waiting for data'.format(timeout))
781 return False
782
783 log.debug(
784 'Retrying server_show() (try {0})'.format(trycount)
785 )
786
787 def show_instance(self, name):
788 '''
789 Find a server by its name (libcloud)
790 '''
791 return self.server_by_name(name)
792
793 def root_password(self, server_id, password):
794 '''
795 Change server(uuid's) root password
796 '''
797 nt_ks = self.compute_conn
798 nt_ks.servers.change_password(server_id, password)
799
800 def server_by_name(self, name):
801 '''
802 Find a server by its name
803 '''
804 return self.server_show_libcloud(
805 self.server_list().get(name, {}).get('id', '')
806 )
807
808 def _volume_get(self, volume_id):
809 '''
810 Organize information about a volume from the volume_id
811 '''
812 if self.volume_conn is None:
813 raise SaltCloudSystemExit('No cinder endpoint available')
814 nt_ks = self.volume_conn
815 volume = nt_ks.volumes.get(volume_id)
816 response = {'name': volume.display_name,
817 'size': volume.size,
818 'id': volume.id,
819 'description': volume.display_description,
820 'attachments': volume.attachments,
821 'status': volume.status
822 }
823 return response
824
825 def volume_list(self, search_opts=None):
826 '''
827 List all block volumes
828 '''
829 if self.volume_conn is None:
830 raise SaltCloudSystemExit('No cinder endpoint available')
831 nt_ks = self.volume_conn
832 volumes = nt_ks.volumes.list(search_opts=search_opts)
833 response = {}
834 for volume in volumes:
835 response[volume.display_name] = {
836 'name': volume.display_name,
837 'size': volume.size,
838 'id': volume.id,
839 'description': volume.display_description,
840 'attachments': volume.attachments,
841 'status': volume.status
842 }
843 return response
844
845 def volume_show(self, name):
846 '''
847 Show one volume
848 '''
849 if self.volume_conn is None:
850 raise SaltCloudSystemExit('No cinder endpoint available')
851 nt_ks = self.volume_conn
852 volumes = self.volume_list(
853 search_opts={'display_name': name},
854 )
855 volume = volumes[name]
856# except Exception as esc:
857# # volume doesn't exist
858# log.error(esc.strerror)
859# return {'name': name, 'status': 'deleted'}
860
861 return volume
862
863 def volume_create(self, name, size=100, snapshot=None, voltype=None,
864 availability_zone=None):
865 '''
866 Create a block device
867 '''
868 if self.volume_conn is None:
869 raise SaltCloudSystemExit('No cinder endpoint available')
870 nt_ks = self.volume_conn
871 response = nt_ks.volumes.create(
872 size=size,
873 display_name=name,
874 volume_type=voltype,
875 snapshot_id=snapshot,
876 availability_zone=availability_zone
877 )
878
879 return self._volume_get(response.id)
880
881 def volume_delete(self, name):
882 '''
883 Delete a block device
884 '''
885 if self.volume_conn is None:
886 raise SaltCloudSystemExit('No cinder endpoint available')
887 nt_ks = self.volume_conn
888 try:
889 volume = self.volume_show(name)
890 except KeyError as exc:
891 raise SaltCloudSystemExit('Unable to find {0} volume: {1}'.format(name, exc))
892 if volume['status'] == 'deleted':
893 return volume
894 response = nt_ks.volumes.delete(volume['id'])
895 return volume
896
897 def volume_detach(self,
898 name,
899 timeout=300):
900 '''
901 Detach a block device
902 '''
903 try:
904 volume = self.volume_show(name)
905 except KeyError as exc:
906 raise SaltCloudSystemExit('Unable to find {0} volume: {1}'.format(name, exc))
907 if not volume['attachments']:
908 return True
909 response = self.compute_conn.volumes.delete_server_volume(
910 volume['attachments'][0]['server_id'],
911 volume['attachments'][0]['id']
912 )
913 trycount = 0
914 start = time.time()
915 while True:
916 trycount += 1
917 try:
918 response = self._volume_get(volume['id'])
919 if response['status'] == 'available':
920 return response
921 except Exception as exc:
922 log.debug('Volume is detaching: {0}'.format(name))
923 time.sleep(1)
924 if time.time() - start > timeout:
925 log.error('Timed out after {0} seconds '
926 'while waiting for data'.format(timeout))
927 return False
928
929 log.debug(
930 'Retrying volume_show() (try {0})'.format(trycount)
931 )
932
933 def volume_attach(self,
934 name,
935 server_name,
936 device='/dev/xvdb',
937 timeout=300):
938 '''
939 Attach a block device
940 '''
941 try:
942 volume = self.volume_show(name)
943 except KeyError as exc:
944 raise SaltCloudSystemExit('Unable to find {0} volume: {1}'.format(name, exc))
945 server = self.server_by_name(server_name)
946 response = self.compute_conn.volumes.create_server_volume(
947 server.id,
948 volume['id'],
949 device=device
950 )
951 trycount = 0
952 start = time.time()
953 while True:
954 trycount += 1
955 try:
956 response = self._volume_get(volume['id'])
957 if response['status'] == 'in-use':
958 return response
959 except Exception as exc:
960 log.debug('Volume is attaching: {0}'.format(name))
961 time.sleep(1)
962 if time.time() - start > timeout:
963 log.error('Timed out after {0} seconds '
964 'while waiting for data'.format(timeout))
965 return False
966
967 log.debug(
968 'Retrying volume_show() (try {0})'.format(trycount)
969 )
970
971 def suspend(self, instance_id):
972 '''
973 Suspend a server
974 '''
975 nt_ks = self.compute_conn
976 response = nt_ks.servers.suspend(instance_id)
977 return True
978
979 def resume(self, instance_id):
980 '''
981 Resume a server
982 '''
983 nt_ks = self.compute_conn
984 response = nt_ks.servers.resume(instance_id)
985 return True
986
987 def lock(self, instance_id):
988 '''
989 Lock an instance
990 '''
991 nt_ks = self.compute_conn
992 response = nt_ks.servers.lock(instance_id)
993 return True
994
995 def delete(self, instance_id):
996 '''
997 Delete a server
998 '''
999 nt_ks = self.compute_conn
1000 response = nt_ks.servers.delete(instance_id)
1001 return True
1002
1003 def flavor_list(self):
1004 '''
1005 Return a list of available flavors (nova flavor-list)
1006 '''
1007 nt_ks = self.compute_conn
1008 ret = {}
1009 for flavor in nt_ks.flavors.list():
1010 links = {}
1011 for link in flavor.links:
1012 links[link['rel']] = link['href']
1013 ret[flavor.name] = {
1014 'disk': flavor.disk,
1015 'id': flavor.id,
1016 'name': flavor.name,
1017 'ram': flavor.ram,
1018 'swap': flavor.swap,
1019 'vcpus': flavor.vcpus,
1020 'links': links,
1021 }
1022 if hasattr(flavor, 'rxtx_factor'):
1023 ret[flavor.name]['rxtx_factor'] = flavor.rxtx_factor
1024 return ret
1025
1026 list_sizes = flavor_list
1027
1028 def flavor_create(self,
1029 name, # pylint: disable=C0103
1030 flavor_id=0, # pylint: disable=C0103
1031 ram=0,
1032 disk=0,
1033 vcpus=1):
1034 '''
1035 Create a flavor
1036 '''
1037 nt_ks = self.compute_conn
1038 nt_ks.flavors.create(
1039 name=name, flavorid=flavor_id, ram=ram, disk=disk, vcpus=vcpus
1040 )
1041 return {'name': name,
1042 'id': flavor_id,
1043 'ram': ram,
1044 'disk': disk,
1045 'vcpus': vcpus}
1046
1047 def flavor_delete(self, flavor_id): # pylint: disable=C0103
1048 '''
1049 Delete a flavor
1050 '''
1051 nt_ks = self.compute_conn
1052 nt_ks.flavors.delete(flavor_id)
1053 return 'Flavor deleted: {0}'.format(flavor_id)
1054
1055 def keypair_list(self):
1056 '''
1057 List keypairs
1058 '''
1059 nt_ks = self.compute_conn
1060 ret = {}
1061 for keypair in nt_ks.keypairs.list():
1062 ret[keypair.name] = {
1063 'name': keypair.name,
1064 'fingerprint': keypair.fingerprint,
1065 'public_key': keypair.public_key,
1066 }
1067 return ret
1068
1069 def keypair_add(self, name, pubfile=None, pubkey=None):
1070 '''
1071 Add a keypair
1072 '''
1073 nt_ks = self.compute_conn
1074 if pubfile:
1075 with salt.utils.fopen(pubfile, 'r') as fp_:
1076 pubkey = fp_.read()
1077 if not pubkey:
1078 return False
1079 nt_ks.keypairs.create(name, public_key=pubkey)
1080 ret = {'name': name, 'pubkey': pubkey}
1081 return ret
1082
1083 def keypair_delete(self, name):
1084 '''
1085 Delete a keypair
1086 '''
1087 nt_ks = self.compute_conn
1088 nt_ks.keypairs.delete(name)
1089 return 'Keypair deleted: {0}'.format(name)
1090
1091 def image_show(self, image_id):
1092 '''
1093 Show image details and metadata
1094 '''
1095 nt_ks = self.compute_conn
1096 image = nt_ks.images.get(image_id)
1097 links = {}
1098 for link in image.links:
1099 links[link['rel']] = link['href']
1100 ret = {
1101 'name': image.name,
1102 'id': image.id,
1103 'status': image.status,
1104 'progress': image.progress,
1105 'created': image.created,
1106 'updated': image.updated,
1107 'metadata': image.metadata,
1108 'links': links,
1109 }
1110 if hasattr(image, 'minDisk'):
1111 ret['minDisk'] = image.minDisk
1112 if hasattr(image, 'minRam'):
1113 ret['minRam'] = image.minRam
1114
1115 return ret
1116
1117 def image_list(self, name=None):
1118 '''
1119 List server images
1120 '''
1121 nt_ks = self.compute_conn
1122 ret = {}
1123 for image in nt_ks.images.list():
1124 links = {}
1125 for link in image.links:
1126 links[link['rel']] = link['href']
1127 ret[image.name] = {
1128 'name': image.name,
1129 'id': image.id,
1130 'status': image.status,
1131 'progress': image.progress,
1132 'created': image.created,
1133 'updated': image.updated,
1134 'metadata': image.metadata,
1135 'links': links,
1136 }
1137 if hasattr(image, 'minDisk'):
1138 ret[image.name]['minDisk'] = image.minDisk
1139 if hasattr(image, 'minRam'):
1140 ret[image.name]['minRam'] = image.minRam
1141 if name:
1142 return {name: ret[name]}
1143 return ret
1144
1145 list_images = image_list
1146
1147 def image_meta_set(self,
1148 image_id=None,
1149 name=None,
1150 **kwargs): # pylint: disable=C0103
1151 '''
1152 Set image metadata
1153 '''
1154 nt_ks = self.compute_conn
1155 if name:
1156 for image in nt_ks.images.list():
1157 if image.name == name:
1158 image_id = image.id # pylint: disable=C0103
1159 if not image_id:
1160 return {'Error': 'A valid image name or id was not specified'}
1161 nt_ks.images.set_meta(image_id, kwargs)
1162 return {image_id: kwargs}
1163
1164 def image_meta_delete(self,
1165 image_id=None, # pylint: disable=C0103
1166 name=None,
1167 keys=None):
1168 '''
1169 Delete image metadata
1170 '''
1171 nt_ks = self.compute_conn
1172 if name:
1173 for image in nt_ks.images.list():
1174 if image.name == name:
1175 image_id = image.id # pylint: disable=C0103
1176 pairs = keys.split(',')
1177 if not image_id:
1178 return {'Error': 'A valid image name or id was not specified'}
1179 nt_ks.images.delete_meta(image_id, pairs)
1180 return {image_id: 'Deleted: {0}'.format(pairs)}
1181
1182 def server_list(self):
1183 '''
1184 List servers
1185 '''
1186 nt_ks = self.compute_conn
1187 ret = {}
1188 for item in nt_ks.servers.list():
1189 try:
1190 ret[item.name] = {
1191 'id': item.id,
1192 'name': item.name,
1193 'state': item.status,
1194 'accessIPv4': item.accessIPv4,
1195 'accessIPv6': item.accessIPv6,
1196 'flavor': {'id': item.flavor['id'],
1197 'links': item.flavor['links']},
1198 'image': {'id': item.image['id'] if item.image else 'Boot From Volume',
1199 'links': item.image['links'] if item.image else ''},
1200 }
1201 except TypeError:
1202 pass
1203 return ret
1204
1205 def server_list_min(self):
1206 '''
1207 List minimal information about servers
1208 '''
1209 nt_ks = self.compute_conn
1210 ret = {}
1211 for item in nt_ks.servers.list(detailed=False):
1212 try:
1213 ret[item.name] = {
1214 'id': item.id,
1215 'status': 'Running'
1216 }
1217 except TypeError:
1218 pass
1219 return ret
1220
1221 def server_list_detailed(self):
1222 '''
1223 Detailed list of servers
1224 '''
1225 nt_ks = self.compute_conn
1226 ret = {}
1227 for item in nt_ks.servers.list():
1228 try:
1229 ret[item.name] = {
1230 'OS-EXT-SRV-ATTR': {},
1231 'OS-EXT-STS': {},
1232 'accessIPv4': item.accessIPv4,
1233 'accessIPv6': item.accessIPv6,
1234 'addresses': item.addresses,
1235 'created': item.created,
1236 'flavor': {'id': item.flavor['id'],
1237 'links': item.flavor['links']},
1238 'hostId': item.hostId,
1239 'id': item.id,
1240 'image': {'id': item.image['id'] if item.image else 'Boot From Volume',
1241 'links': item.image['links'] if item.image else ''},
1242 'key_name': item.key_name,
1243 'links': item.links,
1244 'metadata': item.metadata,
1245 'name': item.name,
1246 'state': item.status,
1247 'tenant_id': item.tenant_id,
1248 'updated': item.updated,
1249 'user_id': item.user_id,
1250 }
1251 except TypeError:
1252 continue
1253
1254 ret[item.name]['progress'] = getattr(item, 'progress', '0')
1255
1256 if hasattr(item.__dict__, 'OS-DCF:diskConfig'):
1257 ret[item.name]['OS-DCF'] = {
1258 'diskConfig': item.__dict__['OS-DCF:diskConfig']
1259 }
1260 if hasattr(item.__dict__, 'OS-EXT-SRV-ATTR:host'):
1261 ret[item.name]['OS-EXT-SRV-ATTR']['host'] = \
1262 item.__dict__['OS-EXT-SRV-ATTR:host']
1263 if hasattr(item.__dict__, 'OS-EXT-SRV-ATTR:hypervisor_hostname'):
1264 ret[item.name]['OS-EXT-SRV-ATTR']['hypervisor_hostname'] = \
1265 item.__dict__['OS-EXT-SRV-ATTR:hypervisor_hostname']
1266 if hasattr(item.__dict__, 'OS-EXT-SRV-ATTR:instance_name'):
1267 ret[item.name]['OS-EXT-SRV-ATTR']['instance_name'] = \
1268 item.__dict__['OS-EXT-SRV-ATTR:instance_name']
1269 if hasattr(item.__dict__, 'OS-EXT-STS:power_state'):
1270 ret[item.name]['OS-EXT-STS']['power_state'] = \
1271 item.__dict__['OS-EXT-STS:power_state']
1272 if hasattr(item.__dict__, 'OS-EXT-STS:task_state'):
1273 ret[item.name]['OS-EXT-STS']['task_state'] = \
1274 item.__dict__['OS-EXT-STS:task_state']
1275 if hasattr(item.__dict__, 'OS-EXT-STS:vm_state'):
1276 ret[item.name]['OS-EXT-STS']['vm_state'] = \
1277 item.__dict__['OS-EXT-STS:vm_state']
1278 if hasattr(item.__dict__, 'security_groups'):
1279 ret[item.name]['security_groups'] = \
1280 item.__dict__['security_groups']
1281 return ret
1282
1283 def server_show(self, server_id):
1284 '''
1285 Show details of one server
1286 '''
1287 ret = {}
1288 try:
1289 servers = self.server_list_detailed()
1290 except AttributeError:
1291 raise SaltCloudSystemExit('Corrupt server in server_list_detailed. Remove corrupt servers.')
1292 for server_name, server in six.iteritems(servers):
1293 if str(server['id']) == server_id:
1294 ret[server_name] = server
1295 return ret
1296
1297 def secgroup_create(self, name, description):
1298 '''
1299 Create a security group
1300 '''
1301 nt_ks = self.compute_conn
1302 nt_ks.security_groups.create(name, description)
1303 ret = {'name': name, 'description': description}
1304 return ret
1305
1306 def secgroup_delete(self, name):
1307 '''
1308 Delete a security group
1309 '''
1310 nt_ks = self.compute_conn
1311 for item in nt_ks.security_groups.list():
1312 if item.name == name:
1313 nt_ks.security_groups.delete(item.id)
1314 return {name: 'Deleted security group: {0}'.format(name)}
1315 return 'Security group not found: {0}'.format(name)
1316
1317 def secgroup_list(self):
1318 '''
1319 List security groups
1320 '''
1321 nt_ks = self.compute_conn
1322 ret = {}
1323 for item in nt_ks.security_groups.list():
1324 ret[item.name] = {
1325 'name': item.name,
1326 'description': item.description,
1327 'id': item.id,
1328 'tenant_id': item.tenant_id,
1329 'rules': item.rules,
1330 }
1331 return ret
1332
1333 def _item_list(self):
1334 '''
1335 List items
1336 '''
1337 nt_ks = self.compute_conn
1338 ret = []
1339 for item in nt_ks.items.list():
1340 ret.append(item.__dict__)
1341 return ret
1342
1343 def _network_show(self, name, network_lst):
1344 '''
1345 Parse the returned network list
1346 '''
1347 for net in network_lst:
1348 if net.label == name:
1349 return net.__dict__
1350 return {}
1351
1352 def network_show(self, name):
1353 '''
1354 Show network information
1355 '''
1356 nt_ks = self.compute_conn
1357 net_list = nt_ks.networks.list()
1358 return self._network_show(name, net_list)
1359
1360 def network_list(self):
1361 '''
1362 List extra private networks
1363 '''
1364 nt_ks = self.compute_conn
1365 return [network.__dict__ for network in nt_ks.networks.list()]
1366
1367 def _sanatize_network_params(self, kwargs):
1368 '''
1369 Sanatize novaclient network parameters
1370 '''
1371 params = [
1372 'label', 'bridge', 'bridge_interface', 'cidr', 'cidr_v6', 'dns1',
1373 'dns2', 'fixed_cidr', 'gateway', 'gateway_v6', 'multi_host',
1374 'priority', 'project_id', 'vlan_start', 'vpn_start'
1375 ]
1376
1377 for variable in six.iterkeys(kwargs): # iterate over a copy, we might delete some
1378 if variable not in params:
1379 del kwargs[variable]
1380 return kwargs
1381
1382 def network_create(self, name, **kwargs):
1383 '''
1384 Create extra private network
1385 '''
1386 nt_ks = self.compute_conn
1387 kwargs['label'] = name
1388 kwargs = self._sanatize_network_params(kwargs)
1389 net = nt_ks.networks.create(**kwargs)
1390 return net.__dict__
1391
1392 def _server_uuid_from_name(self, name):
1393 '''
1394 Get server uuid from name
1395 '''
1396 return self.server_list().get(name, {}).get('id', '')
1397
1398 def virtual_interface_list(self, name):
1399 '''
1400 Get virtual interfaces on slice
1401 '''
1402 nt_ks = self.compute_conn
1403 nets = nt_ks.virtual_interfaces.list(self._server_uuid_from_name(name))
1404 return [network.__dict__ for network in nets]
1405
1406 def virtual_interface_create(self, name, net_name):
1407 '''
1408 Add an interfaces to a slice
1409 '''
1410 nt_ks = self.compute_conn
1411 serverid = self._server_uuid_from_name(name)
1412 networkid = self.network_show(net_name).get('id', None)
1413 if networkid is None:
1414 return {net_name: False}
1415 nets = nt_ks.virtual_interfaces.create(networkid, serverid)
1416 return nets
1417
1418 def floating_ip_pool_list(self):
1419 '''
1420 List all floating IP pools
1421 .. versionadded:: 2016.3.0
1422 '''
1423 nt_ks = self.compute_conn
1424 pools = nt_ks.floating_ip_pools.list()
1425 response = {}
1426 for pool in pools:
1427 response[pool.name] = {
1428 'name': pool.name,
1429 }
1430 return response
1431
1432 def floating_ip_list(self):
1433 '''
1434 List floating IPs
1435 .. versionadded:: 2016.3.0
1436 '''
1437 nt_ks = self.compute_conn
1438 floating_ips = nt_ks.floating_ips.list()
1439 response = {}
1440 for floating_ip in floating_ips:
1441 response[floating_ip.ip] = {
1442 'ip': floating_ip.ip,
1443 'fixed_ip': floating_ip.fixed_ip,
1444 'id': floating_ip.id,
1445 'instance_id': floating_ip.instance_id,
1446 'pool': floating_ip.pool
1447 }
1448 return response
1449
1450 def floating_ip_show(self, ip):
1451 '''
1452 Show info on specific floating IP
1453 .. versionadded:: 2016.3.0
1454 '''
1455 nt_ks = self.compute_conn
1456 floating_ips = nt_ks.floating_ips.list()
1457 for floating_ip in floating_ips:
1458 if floating_ip.ip == ip:
1459 return floating_ip
1460 return {}
1461
1462 def floating_ip_create(self, pool=None):
1463 '''
1464 Allocate a floating IP
1465 .. versionadded:: 2016.3.0
1466 '''
1467 nt_ks = self.compute_conn
1468 floating_ip = nt_ks.floating_ips.create(pool)
1469 response = {
1470 'ip': floating_ip.ip,
1471 'fixed_ip': floating_ip.fixed_ip,
1472 'id': floating_ip.id,
1473 'instance_id': floating_ip.instance_id,
1474 'pool': floating_ip.pool
1475 }
1476 return response
1477
1478 def floating_ip_delete(self, floating_ip):
1479 '''
1480 De-allocate a floating IP
1481 .. versionadded:: 2016.3.0
1482 '''
1483 ip = self.floating_ip_show(floating_ip)
1484 nt_ks = self.compute_conn
1485 return nt_ks.floating_ips.delete(ip)
1486
1487 def floating_ip_associate(self, server_name, floating_ip):
1488 '''
1489 Associate floating IP address to server
1490 .. versionadded:: 2016.3.0
1491 '''
1492 nt_ks = self.compute_conn
1493 server_ = self.server_by_name(server_name)
1494 server = nt_ks.servers.get(server_.__dict__['id'])
1495 server.add_floating_ip(floating_ip)
1496 return self.floating_ip_list()[floating_ip]
1497
1498 def floating_ip_disassociate(self, server_name, floating_ip):
1499 '''
1500 Disassociate a floating IP from server
1501 .. versionadded:: 2016.3.0
1502 '''
1503 nt_ks = self.compute_conn
1504 server_ = self.server_by_name(server_name)
1505 server = nt_ks.servers.get(server_.__dict__['id'])
1506 server.remove_floating_ip(floating_ip)
1507 return self.floating_ip_list()[floating_ip]
1508
1509#
1510# Moved from salt.modules.nova until this works in upstream
1511#
1512
1513def _auth(profile=None):
1514 '''
1515 Set up nova credentials
1516 '''
1517 if profile:
1518 credentials = __salt__['config.option'](profile)
1519 user = credentials['keystone.user']
1520 password = credentials['keystone.password']
1521 tenant = credentials['keystone.tenant']
1522 auth_url = credentials['keystone.auth_url']
1523 region_name = credentials.get('keystone.region_name', None)
1524 api_key = credentials.get('keystone.api_key', None)
1525 os_auth_system = credentials.get('keystone.os_auth_system', None)
1526 use_keystoneauth = credentials.get('keystone.use_keystoneauth', False)
1527 verify = credentials.get('keystone.verify', False)
1528 else:
1529 user = __salt__['config.option']('keystone.user')
1530 password = __salt__['config.option']('keystone.password')
1531 tenant = __salt__['config.option']('keystone.tenant')
1532 auth_url = __salt__['config.option']('keystone.auth_url')
1533 region_name = __salt__['config.option']('keystone.region_name')
1534 api_key = __salt__['config.option']('keystone.api_key')
1535 os_auth_system = __salt__['config.option']('keystone.os_auth_system')
1536 use_keystoneauth = __salt__['config.option']('keystone.use_keystoneauth', False)
1537 verify = __salt__['config.option']('keystone.verify', True)
1538
1539 kwargs = {
1540 'username': user,
1541 'password': password,
1542 'api_key': api_key,
1543 'project_id': tenant,
1544 'auth_url': auth_url,
1545 'region_name': region_name,
1546 'os_auth_plugin': os_auth_system,
1547 'use_keystoneauth': use_keystoneauth,
1548 'verify': verify
1549 }
1550
1551 return SaltNova(**kwargs)
1552
1553
1554def boot(name, flavor_id=0, image_id=0, profile=None, timeout=300):
1555 '''
1556 Boot (create) a new instance
1557 name
1558 Name of the new instance (must be first)
1559 flavor_id
1560 Unique integer ID for the flavor
1561 image_id
1562 Unique integer ID for the image
1563 timeout
1564 How long to wait, after creating the instance, for the provider to
1565 return information about it (default 300 seconds).
1566 .. versionadded:: 2014.1.0
1567 CLI Example:
1568 .. code-block:: bash
1569 salt '*' nova.boot myinstance flavor_id=4596 image_id=2
1570 The flavor_id and image_id are obtained from nova.flavor_list and
1571 nova.image_list
1572 .. code-block:: bash
1573 salt '*' nova.flavor_list
1574 salt '*' nova.image_list
1575 '''
1576 conn = _auth(profile)
1577 return conn.boot(name, flavor_id, image_id, timeout)
1578
1579
1580def volume_list(search_opts=None, profile=None):
1581 '''
1582 List storage volumes
1583 search_opts
1584 Dictionary of search options
1585 profile
1586 Profile to use
1587 CLI Example:
1588 .. code-block:: bash
1589 salt '*' nova.volume_list \
1590 search_opts='{"display_name": "myblock"}' \
1591 profile=openstack
1592 '''
1593 conn = _auth(profile)
1594 return conn.volume_list(search_opts=search_opts)
1595
1596
1597def volume_show(name, profile=None):
1598 '''
1599 Create a block storage volume
1600 name
1601 Name of the volume
1602 profile
1603 Profile to use
1604 CLI Example:
1605 .. code-block:: bash
1606 salt '*' nova.volume_show myblock profile=openstack
1607 '''
1608 conn = _auth(profile)
1609 return conn.volume_show(name)
1610
1611
1612def volume_create(name, size=100, snapshot=None, voltype=None,
1613 profile=None):
1614 '''
1615 Create a block storage volume
1616 name
1617 Name of the new volume (must be first)
1618 size
1619 Volume size
1620 snapshot
1621 Block storage snapshot id
1622 voltype
1623 Type of storage
1624 profile
1625 Profile to build on
1626 CLI Example:
1627 .. code-block:: bash
1628 salt '*' nova.volume_create myblock size=300 profile=openstack
1629 '''
1630 conn = _auth(profile)
1631 return conn.volume_create(
1632 name,
1633 size,
1634 snapshot,
1635 voltype
1636 )
1637
1638
1639def volume_delete(name, profile=None):
1640 '''
1641 Destroy the volume
1642 name
1643 Name of the volume
1644 profile
1645 Profile to build on
1646 CLI Example:
1647 .. code-block:: bash
1648 salt '*' nova.volume_delete myblock profile=openstack
1649 '''
1650 conn = _auth(profile)
1651 return conn.volume_delete(name)
1652
1653
1654def volume_detach(name,
1655 profile=None,
1656 timeout=300):
1657 '''
1658 Attach a block storage volume
1659 name
1660 Name of the new volume to attach
1661 server_name
1662 Name of the server to detach from
1663 profile
1664 Profile to build on
1665 CLI Example:
1666 .. code-block:: bash
1667 salt '*' nova.volume_detach myblock profile=openstack
1668 '''
1669 conn = _auth(profile)
1670 return conn.volume_detach(
1671 name,
1672 timeout
1673 )
1674
1675
1676def volume_attach(name,
1677 server_name,
1678 device='/dev/xvdb',
1679 profile=None,
1680 timeout=300):
1681 '''
1682 Attach a block storage volume
1683 name
1684 Name of the new volume to attach
1685 server_name
1686 Name of the server to attach to
1687 device
1688 Name of the device on the server
1689 profile
1690 Profile to build on
1691 CLI Example:
1692 .. code-block:: bash
1693 salt '*' nova.volume_attach myblock slice.example.com profile=openstack
1694 salt '*' nova.volume_attach myblock server.example.com \
1695 device='/dev/xvdb' profile=openstack
1696 '''
1697 conn = _auth(profile)
1698 return conn.volume_attach(
1699 name,
1700 server_name,
1701 device,
1702 timeout
1703 )
1704
1705
1706def suspend(instance_id, profile=None):
1707 '''
1708 Suspend an instance
1709 instance_id
1710 ID of the instance to be suspended
1711 CLI Example:
1712 .. code-block:: bash
1713 salt '*' nova.suspend 1138
1714 '''
1715 conn = _auth(profile)
1716 return conn.suspend(instance_id)
1717
1718
1719def resume(instance_id, profile=None):
1720 '''
1721 Resume an instance
1722 instance_id
1723 ID of the instance to be resumed
1724 CLI Example:
1725 .. code-block:: bash
1726 salt '*' nova.resume 1138
1727 '''
1728 conn = _auth(profile)
1729 return conn.resume(instance_id)
1730
1731
1732def lock(instance_id, profile=None):
1733 '''
1734 Lock an instance
1735 instance_id
1736 ID of the instance to be locked
1737 CLI Example:
1738 .. code-block:: bash
1739 salt '*' nova.lock 1138
1740 '''
1741 conn = _auth(profile)
1742 return conn.lock(instance_id)
1743
1744
1745def delete(instance_id, profile=None):
1746 '''
1747 Delete an instance
1748 instance_id
1749 ID of the instance to be deleted
1750 CLI Example:
1751 .. code-block:: bash
1752 salt '*' nova.delete 1138
1753 '''
1754 conn = _auth(profile)
1755 return conn.delete(instance_id)
1756
1757
1758def flavor_list(profile=None):
1759 '''
1760 Return a list of available flavors (nova flavor-list)
1761 CLI Example:
1762 .. code-block:: bash
1763 salt '*' nova.flavor_list
1764 '''
1765 conn = _auth(profile)
1766 return conn.flavor_list()
1767
1768
1769def flavor_create(name, # pylint: disable=C0103
1770 flavor_id=0, # pylint: disable=C0103
1771 ram=0,
1772 disk=0,
1773 vcpus=1,
1774 profile=None):
1775 '''
1776 Add a flavor to nova (nova flavor-create). The following parameters are
1777 required:
1778 name
1779 Name of the new flavor (must be first)
1780 flavor_id
1781 Unique integer ID for the new flavor
1782 ram
1783 Memory size in MB
1784 disk
1785 Disk size in GB
1786 vcpus
1787 Number of vcpus
1788 CLI Example:
1789 .. code-block:: bash
1790 salt '*' nova.flavor_create myflavor flavor_id=6 \
1791 ram=4096 disk=10 vcpus=1
1792 '''
1793 conn = _auth(profile)
1794 return conn.flavor_create(
1795 name,
1796 flavor_id,
1797 ram,
1798 disk,
1799 vcpus
1800 )
1801
1802
1803def flavor_delete(flavor_id, profile=None): # pylint: disable=C0103
1804 '''
1805 Delete a flavor from nova by id (nova flavor-delete)
1806 CLI Example:
1807 .. code-block:: bash
1808 salt '*' nova.flavor_delete 7
1809 '''
1810 conn = _auth(profile)
1811 return conn.flavor_delete(flavor_id)
1812
1813
1814def keypair_list(profile=None):
1815 '''
1816 Return a list of available keypairs (nova keypair-list)
1817 CLI Example:
1818 .. code-block:: bash
1819 salt '*' nova.keypair_list
1820 '''
1821 conn = _auth(profile)
1822 return conn.keypair_list()
1823
1824
1825def keypair_add(name, pubfile=None, pubkey=None, profile=None):
1826 '''
1827 Add a keypair to nova (nova keypair-add)
1828 CLI Examples:
1829 .. code-block:: bash
1830 salt '*' nova.keypair_add mykey pubfile='/home/myuser/.ssh/id_rsa.pub'
1831 salt '*' nova.keypair_add mykey pubkey='ssh-rsa <key> myuser@mybox'
1832 '''
1833 conn = _auth(profile)
1834 return conn.keypair_add(
1835 name,
1836 pubfile,
1837 pubkey
1838 )
1839
1840
1841def keypair_delete(name, profile=None):
1842 '''
1843 Add a keypair to nova (nova keypair-delete)
1844 CLI Example:
1845 .. code-block:: bash
1846 salt '*' nova.keypair_delete mykey'
1847 '''
1848 conn = _auth(profile)
1849 return conn.keypair_delete(name)
1850
1851
1852def image_list(name=None, profile=None):
1853 '''
1854 Return a list of available images (nova images-list + nova image-show)
1855 If a name is provided, only that image will be displayed.
1856 CLI Examples:
1857 .. code-block:: bash
1858 salt '*' nova.image_list
1859 salt '*' nova.image_list myimage
1860 '''
1861 conn = _auth(profile)
1862 return conn.image_list(name)
1863
1864
1865def image_meta_set(image_id=None,
1866 name=None,
1867 profile=None,
1868 **kwargs): # pylint: disable=C0103
1869 '''
1870 Sets a key=value pair in the metadata for an image (nova image-meta set)
1871 CLI Examples:
1872 .. code-block:: bash
1873 salt '*' nova.image_meta_set 6f52b2ff-0b31-4d84-8fd1-af45b84824f6 \
1874 cheese=gruyere
1875 salt '*' nova.image_meta_set name=myimage salad=pasta beans=baked
1876 '''
1877 conn = _auth(profile)
1878 return conn.image_meta_set(
1879 image_id,
1880 name,
1881 **kwargs
1882 )
1883
1884
1885def image_meta_delete(image_id=None, # pylint: disable=C0103
1886 name=None,
1887 keys=None,
1888 profile=None):
1889 '''
1890 Delete a key=value pair from the metadata for an image
1891 (nova image-meta set)
1892 CLI Examples:
1893 .. code-block:: bash
1894 salt '*' nova.image_meta_delete \
1895 6f52b2ff-0b31-4d84-8fd1-af45b84824f6 keys=cheese
1896 salt '*' nova.image_meta_delete name=myimage keys=salad,beans
1897 '''
1898 conn = _auth(profile)
1899 return conn.image_meta_delete(
1900 image_id,
1901 name,
1902 keys
1903 )
1904
1905
1906def list_(profile=None):
1907 '''
1908 To maintain the feel of the nova command line, this function simply calls
1909 the server_list function.
1910 CLI Example:
1911 .. code-block:: bash
1912 salt '*' nova.list
1913 '''
1914 return server_list(profile=profile)
1915
1916
1917def server_list(profile=None):
1918 '''
1919 Return list of active servers
1920 CLI Example:
1921 .. code-block:: bash
1922 salt '*' nova.server_list
1923 '''
1924 conn = _auth(profile)
1925 return conn.server_list()
1926
1927
1928def show(server_id, profile=None):
1929 '''
1930 To maintain the feel of the nova command line, this function simply calls
1931 the server_show function.
1932 CLI Example:
1933 .. code-block:: bash
1934 salt '*' nova.show
1935 '''
1936 return server_show(server_id, profile)
1937
1938
1939def server_list_detailed(profile=None):
1940 '''
1941 Return detailed list of active servers
1942 CLI Example:
1943 .. code-block:: bash
1944 salt '*' nova.server_list_detailed
1945 '''
1946 conn = _auth(profile)
1947 return conn.server_list_detailed()
1948
1949
1950def server_show(server_id, profile=None):
1951 '''
1952 Return detailed information for an active server
1953 CLI Example:
1954 .. code-block:: bash
1955 salt '*' nova.server_show <server_id>
1956 '''
1957 conn = _auth(profile)
1958 return conn.server_show(server_id)
1959
1960
1961def secgroup_create(name, description, profile=None):
1962 '''
1963 Add a secgroup to nova (nova secgroup-create)
1964 CLI Example:
1965 .. code-block:: bash
1966 salt '*' nova.secgroup_create mygroup 'This is my security group'
1967 '''
1968 conn = _auth(profile)
1969 return conn.secgroup_create(name, description)
1970
1971
1972def secgroup_delete(name, profile=None):
1973 '''
1974 Delete a secgroup to nova (nova secgroup-delete)
1975 CLI Example:
1976 .. code-block:: bash
1977 salt '*' nova.secgroup_delete mygroup
1978 '''
1979 conn = _auth(profile)
1980 return conn.secgroup_delete(name)
1981
1982
1983def secgroup_list(profile=None):
1984 '''
1985 Return a list of available security groups (nova items-list)
1986 CLI Example:
1987 .. code-block:: bash
1988 salt '*' nova.secgroup_list
1989 '''
1990 conn = _auth(profile)
1991 return conn.secgroup_list()
1992
1993
1994def server_by_name(name, profile=None):
1995 '''
1996 Return information about a server
1997 name
1998 Server Name
1999 CLI Example:
2000 .. code-block:: bash
2001 salt '*' nova.server_by_name myserver profile=openstack
2002 '''
2003 conn = _auth(profile)
2004 return conn.server_by_name(name)
2005