blob: 4748151ceffa367686de6a85d4d66c84c96423b1 [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
Adam Tenglerfac39bf2017-06-28 11:01:58 +0000496def _format_v2_endpoints(endpoints_v2, services):
497 catalog = []
498 for endpoint_v2 in endpoints_v2:
499 endpoints = []
500 endpoint = endpoint_v2.copy()
501 if 'internalurl' in endpoint:
502 internalurl = endpoint.pop('internalurl')
503 endpoint['internalURL'] = internalurl
504
505 if 'adminurl' in endpoint:
506 adminurl = endpoint.pop('adminurl')
507 endpoint['adminURL'] = adminurl
508
509 if 'publicurl' in endpoint:
510 publicurl = endpoint.pop('publicurl')
511 endpoint['publicURL'] = publicurl
512
513 etype = endpoint.pop('type', '')
514 ename = endpoint.pop('name', '')
515 if endpoint.get('service_id', None) and not etype and not ename:
516 service = [s for s in services if s.get('id', '') == endpoint.get('service_id')]
517 etype = service[0].get('type', '')
518 ename = service[0].get('name', '')
519
520 entry = {
521 'type': etype,
522 'name': ename,
523 'id': endpoint.pop('id'),
524 'region': endpoint.get('region'),
525 'endpoints': [endpoint]
526 }
527 catalog.append(entry)
528
529 return catalog
530
531
Adam Tenglere8afccc2017-06-27 17:57:21 +0000532# Function alias to not shadow built-ins
533class SaltNova(object):
534 '''
535 Class for all novaclient functions
536 '''
537 extensions = []
538
539 def __init__(
540 self,
541 username,
542 project_id,
543 auth_url,
544 region_name=None,
545 password=None,
546 os_auth_plugin=None,
547 use_keystoneauth=False,
548 verify=True,
549 **kwargs
550 ):
551 '''
552 Set up nova credentials
553 '''
554 if all([use_keystoneauth, HAS_KEYSTONEAUTH]):
555 self._new_init(username=username,
556 project_id=project_id,
557 auth_url=auth_url,
558 region_name=region_name,
559 password=password,
560 os_auth_plugin=os_auth_plugin,
561 verify=verify,
562 **kwargs)
563 else:
564 self._old_init(username=username,
565 project_id=project_id,
566 auth_url=auth_url,
567 region_name=region_name,
568 password=password,
569 os_auth_plugin=os_auth_plugin,
570 verify=verify,
571 **kwargs)
572
573 def _new_init(self, username, project_id, auth_url, region_name, password, os_auth_plugin, auth=None, verify=True, **kwargs):
574 if auth is None:
575 auth = {}
576
577 loader = keystoneauth1.loading.get_plugin_loader(os_auth_plugin or 'password')
578
579 self.client_kwargs = kwargs.copy()
580 self.kwargs = auth.copy()
581 if not self.extensions:
582 if hasattr(OpenStackComputeShell, '_discover_extensions'):
583 self.extensions = OpenStackComputeShell()._discover_extensions('2.0')
584 else:
585 self.extensions = client.discover_extensions('2.0')
586 for extension in self.extensions:
587 extension.run_hooks('__pre_parse_args__')
588 self.client_kwargs['extensions'] = self.extensions
589
590 self.kwargs['username'] = username
591 self.kwargs['project_name'] = project_id
592 self.kwargs['auth_url'] = auth_url
593 self.kwargs['password'] = password
594 if auth_url.endswith('3'):
595 self.kwargs['user_domain_name'] = kwargs.get('user_domain_name', 'default')
596 self.kwargs['project_domain_name'] = kwargs.get('project_domain_name', 'default')
597
598 self.client_kwargs['region_name'] = region_name
599 self.client_kwargs['service_type'] = 'compute'
600
601 if hasattr(self, 'extensions'):
602 # needs an object, not a dictionary
603 self.kwargstruct = KwargsStruct(**self.client_kwargs)
604 for extension in self.extensions:
605 extension.run_hooks('__post_parse_args__', self.kwargstruct)
606 self.client_kwargs = self.kwargstruct.__dict__
607
608 # Requires novaclient version >= 2.6.1
609 self.version = str(kwargs.get('version', 2))
610
611 self.client_kwargs = sanatize_novaclient(self.client_kwargs)
612 options = loader.load_from_options(**self.kwargs)
613 self.session = keystoneauth1.session.Session(auth=options, verify=verify)
614 conn = client.Client(version=self.version, session=self.session, **self.client_kwargs)
615 self.kwargs['auth_token'] = conn.client.session.get_token()
616 if conn.client.get_endpoint(service_type='identity').endswith('v3'):
617 self.catalog = conn.client.session.get('/auth/catalog', endpoint_filter={'service_type': 'identity'}).json().get('catalog', [])
618 self._v3_setup(region_name)
619 else:
620 if OCATA:
Adam Tenglerfac39bf2017-06-28 11:01:58 +0000621 endpoints_v2 = conn.client.session.get('/endpoints', endpoint_filter={'service_type': 'identity', 'interface': 'admin'}).json().get('endpoints', [])
622 services = conn.client.session.get('/OS-KSADM/services', endpoint_filter={'service_type': 'identity', 'interface': 'admin'}).json().get('OS-KSADM:services', [])
623 self.catalog = _format_v2_endpoints(endpoints_v2, services)
624 else:
625 self.catalog = conn.client.service_catalog.catalog['access']['serviceCatalog']
Adam Tenglere8afccc2017-06-27 17:57:21 +0000626 self._v2_setup(region_name)
627
628 def _old_init(self, username, project_id, auth_url, region_name, password, os_auth_plugin, **kwargs):
629 self.kwargs = kwargs.copy()
630 if not self.extensions:
631 if hasattr(OpenStackComputeShell, '_discover_extensions'):
632 self.extensions = OpenStackComputeShell()._discover_extensions('2.0')
633 else:
634 self.extensions = client.discover_extensions('2.0')
635 for extension in self.extensions:
636 extension.run_hooks('__pre_parse_args__')
637 self.kwargs['extensions'] = self.extensions
638
639 self.kwargs['username'] = username
640 self.kwargs['project_id'] = project_id
641 self.kwargs['auth_url'] = auth_url
642 self.kwargs['region_name'] = region_name
643 self.kwargs['service_type'] = 'compute'
644
645 # used in novaclient extensions to see if they are rackspace or not, to know if it needs to load
646 # the hooks for that extension or not. This is cleaned up by sanatize_novaclient
647 self.kwargs['os_auth_url'] = auth_url
648
649 if os_auth_plugin is not None:
650 if OCATA:
651 msg = 'Module auth_plugin is no longer present in python-novaclient >= 7.0.0'
652 raise Exception(msg)
653 else:
654 novaclient.auth_plugin.discover_auth_systems()
655 auth_plugin = novaclient.auth_plugin.load_plugin(os_auth_plugin)
656 self.kwargs['auth_plugin'] = auth_plugin
657 self.kwargs['auth_system'] = os_auth_plugin
658
659 if not self.kwargs.get('api_key', None):
660 self.kwargs['api_key'] = password
661
662 # This has to be run before sanatize_novaclient before extra variables are cleaned out.
663 if hasattr(self, 'extensions'):
664 # needs an object, not a dictionary
665 self.kwargstruct = KwargsStruct(**self.kwargs)
666 for extension in self.extensions:
667 extension.run_hooks('__post_parse_args__', self.kwargstruct)
668 self.kwargs = self.kwargstruct.__dict__
669
670 self.kwargs = sanatize_novaclient(self.kwargs)
671
672 # Requires novaclient version >= 2.6.1
673 self.kwargs['version'] = str(kwargs.get('version', 2))
674
675 conn = client.Client(**self.kwargs)
676 try:
677 conn.client.authenticate()
678 except novaclient.exceptions.AmbiguousEndpoints:
679 raise SaltCloudSystemExit(
680 "Nova provider requires a 'region_name' to be specified"
681 )
682
683 self.kwargs['auth_token'] = conn.client.auth_token
684
685 # There is currently no way to get service catalog in the expected format with Ocata compatible
686 # python-novaclient in _old_init, because there is no session
687 if OCATA:
688 msg = 'Method service_catalog is no longer present in python-novaclient >= 7.0.0'
689 raise Exception(msg)
690
691 self.catalog = conn.client.service_catalog.catalog['access']['serviceCatalog']
692
693 self._v2_setup(region_name)
694
695 def _v3_setup(self, region_name):
696 if region_name is not None:
697 servers_endpoints = get_entry(self.catalog, 'type', 'compute')['endpoints']
698 self.kwargs['bypass_url'] = get_entry_multi(
699 servers_endpoints,
700 [('region', region_name), ('interface', 'public')]
701 )['url']
702
703 if hasattr(self, 'session'):
704 self.compute_conn = client.Client(version=self.version, session=self.session, **self.client_kwargs)
705 else:
706 self.compute_conn = client.Client(**self.kwargs)
707
708 volume_endpoints = get_entry(self.catalog, 'type', 'volume', raise_error=False).get('endpoints', {})
709 if volume_endpoints:
710 if region_name is not None:
711 self.kwargs['bypass_url'] = get_entry_multi(
712 volume_endpoints,
713 [('region', region_name), ('interface', 'public')]
714 )['url']
715
Adam Tenglerfac39bf2017-06-28 11:01:58 +0000716 if hasattr(self, 'session'):
717 self.volume_conn = client.Client(version=self.version, session=self.session, **self.client_kwargs)
718 else:
719 self.volume_conn = client.Client(**self.kwargs)
720
Adam Tenglere8afccc2017-06-27 17:57:21 +0000721 if hasattr(self, 'extensions'):
722 self.expand_extensions()
723 else:
724 self.volume_conn = None
725
726 def _v2_setup(self, region_name):
727 if region_name is not None:
728 servers_endpoints = get_entry(self.catalog, 'type', 'compute')['endpoints']
729 self.kwargs['bypass_url'] = get_entry(
730 servers_endpoints,
731 'region',
732 region_name
733 )['publicURL']
734
735 if hasattr(self, 'session'):
736 self.compute_conn = client.Client(version=self.version, session=self.session, **self.client_kwargs)
737 else:
738 self.compute_conn = client.Client(**self.kwargs)
739
740 volume_endpoints = get_entry(self.catalog, 'type', 'volume', raise_error=False).get('endpoints', {})
741 if volume_endpoints:
742 if region_name is not None:
743 self.kwargs['bypass_url'] = get_entry(
744 volume_endpoints,
745 'region',
746 region_name
747 )['publicURL']
748
Adam Tenglerfac39bf2017-06-28 11:01:58 +0000749 if hasattr(self, 'session'):
750 self.volume_conn = client.Client(version=self.version, session=self.session, **self.client_kwargs)
751 else:
752 self.volume_conn = client.Client(**self.kwargs)
753
Adam Tenglere8afccc2017-06-27 17:57:21 +0000754 if hasattr(self, 'extensions'):
755 self.expand_extensions()
756 else:
757 self.volume_conn = None
758
759 def expand_extensions(self):
760 for connection in (self.compute_conn, self.volume_conn):
761 if connection is None:
762 continue
763 for extension in self.extensions:
764 for attr in extension.module.__dict__:
765 if not inspect.isclass(getattr(extension.module, attr)):
766 continue
767 for key, value in six.iteritems(connection.__dict__):
768 if not isinstance(value, novaclient.base.Manager):
769 continue
770 if value.__class__.__name__ == attr:
771 setattr(connection, key, extension.manager_class(connection))
772
773 def get_catalog(self):
774 '''
775 Return service catalog
776 '''
777 return self.catalog
778
779 def server_show_libcloud(self, uuid):
780 '''
781 Make output look like libcloud output for consistency
782 '''
783 server_info = self.server_show(uuid)
784 server = next(six.itervalues(server_info))
785 server_name = next(six.iterkeys(server_info))
786 if not hasattr(self, 'password'):
787 self.password = None
788 ret = NovaServer(server_name, server, self.password)
789
790 return ret
791
792 def boot(self, name, flavor_id=0, image_id=0, timeout=300, **kwargs):
793 '''
794 Boot a cloud server.
795 '''
796 nt_ks = self.compute_conn
797 kwargs['name'] = name
798 kwargs['flavor'] = flavor_id
799 kwargs['image'] = image_id or None
800 ephemeral = kwargs.pop('ephemeral', [])
801 block_device = kwargs.pop('block_device', [])
802 boot_volume = kwargs.pop('boot_volume', None)
803 snapshot = kwargs.pop('snapshot', None)
804 swap = kwargs.pop('swap', None)
805 kwargs['block_device_mapping_v2'] = _parse_block_device_mapping_v2(
806 block_device=block_device, boot_volume=boot_volume, snapshot=snapshot,
807 ephemeral=ephemeral, swap=swap
808 )
809 response = nt_ks.servers.create(**kwargs)
810 self.uuid = response.id
811 self.password = getattr(response, 'adminPass', None)
812
813 start = time.time()
814 trycount = 0
815 while True:
816 trycount += 1
817 try:
818 return self.server_show_libcloud(self.uuid)
819 except Exception as exc:
820 log.debug(
821 'Server information not yet available: {0}'.format(exc)
822 )
823 time.sleep(1)
824 if time.time() - start > timeout:
825 log.error('Timed out after {0} seconds '
826 'while waiting for data'.format(timeout))
827 return False
828
829 log.debug(
830 'Retrying server_show() (try {0})'.format(trycount)
831 )
832
833 def show_instance(self, name):
834 '''
835 Find a server by its name (libcloud)
836 '''
837 return self.server_by_name(name)
838
839 def root_password(self, server_id, password):
840 '''
841 Change server(uuid's) root password
842 '''
843 nt_ks = self.compute_conn
844 nt_ks.servers.change_password(server_id, password)
845
846 def server_by_name(self, name):
847 '''
848 Find a server by its name
849 '''
850 return self.server_show_libcloud(
851 self.server_list().get(name, {}).get('id', '')
852 )
853
854 def _volume_get(self, volume_id):
855 '''
856 Organize information about a volume from the volume_id
857 '''
858 if self.volume_conn is None:
859 raise SaltCloudSystemExit('No cinder endpoint available')
860 nt_ks = self.volume_conn
861 volume = nt_ks.volumes.get(volume_id)
862 response = {'name': volume.display_name,
863 'size': volume.size,
864 'id': volume.id,
865 'description': volume.display_description,
866 'attachments': volume.attachments,
867 'status': volume.status
868 }
869 return response
870
871 def volume_list(self, search_opts=None):
872 '''
873 List all block volumes
874 '''
875 if self.volume_conn is None:
876 raise SaltCloudSystemExit('No cinder endpoint available')
877 nt_ks = self.volume_conn
878 volumes = nt_ks.volumes.list(search_opts=search_opts)
879 response = {}
880 for volume in volumes:
881 response[volume.display_name] = {
882 'name': volume.display_name,
883 'size': volume.size,
884 'id': volume.id,
885 'description': volume.display_description,
886 'attachments': volume.attachments,
887 'status': volume.status
888 }
889 return response
890
891 def volume_show(self, name):
892 '''
893 Show one volume
894 '''
895 if self.volume_conn is None:
896 raise SaltCloudSystemExit('No cinder endpoint available')
897 nt_ks = self.volume_conn
898 volumes = self.volume_list(
899 search_opts={'display_name': name},
900 )
901 volume = volumes[name]
902# except Exception as esc:
903# # volume doesn't exist
904# log.error(esc.strerror)
905# return {'name': name, 'status': 'deleted'}
906
907 return volume
908
909 def volume_create(self, name, size=100, snapshot=None, voltype=None,
910 availability_zone=None):
911 '''
912 Create a block device
913 '''
914 if self.volume_conn is None:
915 raise SaltCloudSystemExit('No cinder endpoint available')
916 nt_ks = self.volume_conn
917 response = nt_ks.volumes.create(
918 size=size,
919 display_name=name,
920 volume_type=voltype,
921 snapshot_id=snapshot,
922 availability_zone=availability_zone
923 )
924
925 return self._volume_get(response.id)
926
927 def volume_delete(self, name):
928 '''
929 Delete a block device
930 '''
931 if self.volume_conn is None:
932 raise SaltCloudSystemExit('No cinder endpoint available')
933 nt_ks = self.volume_conn
934 try:
935 volume = self.volume_show(name)
936 except KeyError as exc:
937 raise SaltCloudSystemExit('Unable to find {0} volume: {1}'.format(name, exc))
938 if volume['status'] == 'deleted':
939 return volume
940 response = nt_ks.volumes.delete(volume['id'])
941 return volume
942
943 def volume_detach(self,
944 name,
945 timeout=300):
946 '''
947 Detach a block device
948 '''
949 try:
950 volume = self.volume_show(name)
951 except KeyError as exc:
952 raise SaltCloudSystemExit('Unable to find {0} volume: {1}'.format(name, exc))
953 if not volume['attachments']:
954 return True
955 response = self.compute_conn.volumes.delete_server_volume(
956 volume['attachments'][0]['server_id'],
957 volume['attachments'][0]['id']
958 )
959 trycount = 0
960 start = time.time()
961 while True:
962 trycount += 1
963 try:
964 response = self._volume_get(volume['id'])
965 if response['status'] == 'available':
966 return response
967 except Exception as exc:
968 log.debug('Volume is detaching: {0}'.format(name))
969 time.sleep(1)
970 if time.time() - start > timeout:
971 log.error('Timed out after {0} seconds '
972 'while waiting for data'.format(timeout))
973 return False
974
975 log.debug(
976 'Retrying volume_show() (try {0})'.format(trycount)
977 )
978
979 def volume_attach(self,
980 name,
981 server_name,
982 device='/dev/xvdb',
983 timeout=300):
984 '''
985 Attach a block device
986 '''
987 try:
988 volume = self.volume_show(name)
989 except KeyError as exc:
990 raise SaltCloudSystemExit('Unable to find {0} volume: {1}'.format(name, exc))
991 server = self.server_by_name(server_name)
992 response = self.compute_conn.volumes.create_server_volume(
993 server.id,
994 volume['id'],
995 device=device
996 )
997 trycount = 0
998 start = time.time()
999 while True:
1000 trycount += 1
1001 try:
1002 response = self._volume_get(volume['id'])
1003 if response['status'] == 'in-use':
1004 return response
1005 except Exception as exc:
1006 log.debug('Volume is attaching: {0}'.format(name))
1007 time.sleep(1)
1008 if time.time() - start > timeout:
1009 log.error('Timed out after {0} seconds '
1010 'while waiting for data'.format(timeout))
1011 return False
1012
1013 log.debug(
1014 'Retrying volume_show() (try {0})'.format(trycount)
1015 )
1016
1017 def suspend(self, instance_id):
1018 '''
1019 Suspend a server
1020 '''
1021 nt_ks = self.compute_conn
1022 response = nt_ks.servers.suspend(instance_id)
1023 return True
1024
1025 def resume(self, instance_id):
1026 '''
1027 Resume a server
1028 '''
1029 nt_ks = self.compute_conn
1030 response = nt_ks.servers.resume(instance_id)
1031 return True
1032
1033 def lock(self, instance_id):
1034 '''
1035 Lock an instance
1036 '''
1037 nt_ks = self.compute_conn
1038 response = nt_ks.servers.lock(instance_id)
1039 return True
1040
1041 def delete(self, instance_id):
1042 '''
1043 Delete a server
1044 '''
1045 nt_ks = self.compute_conn
1046 response = nt_ks.servers.delete(instance_id)
1047 return True
1048
1049 def flavor_list(self):
1050 '''
1051 Return a list of available flavors (nova flavor-list)
1052 '''
1053 nt_ks = self.compute_conn
1054 ret = {}
1055 for flavor in nt_ks.flavors.list():
1056 links = {}
1057 for link in flavor.links:
1058 links[link['rel']] = link['href']
1059 ret[flavor.name] = {
1060 'disk': flavor.disk,
1061 'id': flavor.id,
1062 'name': flavor.name,
1063 'ram': flavor.ram,
1064 'swap': flavor.swap,
1065 'vcpus': flavor.vcpus,
1066 'links': links,
1067 }
1068 if hasattr(flavor, 'rxtx_factor'):
1069 ret[flavor.name]['rxtx_factor'] = flavor.rxtx_factor
1070 return ret
1071
1072 list_sizes = flavor_list
1073
1074 def flavor_create(self,
1075 name, # pylint: disable=C0103
1076 flavor_id=0, # pylint: disable=C0103
1077 ram=0,
1078 disk=0,
1079 vcpus=1):
1080 '''
1081 Create a flavor
1082 '''
1083 nt_ks = self.compute_conn
1084 nt_ks.flavors.create(
1085 name=name, flavorid=flavor_id, ram=ram, disk=disk, vcpus=vcpus
1086 )
1087 return {'name': name,
1088 'id': flavor_id,
1089 'ram': ram,
1090 'disk': disk,
1091 'vcpus': vcpus}
1092
1093 def flavor_delete(self, flavor_id): # pylint: disable=C0103
1094 '''
1095 Delete a flavor
1096 '''
1097 nt_ks = self.compute_conn
1098 nt_ks.flavors.delete(flavor_id)
1099 return 'Flavor deleted: {0}'.format(flavor_id)
1100
1101 def keypair_list(self):
1102 '''
1103 List keypairs
1104 '''
1105 nt_ks = self.compute_conn
1106 ret = {}
1107 for keypair in nt_ks.keypairs.list():
1108 ret[keypair.name] = {
1109 'name': keypair.name,
1110 'fingerprint': keypair.fingerprint,
1111 'public_key': keypair.public_key,
1112 }
1113 return ret
1114
1115 def keypair_add(self, name, pubfile=None, pubkey=None):
1116 '''
1117 Add a keypair
1118 '''
1119 nt_ks = self.compute_conn
1120 if pubfile:
1121 with salt.utils.fopen(pubfile, 'r') as fp_:
1122 pubkey = fp_.read()
1123 if not pubkey:
1124 return False
1125 nt_ks.keypairs.create(name, public_key=pubkey)
1126 ret = {'name': name, 'pubkey': pubkey}
1127 return ret
1128
1129 def keypair_delete(self, name):
1130 '''
1131 Delete a keypair
1132 '''
1133 nt_ks = self.compute_conn
1134 nt_ks.keypairs.delete(name)
1135 return 'Keypair deleted: {0}'.format(name)
1136
1137 def image_show(self, image_id):
1138 '''
1139 Show image details and metadata
1140 '''
1141 nt_ks = self.compute_conn
1142 image = nt_ks.images.get(image_id)
1143 links = {}
1144 for link in image.links:
1145 links[link['rel']] = link['href']
1146 ret = {
1147 'name': image.name,
1148 'id': image.id,
1149 'status': image.status,
1150 'progress': image.progress,
1151 'created': image.created,
1152 'updated': image.updated,
1153 'metadata': image.metadata,
1154 'links': links,
1155 }
1156 if hasattr(image, 'minDisk'):
1157 ret['minDisk'] = image.minDisk
1158 if hasattr(image, 'minRam'):
1159 ret['minRam'] = image.minRam
1160
1161 return ret
1162
1163 def image_list(self, name=None):
1164 '''
1165 List server images
1166 '''
1167 nt_ks = self.compute_conn
1168 ret = {}
1169 for image in nt_ks.images.list():
1170 links = {}
1171 for link in image.links:
1172 links[link['rel']] = link['href']
1173 ret[image.name] = {
1174 'name': image.name,
1175 'id': image.id,
1176 'status': image.status,
1177 'progress': image.progress,
1178 'created': image.created,
1179 'updated': image.updated,
1180 'metadata': image.metadata,
1181 'links': links,
1182 }
1183 if hasattr(image, 'minDisk'):
1184 ret[image.name]['minDisk'] = image.minDisk
1185 if hasattr(image, 'minRam'):
1186 ret[image.name]['minRam'] = image.minRam
1187 if name:
1188 return {name: ret[name]}
1189 return ret
1190
1191 list_images = image_list
1192
1193 def image_meta_set(self,
1194 image_id=None,
1195 name=None,
1196 **kwargs): # pylint: disable=C0103
1197 '''
1198 Set image metadata
1199 '''
1200 nt_ks = self.compute_conn
1201 if name:
1202 for image in nt_ks.images.list():
1203 if image.name == name:
1204 image_id = image.id # pylint: disable=C0103
1205 if not image_id:
1206 return {'Error': 'A valid image name or id was not specified'}
1207 nt_ks.images.set_meta(image_id, kwargs)
1208 return {image_id: kwargs}
1209
1210 def image_meta_delete(self,
1211 image_id=None, # pylint: disable=C0103
1212 name=None,
1213 keys=None):
1214 '''
1215 Delete image metadata
1216 '''
1217 nt_ks = self.compute_conn
1218 if name:
1219 for image in nt_ks.images.list():
1220 if image.name == name:
1221 image_id = image.id # pylint: disable=C0103
1222 pairs = keys.split(',')
1223 if not image_id:
1224 return {'Error': 'A valid image name or id was not specified'}
1225 nt_ks.images.delete_meta(image_id, pairs)
1226 return {image_id: 'Deleted: {0}'.format(pairs)}
1227
1228 def server_list(self):
1229 '''
1230 List servers
1231 '''
1232 nt_ks = self.compute_conn
1233 ret = {}
1234 for item in nt_ks.servers.list():
1235 try:
1236 ret[item.name] = {
1237 'id': item.id,
1238 'name': item.name,
1239 'state': item.status,
1240 'accessIPv4': item.accessIPv4,
1241 'accessIPv6': item.accessIPv6,
1242 'flavor': {'id': item.flavor['id'],
1243 'links': item.flavor['links']},
1244 'image': {'id': item.image['id'] if item.image else 'Boot From Volume',
1245 'links': item.image['links'] if item.image else ''},
1246 }
1247 except TypeError:
1248 pass
1249 return ret
1250
1251 def server_list_min(self):
1252 '''
1253 List minimal information about servers
1254 '''
1255 nt_ks = self.compute_conn
1256 ret = {}
1257 for item in nt_ks.servers.list(detailed=False):
1258 try:
1259 ret[item.name] = {
1260 'id': item.id,
1261 'status': 'Running'
1262 }
1263 except TypeError:
1264 pass
1265 return ret
1266
1267 def server_list_detailed(self):
1268 '''
1269 Detailed list of servers
1270 '''
1271 nt_ks = self.compute_conn
1272 ret = {}
1273 for item in nt_ks.servers.list():
1274 try:
1275 ret[item.name] = {
1276 'OS-EXT-SRV-ATTR': {},
1277 'OS-EXT-STS': {},
1278 'accessIPv4': item.accessIPv4,
1279 'accessIPv6': item.accessIPv6,
1280 'addresses': item.addresses,
1281 'created': item.created,
1282 'flavor': {'id': item.flavor['id'],
1283 'links': item.flavor['links']},
1284 'hostId': item.hostId,
1285 'id': item.id,
1286 'image': {'id': item.image['id'] if item.image else 'Boot From Volume',
1287 'links': item.image['links'] if item.image else ''},
1288 'key_name': item.key_name,
1289 'links': item.links,
1290 'metadata': item.metadata,
1291 'name': item.name,
1292 'state': item.status,
1293 'tenant_id': item.tenant_id,
1294 'updated': item.updated,
1295 'user_id': item.user_id,
1296 }
1297 except TypeError:
1298 continue
1299
1300 ret[item.name]['progress'] = getattr(item, 'progress', '0')
1301
1302 if hasattr(item.__dict__, 'OS-DCF:diskConfig'):
1303 ret[item.name]['OS-DCF'] = {
1304 'diskConfig': item.__dict__['OS-DCF:diskConfig']
1305 }
1306 if hasattr(item.__dict__, 'OS-EXT-SRV-ATTR:host'):
1307 ret[item.name]['OS-EXT-SRV-ATTR']['host'] = \
1308 item.__dict__['OS-EXT-SRV-ATTR:host']
1309 if hasattr(item.__dict__, 'OS-EXT-SRV-ATTR:hypervisor_hostname'):
1310 ret[item.name]['OS-EXT-SRV-ATTR']['hypervisor_hostname'] = \
1311 item.__dict__['OS-EXT-SRV-ATTR:hypervisor_hostname']
1312 if hasattr(item.__dict__, 'OS-EXT-SRV-ATTR:instance_name'):
1313 ret[item.name]['OS-EXT-SRV-ATTR']['instance_name'] = \
1314 item.__dict__['OS-EXT-SRV-ATTR:instance_name']
1315 if hasattr(item.__dict__, 'OS-EXT-STS:power_state'):
1316 ret[item.name]['OS-EXT-STS']['power_state'] = \
1317 item.__dict__['OS-EXT-STS:power_state']
1318 if hasattr(item.__dict__, 'OS-EXT-STS:task_state'):
1319 ret[item.name]['OS-EXT-STS']['task_state'] = \
1320 item.__dict__['OS-EXT-STS:task_state']
1321 if hasattr(item.__dict__, 'OS-EXT-STS:vm_state'):
1322 ret[item.name]['OS-EXT-STS']['vm_state'] = \
1323 item.__dict__['OS-EXT-STS:vm_state']
1324 if hasattr(item.__dict__, 'security_groups'):
1325 ret[item.name]['security_groups'] = \
1326 item.__dict__['security_groups']
1327 return ret
1328
1329 def server_show(self, server_id):
1330 '''
1331 Show details of one server
1332 '''
1333 ret = {}
1334 try:
1335 servers = self.server_list_detailed()
1336 except AttributeError:
1337 raise SaltCloudSystemExit('Corrupt server in server_list_detailed. Remove corrupt servers.')
1338 for server_name, server in six.iteritems(servers):
1339 if str(server['id']) == server_id:
1340 ret[server_name] = server
1341 return ret
1342
1343 def secgroup_create(self, name, description):
1344 '''
1345 Create a security group
1346 '''
1347 nt_ks = self.compute_conn
1348 nt_ks.security_groups.create(name, description)
1349 ret = {'name': name, 'description': description}
1350 return ret
1351
1352 def secgroup_delete(self, name):
1353 '''
1354 Delete a security group
1355 '''
1356 nt_ks = self.compute_conn
1357 for item in nt_ks.security_groups.list():
1358 if item.name == name:
1359 nt_ks.security_groups.delete(item.id)
1360 return {name: 'Deleted security group: {0}'.format(name)}
1361 return 'Security group not found: {0}'.format(name)
1362
1363 def secgroup_list(self):
1364 '''
1365 List security groups
1366 '''
1367 nt_ks = self.compute_conn
1368 ret = {}
1369 for item in nt_ks.security_groups.list():
1370 ret[item.name] = {
1371 'name': item.name,
1372 'description': item.description,
1373 'id': item.id,
1374 'tenant_id': item.tenant_id,
1375 'rules': item.rules,
1376 }
1377 return ret
1378
1379 def _item_list(self):
1380 '''
1381 List items
1382 '''
1383 nt_ks = self.compute_conn
1384 ret = []
1385 for item in nt_ks.items.list():
1386 ret.append(item.__dict__)
1387 return ret
1388
1389 def _network_show(self, name, network_lst):
1390 '''
1391 Parse the returned network list
1392 '''
1393 for net in network_lst:
1394 if net.label == name:
1395 return net.__dict__
1396 return {}
1397
1398 def network_show(self, name):
1399 '''
1400 Show network information
1401 '''
1402 nt_ks = self.compute_conn
1403 net_list = nt_ks.networks.list()
1404 return self._network_show(name, net_list)
1405
1406 def network_list(self):
1407 '''
1408 List extra private networks
1409 '''
1410 nt_ks = self.compute_conn
1411 return [network.__dict__ for network in nt_ks.networks.list()]
1412
1413 def _sanatize_network_params(self, kwargs):
1414 '''
1415 Sanatize novaclient network parameters
1416 '''
1417 params = [
1418 'label', 'bridge', 'bridge_interface', 'cidr', 'cidr_v6', 'dns1',
1419 'dns2', 'fixed_cidr', 'gateway', 'gateway_v6', 'multi_host',
1420 'priority', 'project_id', 'vlan_start', 'vpn_start'
1421 ]
1422
1423 for variable in six.iterkeys(kwargs): # iterate over a copy, we might delete some
1424 if variable not in params:
1425 del kwargs[variable]
1426 return kwargs
1427
1428 def network_create(self, name, **kwargs):
1429 '''
1430 Create extra private network
1431 '''
1432 nt_ks = self.compute_conn
1433 kwargs['label'] = name
1434 kwargs = self._sanatize_network_params(kwargs)
1435 net = nt_ks.networks.create(**kwargs)
1436 return net.__dict__
1437
1438 def _server_uuid_from_name(self, name):
1439 '''
1440 Get server uuid from name
1441 '''
1442 return self.server_list().get(name, {}).get('id', '')
1443
1444 def virtual_interface_list(self, name):
1445 '''
1446 Get virtual interfaces on slice
1447 '''
1448 nt_ks = self.compute_conn
1449 nets = nt_ks.virtual_interfaces.list(self._server_uuid_from_name(name))
1450 return [network.__dict__ for network in nets]
1451
1452 def virtual_interface_create(self, name, net_name):
1453 '''
1454 Add an interfaces to a slice
1455 '''
1456 nt_ks = self.compute_conn
1457 serverid = self._server_uuid_from_name(name)
1458 networkid = self.network_show(net_name).get('id', None)
1459 if networkid is None:
1460 return {net_name: False}
1461 nets = nt_ks.virtual_interfaces.create(networkid, serverid)
1462 return nets
1463
1464 def floating_ip_pool_list(self):
1465 '''
1466 List all floating IP pools
1467 .. versionadded:: 2016.3.0
1468 '''
1469 nt_ks = self.compute_conn
1470 pools = nt_ks.floating_ip_pools.list()
1471 response = {}
1472 for pool in pools:
1473 response[pool.name] = {
1474 'name': pool.name,
1475 }
1476 return response
1477
1478 def floating_ip_list(self):
1479 '''
1480 List floating IPs
1481 .. versionadded:: 2016.3.0
1482 '''
1483 nt_ks = self.compute_conn
1484 floating_ips = nt_ks.floating_ips.list()
1485 response = {}
1486 for floating_ip in floating_ips:
1487 response[floating_ip.ip] = {
1488 'ip': floating_ip.ip,
1489 'fixed_ip': floating_ip.fixed_ip,
1490 'id': floating_ip.id,
1491 'instance_id': floating_ip.instance_id,
1492 'pool': floating_ip.pool
1493 }
1494 return response
1495
1496 def floating_ip_show(self, ip):
1497 '''
1498 Show info on specific floating IP
1499 .. versionadded:: 2016.3.0
1500 '''
1501 nt_ks = self.compute_conn
1502 floating_ips = nt_ks.floating_ips.list()
1503 for floating_ip in floating_ips:
1504 if floating_ip.ip == ip:
1505 return floating_ip
1506 return {}
1507
1508 def floating_ip_create(self, pool=None):
1509 '''
1510 Allocate a floating IP
1511 .. versionadded:: 2016.3.0
1512 '''
1513 nt_ks = self.compute_conn
1514 floating_ip = nt_ks.floating_ips.create(pool)
1515 response = {
1516 'ip': floating_ip.ip,
1517 'fixed_ip': floating_ip.fixed_ip,
1518 'id': floating_ip.id,
1519 'instance_id': floating_ip.instance_id,
1520 'pool': floating_ip.pool
1521 }
1522 return response
1523
1524 def floating_ip_delete(self, floating_ip):
1525 '''
1526 De-allocate a floating IP
1527 .. versionadded:: 2016.3.0
1528 '''
1529 ip = self.floating_ip_show(floating_ip)
1530 nt_ks = self.compute_conn
1531 return nt_ks.floating_ips.delete(ip)
1532
1533 def floating_ip_associate(self, server_name, floating_ip):
1534 '''
1535 Associate floating IP address to server
1536 .. versionadded:: 2016.3.0
1537 '''
1538 nt_ks = self.compute_conn
1539 server_ = self.server_by_name(server_name)
1540 server = nt_ks.servers.get(server_.__dict__['id'])
1541 server.add_floating_ip(floating_ip)
1542 return self.floating_ip_list()[floating_ip]
1543
1544 def floating_ip_disassociate(self, server_name, floating_ip):
1545 '''
1546 Disassociate a floating IP from server
1547 .. versionadded:: 2016.3.0
1548 '''
1549 nt_ks = self.compute_conn
1550 server_ = self.server_by_name(server_name)
1551 server = nt_ks.servers.get(server_.__dict__['id'])
1552 server.remove_floating_ip(floating_ip)
1553 return self.floating_ip_list()[floating_ip]
1554
1555#
1556# Moved from salt.modules.nova until this works in upstream
1557#
1558
1559def _auth(profile=None):
1560 '''
1561 Set up nova credentials
1562 '''
1563 if profile:
1564 credentials = __salt__['config.option'](profile)
1565 user = credentials['keystone.user']
1566 password = credentials['keystone.password']
1567 tenant = credentials['keystone.tenant']
1568 auth_url = credentials['keystone.auth_url']
1569 region_name = credentials.get('keystone.region_name', None)
1570 api_key = credentials.get('keystone.api_key', None)
1571 os_auth_system = credentials.get('keystone.os_auth_system', None)
1572 use_keystoneauth = credentials.get('keystone.use_keystoneauth', False)
1573 verify = credentials.get('keystone.verify', False)
1574 else:
1575 user = __salt__['config.option']('keystone.user')
1576 password = __salt__['config.option']('keystone.password')
1577 tenant = __salt__['config.option']('keystone.tenant')
1578 auth_url = __salt__['config.option']('keystone.auth_url')
1579 region_name = __salt__['config.option']('keystone.region_name')
1580 api_key = __salt__['config.option']('keystone.api_key')
1581 os_auth_system = __salt__['config.option']('keystone.os_auth_system')
1582 use_keystoneauth = __salt__['config.option']('keystone.use_keystoneauth', False)
1583 verify = __salt__['config.option']('keystone.verify', True)
1584
1585 kwargs = {
1586 'username': user,
1587 'password': password,
1588 'api_key': api_key,
1589 'project_id': tenant,
1590 'auth_url': auth_url,
1591 'region_name': region_name,
1592 'os_auth_plugin': os_auth_system,
1593 'use_keystoneauth': use_keystoneauth,
1594 'verify': verify
1595 }
1596
1597 return SaltNova(**kwargs)
1598
1599
1600def boot(name, flavor_id=0, image_id=0, profile=None, timeout=300):
1601 '''
1602 Boot (create) a new instance
1603 name
1604 Name of the new instance (must be first)
1605 flavor_id
1606 Unique integer ID for the flavor
1607 image_id
1608 Unique integer ID for the image
1609 timeout
1610 How long to wait, after creating the instance, for the provider to
1611 return information about it (default 300 seconds).
1612 .. versionadded:: 2014.1.0
1613 CLI Example:
1614 .. code-block:: bash
1615 salt '*' nova.boot myinstance flavor_id=4596 image_id=2
1616 The flavor_id and image_id are obtained from nova.flavor_list and
1617 nova.image_list
1618 .. code-block:: bash
1619 salt '*' nova.flavor_list
1620 salt '*' nova.image_list
1621 '''
1622 conn = _auth(profile)
1623 return conn.boot(name, flavor_id, image_id, timeout)
1624
1625
1626def volume_list(search_opts=None, profile=None):
1627 '''
1628 List storage volumes
1629 search_opts
1630 Dictionary of search options
1631 profile
1632 Profile to use
1633 CLI Example:
1634 .. code-block:: bash
1635 salt '*' nova.volume_list \
1636 search_opts='{"display_name": "myblock"}' \
1637 profile=openstack
1638 '''
1639 conn = _auth(profile)
1640 return conn.volume_list(search_opts=search_opts)
1641
1642
1643def volume_show(name, profile=None):
1644 '''
1645 Create a block storage volume
1646 name
1647 Name of the volume
1648 profile
1649 Profile to use
1650 CLI Example:
1651 .. code-block:: bash
1652 salt '*' nova.volume_show myblock profile=openstack
1653 '''
1654 conn = _auth(profile)
1655 return conn.volume_show(name)
1656
1657
1658def volume_create(name, size=100, snapshot=None, voltype=None,
1659 profile=None):
1660 '''
1661 Create a block storage volume
1662 name
1663 Name of the new volume (must be first)
1664 size
1665 Volume size
1666 snapshot
1667 Block storage snapshot id
1668 voltype
1669 Type of storage
1670 profile
1671 Profile to build on
1672 CLI Example:
1673 .. code-block:: bash
1674 salt '*' nova.volume_create myblock size=300 profile=openstack
1675 '''
1676 conn = _auth(profile)
1677 return conn.volume_create(
1678 name,
1679 size,
1680 snapshot,
1681 voltype
1682 )
1683
1684
1685def volume_delete(name, profile=None):
1686 '''
1687 Destroy the volume
1688 name
1689 Name of the volume
1690 profile
1691 Profile to build on
1692 CLI Example:
1693 .. code-block:: bash
1694 salt '*' nova.volume_delete myblock profile=openstack
1695 '''
1696 conn = _auth(profile)
1697 return conn.volume_delete(name)
1698
1699
1700def volume_detach(name,
1701 profile=None,
1702 timeout=300):
1703 '''
1704 Attach a block storage volume
1705 name
1706 Name of the new volume to attach
1707 server_name
1708 Name of the server to detach from
1709 profile
1710 Profile to build on
1711 CLI Example:
1712 .. code-block:: bash
1713 salt '*' nova.volume_detach myblock profile=openstack
1714 '''
1715 conn = _auth(profile)
1716 return conn.volume_detach(
1717 name,
1718 timeout
1719 )
1720
1721
1722def volume_attach(name,
1723 server_name,
1724 device='/dev/xvdb',
1725 profile=None,
1726 timeout=300):
1727 '''
1728 Attach a block storage volume
1729 name
1730 Name of the new volume to attach
1731 server_name
1732 Name of the server to attach to
1733 device
1734 Name of the device on the server
1735 profile
1736 Profile to build on
1737 CLI Example:
1738 .. code-block:: bash
1739 salt '*' nova.volume_attach myblock slice.example.com profile=openstack
1740 salt '*' nova.volume_attach myblock server.example.com \
1741 device='/dev/xvdb' profile=openstack
1742 '''
1743 conn = _auth(profile)
1744 return conn.volume_attach(
1745 name,
1746 server_name,
1747 device,
1748 timeout
1749 )
1750
1751
1752def suspend(instance_id, profile=None):
1753 '''
1754 Suspend an instance
1755 instance_id
1756 ID of the instance to be suspended
1757 CLI Example:
1758 .. code-block:: bash
1759 salt '*' nova.suspend 1138
1760 '''
1761 conn = _auth(profile)
1762 return conn.suspend(instance_id)
1763
1764
1765def resume(instance_id, profile=None):
1766 '''
1767 Resume an instance
1768 instance_id
1769 ID of the instance to be resumed
1770 CLI Example:
1771 .. code-block:: bash
1772 salt '*' nova.resume 1138
1773 '''
1774 conn = _auth(profile)
1775 return conn.resume(instance_id)
1776
1777
1778def lock(instance_id, profile=None):
1779 '''
1780 Lock an instance
1781 instance_id
1782 ID of the instance to be locked
1783 CLI Example:
1784 .. code-block:: bash
1785 salt '*' nova.lock 1138
1786 '''
1787 conn = _auth(profile)
1788 return conn.lock(instance_id)
1789
1790
1791def delete(instance_id, profile=None):
1792 '''
1793 Delete an instance
1794 instance_id
1795 ID of the instance to be deleted
1796 CLI Example:
1797 .. code-block:: bash
1798 salt '*' nova.delete 1138
1799 '''
1800 conn = _auth(profile)
1801 return conn.delete(instance_id)
1802
1803
1804def flavor_list(profile=None):
1805 '''
1806 Return a list of available flavors (nova flavor-list)
1807 CLI Example:
1808 .. code-block:: bash
1809 salt '*' nova.flavor_list
1810 '''
1811 conn = _auth(profile)
1812 return conn.flavor_list()
1813
1814
1815def flavor_create(name, # pylint: disable=C0103
1816 flavor_id=0, # pylint: disable=C0103
1817 ram=0,
1818 disk=0,
1819 vcpus=1,
1820 profile=None):
1821 '''
1822 Add a flavor to nova (nova flavor-create). The following parameters are
1823 required:
1824 name
1825 Name of the new flavor (must be first)
1826 flavor_id
1827 Unique integer ID for the new flavor
1828 ram
1829 Memory size in MB
1830 disk
1831 Disk size in GB
1832 vcpus
1833 Number of vcpus
1834 CLI Example:
1835 .. code-block:: bash
1836 salt '*' nova.flavor_create myflavor flavor_id=6 \
1837 ram=4096 disk=10 vcpus=1
1838 '''
1839 conn = _auth(profile)
1840 return conn.flavor_create(
1841 name,
1842 flavor_id,
1843 ram,
1844 disk,
1845 vcpus
1846 )
1847
1848
1849def flavor_delete(flavor_id, profile=None): # pylint: disable=C0103
1850 '''
1851 Delete a flavor from nova by id (nova flavor-delete)
1852 CLI Example:
1853 .. code-block:: bash
1854 salt '*' nova.flavor_delete 7
1855 '''
1856 conn = _auth(profile)
1857 return conn.flavor_delete(flavor_id)
1858
1859
1860def keypair_list(profile=None):
1861 '''
1862 Return a list of available keypairs (nova keypair-list)
1863 CLI Example:
1864 .. code-block:: bash
1865 salt '*' nova.keypair_list
1866 '''
1867 conn = _auth(profile)
1868 return conn.keypair_list()
1869
1870
1871def keypair_add(name, pubfile=None, pubkey=None, profile=None):
1872 '''
1873 Add a keypair to nova (nova keypair-add)
1874 CLI Examples:
1875 .. code-block:: bash
1876 salt '*' nova.keypair_add mykey pubfile='/home/myuser/.ssh/id_rsa.pub'
1877 salt '*' nova.keypair_add mykey pubkey='ssh-rsa <key> myuser@mybox'
1878 '''
1879 conn = _auth(profile)
1880 return conn.keypair_add(
1881 name,
1882 pubfile,
1883 pubkey
1884 )
1885
1886
1887def keypair_delete(name, profile=None):
1888 '''
1889 Add a keypair to nova (nova keypair-delete)
1890 CLI Example:
1891 .. code-block:: bash
1892 salt '*' nova.keypair_delete mykey'
1893 '''
1894 conn = _auth(profile)
1895 return conn.keypair_delete(name)
1896
1897
1898def image_list(name=None, profile=None):
1899 '''
1900 Return a list of available images (nova images-list + nova image-show)
1901 If a name is provided, only that image will be displayed.
1902 CLI Examples:
1903 .. code-block:: bash
1904 salt '*' nova.image_list
1905 salt '*' nova.image_list myimage
1906 '''
1907 conn = _auth(profile)
1908 return conn.image_list(name)
1909
1910
1911def image_meta_set(image_id=None,
1912 name=None,
1913 profile=None,
1914 **kwargs): # pylint: disable=C0103
1915 '''
1916 Sets a key=value pair in the metadata for an image (nova image-meta set)
1917 CLI Examples:
1918 .. code-block:: bash
1919 salt '*' nova.image_meta_set 6f52b2ff-0b31-4d84-8fd1-af45b84824f6 \
1920 cheese=gruyere
1921 salt '*' nova.image_meta_set name=myimage salad=pasta beans=baked
1922 '''
1923 conn = _auth(profile)
1924 return conn.image_meta_set(
1925 image_id,
1926 name,
1927 **kwargs
1928 )
1929
1930
1931def image_meta_delete(image_id=None, # pylint: disable=C0103
1932 name=None,
1933 keys=None,
1934 profile=None):
1935 '''
1936 Delete a key=value pair from the metadata for an image
1937 (nova image-meta set)
1938 CLI Examples:
1939 .. code-block:: bash
1940 salt '*' nova.image_meta_delete \
1941 6f52b2ff-0b31-4d84-8fd1-af45b84824f6 keys=cheese
1942 salt '*' nova.image_meta_delete name=myimage keys=salad,beans
1943 '''
1944 conn = _auth(profile)
1945 return conn.image_meta_delete(
1946 image_id,
1947 name,
1948 keys
1949 )
1950
1951
1952def list_(profile=None):
1953 '''
1954 To maintain the feel of the nova command line, this function simply calls
1955 the server_list function.
1956 CLI Example:
1957 .. code-block:: bash
1958 salt '*' nova.list
1959 '''
1960 return server_list(profile=profile)
1961
1962
1963def server_list(profile=None):
1964 '''
1965 Return list of active servers
1966 CLI Example:
1967 .. code-block:: bash
1968 salt '*' nova.server_list
1969 '''
1970 conn = _auth(profile)
1971 return conn.server_list()
1972
1973
1974def show(server_id, profile=None):
1975 '''
1976 To maintain the feel of the nova command line, this function simply calls
1977 the server_show function.
1978 CLI Example:
1979 .. code-block:: bash
1980 salt '*' nova.show
1981 '''
1982 return server_show(server_id, profile)
1983
1984
1985def server_list_detailed(profile=None):
1986 '''
1987 Return detailed list of active servers
1988 CLI Example:
1989 .. code-block:: bash
1990 salt '*' nova.server_list_detailed
1991 '''
1992 conn = _auth(profile)
1993 return conn.server_list_detailed()
1994
1995
1996def server_show(server_id, profile=None):
1997 '''
1998 Return detailed information for an active server
1999 CLI Example:
2000 .. code-block:: bash
2001 salt '*' nova.server_show <server_id>
2002 '''
2003 conn = _auth(profile)
2004 return conn.server_show(server_id)
2005
2006
2007def secgroup_create(name, description, profile=None):
2008 '''
2009 Add a secgroup to nova (nova secgroup-create)
2010 CLI Example:
2011 .. code-block:: bash
2012 salt '*' nova.secgroup_create mygroup 'This is my security group'
2013 '''
2014 conn = _auth(profile)
2015 return conn.secgroup_create(name, description)
2016
2017
2018def secgroup_delete(name, profile=None):
2019 '''
2020 Delete a secgroup to nova (nova secgroup-delete)
2021 CLI Example:
2022 .. code-block:: bash
2023 salt '*' nova.secgroup_delete mygroup
2024 '''
2025 conn = _auth(profile)
2026 return conn.secgroup_delete(name)
2027
2028
2029def secgroup_list(profile=None):
2030 '''
2031 Return a list of available security groups (nova items-list)
2032 CLI Example:
2033 .. code-block:: bash
2034 salt '*' nova.secgroup_list
2035 '''
2036 conn = _auth(profile)
2037 return conn.secgroup_list()
2038
2039
2040def server_by_name(name, profile=None):
2041 '''
2042 Return information about a server
2043 name
2044 Server Name
2045 CLI Example:
2046 .. code-block:: bash
2047 salt '*' nova.server_by_name myserver profile=openstack
2048 '''
2049 conn = _auth(profile)
2050 return conn.server_by_name(name)
2051