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