1# Licensed to the Apache Software Foundation (ASF) under one or more 2# contributor license agreements. See the NOTICE file distributed with 3# this work for additional information regarding copyright ownership. 4# The ASF licenses this file to You under the Apache License, Version 2.0 5# (the "License"); you may not use this file except in compliance with 6# the License. You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15""" 16OpenStack driver 17""" 18 19from libcloud.common.exceptions import BaseHTTPError 20from libcloud.utils.iso8601 import parse_date 21 22try: 23 import simplejson as json 24except ImportError: 25 import json 26 27import warnings 28import base64 29 30from libcloud.utils.py3 import httplib 31from libcloud.utils.py3 import b 32from libcloud.utils.py3 import next 33from libcloud.utils.py3 import urlparse 34from libcloud.utils.py3 import parse_qs 35 36 37from libcloud.common.openstack import OpenStackBaseConnection 38from libcloud.common.openstack import OpenStackDriverMixin 39from libcloud.common.openstack import OpenStackException 40from libcloud.common.openstack import OpenStackResponse 41from libcloud.utils.networking import is_public_subnet 42from libcloud.compute.base import NodeSize, NodeImage, NodeImageMember, \ 43 UuidMixin 44from libcloud.compute.base import (NodeDriver, Node, NodeLocation, 45 StorageVolume, VolumeSnapshot) 46from libcloud.compute.base import KeyPair 47from libcloud.compute.types import NodeState, StorageVolumeState, Provider, \ 48 VolumeSnapshotState, Type, LibcloudError 49from libcloud.pricing import get_size_price 50from libcloud.utils.xml import findall 51from libcloud.utils.py3 import ET 52 53__all__ = [ 54 'OpenStack_1_0_Response', 55 'OpenStack_1_0_Connection', 56 'OpenStack_1_0_NodeDriver', 57 'OpenStack_1_0_SharedIpGroup', 58 'OpenStack_1_0_NodeIpAddresses', 59 'OpenStack_1_1_Response', 60 'OpenStack_1_1_Connection', 61 'OpenStack_1_1_NodeDriver', 62 'OpenStack_1_1_FloatingIpPool', 63 'OpenStack_2_FloatingIpPool', 64 'OpenStack_1_1_FloatingIpAddress', 65 'OpenStack_2_PortInterfaceState', 66 'OpenStack_2_PortInterface', 67 'OpenStackNodeDriver' 68] 69 70ATOM_NAMESPACE = "http://www.w3.org/2005/Atom" 71 72DEFAULT_API_VERSION = '1.1' 73 74PAGINATION_LIMIT = 1000 75 76 77class OpenStackComputeConnection(OpenStackBaseConnection): 78 # default config for http://devstack.org/ 79 service_type = 'compute' 80 service_name = 'nova' 81 service_region = 'RegionOne' 82 83 84class OpenStackImageConnection(OpenStackBaseConnection): 85 service_type = 'image' 86 service_name = 'glance' 87 service_region = 'RegionOne' 88 89 90class OpenStackNetworkConnection(OpenStackBaseConnection): 91 service_type = 'network' 92 service_name = 'neutron' 93 service_region = 'RegionOne' 94 95 96class OpenStackVolumeV2Connection(OpenStackBaseConnection): 97 service_type = 'volumev2' 98 service_name = 'cinderv2' 99 service_region = 'RegionOne' 100 101 102class OpenStackVolumeV3Connection(OpenStackBaseConnection): 103 service_type = 'volumev3' 104 service_name = 'cinderv3' 105 service_region = 'RegionOne' 106 107 108class OpenStackNodeDriver(NodeDriver, OpenStackDriverMixin): 109 """ 110 Base OpenStack node driver. Should not be used directly. 111 """ 112 api_name = 'openstack' 113 name = 'OpenStack' 114 website = 'http://openstack.org/' 115 116 NODE_STATE_MAP = { 117 'BUILD': NodeState.PENDING, 118 'REBUILD': NodeState.PENDING, 119 'ACTIVE': NodeState.RUNNING, 120 'SUSPENDED': NodeState.SUSPENDED, 121 'SHUTOFF': NodeState.STOPPED, 122 'DELETED': NodeState.TERMINATED, 123 'QUEUE_RESIZE': NodeState.PENDING, 124 'PREP_RESIZE': NodeState.PENDING, 125 'VERIFY_RESIZE': NodeState.RUNNING, 126 'PASSWORD': NodeState.PENDING, 127 'RESCUE': NodeState.PENDING, 128 'REBOOT': NodeState.REBOOTING, 129 'RESIZE': NodeState.RECONFIGURING, 130 'HARD_REBOOT': NodeState.REBOOTING, 131 'SHARE_IP': NodeState.PENDING, 132 'SHARE_IP_NO_CONFIG': NodeState.PENDING, 133 'DELETE_IP': NodeState.PENDING, 134 'ERROR': NodeState.ERROR, 135 'UNKNOWN': NodeState.UNKNOWN 136 } 137 138 # http://developer.openstack.org/api-ref-blockstorage-v2.html#volumes-v2 139 VOLUME_STATE_MAP = { 140 'creating': StorageVolumeState.CREATING, 141 'available': StorageVolumeState.AVAILABLE, 142 'attaching': StorageVolumeState.ATTACHING, 143 'in-use': StorageVolumeState.INUSE, 144 'deleting': StorageVolumeState.DELETING, 145 'error': StorageVolumeState.ERROR, 146 'error_deleting': StorageVolumeState.ERROR, 147 'backing-up': StorageVolumeState.BACKUP, 148 'restoring-backup': StorageVolumeState.BACKUP, 149 'error_restoring': StorageVolumeState.ERROR, 150 'error_extending': StorageVolumeState.ERROR, 151 } 152 153 # http://developer.openstack.org/api-ref-blockstorage-v2.html#ext-backups-v2 154 SNAPSHOT_STATE_MAP = { 155 'creating': VolumeSnapshotState.CREATING, 156 'available': VolumeSnapshotState.AVAILABLE, 157 'deleting': VolumeSnapshotState.DELETING, 158 'error': VolumeSnapshotState.ERROR, 159 'restoring': VolumeSnapshotState.RESTORING, 160 'error_restoring': VolumeSnapshotState.ERROR 161 } 162 163 def __new__(cls, key, secret=None, secure=True, host=None, port=None, 164 api_version=DEFAULT_API_VERSION, **kwargs): 165 if cls is OpenStackNodeDriver: 166 if api_version == '1.0': 167 cls = OpenStack_1_0_NodeDriver 168 elif api_version == '1.1': 169 cls = OpenStack_1_1_NodeDriver 170 elif api_version in ['2.0', '2.1', '2.2']: 171 cls = OpenStack_2_NodeDriver 172 else: 173 raise NotImplementedError( 174 "No OpenStackNodeDriver found for API version %s" % 175 (api_version)) 176 return super(OpenStackNodeDriver, cls).__new__(cls) 177 178 def __init__(self, *args, **kwargs): 179 OpenStackDriverMixin.__init__(self, **kwargs) 180 super(OpenStackNodeDriver, self).__init__(*args, **kwargs) 181 182 @staticmethod 183 def _paginated_request(url, obj, connection, params=None): 184 """ 185 Perform multiple calls in order to have a full list of elements when 186 the API responses are paginated. 187 188 :param url: API endpoint 189 :type url: ``str`` 190 191 :param obj: Result object key 192 :type obj: ``str`` 193 194 :param connection: The API connection to use to perform the request 195 :type connection: ``obj`` 196 197 :param params: Any request parameters 198 :type params: ``dict`` 199 200 :return: ``list`` of API response objects 201 :rtype: ``list`` 202 """ 203 params = params or {} 204 objects = list() 205 loop_count = 0 206 while True: 207 data = connection.request(url, params=params) 208 values = data.object.get(obj, list()) 209 objects.extend(values) 210 links = data.object.get('%s_links' % obj, list()) 211 next_links = [n for n in links if n['rel'] == 'next'] 212 if next_links: 213 next_link = next_links[0] 214 query = urlparse.urlparse(next_link['href']) 215 # The query[4] references the query parameters from the url 216 params.update(parse_qs(query[4])) 217 else: 218 break 219 220 # Prevent the pagination from looping indefinitely in case 221 # the API returns a loop for some reason. 222 loop_count += 1 223 if loop_count > PAGINATION_LIMIT: 224 raise OpenStackException( 225 'Pagination limit reached for %s, the limit is %d. ' 226 'This might indicate that your API is returning a ' 227 'looping next target for pagination!' % ( 228 url, PAGINATION_LIMIT 229 ), None 230 ) 231 return {obj: objects} 232 233 def _paginated_request_next(self, path, request_method, response_key): 234 """ 235 Perform multiple calls and retrieve all the elements for a paginated 236 response. 237 238 This method utilizes "next" attribute in the response object. 239 240 It also includes an infinite loop protection (if the "next" value 241 matches the current path, it will abort). 242 243 :param request_method: Method to call which will send the request and 244 return a response. This method will get passed 245 in "path" as a first argument. 246 247 :param response_key: Key in the response object dictionary which 248 contains actual objects we are interested in. 249 """ 250 iteration_count = 0 251 252 result = [] 253 while path: 254 response = request_method(path) 255 items = response.object.get(response_key, []) or [] 256 result.extend(items) 257 258 # Retrieve next path 259 next_path = response.object.get('next', None) 260 261 if next_path == path: 262 # Likely an infinite loop since the next path matches the 263 # current one 264 break 265 266 if iteration_count > PAGINATION_LIMIT: 267 # We have iterated over PAGINATION_LIMIT pages, likely an 268 # API returned an invalid response 269 raise OpenStackException( 270 'Pagination limit reached for %s, the limit is %d. ' 271 'This might indicate that your API is returning a ' 272 'looping next target for pagination!' % ( 273 path, PAGINATION_LIMIT 274 ), None 275 ) 276 277 path = next_path 278 iteration_count += 1 279 280 return result 281 282 def destroy_node(self, node): 283 uri = '/servers/%s' % (node.id) 284 resp = self.connection.request(uri, method='DELETE') 285 # The OpenStack and Rackspace documentation both say this API will 286 # return a 204, but in-fact, everyone everywhere agrees it actually 287 # returns a 202, so we are going to accept either, and someday, 288 # someone will fix either the implementation or the documentation to 289 # agree. 290 return resp.status in (httplib.NO_CONTENT, httplib.ACCEPTED) 291 292 def reboot_node(self, node): 293 # pylint: disable=no-member 294 return self._reboot_node(node, reboot_type='HARD') 295 296 def start_node(self, node): 297 # pylint: disable=no-member 298 return self._post_simple_node_action(node, 'os-start') 299 300 def stop_node(self, node): 301 # pylint: disable=no-member 302 return self._post_simple_node_action(node, 'os-stop') 303 304 def list_nodes(self, ex_all_tenants=False): 305 """ 306 List the nodes in a tenant 307 308 :param ex_all_tenants: List nodes for all the tenants. Note: Your user 309 must have admin privileges for this 310 functionality to work. 311 :type ex_all_tenants: ``bool`` 312 """ 313 params = {} 314 if ex_all_tenants: 315 params = {'all_tenants': 1} 316 317 # pylint: disable=no-member 318 return self._to_nodes( 319 self.connection.request('/servers/detail', params=params).object) 320 321 def create_volume(self, size, name, location=None, snapshot=None, 322 ex_volume_type=None): 323 """ 324 Create a new volume. 325 326 :param size: Size of volume in gigabytes (required) 327 :type size: ``int`` 328 329 :param name: Name of the volume to be created 330 :type name: ``str`` 331 332 :param location: Which data center to create a volume in. If 333 empty, undefined behavior will be selected. 334 (optional) 335 :type location: :class:`.NodeLocation` 336 337 :param snapshot: Snapshot from which to create the new 338 volume. (optional) 339 :type snapshot: :class:`.VolumeSnapshot` 340 341 :param ex_volume_type: What kind of volume to create. 342 (optional) 343 :type ex_volume_type: ``str`` 344 345 :return: The newly created volume. 346 :rtype: :class:`StorageVolume` 347 """ 348 volume = { 349 'display_name': name, 350 'display_description': name, 351 'size': size, 352 'metadata': { 353 'contents': name, 354 }, 355 } 356 357 if ex_volume_type: 358 volume['volume_type'] = ex_volume_type 359 360 if location: 361 volume['availability_zone'] = location 362 363 if snapshot: 364 volume['snapshot_id'] = snapshot.id 365 366 resp = self.connection.request('/os-volumes', 367 method='POST', 368 data={'volume': volume}) 369 370 # pylint: disable=no-member 371 return self._to_volume(resp.object) 372 373 def destroy_volume(self, volume): 374 return self.connection.request('/os-volumes/%s' % volume.id, 375 method='DELETE').success() 376 377 def attach_volume(self, node, volume, device="auto"): 378 # when "auto" or None is provided for device, openstack will let 379 # the guest OS pick the next available device (fi. /dev/vdb) 380 if device == "auto": 381 device = None 382 return self.connection.request( 383 '/servers/%s/os-volume_attachments' % node.id, 384 method='POST', 385 data={ 386 'volumeAttachment': { 387 'volumeId': volume.id, 388 'device': device, 389 } 390 }).success() 391 392 def detach_volume(self, volume, ex_node=None): 393 # when ex_node is not provided, volume is detached from all nodes 394 failed_nodes = [] 395 for attachment in volume.extra['attachments']: 396 if not ex_node or ex_node.id in filter(None, (attachment.get( 397 'serverId' 398 ), attachment.get('server_id'))): 399 response = self.connection.request( 400 '/servers/%s/os-volume_attachments/%s' % 401 (attachment.get('serverId') or attachment['server_id'], 402 attachment['id']), 403 method='DELETE') 404 405 if not response.success(): 406 failed_nodes.append( 407 attachment.get('serverId') or attachment['server_id'] 408 ) 409 if failed_nodes: 410 raise OpenStackException( 411 'detach_volume failed for nodes with id: %s' % 412 ', '.join(failed_nodes), 500, self 413 ) 414 return True 415 416 def list_volumes(self): 417 # pylint: disable=no-member 418 return self._to_volumes( 419 self.connection.request('/os-volumes').object) 420 421 def ex_get_volume(self, volumeId): 422 # pylint: disable=no-member 423 return self._to_volume( 424 self.connection.request('/os-volumes/%s' % volumeId).object) 425 426 def list_images(self, location=None, ex_only_active=True): 427 """ 428 Lists all active images 429 430 @inherits: :class:`NodeDriver.list_images` 431 432 :param ex_only_active: True if list only active (optional) 433 :type ex_only_active: ``bool`` 434 435 """ 436 # pylint: disable=no-member 437 return self._to_images( 438 self.connection.request('/images/detail').object, ex_only_active) 439 440 def get_image(self, image_id): 441 """ 442 Get an image based on an image_id 443 444 @inherits: :class:`NodeDriver.get_image` 445 446 :param image_id: Image identifier 447 :type image_id: ``str`` 448 449 :return: A NodeImage object 450 :rtype: :class:`NodeImage` 451 452 """ 453 # pylint: disable=no-member 454 return self._to_image(self.connection.request( 455 '/images/%s' % (image_id,)).object['image']) 456 457 def list_sizes(self, location=None): 458 # pylint: disable=no-member 459 return self._to_sizes( 460 self.connection.request('/flavors/detail').object) 461 462 def list_locations(self): 463 return [NodeLocation(0, '', '', self)] 464 465 def _ex_connection_class_kwargs(self): 466 return self.openstack_connection_kwargs() 467 468 def ex_get_node_details(self, node_id): 469 """ 470 Lists details of the specified server. 471 472 :param node_id: ID of the node which should be used 473 :type node_id: ``str`` 474 475 :rtype: :class:`Node` 476 """ 477 # @TODO: Remove this if in 0.6 478 if isinstance(node_id, Node): 479 node_id = node_id.id 480 481 uri = '/servers/%s' % (node_id) 482 try: 483 resp = self.connection.request(uri, method='GET') 484 except BaseHTTPError as e: 485 if e.code == httplib.NOT_FOUND: 486 return None 487 raise 488 489 # pylint: disable=no-member 490 return self._to_node_from_obj(resp.object) 491 492 def ex_soft_reboot_node(self, node): 493 """ 494 Soft reboots the specified server 495 496 :param node: node 497 :type node: :class:`Node` 498 499 :rtype: ``bool`` 500 """ 501 # pylint: disable=no-member 502 return self._reboot_node(node, reboot_type='SOFT') 503 504 def ex_hard_reboot_node(self, node): 505 """ 506 Hard reboots the specified server 507 508 :param node: node 509 :type node: :class:`Node` 510 511 :rtype: ``bool`` 512 """ 513 # pylint: disable=no-member 514 return self._reboot_node(node, reboot_type='HARD') 515 516 517class OpenStackNodeSize(NodeSize): 518 """ 519 NodeSize class for the OpenStack.org driver. 520 521 Following the example of OpenNebula.org driver 522 and following guidelines: 523 https://issues.apache.org/jira/browse/LIBCLOUD-119 524 """ 525 526 def __init__(self, id, name, ram, disk, bandwidth, price, driver, 527 vcpus=None, ephemeral_disk=None, swap=None, extra=None): 528 super(OpenStackNodeSize, self).__init__(id=id, name=name, ram=ram, 529 disk=disk, 530 bandwidth=bandwidth, 531 price=price, driver=driver) 532 self.vcpus = vcpus 533 self.ephemeral_disk = ephemeral_disk 534 self.swap = swap 535 self.extra = extra 536 537 def __repr__(self): 538 return (('<OpenStackNodeSize: id=%s, name=%s, ram=%s, disk=%s, ' 539 'bandwidth=%s, price=%s, driver=%s, vcpus=%s, ...>') 540 % (self.id, self.name, self.ram, self.disk, self.bandwidth, 541 self.price, self.driver.name, self.vcpus)) 542 543 544class OpenStack_1_0_Response(OpenStackResponse): 545 def __init__(self, *args, **kwargs): 546 # done because of a circular reference from 547 # NodeDriver -> Connection -> Response 548 self.node_driver = OpenStack_1_0_NodeDriver 549 super(OpenStack_1_0_Response, self).__init__(*args, **kwargs) 550 551 552class OpenStack_1_0_Connection(OpenStackComputeConnection): 553 responseCls = OpenStack_1_0_Response 554 default_content_type = 'application/xml; charset=UTF-8' 555 accept_format = 'application/xml' 556 XML_NAMESPACE = 'http://docs.rackspacecloud.com/servers/api/v1.0' 557 558 559class OpenStack_1_0_NodeDriver(OpenStackNodeDriver): 560 """ 561 OpenStack node driver. 562 563 Extra node attributes: 564 - password: root password, available after create. 565 - hostId: represents the host your cloud server runs on 566 - imageId: id of image 567 - flavorId: id of flavor 568 """ 569 connectionCls = OpenStack_1_0_Connection 570 type = Provider.OPENSTACK 571 572 features = {'create_node': ['generates_password']} 573 574 def __init__(self, *args, **kwargs): 575 self._ex_force_api_version = str(kwargs.pop('ex_force_api_version', 576 None)) 577 self.XML_NAMESPACE = self.connectionCls.XML_NAMESPACE 578 super(OpenStack_1_0_NodeDriver, self).__init__(*args, **kwargs) 579 580 def _to_images(self, object, ex_only_active): 581 images = [] 582 for image in findall(object, 'image', self.XML_NAMESPACE): 583 if ex_only_active and image.get('status') != 'ACTIVE': 584 continue 585 images.append(self._to_image(image)) 586 587 return images 588 589 def _to_image(self, element): 590 return NodeImage(id=element.get('id'), 591 name=element.get('name'), 592 driver=self.connection.driver, 593 extra={'updated': element.get('updated'), 594 'created': element.get('created'), 595 'status': element.get('status'), 596 'serverId': element.get('serverId'), 597 'progress': element.get('progress'), 598 'minDisk': element.get('minDisk'), 599 'minRam': element.get('minRam') 600 } 601 ) 602 603 def _change_password_or_name(self, node, name=None, password=None): 604 uri = '/servers/%s' % (node.id) 605 606 if not name: 607 name = node.name 608 609 body = {'xmlns': self.XML_NAMESPACE, 610 'name': name} 611 612 if password is not None: 613 body['adminPass'] = password 614 615 server_elm = ET.Element('server', body) 616 617 resp = self.connection.request( 618 uri, method='PUT', data=ET.tostring(server_elm)) 619 620 if resp.status == httplib.NO_CONTENT and password is not None: 621 node.extra['password'] = password 622 623 return resp.status == httplib.NO_CONTENT 624 625 def create_node(self, name, size, image, ex_metadata=None, ex_files=None, 626 ex_shared_ip_group=None, ex_shared_ip_group_id=None): 627 """ 628 Create a new node 629 630 @inherits: :class:`NodeDriver.create_node` 631 632 :keyword ex_metadata: Key/Value metadata to associate with a node 633 :type ex_metadata: ``dict`` 634 635 :keyword ex_files: File Path => File contents to create on 636 the node 637 :type ex_files: ``dict`` 638 639 :keyword ex_shared_ip_group_id: The server is launched into 640 that shared IP group 641 :type ex_shared_ip_group_id: ``str`` 642 """ 643 attributes = {'xmlns': self.XML_NAMESPACE, 644 'name': name, 645 'imageId': str(image.id), 646 'flavorId': str(size.id)} 647 648 if ex_shared_ip_group: 649 # Deprecate this. Be explicit and call the variable 650 # ex_shared_ip_group_id since user needs to pass in the id, not the 651 # name. 652 warnings.warn('ex_shared_ip_group argument is deprecated.' 653 ' Please use ex_shared_ip_group_id') 654 655 if ex_shared_ip_group_id: 656 attributes['sharedIpGroupId'] = ex_shared_ip_group_id 657 658 server_elm = ET.Element('server', attributes) 659 660 metadata_elm = self._metadata_to_xml(ex_metadata or {}) 661 if metadata_elm: 662 server_elm.append(metadata_elm) 663 664 files_elm = self._files_to_xml(ex_files or {}) 665 if files_elm: 666 server_elm.append(files_elm) 667 668 resp = self.connection.request("/servers", 669 method='POST', 670 data=ET.tostring(server_elm)) 671 return self._to_node(resp.object) 672 673 def ex_set_password(self, node, password): 674 """ 675 Sets the Node's root password. 676 677 This will reboot the instance to complete the operation. 678 679 :class:`Node.extra['password']` will be set to the new value if the 680 operation was successful. 681 682 :param node: node to set password 683 :type node: :class:`Node` 684 685 :param password: new password. 686 :type password: ``str`` 687 688 :rtype: ``bool`` 689 """ 690 return self._change_password_or_name(node, password=password) 691 692 def ex_set_server_name(self, node, name): 693 """ 694 Sets the Node's name. 695 696 This will reboot the instance to complete the operation. 697 698 :param node: node to set name 699 :type node: :class:`Node` 700 701 :param name: new name 702 :type name: ``str`` 703 704 :rtype: ``bool`` 705 """ 706 return self._change_password_or_name(node, name=name) 707 708 def ex_resize_node(self, node, size): 709 """ 710 Change an existing server flavor / scale the server up or down. 711 712 :param node: node to resize. 713 :type node: :class:`Node` 714 715 :param size: new size. 716 :type size: :class:`NodeSize` 717 718 :rtype: ``bool`` 719 """ 720 elm = ET.Element( 721 'resize', 722 {'xmlns': self.XML_NAMESPACE, 723 'flavorId': str(size.id)} 724 ) 725 726 resp = self.connection.request("/servers/%s/action" % (node.id), 727 method='POST', 728 data=ET.tostring(elm)) 729 return resp.status == httplib.ACCEPTED 730 731 def ex_resize(self, node, size): 732 """ 733 NOTE: This method is here for backward compatibility reasons. 734 735 You should use ``ex_resize_node`` instead. 736 """ 737 return self.ex_resize_node(node=node, size=size) 738 739 def ex_confirm_resize(self, node): 740 """ 741 Confirm a resize request which is currently in progress. If a resize 742 request is not explicitly confirmed or reverted it's automatically 743 confirmed after 24 hours. 744 745 For more info refer to the API documentation: http://goo.gl/zjFI1 746 747 :param node: node for which the resize request will be confirmed. 748 :type node: :class:`Node` 749 750 :rtype: ``bool`` 751 """ 752 elm = ET.Element( 753 'confirmResize', 754 {'xmlns': self.XML_NAMESPACE}, 755 ) 756 757 resp = self.connection.request("/servers/%s/action" % (node.id), 758 method='POST', 759 data=ET.tostring(elm)) 760 return resp.status == httplib.NO_CONTENT 761 762 def ex_revert_resize(self, node): 763 """ 764 Revert a resize request which is currently in progress. 765 All resizes are automatically confirmed after 24 hours if they have 766 not already been confirmed explicitly or reverted. 767 768 For more info refer to the API documentation: http://goo.gl/AizBu 769 770 :param node: node for which the resize request will be reverted. 771 :type node: :class:`Node` 772 773 :rtype: ``bool`` 774 """ 775 elm = ET.Element( 776 'revertResize', 777 {'xmlns': self.XML_NAMESPACE} 778 ) 779 780 resp = self.connection.request("/servers/%s/action" % (node.id), 781 method='POST', 782 data=ET.tostring(elm)) 783 return resp.status == httplib.NO_CONTENT 784 785 def ex_rebuild(self, node_id, image_id): 786 """ 787 Rebuilds the specified server. 788 789 :param node_id: ID of the node which should be used 790 :type node_id: ``str`` 791 792 :param image_id: ID of the image which should be used 793 :type image_id: ``str`` 794 795 :rtype: ``bool`` 796 """ 797 # @TODO: Remove those ifs in 0.6 798 if isinstance(node_id, Node): 799 node_id = node_id.id 800 801 if isinstance(image_id, NodeImage): 802 image_id = image_id.id 803 804 elm = ET.Element( 805 'rebuild', 806 {'xmlns': self.XML_NAMESPACE, 807 'imageId': image_id} 808 ) 809 810 resp = self.connection.request("/servers/%s/action" % node_id, 811 method='POST', 812 data=ET.tostring(elm)) 813 return resp.status == httplib.ACCEPTED 814 815 def ex_create_ip_group(self, group_name, node_id=None): 816 """ 817 Creates a shared IP group. 818 819 :param group_name: group name which should be used 820 :type group_name: ``str`` 821 822 :param node_id: ID of the node which should be used 823 :type node_id: ``str`` 824 825 :rtype: ``bool`` 826 """ 827 # @TODO: Remove this if in 0.6 828 if isinstance(node_id, Node): 829 node_id = node_id.id 830 831 group_elm = ET.Element( 832 'sharedIpGroup', 833 {'xmlns': self.XML_NAMESPACE, 834 'name': group_name} 835 ) 836 837 if node_id: 838 ET.SubElement( 839 group_elm, 840 'server', 841 {'id': node_id} 842 ) 843 844 resp = self.connection.request('/shared_ip_groups', 845 method='POST', 846 data=ET.tostring(group_elm)) 847 return self._to_shared_ip_group(resp.object) 848 849 def ex_list_ip_groups(self, details=False): 850 """ 851 Lists IDs and names for shared IP groups. 852 If details lists all details for shared IP groups. 853 854 :param details: True if details is required 855 :type details: ``bool`` 856 857 :rtype: ``list`` of :class:`OpenStack_1_0_SharedIpGroup` 858 """ 859 uri = '/shared_ip_groups/detail' if details else '/shared_ip_groups' 860 resp = self.connection.request(uri, 861 method='GET') 862 groups = findall(resp.object, 'sharedIpGroup', 863 self.XML_NAMESPACE) 864 return [self._to_shared_ip_group(el) for el in groups] 865 866 def ex_delete_ip_group(self, group_id): 867 """ 868 Deletes the specified shared IP group. 869 870 :param group_id: group id which should be used 871 :type group_id: ``str`` 872 873 :rtype: ``bool`` 874 """ 875 uri = '/shared_ip_groups/%s' % group_id 876 resp = self.connection.request(uri, method='DELETE') 877 return resp.status == httplib.NO_CONTENT 878 879 def ex_share_ip(self, group_id, node_id, ip, configure_node=True): 880 """ 881 Shares an IP address to the specified server. 882 883 :param group_id: group id which should be used 884 :type group_id: ``str`` 885 886 :param node_id: ID of the node which should be used 887 :type node_id: ``str`` 888 889 :param ip: ip which should be used 890 :type ip: ``str`` 891 892 :param configure_node: configure node 893 :type configure_node: ``bool`` 894 895 :rtype: ``bool`` 896 """ 897 # @TODO: Remove this if in 0.6 898 if isinstance(node_id, Node): 899 node_id = node_id.id 900 901 if configure_node: 902 str_configure = 'true' 903 else: 904 str_configure = 'false' 905 906 elm = ET.Element( 907 'shareIp', 908 {'xmlns': self.XML_NAMESPACE, 909 'sharedIpGroupId': group_id, 910 'configureServer': str_configure}, 911 ) 912 913 uri = '/servers/%s/ips/public/%s' % (node_id, ip) 914 915 resp = self.connection.request(uri, 916 method='PUT', 917 data=ET.tostring(elm)) 918 return resp.status == httplib.ACCEPTED 919 920 def ex_unshare_ip(self, node_id, ip): 921 """ 922 Removes a shared IP address from the specified server. 923 924 :param node_id: ID of the node which should be used 925 :type node_id: ``str`` 926 927 :param ip: ip which should be used 928 :type ip: ``str`` 929 930 :rtype: ``bool`` 931 """ 932 # @TODO: Remove this if in 0.6 933 if isinstance(node_id, Node): 934 node_id = node_id.id 935 936 uri = '/servers/%s/ips/public/%s' % (node_id, ip) 937 938 resp = self.connection.request(uri, 939 method='DELETE') 940 return resp.status == httplib.ACCEPTED 941 942 def ex_list_ip_addresses(self, node_id): 943 """ 944 List all server addresses. 945 946 :param node_id: ID of the node which should be used 947 :type node_id: ``str`` 948 949 :rtype: :class:`OpenStack_1_0_NodeIpAddresses` 950 """ 951 # @TODO: Remove this if in 0.6 952 if isinstance(node_id, Node): 953 node_id = node_id.id 954 955 uri = '/servers/%s/ips' % node_id 956 resp = self.connection.request(uri, 957 method='GET') 958 return self._to_ip_addresses(resp.object) 959 960 def _metadata_to_xml(self, metadata): 961 if not metadata: 962 return None 963 964 metadata_elm = ET.Element('metadata') 965 for k, v in list(metadata.items()): 966 meta_elm = ET.SubElement(metadata_elm, 'meta', {'key': str(k)}) 967 meta_elm.text = str(v) 968 969 return metadata_elm 970 971 def _files_to_xml(self, files): 972 if not files: 973 return None 974 975 personality_elm = ET.Element('personality') 976 for k, v in list(files.items()): 977 file_elm = ET.SubElement(personality_elm, 978 'file', 979 {'path': str(k)}) 980 file_elm.text = base64.b64encode(b(v)).decode('ascii') 981 982 return personality_elm 983 984 def _reboot_node(self, node, reboot_type='SOFT'): 985 resp = self._node_action(node, ['reboot', ('type', reboot_type)]) 986 return resp.status == httplib.ACCEPTED 987 988 def _node_action(self, node, body): 989 if isinstance(body, list): 990 attr = ' '.join(['%s="%s"' % (item[0], item[1]) 991 for item in body[1:]]) 992 body = '<%s xmlns="%s" %s/>' % (body[0], self.XML_NAMESPACE, attr) 993 uri = '/servers/%s/action' % (node.id) 994 resp = self.connection.request(uri, method='POST', data=body) 995 return resp 996 997 def _to_nodes(self, object): 998 node_elements = findall(object, 'server', self.XML_NAMESPACE) 999 return [self._to_node(el) for el in node_elements] 1000 1001 def _to_node_from_obj(self, obj): 1002 return self._to_node(findall(obj, 'server', self.XML_NAMESPACE)[0]) 1003 1004 def _to_node(self, el): 1005 def get_ips(el): 1006 return [ip.get('addr') for ip in el] 1007 1008 def get_meta_dict(el): 1009 d = {} 1010 for meta in el: 1011 d[meta.get('key')] = meta.text 1012 return d 1013 1014 public_ip = get_ips(findall(el, 'addresses/public/ip', 1015 self.XML_NAMESPACE)) 1016 private_ip = get_ips(findall(el, 'addresses/private/ip', 1017 self.XML_NAMESPACE)) 1018 metadata = get_meta_dict(findall(el, 'metadata/meta', 1019 self.XML_NAMESPACE)) 1020 1021 n = Node(id=el.get('id'), 1022 name=el.get('name'), 1023 state=self.NODE_STATE_MAP.get( 1024 el.get('status'), NodeState.UNKNOWN), 1025 public_ips=public_ip, 1026 private_ips=private_ip, 1027 driver=self.connection.driver, 1028 # pylint: disable=no-member 1029 extra={ 1030 'password': el.get('adminPass'), 1031 'hostId': el.get('hostId'), 1032 'imageId': el.get('imageId'), 1033 'flavorId': el.get('flavorId'), 1034 'uri': "https://%s%s/servers/%s" % ( 1035 self.connection.host, 1036 self.connection.request_path, el.get('id')), 1037 'service_name': self.connection.get_service_name(), 1038 'metadata': metadata}) 1039 return n 1040 1041 def _to_sizes(self, object): 1042 elements = findall(object, 'flavor', self.XML_NAMESPACE) 1043 return [self._to_size(el) for el in elements] 1044 1045 def _to_size(self, el): 1046 vcpus = int(el.get('vcpus')) if el.get('vcpus', None) else None 1047 return OpenStackNodeSize(id=el.get('id'), 1048 name=el.get('name'), 1049 ram=int(el.get('ram')), 1050 disk=int(el.get('disk')), 1051 # XXX: needs hardcode 1052 vcpus=vcpus, 1053 bandwidth=None, 1054 extra=el.get('extra_specs'), 1055 # Hardcoded 1056 price=self._get_size_price(el.get('id')), 1057 driver=self.connection.driver) 1058 1059 def ex_limits(self): 1060 """ 1061 Extra call to get account's limits, such as 1062 rates (for example amount of POST requests per day) 1063 and absolute limits like total amount of available 1064 RAM to be used by servers. 1065 1066 :return: dict with keys 'rate' and 'absolute' 1067 :rtype: ``dict`` 1068 """ 1069 1070 def _to_rate(el): 1071 rate = {} 1072 for item in list(el.items()): 1073 rate[item[0]] = item[1] 1074 1075 return rate 1076 1077 def _to_absolute(el): 1078 return {el.get('name'): el.get('value')} 1079 1080 limits = self.connection.request("/limits").object 1081 rate = [_to_rate(el) for el in findall(limits, 'rate/limit', 1082 self.XML_NAMESPACE)] 1083 absolute = {} 1084 for item in findall(limits, 'absolute/limit', 1085 self.XML_NAMESPACE): 1086 absolute.update(_to_absolute(item)) 1087 1088 return {"rate": rate, "absolute": absolute} 1089 1090 def create_image(self, node, name, description=None, reboot=True): 1091 """Create an image for node. 1092 1093 @inherits: :class:`NodeDriver.create_image` 1094 1095 :param node: node to use as a base for image 1096 :type node: :class:`Node` 1097 1098 :param name: name for new image 1099 :type name: ``str`` 1100 1101 :rtype: :class:`NodeImage` 1102 """ 1103 1104 image_elm = ET.Element( 1105 'image', 1106 {'xmlns': self.XML_NAMESPACE, 1107 'name': name, 1108 'serverId': node.id} 1109 ) 1110 1111 return self._to_image( 1112 self.connection.request("/images", method="POST", 1113 data=ET.tostring(image_elm)).object) 1114 1115 def delete_image(self, image): 1116 """Delete an image for node. 1117 1118 @inherits: :class:`NodeDriver.delete_image` 1119 1120 :param image: the image to be deleted 1121 :type image: :class:`NodeImage` 1122 1123 :rtype: ``bool`` 1124 """ 1125 uri = '/images/%s' % image.id 1126 resp = self.connection.request(uri, method='DELETE') 1127 return resp.status == httplib.NO_CONTENT 1128 1129 def _to_shared_ip_group(self, el): 1130 servers_el = findall(el, 'servers', self.XML_NAMESPACE) 1131 if servers_el: 1132 servers = [s.get('id') 1133 for s in findall(servers_el[0], 'server', 1134 self.XML_NAMESPACE)] 1135 else: 1136 servers = None 1137 return OpenStack_1_0_SharedIpGroup(id=el.get('id'), 1138 name=el.get('name'), 1139 servers=servers) 1140 1141 def _to_ip_addresses(self, el): 1142 public_ips = [ip.get('addr') for ip in findall( 1143 findall(el, 'public', self.XML_NAMESPACE)[0], 1144 'ip', self.XML_NAMESPACE)] 1145 private_ips = [ip.get('addr') for ip in findall( 1146 findall(el, 'private', self.XML_NAMESPACE)[0], 1147 'ip', self.XML_NAMESPACE)] 1148 1149 return OpenStack_1_0_NodeIpAddresses(public_ips, private_ips) 1150 1151 def _get_size_price(self, size_id): 1152 try: 1153 return get_size_price(driver_type='compute', 1154 driver_name=self.api_name, 1155 size_id=size_id) 1156 except KeyError: 1157 return 0.0 1158 1159 1160class OpenStack_1_0_SharedIpGroup(object): 1161 """ 1162 Shared IP group info. 1163 """ 1164 1165 def __init__(self, id, name, servers=None): 1166 self.id = str(id) 1167 self.name = name 1168 self.servers = servers 1169 1170 1171class OpenStack_1_0_NodeIpAddresses(object): 1172 """ 1173 List of public and private IP addresses of a Node. 1174 """ 1175 1176 def __init__(self, public_addresses, private_addresses): 1177 self.public_addresses = public_addresses 1178 self.private_addresses = private_addresses 1179 1180 1181class OpenStack_1_1_Response(OpenStackResponse): 1182 def __init__(self, *args, **kwargs): 1183 # done because of a circular reference from 1184 # NodeDriver -> Connection -> Response 1185 self.node_driver = OpenStack_1_1_NodeDriver 1186 super(OpenStack_1_1_Response, self).__init__(*args, **kwargs) 1187 1188 1189class OpenStackNetwork(object): 1190 """ 1191 A Virtual Network. 1192 """ 1193 1194 def __init__(self, id, name, cidr, driver, extra=None): 1195 self.id = str(id) 1196 self.name = name 1197 self.cidr = cidr 1198 self.driver = driver 1199 self.extra = extra or {} 1200 1201 def __repr__(self): 1202 return '<OpenStackNetwork id="%s" name="%s" cidr="%s">' % (self.id, 1203 self.name, 1204 self.cidr,) 1205 1206 1207class OpenStackSecurityGroup(object): 1208 """ 1209 A Security Group. 1210 """ 1211 1212 def __init__(self, id, tenant_id, name, description, driver, rules=None, 1213 extra=None): 1214 """ 1215 Constructor. 1216 1217 :keyword id: Group id. 1218 :type id: ``str`` 1219 1220 :keyword tenant_id: Owner of the security group. 1221 :type tenant_id: ``str`` 1222 1223 :keyword name: Human-readable name for the security group. Might 1224 not be unique. 1225 :type name: ``str`` 1226 1227 :keyword description: Human-readable description of a security 1228 group. 1229 :type description: ``str`` 1230 1231 :keyword rules: Rules associated with this group. 1232 :type rules: ``list`` of 1233 :class:`OpenStackSecurityGroupRule` 1234 1235 :keyword extra: Extra attributes associated with this group. 1236 :type extra: ``dict`` 1237 """ 1238 self.id = id 1239 self.tenant_id = tenant_id 1240 self.name = name 1241 self.description = description 1242 self.driver = driver 1243 self.rules = rules or [] 1244 self.extra = extra or {} 1245 1246 def __repr__(self): 1247 return ('<OpenStackSecurityGroup id=%s tenant_id=%s name=%s \ 1248 description=%s>' % (self.id, self.tenant_id, self.name, 1249 self.description)) 1250 1251 1252class OpenStackSecurityGroupRule(object): 1253 """ 1254 A Rule of a Security Group. 1255 """ 1256 1257 def __init__(self, id, parent_group_id, ip_protocol, from_port, to_port, 1258 driver, ip_range=None, group=None, tenant_id=None, 1259 direction=None, extra=None): 1260 """ 1261 Constructor. 1262 1263 :keyword id: Rule id. 1264 :type id: ``str`` 1265 1266 :keyword parent_group_id: ID of the parent security group. 1267 :type parent_group_id: ``str`` 1268 1269 :keyword ip_protocol: IP Protocol (icmp, tcp, udp, etc). 1270 :type ip_protocol: ``str`` 1271 1272 :keyword from_port: Port at start of range. 1273 :type from_port: ``int`` 1274 1275 :keyword to_port: Port at end of range. 1276 :type to_port: ``int`` 1277 1278 :keyword ip_range: CIDR for address range. 1279 :type ip_range: ``str`` 1280 1281 :keyword group: Name of a source security group to apply to rule. 1282 :type group: ``str`` 1283 1284 :keyword tenant_id: Owner of the security group. 1285 :type tenant_id: ``str`` 1286 1287 :keyword direction: Security group Direction (ingress or egress). 1288 :type direction: ``str`` 1289 1290 :keyword extra: Extra attributes associated with this rule. 1291 :type extra: ``dict`` 1292 """ 1293 self.id = id 1294 self.parent_group_id = parent_group_id 1295 self.ip_protocol = ip_protocol 1296 self.from_port = from_port 1297 self.to_port = to_port 1298 self.driver = driver 1299 self.ip_range = '' 1300 self.group = {} 1301 self.direction = 'ingress' 1302 1303 if group is None: 1304 self.ip_range = ip_range 1305 else: 1306 self.group = {'name': group, 'tenant_id': tenant_id} 1307 1308 # by default in old versions only ingress was used 1309 if direction is not None: 1310 if direction in ['ingress', 'egress']: 1311 self.direction = direction 1312 else: 1313 raise OpenStackException("Security group direction incorrect " 1314 "value: ingress or egress.", 500, 1315 driver) 1316 1317 self.tenant_id = tenant_id 1318 self.extra = extra or {} 1319 1320 def __repr__(self): 1321 return ('<OpenStackSecurityGroupRule id=%s parent_group_id=%s \ 1322 ip_protocol=%s from_port=%s to_port=%s>' % (self.id, 1323 self.parent_group_id, self.ip_protocol, self.from_port, 1324 self.to_port)) 1325 1326 1327class OpenStackKeyPair(object): 1328 """ 1329 A KeyPair. 1330 """ 1331 1332 def __init__(self, name, fingerprint, public_key, driver, private_key=None, 1333 extra=None): 1334 """ 1335 Constructor. 1336 1337 :keyword name: Name of the KeyPair. 1338 :type name: ``str`` 1339 1340 :keyword fingerprint: Fingerprint of the KeyPair 1341 :type fingerprint: ``str`` 1342 1343 :keyword public_key: Public key in OpenSSH format. 1344 :type public_key: ``str`` 1345 1346 :keyword private_key: Private key in PEM format. 1347 :type private_key: ``str`` 1348 1349 :keyword extra: Extra attributes associated with this KeyPair. 1350 :type extra: ``dict`` 1351 """ 1352 self.name = name 1353 self.fingerprint = fingerprint 1354 self.public_key = public_key 1355 self.private_key = private_key 1356 self.driver = driver 1357 self.extra = extra or {} 1358 1359 def __repr__(self): 1360 return ('<OpenStackKeyPair name=%s fingerprint=%s public_key=%s ...>' 1361 % (self.name, self.fingerprint, self.public_key)) 1362 1363 1364class OpenStack_1_1_Connection(OpenStackComputeConnection): 1365 responseCls = OpenStack_1_1_Response 1366 accept_format = 'application/json' 1367 default_content_type = 'application/json; charset=UTF-8' 1368 1369 def encode_data(self, data): 1370 return json.dumps(data) 1371 1372 1373class OpenStack_1_1_NodeDriver(OpenStackNodeDriver): 1374 """ 1375 OpenStack node driver. 1376 """ 1377 connectionCls = OpenStack_1_1_Connection 1378 type = Provider.OPENSTACK 1379 1380 features = {"create_node": ["generates_password"]} 1381 _networks_url_prefix = '/os-networks' 1382 1383 def __init__(self, *args, **kwargs): 1384 self._ex_force_api_version = str(kwargs.pop('ex_force_api_version', 1385 None)) 1386 super(OpenStack_1_1_NodeDriver, self).__init__(*args, **kwargs) 1387 1388 def create_node(self, name, size, image=None, ex_keyname=None, 1389 ex_userdata=None, 1390 ex_config_drive=None, ex_security_groups=None, 1391 ex_metadata=None, ex_files=None, networks=None, 1392 ex_disk_config=None, 1393 ex_admin_pass=None, 1394 ex_availability_zone=None, ex_blockdevicemappings=None): 1395 """Create a new node 1396 1397 @inherits: :class:`NodeDriver.create_node` 1398 1399 :keyword ex_keyname: The name of the key pair 1400 :type ex_keyname: ``str`` 1401 1402 :keyword ex_userdata: String containing user data 1403 see 1404 https://help.ubuntu.com/community/CloudInit 1405 :type ex_userdata: ``str`` 1406 1407 :keyword ex_config_drive: Enable config drive 1408 see 1409 http://docs.openstack.org/grizzly/openstack-compute/admin/content/config-drive.html 1410 :type ex_config_drive: ``bool`` 1411 1412 :keyword ex_security_groups: List of security groups to assign to 1413 the node 1414 :type ex_security_groups: ``list`` of 1415 :class:`OpenStackSecurityGroup` 1416 1417 :keyword ex_metadata: Key/Value metadata to associate with a node 1418 :type ex_metadata: ``dict`` 1419 1420 :keyword ex_files: File Path => File contents to create on 1421 the node 1422 :type ex_files: ``dict`` 1423 1424 1425 :keyword networks: The server is launched into a set of Networks. 1426 :type networks: ``list`` of :class:`OpenStackNetwork` 1427 1428 :keyword ex_disk_config: Name of the disk configuration. 1429 Can be either ``AUTO`` or ``MANUAL``. 1430 :type ex_disk_config: ``str`` 1431 1432 :keyword ex_config_drive: If True enables metadata injection in a 1433 server through a configuration drive. 1434 :type ex_config_drive: ``bool`` 1435 1436 :keyword ex_admin_pass: The root password for the node 1437 :type ex_admin_pass: ``str`` 1438 1439 :keyword ex_availability_zone: Nova availability zone for the node 1440 :type ex_availability_zone: ``str`` 1441 """ 1442 ex_metadata = ex_metadata or {} 1443 ex_files = ex_files or {} 1444 networks = networks or [] 1445 ex_security_groups = ex_security_groups or [] 1446 1447 server_params = self._create_args_to_params( 1448 node=None, 1449 name=name, 1450 size=size, image=image, ex_keyname=ex_keyname, 1451 ex_userdata=ex_userdata, ex_config_drive=ex_config_drive, 1452 ex_security_groups=ex_security_groups, ex_metadata=ex_metadata, 1453 ex_files=ex_files, networks=networks, 1454 ex_disk_config=ex_disk_config, 1455 ex_availability_zone=ex_availability_zone, 1456 ex_blockdevicemappings=ex_blockdevicemappings) 1457 1458 resp = self.connection.request("/servers", 1459 method='POST', 1460 data={'server': server_params}) 1461 1462 create_response = resp.object['server'] 1463 server_resp = self.connection.request( 1464 '/servers/%s' % create_response['id']) 1465 server_object = server_resp.object['server'] 1466 1467 # adminPass is not always present 1468 # http://docs.openstack.org/essex/openstack-compute/admin/ 1469 # content/configuring-compute-API.html#d6e1833 1470 server_object['adminPass'] = create_response.get('adminPass', None) 1471 1472 return self._to_node(server_object) 1473 1474 def _to_images(self, obj, ex_only_active): 1475 images = [] 1476 for image in obj['images']: 1477 if ex_only_active and image.get('status') != 'ACTIVE': 1478 continue 1479 images.append(self._to_image(image)) 1480 1481 return images 1482 1483 def _to_image(self, api_image): 1484 server = api_image.get('server', {}) 1485 updated = api_image.get('updated_at') or api_image['updated'] 1486 created = api_image.get('created_at') or api_image['created'] 1487 min_ram = api_image.get('min_ram') 1488 1489 if min_ram is None: 1490 min_ram = api_image.get('minRam') 1491 1492 min_disk = api_image.get('min_disk') 1493 1494 if min_disk is None: 1495 min_disk = api_image.get('minDisk') 1496 1497 return NodeImage( 1498 id=api_image['id'], 1499 name=api_image['name'], 1500 driver=self, 1501 extra=dict( 1502 visibility=api_image.get('visibility'), 1503 updated=updated, 1504 created=created, 1505 status=api_image['status'], 1506 progress=api_image.get('progress'), 1507 metadata=api_image.get('metadata'), 1508 os_type=api_image.get('os_type'), 1509 serverId=server.get('id'), 1510 minDisk=min_disk, 1511 minRam=min_ram, 1512 ) 1513 ) 1514 1515 def _to_image_member(self, api_image_member): 1516 created = api_image_member['created_at'] 1517 updated = api_image_member.get('updated_at') 1518 return NodeImageMember( 1519 id=api_image_member['member_id'], 1520 image_id=api_image_member['image_id'], 1521 state=api_image_member['status'], 1522 created=created, 1523 driver=self, 1524 extra=dict( 1525 schema=api_image_member.get('schema'), 1526 updated=updated, 1527 ) 1528 ) 1529 1530 def _to_nodes(self, obj): 1531 servers = obj['servers'] 1532 return [self._to_node(server) for server in servers] 1533 1534 def _to_volumes(self, obj): 1535 volumes = obj['volumes'] 1536 return [self._to_volume(volume) for volume in volumes] 1537 1538 def _to_snapshots(self, obj): 1539 snapshots = obj['snapshots'] 1540 return [self._to_snapshot(snapshot) for snapshot in snapshots] 1541 1542 def _to_sizes(self, obj): 1543 flavors = obj['flavors'] 1544 return [self._to_size(flavor) for flavor in flavors] 1545 1546 def _create_args_to_params(self, node, **kwargs): 1547 server_params = { 1548 'name': kwargs.get('name'), 1549 'metadata': kwargs.get('ex_metadata', {}) or {}, 1550 'personality': self._files_to_personality(kwargs.get("ex_files", 1551 {}) or {}) 1552 } 1553 1554 if kwargs.get('ex_availability_zone', None): 1555 server_params['availability_zone'] = kwargs['ex_availability_zone'] 1556 1557 if kwargs.get('ex_keyname', None): 1558 server_params['key_name'] = kwargs['ex_keyname'] 1559 1560 if kwargs.get('ex_userdata', None): 1561 server_params['user_data'] = base64.b64encode( 1562 b(kwargs['ex_userdata'])).decode('ascii') 1563 1564 if kwargs.get('ex_disk_config', None): 1565 server_params['OS-DCF:diskConfig'] = kwargs['ex_disk_config'] 1566 1567 if kwargs.get('ex_config_drive', None): 1568 server_params['config_drive'] = str(kwargs['ex_config_drive']) 1569 1570 if kwargs.get('ex_admin_pass', None): 1571 server_params['adminPass'] = kwargs['ex_admin_pass'] 1572 1573 if kwargs.get('networks', None): 1574 networks = kwargs['networks'] or [] 1575 networks = [{'uuid': network.id} for network in networks] 1576 server_params['networks'] = networks 1577 1578 if kwargs.get('ex_security_groups', None): 1579 server_params['security_groups'] = [] 1580 for security_group in kwargs['ex_security_groups'] or []: 1581 name = security_group.name 1582 server_params['security_groups'].append({'name': name}) 1583 1584 if kwargs.get('ex_blockdevicemappings', None): 1585 server_params['block_device_mapping_v2'] = \ 1586 kwargs['ex_blockdevicemappings'] 1587 1588 if kwargs.get('name', None): 1589 server_params['name'] = kwargs.get('name') 1590 else: 1591 server_params['name'] = node.name 1592 1593 if kwargs.get('image', None): 1594 server_params['imageRef'] = kwargs.get('image').id 1595 else: 1596 server_params['imageRef'] = node.extra.get( 1597 'imageId', '' 1598 ) if node else '' 1599 1600 if kwargs.get('size', None): 1601 server_params['flavorRef'] = kwargs.get('size').id 1602 else: 1603 server_params['flavorRef'] = node.extra.get('flavorId') 1604 1605 return server_params 1606 1607 def _files_to_personality(self, files): 1608 rv = [] 1609 1610 for k, v in list(files.items()): 1611 rv.append({'path': k, 'contents': base64.b64encode(b(v))}) 1612 1613 return rv 1614 1615 def _reboot_node(self, node, reboot_type='SOFT'): 1616 resp = self._node_action(node, 'reboot', type=reboot_type) 1617 return resp.status == httplib.ACCEPTED 1618 1619 def ex_set_password(self, node, password): 1620 """ 1621 Changes the administrator password for a specified server. 1622 1623 :param node: Node to rebuild. 1624 :type node: :class:`Node` 1625 1626 :param password: The administrator password. 1627 :type password: ``str`` 1628 1629 :rtype: ``bool`` 1630 """ 1631 resp = self._node_action(node, 'changePassword', adminPass=password) 1632 node.extra['password'] = password 1633 return resp.status == httplib.ACCEPTED 1634 1635 def ex_rebuild(self, node, image, **kwargs): 1636 """ 1637 Rebuild a Node. 1638 1639 :param node: Node to rebuild. 1640 :type node: :class:`Node` 1641 1642 :param image: New image to use. 1643 :type image: :class:`NodeImage` 1644 1645 :keyword ex_metadata: Key/Value metadata to associate with a node 1646 :type ex_metadata: ``dict`` 1647 1648 :keyword ex_files: File Path => File contents to create on 1649 the node 1650 :type ex_files: ``dict`` 1651 1652 :keyword ex_keyname: Name of existing public key to inject into 1653 instance 1654 :type ex_keyname: ``str`` 1655 1656 :keyword ex_userdata: String containing user data 1657 see 1658 https://help.ubuntu.com/community/CloudInit 1659 :type ex_userdata: ``str`` 1660 1661 :keyword ex_security_groups: List of security groups to assign to 1662 the node 1663 :type ex_security_groups: ``list`` of 1664 :class:`OpenStackSecurityGroup` 1665 1666 :keyword ex_disk_config: Name of the disk configuration. 1667 Can be either ``AUTO`` or ``MANUAL``. 1668 :type ex_disk_config: ``str`` 1669 1670 :keyword ex_config_drive: If True enables metadata injection in a 1671 server through a configuration drive. 1672 :type ex_config_drive: ``bool`` 1673 1674 :rtype: ``bool`` 1675 """ 1676 server_params = self._create_args_to_params(node, image=image, 1677 **kwargs) 1678 resp = self._node_action(node, 'rebuild', **server_params) 1679 return resp.status == httplib.ACCEPTED 1680 1681 def ex_resize(self, node, size): 1682 """ 1683 Change a node size. 1684 1685 :param node: Node to resize. 1686 :type node: :class:`Node` 1687 1688 :type size: :class:`NodeSize` 1689 :param size: New size to use. 1690 1691 :rtype: ``bool`` 1692 """ 1693 server_params = {'flavorRef': size.id} 1694 resp = self._node_action(node, 'resize', **server_params) 1695 return resp.status == httplib.ACCEPTED 1696 1697 def ex_confirm_resize(self, node): 1698 """ 1699 Confirms a pending resize action. 1700 1701 :param node: Node to resize. 1702 :type node: :class:`Node` 1703 1704 :rtype: ``bool`` 1705 """ 1706 resp = self._node_action(node, 'confirmResize') 1707 return resp.status == httplib.NO_CONTENT 1708 1709 def ex_revert_resize(self, node): 1710 """ 1711 Cancels and reverts a pending resize action. 1712 1713 :param node: Node to resize. 1714 :type node: :class:`Node` 1715 1716 :rtype: ``bool`` 1717 """ 1718 resp = self._node_action(node, 'revertResize') 1719 return resp.status == httplib.ACCEPTED 1720 1721 def create_image(self, node, name, metadata=None): 1722 """ 1723 Creates a new image. 1724 1725 :param node: Node 1726 :type node: :class:`Node` 1727 1728 :param name: The name for the new image. 1729 :type name: ``str`` 1730 1731 :param metadata: Key and value pairs for metadata. 1732 :type metadata: ``dict`` 1733 1734 :rtype: :class:`NodeImage` 1735 """ 1736 optional_params = {} 1737 if metadata: 1738 optional_params['metadata'] = metadata 1739 resp = self._node_action(node, 'createImage', name=name, 1740 **optional_params) 1741 image_id = self._extract_image_id_from_url(resp.headers['location']) 1742 return self.get_image(image_id=image_id) 1743 1744 def ex_set_server_name(self, node, name): 1745 """ 1746 Sets the Node's name. 1747 1748 :param node: Node 1749 :type node: :class:`Node` 1750 1751 :param name: The name of the server. 1752 :type name: ``str`` 1753 1754 :rtype: :class:`Node` 1755 """ 1756 return self._update_node(node, name=name) 1757 1758 def ex_get_metadata(self, node): 1759 """ 1760 Get a Node's metadata. 1761 1762 :param node: Node 1763 :type node: :class:`Node` 1764 1765 :return: Key/Value metadata associated with node. 1766 :rtype: ``dict`` 1767 """ 1768 return self.connection.request( 1769 '/servers/%s/metadata' % (node.id,), 1770 method='GET',).object['metadata'] 1771 1772 def ex_set_metadata(self, node, metadata): 1773 """ 1774 Sets the Node's metadata. 1775 1776 :param node: Node 1777 :type node: :class:`Node` 1778 1779 :param metadata: Key/Value metadata to associate with a node 1780 :type metadata: ``dict`` 1781 1782 :rtype: ``dict`` 1783 """ 1784 return self.connection.request( 1785 '/servers/%s/metadata' % (node.id,), method='PUT', 1786 data={'metadata': metadata} 1787 ).object['metadata'] 1788 1789 def ex_update_node(self, node, **node_updates): 1790 """ 1791 Update the Node's editable attributes. The OpenStack API currently 1792 supports editing name and IPv4/IPv6 access addresses. 1793 1794 The driver currently only supports updating the node name. 1795 1796 :param node: Node 1797 :type node: :class:`Node` 1798 1799 :keyword name: New name for the server 1800 :type name: ``str`` 1801 1802 :rtype: :class:`Node` 1803 """ 1804 potential_data = self._create_args_to_params(node, **node_updates) 1805 updates = {'name': potential_data['name']} 1806 return self._update_node(node, **updates) 1807 1808 def _to_networks(self, obj): 1809 networks = obj['networks'] 1810 return [self._to_network(network) for network in networks] 1811 1812 def _to_network(self, obj): 1813 return OpenStackNetwork(id=obj['id'], 1814 name=obj['label'], 1815 cidr=obj.get('cidr', None), 1816 driver=self) 1817 1818 def ex_list_networks(self): 1819 """ 1820 Get a list of Networks that are available. 1821 1822 :rtype: ``list`` of :class:`OpenStackNetwork` 1823 """ 1824 response = self.connection.request(self._networks_url_prefix).object 1825 return self._to_networks(response) 1826 1827 def ex_get_network(self, network_id): 1828 """ 1829 Retrieve the Network with the given ID 1830 1831 :param networkId: ID of the network 1832 :type networkId: ``str`` 1833 1834 :rtype :class:`OpenStackNetwork` 1835 """ 1836 request_url = "{networks_url_prefix}/{network_id}".format( 1837 networks_url_prefix=self._networks_url_prefix, 1838 network_id=network_id 1839 ) 1840 response = self.connection.request(request_url).object 1841 return self._to_network(response['network']) 1842 1843 def ex_create_network(self, name, cidr): 1844 """ 1845 Create a new Network 1846 1847 :param name: Name of network which should be used 1848 :type name: ``str`` 1849 1850 :param cidr: cidr of network which should be used 1851 :type cidr: ``str`` 1852 1853 :rtype: :class:`OpenStackNetwork` 1854 """ 1855 data = {'network': {'cidr': cidr, 'label': name}} 1856 response = self.connection.request(self._networks_url_prefix, 1857 method='POST', data=data).object 1858 return self._to_network(response['network']) 1859 1860 def ex_delete_network(self, network): 1861 """ 1862 Delete a Network 1863 1864 :param network: Network which should be used 1865 :type network: :class:`OpenStackNetwork` 1866 1867 :rtype: ``bool`` 1868 """ 1869 resp = self.connection.request('%s/%s' % (self._networks_url_prefix, 1870 network.id), 1871 method='DELETE') 1872 return resp.status in (httplib.NO_CONTENT, httplib.ACCEPTED) 1873 1874 def ex_get_console_output(self, node, length=None): 1875 """ 1876 Get console output 1877 1878 :param node: node 1879 :type node: :class:`Node` 1880 1881 :param length: Optional number of lines to fetch from the 1882 console log 1883 :type length: ``int`` 1884 1885 :return: Dictionary with the output 1886 :rtype: ``dict`` 1887 """ 1888 1889 data = { 1890 "os-getConsoleOutput": { 1891 "length": length 1892 } 1893 } 1894 1895 resp = self.connection.request('/servers/%s/action' % node.id, 1896 method='POST', data=data).object 1897 return resp 1898 1899 def ex_list_snapshots(self): 1900 return self._to_snapshots( 1901 self.connection.request('/os-snapshots').object) 1902 1903 def ex_get_snapshot(self, snapshotId): 1904 return self._to_snapshot( 1905 self.connection.request('/os-snapshots/%s' % snapshotId).object) 1906 1907 def list_volume_snapshots(self, volume): 1908 return [snapshot for snapshot in self.ex_list_snapshots() 1909 if snapshot.extra['volume_id'] == volume.id] 1910 1911 def create_volume_snapshot(self, volume, name=None, ex_description=None, 1912 ex_force=True): 1913 """ 1914 Create snapshot from volume 1915 1916 :param volume: Instance of `StorageVolume` 1917 :type volume: `StorageVolume` 1918 1919 :param name: Name of snapshot (optional) 1920 :type name: `str` | `NoneType` 1921 1922 :param ex_description: Description of the snapshot (optional) 1923 :type ex_description: `str` | `NoneType` 1924 1925 :param ex_force: Specifies if we create a snapshot that is not in 1926 state `available`. For example `in-use`. Defaults 1927 to True. (optional) 1928 :type ex_force: `bool` 1929 1930 :rtype: :class:`VolumeSnapshot` 1931 """ 1932 data = {'snapshot': {'volume_id': volume.id, 'force': ex_force}} 1933 1934 if name is not None: 1935 data['snapshot']['display_name'] = name 1936 1937 if ex_description is not None: 1938 data['snapshot']['display_description'] = ex_description 1939 1940 return self._to_snapshot(self.connection.request('/os-snapshots', 1941 method='POST', 1942 data=data).object) 1943 1944 def destroy_volume_snapshot(self, snapshot): 1945 resp = self.connection.request('/os-snapshots/%s' % snapshot.id, 1946 method='DELETE') 1947 return resp.status == httplib.NO_CONTENT 1948 1949 def ex_create_snapshot(self, volume, name, description=None, force=False): 1950 """ 1951 Create a snapshot based off of a volume. 1952 1953 :param volume: volume 1954 :type volume: :class:`StorageVolume` 1955 1956 :keyword name: New name for the volume snapshot 1957 :type name: ``str`` 1958 1959 :keyword description: Description of the snapshot (optional) 1960 :type description: ``str`` 1961 1962 :keyword force: Whether to force creation (optional) 1963 :type force: ``bool`` 1964 1965 :rtype: :class:`VolumeSnapshot` 1966 """ 1967 warnings.warn('This method has been deprecated in favor of the ' 1968 'create_volume_snapshot method') 1969 return self.create_volume_snapshot(volume, name, 1970 ex_description=description, 1971 ex_force=force) 1972 1973 def ex_delete_snapshot(self, snapshot): 1974 """ 1975 Delete a VolumeSnapshot 1976 1977 :param snapshot: snapshot 1978 :type snapshot: :class:`VolumeSnapshot` 1979 1980 :rtype: ``bool`` 1981 """ 1982 warnings.warn('This method has been deprecated in favor of the ' 1983 'destroy_volume_snapshot method') 1984 return self.destroy_volume_snapshot(snapshot) 1985 1986 def _to_security_group_rules(self, obj): 1987 return [self._to_security_group_rule(security_group_rule) for 1988 security_group_rule in obj] 1989 1990 def _to_security_group_rule(self, obj): 1991 ip_range = group = tenant_id = None 1992 if obj['group'] == {}: 1993 ip_range = obj['ip_range'].get('cidr', None) 1994 else: 1995 group = obj['group'].get('name', None) 1996 tenant_id = obj['group'].get('tenant_id', None) 1997 1998 return OpenStackSecurityGroupRule( 1999 id=obj['id'], parent_group_id=obj['parent_group_id'], 2000 ip_protocol=obj['ip_protocol'], from_port=obj['from_port'], 2001 to_port=obj['to_port'], driver=self, ip_range=ip_range, 2002 group=group, tenant_id=tenant_id) 2003 2004 def _to_security_groups(self, obj): 2005 security_groups = obj['security_groups'] 2006 return [self._to_security_group(security_group) for security_group in 2007 security_groups] 2008 2009 def _to_security_group(self, obj): 2010 rules = self._to_security_group_rules(obj.get('security_group_rules', 2011 obj.get('rules', []))) 2012 return OpenStackSecurityGroup(id=obj['id'], 2013 tenant_id=obj['tenant_id'], 2014 name=obj['name'], 2015 description=obj.get('description', ''), 2016 rules=rules, 2017 driver=self) 2018 2019 def ex_list_security_groups(self): 2020 """ 2021 Get a list of Security Groups that are available. 2022 2023 :rtype: ``list`` of :class:`OpenStackSecurityGroup` 2024 """ 2025 return self._to_security_groups( 2026 self.connection.request('/os-security-groups').object) 2027 2028 def ex_get_node_security_groups(self, node): 2029 """ 2030 Get Security Groups of the specified server. 2031 2032 :rtype: ``list`` of :class:`OpenStackSecurityGroup` 2033 """ 2034 return self._to_security_groups( 2035 self.connection.request('/servers/%s/os-security-groups' % 2036 (node.id)).object) 2037 2038 def ex_create_security_group(self, name, description): 2039 """ 2040 Create a new Security Group 2041 2042 :param name: Name of the new Security Group 2043 :type name: ``str`` 2044 2045 :param description: Description of the new Security Group 2046 :type description: ``str`` 2047 2048 :rtype: :class:`OpenStackSecurityGroup` 2049 """ 2050 return self._to_security_group(self.connection.request( 2051 '/os-security-groups', method='POST', 2052 data={'security_group': {'name': name, 'description': description}} 2053 ).object['security_group']) 2054 2055 def ex_delete_security_group(self, security_group): 2056 """ 2057 Delete a Security Group. 2058 2059 :param security_group: Security Group should be deleted 2060 :type security_group: :class:`OpenStackSecurityGroup` 2061 2062 :rtype: ``bool`` 2063 """ 2064 resp = self.connection.request('/os-security-groups/%s' % 2065 (security_group.id), 2066 method='DELETE') 2067 return resp.status in (httplib.NO_CONTENT, httplib.ACCEPTED) 2068 2069 def ex_create_security_group_rule(self, security_group, ip_protocol, 2070 from_port, to_port, cidr=None, 2071 source_security_group=None): 2072 """ 2073 Create a new Rule in a Security Group 2074 2075 :param security_group: Security Group in which to add the rule 2076 :type security_group: :class:`OpenStackSecurityGroup` 2077 2078 :param ip_protocol: Protocol to which this rule applies 2079 Examples: tcp, udp, ... 2080 :type ip_protocol: ``str`` 2081 2082 :param from_port: First port of the port range 2083 :type from_port: ``int`` 2084 2085 :param to_port: Last port of the port range 2086 :type to_port: ``int`` 2087 2088 :param cidr: CIDR notation of the source IP range for this rule 2089 :type cidr: ``str`` 2090 2091 :param source_security_group: Existing Security Group to use as the 2092 source (instead of CIDR) 2093 :type source_security_group: L{OpenStackSecurityGroup 2094 2095 :rtype: :class:`OpenStackSecurityGroupRule` 2096 """ 2097 source_security_group_id = None 2098 if type(source_security_group) == OpenStackSecurityGroup: 2099 source_security_group_id = source_security_group.id 2100 2101 return self._to_security_group_rule(self.connection.request( 2102 '/os-security-group-rules', method='POST', 2103 data={'security_group_rule': { 2104 'ip_protocol': ip_protocol, 2105 'from_port': from_port, 2106 'to_port': to_port, 2107 'cidr': cidr, 2108 'group_id': source_security_group_id, 2109 'parent_group_id': security_group.id}} 2110 ).object['security_group_rule']) 2111 2112 def ex_delete_security_group_rule(self, rule): 2113 """ 2114 Delete a Rule from a Security Group. 2115 2116 :param rule: Rule should be deleted 2117 :type rule: :class:`OpenStackSecurityGroupRule` 2118 2119 :rtype: ``bool`` 2120 """ 2121 resp = self.connection.request('/os-security-group-rules/%s' % 2122 (rule.id), method='DELETE') 2123 return resp.status == httplib.NO_CONTENT 2124 2125 def _to_key_pairs(self, obj): 2126 key_pairs = obj['keypairs'] 2127 key_pairs = [self._to_key_pair(key_pair['keypair']) for key_pair in 2128 key_pairs] 2129 return key_pairs 2130 2131 def _to_key_pair(self, obj): 2132 key_pair = KeyPair(name=obj['name'], 2133 fingerprint=obj['fingerprint'], 2134 public_key=obj['public_key'], 2135 private_key=obj.get('private_key', None), 2136 driver=self) 2137 return key_pair 2138 2139 def list_key_pairs(self): 2140 response = self.connection.request('/os-keypairs') 2141 key_pairs = self._to_key_pairs(response.object) 2142 return key_pairs 2143 2144 def get_key_pair(self, name): 2145 self.connection.set_context({'key_pair_name': name}) 2146 2147 response = self.connection.request('/os-keypairs/%s' % (name)) 2148 key_pair = self._to_key_pair(response.object['keypair']) 2149 return key_pair 2150 2151 def create_key_pair(self, name): 2152 data = {'keypair': {'name': name}} 2153 response = self.connection.request('/os-keypairs', method='POST', 2154 data=data) 2155 key_pair = self._to_key_pair(response.object['keypair']) 2156 return key_pair 2157 2158 def import_key_pair_from_string(self, name, key_material): 2159 data = {'keypair': {'name': name, 'public_key': key_material}} 2160 response = self.connection.request('/os-keypairs', method='POST', 2161 data=data) 2162 key_pair = self._to_key_pair(response.object['keypair']) 2163 return key_pair 2164 2165 def delete_key_pair(self, key_pair): 2166 """ 2167 Delete a KeyPair. 2168 2169 :param keypair: KeyPair to delete 2170 :type keypair: :class:`OpenStackKeyPair` 2171 2172 :rtype: ``bool`` 2173 """ 2174 response = self.connection.request('/os-keypairs/%s' % (key_pair.name), 2175 method='DELETE') 2176 return response.status == httplib.ACCEPTED 2177 2178 def ex_list_keypairs(self): 2179 """ 2180 Get a list of KeyPairs that are available. 2181 2182 :rtype: ``list`` of :class:`OpenStackKeyPair` 2183 """ 2184 warnings.warn('This method has been deprecated in favor of ' 2185 'list_key_pairs method') 2186 2187 return self.list_key_pairs() 2188 2189 def ex_create_keypair(self, name): 2190 """ 2191 Create a new KeyPair 2192 2193 :param name: Name of the new KeyPair 2194 :type name: ``str`` 2195 2196 :rtype: :class:`OpenStackKeyPair` 2197 """ 2198 warnings.warn('This method has been deprecated in favor of ' 2199 'create_key_pair method') 2200 2201 return self.create_key_pair(name=name) 2202 2203 def ex_import_keypair(self, name, keyfile): 2204 """ 2205 Import a KeyPair from a file 2206 2207 :param name: Name of the new KeyPair 2208 :type name: ``str`` 2209 2210 :param keyfile: Path to the public key file (in OpenSSH format) 2211 :type keyfile: ``str`` 2212 2213 :rtype: :class:`OpenStackKeyPair` 2214 """ 2215 warnings.warn('This method has been deprecated in favor of ' 2216 'import_key_pair_from_file method') 2217 2218 return self.import_key_pair_from_file(name=name, key_file_path=keyfile) 2219 2220 def ex_import_keypair_from_string(self, name, key_material): 2221 """ 2222 Import a KeyPair from a string 2223 2224 :param name: Name of the new KeyPair 2225 :type name: ``str`` 2226 2227 :param key_material: Public key (in OpenSSH format) 2228 :type key_material: ``str`` 2229 2230 :rtype: :class:`OpenStackKeyPair` 2231 """ 2232 warnings.warn('This method has been deprecated in favor of ' 2233 'import_key_pair_from_string method') 2234 2235 return self.import_key_pair_from_string(name=name, 2236 key_material=key_material) 2237 2238 def ex_delete_keypair(self, keypair): 2239 """ 2240 Delete a KeyPair. 2241 2242 :param keypair: KeyPair to delete 2243 :type keypair: :class:`OpenStackKeyPair` 2244 2245 :rtype: ``bool`` 2246 """ 2247 warnings.warn('This method has been deprecated in favor of ' 2248 'delete_key_pair method') 2249 2250 return self.delete_key_pair(key_pair=keypair) 2251 2252 def ex_get_size(self, size_id): 2253 """ 2254 Get a NodeSize 2255 2256 :param size_id: ID of the size which should be used 2257 :type size_id: ``str`` 2258 2259 :rtype: :class:`NodeSize` 2260 """ 2261 return self._to_size(self.connection.request( 2262 '/flavors/%s' % (size_id,)) .object['flavor']) 2263 2264 def ex_get_size_extra_specs(self, size_id): 2265 """ 2266 Get the extra_specs field of a NodeSize 2267 2268 :param size_id: ID of the size which should be used 2269 :type size_id: ``str`` 2270 2271 :rtype: `dict` 2272 """ 2273 return self.connection.request( 2274 '/flavors/%s/os-extra_specs' % (size_id,)) .object['extra_specs'] 2275 2276 def get_image(self, image_id): 2277 """ 2278 Get a NodeImage 2279 2280 @inherits: :class:`NodeDriver.get_image` 2281 2282 :param image_id: ID of the image which should be used 2283 :type image_id: ``str`` 2284 2285 :rtype: :class:`NodeImage` 2286 """ 2287 return self._to_image(self.connection.request( 2288 '/images/%s' % (image_id,)).object['image']) 2289 2290 def delete_image(self, image): 2291 """ 2292 Delete a NodeImage 2293 2294 @inherits: :class:`NodeDriver.delete_image` 2295 2296 :param image: image witch should be used 2297 :type image: :class:`NodeImage` 2298 2299 :rtype: ``bool`` 2300 """ 2301 resp = self.connection.request('/images/%s' % (image.id,), 2302 method='DELETE') 2303 return resp.status == httplib.NO_CONTENT 2304 2305 def _node_action(self, node, action, **params): 2306 params = params or None 2307 return self.connection.request('/servers/%s/action' % (node.id,), 2308 method='POST', data={action: params}) 2309 2310 def _update_node(self, node, **node_updates): 2311 """ 2312 Updates the editable attributes of a server, which currently include 2313 its name and IPv4/IPv6 access addresses. 2314 """ 2315 return self._to_node( 2316 self.connection.request( 2317 '/servers/%s' % (node.id,), method='PUT', 2318 data={'server': node_updates} 2319 ).object['server'] 2320 ) 2321 2322 def _to_node_from_obj(self, obj): 2323 return self._to_node(obj['server']) 2324 2325 def _to_node(self, api_node): 2326 public_networks_labels = ['public', 'internet'] 2327 2328 public_ips, private_ips = [], [] 2329 2330 for label, values in api_node['addresses'].items(): 2331 for value in values: 2332 ip = value['addr'] 2333 is_public_ip = False 2334 2335 try: 2336 is_public_ip = is_public_subnet(ip) 2337 except Exception: 2338 # IPv6 2339 2340 # Openstack Icehouse sets 'OS-EXT-IPS:type' to 'floating' 2341 # for public and 'fixed' for private 2342 explicit_ip_type = value.get('OS-EXT-IPS:type', None) 2343 2344 if label in public_networks_labels: 2345 is_public_ip = True 2346 elif explicit_ip_type == 'floating': 2347 is_public_ip = True 2348 elif explicit_ip_type == 'fixed': 2349 is_public_ip = False 2350 2351 if is_public_ip: 2352 public_ips.append(ip) 2353 else: 2354 private_ips.append(ip) 2355 2356 # Sometimes 'image' attribute is not present if the node is in an error 2357 # state 2358 image = api_node.get('image', None) 2359 image_id = image.get('id', None) if image else None 2360 config_drive = api_node.get("config_drive", False) 2361 volumes_attached = api_node.get('os-extended-volumes:volumes_attached') 2362 created = parse_date(api_node["created"]) 2363 2364 return Node( 2365 id=api_node['id'], 2366 name=api_node['name'], 2367 state=self.NODE_STATE_MAP.get(api_node['status'], 2368 NodeState.UNKNOWN), 2369 public_ips=public_ips, 2370 private_ips=private_ips, 2371 created_at=created, 2372 driver=self, 2373 extra=dict( 2374 addresses=api_node['addresses'], 2375 hostId=api_node['hostId'], 2376 access_ip=api_node.get('accessIPv4'), 2377 access_ipv6=api_node.get('accessIPv6', None), 2378 # Docs says "tenantId", but actual is "tenant_id". *sigh* 2379 # Best handle both. 2380 tenantId=api_node.get('tenant_id') or api_node['tenantId'], 2381 userId=api_node.get('user_id', None), 2382 imageId=image_id, 2383 flavorId=api_node['flavor']['id'], 2384 uri=next(link['href'] for link in api_node['links'] if 2385 link['rel'] == 'self'), 2386 # pylint: disable=no-member 2387 service_name=self.connection.get_service_name(), 2388 metadata=api_node['metadata'], 2389 password=api_node.get('adminPass', None), 2390 created=api_node['created'], 2391 updated=api_node['updated'], 2392 key_name=api_node.get('key_name', None), 2393 disk_config=api_node.get('OS-DCF:diskConfig', None), 2394 config_drive=config_drive, 2395 availability_zone=api_node.get('OS-EXT-AZ:availability_zone'), 2396 volumes_attached=volumes_attached, 2397 task_state=api_node.get("OS-EXT-STS:task_state", None), 2398 vm_state=api_node.get("OS-EXT-STS:vm_state", None), 2399 power_state=api_node.get("OS-EXT-STS:power_state", None), 2400 progress=api_node.get("progress", None), 2401 fault=api_node.get('fault') 2402 ), 2403 ) 2404 2405 def _to_volume(self, api_node): 2406 if 'volume' in api_node: 2407 api_node = api_node['volume'] 2408 2409 state = self.VOLUME_STATE_MAP.get(api_node['status'], 2410 StorageVolumeState.UNKNOWN) 2411 2412 return StorageVolume( 2413 id=api_node['id'], 2414 name=api_node.get('displayName', api_node.get('name')), 2415 size=api_node['size'], 2416 state=state, 2417 driver=self, 2418 extra={ 2419 'description': api_node.get('displayDescription', 2420 api_node.get('description')), 2421 'attachments': [att for att in api_node['attachments'] if att], 2422 # TODO: remove in 1.18.0 2423 'state': api_node.get('status', None), 2424 'snapshot_id': api_node.get('snapshot_id', 2425 api_node.get('snapshotId')), 2426 'location': api_node.get('availability_zone', 2427 api_node.get('availabilityZone')), 2428 'volume_type': api_node.get('volume_type', 2429 api_node.get('volumeType')), 2430 'metadata': api_node.get('metadata', None), 2431 'created_at': api_node.get('created_at', 2432 api_node.get('createdAt')) 2433 } 2434 ) 2435 2436 def _to_snapshot(self, data): 2437 if 'snapshot' in data: 2438 data = data['snapshot'] 2439 2440 volume_id = data.get('volume_id', data.get('volumeId', None)) 2441 display_name = data.get('name', 2442 data.get('display_name', 2443 data.get('displayName', None))) 2444 created_at = data.get('created_at', data.get('createdAt', None)) 2445 description = data.get('description', 2446 data.get('display_description', 2447 data.get('displayDescription', None))) 2448 status = data.get('status', None) 2449 2450 extra = {'volume_id': volume_id, 2451 'name': display_name, 2452 'created': created_at, 2453 'description': description, 2454 'status': status} 2455 2456 state = self.SNAPSHOT_STATE_MAP.get( 2457 status, 2458 VolumeSnapshotState.UNKNOWN 2459 ) 2460 2461 try: 2462 created_dt = parse_date(created_at) 2463 except ValueError: 2464 created_dt = None 2465 2466 snapshot = VolumeSnapshot(id=data['id'], driver=self, 2467 size=data['size'], extra=extra, 2468 created=created_dt, state=state, 2469 name=display_name) 2470 return snapshot 2471 2472 def _to_size(self, api_flavor, price=None, bandwidth=None): 2473 # if provider-specific subclasses can get better values for 2474 # price/bandwidth, then can pass them in when they super(). 2475 if not price: 2476 price = self._get_size_price(str(api_flavor['id'])) 2477 2478 extra = api_flavor.get('OS-FLV-WITH-EXT-SPECS:extra_specs', {}) 2479 extra['disabled'] = api_flavor.get('OS-FLV-DISABLED:disabled', None) 2480 return OpenStackNodeSize( 2481 id=api_flavor['id'], 2482 name=api_flavor['name'], 2483 ram=api_flavor['ram'], 2484 disk=api_flavor['disk'], 2485 vcpus=api_flavor['vcpus'], 2486 ephemeral_disk=api_flavor.get('OS-FLV-EXT-DATA:ephemeral', None), 2487 swap=api_flavor['swap'], 2488 extra=extra, 2489 bandwidth=bandwidth, 2490 price=price, 2491 driver=self, 2492 ) 2493 2494 def _get_size_price(self, size_id): 2495 try: 2496 return get_size_price( 2497 driver_type='compute', 2498 driver_name=self.api_name, 2499 size_id=size_id, 2500 ) 2501 except KeyError: 2502 return(0.0) 2503 2504 def _extract_image_id_from_url(self, location_header): 2505 path = urlparse.urlparse(location_header).path 2506 image_id = path.split('/')[-1] 2507 return image_id 2508 2509 def ex_rescue(self, node, password=None): 2510 # Requires Rescue Mode extension 2511 """ 2512 Rescue a node 2513 2514 :param node: node 2515 :type node: :class:`Node` 2516 2517 :param password: password 2518 :type password: ``str`` 2519 2520 :rtype: :class:`Node` 2521 """ 2522 if password: 2523 resp = self._node_action(node, 'rescue', adminPass=password) 2524 else: 2525 resp = self._node_action(node, 'rescue') 2526 password = json.loads(resp.body)['adminPass'] 2527 node.extra['password'] = password 2528 return node 2529 2530 def ex_unrescue(self, node): 2531 """ 2532 Unrescue a node 2533 2534 :param node: node 2535 :type node: :class:`Node` 2536 2537 :rtype: ``bool`` 2538 """ 2539 resp = self._node_action(node, 'unrescue') 2540 return resp.status == httplib.ACCEPTED 2541 2542 def _to_floating_ip_pools(self, obj): 2543 pool_elements = obj['floating_ip_pools'] 2544 return [self._to_floating_ip_pool(pool) for pool in pool_elements] 2545 2546 def _to_floating_ip_pool(self, obj): 2547 return OpenStack_1_1_FloatingIpPool(obj['name'], self.connection) 2548 2549 def ex_list_floating_ip_pools(self): 2550 """ 2551 List available floating IP pools 2552 2553 :rtype: ``list`` of :class:`OpenStack_1_1_FloatingIpPool` 2554 """ 2555 return self._to_floating_ip_pools( 2556 self.connection.request('/os-floating-ip-pools').object) 2557 2558 def _to_floating_ips(self, obj): 2559 ip_elements = obj['floating_ips'] 2560 return [self._to_floating_ip(ip) for ip in ip_elements] 2561 2562 def _to_floating_ip(self, obj): 2563 return OpenStack_1_1_FloatingIpAddress(id=obj['id'], 2564 ip_address=obj['ip'], 2565 pool=None, 2566 node_id=obj['instance_id'], 2567 driver=self) 2568 2569 def ex_list_floating_ips(self): 2570 """ 2571 List floating IPs 2572 2573 :rtype: ``list`` of :class:`OpenStack_1_1_FloatingIpAddress` 2574 """ 2575 return self._to_floating_ips( 2576 self.connection.request('/os-floating-ips').object) 2577 2578 def ex_get_floating_ip(self, ip): 2579 """ 2580 Get specified floating IP 2581 2582 :param ip: floating IP to get 2583 :type ip: ``str`` 2584 2585 :rtype: :class:`OpenStack_1_1_FloatingIpAddress` 2586 """ 2587 floating_ips = self.ex_list_floating_ips() 2588 ip_obj, = [x for x in floating_ips if x.ip_address == ip] 2589 return ip_obj 2590 2591 def ex_create_floating_ip(self, ip_pool=None): 2592 """ 2593 Create new floating IP. The ip_pool attribute is optional only if your 2594 infrastructure has only one IP pool available. 2595 2596 :param ip_pool: name of the floating IP pool 2597 :type ip_pool: ``str`` 2598 2599 :rtype: :class:`OpenStack_1_1_FloatingIpAddress` 2600 """ 2601 data = {'pool': ip_pool} if ip_pool is not None else {} 2602 resp = self.connection.request('/os-floating-ips', 2603 method='POST', 2604 data=data) 2605 2606 data = resp.object['floating_ip'] 2607 id = data['id'] 2608 ip_address = data['ip'] 2609 return OpenStack_1_1_FloatingIpAddress(id=id, 2610 ip_address=ip_address, 2611 pool=None, 2612 node_id=None, 2613 driver=self) 2614 2615 def ex_delete_floating_ip(self, ip): 2616 """ 2617 Delete specified floating IP 2618 2619 :param ip: floating IP to remove 2620 :type ip: :class:`OpenStack_1_1_FloatingIpAddress` 2621 2622 :rtype: ``bool`` 2623 """ 2624 resp = self.connection.request('/os-floating-ips/%s' % ip.id, 2625 method='DELETE') 2626 return resp.status in (httplib.NO_CONTENT, httplib.ACCEPTED) 2627 2628 def ex_attach_floating_ip_to_node(self, node, ip): 2629 """ 2630 Attach the floating IP to the node 2631 2632 :param node: node 2633 :type node: :class:`Node` 2634 2635 :param ip: floating IP to attach 2636 :type ip: ``str`` or :class:`OpenStack_1_1_FloatingIpAddress` 2637 2638 :rtype: ``bool`` 2639 """ 2640 address = ip.ip_address if hasattr(ip, 'ip_address') else ip 2641 data = { 2642 'addFloatingIp': {'address': address} 2643 } 2644 resp = self.connection.request('/servers/%s/action' % node.id, 2645 method='POST', data=data) 2646 return resp.status == httplib.ACCEPTED 2647 2648 def ex_detach_floating_ip_from_node(self, node, ip): 2649 """ 2650 Detach the floating IP from the node 2651 2652 :param node: node 2653 :type node: :class:`Node` 2654 2655 :param ip: floating IP to remove 2656 :type ip: ``str`` or :class:`OpenStack_1_1_FloatingIpAddress` 2657 2658 :rtype: ``bool`` 2659 """ 2660 address = ip.ip_address if hasattr(ip, 'ip_address') else ip 2661 data = { 2662 'removeFloatingIp': {'address': address} 2663 } 2664 resp = self.connection.request('/servers/%s/action' % node.id, 2665 method='POST', data=data) 2666 return resp.status == httplib.ACCEPTED 2667 2668 def ex_get_metadata_for_node(self, node): 2669 """ 2670 Return the metadata associated with the node. 2671 2672 :param node: Node instance 2673 :type node: :class:`Node` 2674 2675 :return: A dictionary or other mapping of strings to strings, 2676 associating tag names with tag values. 2677 :type tags: ``dict`` 2678 """ 2679 return node.extra['metadata'] 2680 2681 def ex_pause_node(self, node): 2682 return self._post_simple_node_action(node, 'pause') 2683 2684 def ex_unpause_node(self, node): 2685 return self._post_simple_node_action(node, 'unpause') 2686 2687 def ex_start_node(self, node): 2688 # NOTE: This method is here for backward compatibility reasons after 2689 # this method was promoted to be part of the standard compute API in 2690 # Libcloud v2.7.0 2691 return self.start_node(node=node) 2692 2693 def ex_stop_node(self, node): 2694 # NOTE: This method is here for backward compatibility reasons after 2695 # this method was promoted to be part of the standard compute API in 2696 # Libcloud v2.7.0 2697 return self.stop_node(node=node) 2698 2699 def ex_suspend_node(self, node): 2700 return self._post_simple_node_action(node, 'suspend') 2701 2702 def ex_resume_node(self, node): 2703 return self._post_simple_node_action(node, 'resume') 2704 2705 def _post_simple_node_action(self, node, action): 2706 """ Post a simple, data-less action to the OS node action endpoint 2707 :param `Node` node: 2708 :param str action: the action to call 2709 :return `bool`: a boolean that indicates success 2710 """ 2711 uri = '/servers/{node_id}/action'.format(node_id=node.id) 2712 resp = self.connection.request(uri, method='POST', data={action: None}) 2713 return resp.status == httplib.ACCEPTED 2714 2715 2716class OpenStack_2_Connection(OpenStackComputeConnection): 2717 responseCls = OpenStack_1_1_Response 2718 accept_format = 'application/json' 2719 default_content_type = 'application/json; charset=UTF-8' 2720 2721 def encode_data(self, data): 2722 return json.dumps(data) 2723 2724 2725class OpenStack_2_ImageConnection(OpenStackImageConnection): 2726 responseCls = OpenStack_1_1_Response 2727 accept_format = 'application/json' 2728 default_content_type = 'application/json; charset=UTF-8' 2729 2730 def encode_data(self, data): 2731 return json.dumps(data) 2732 2733 2734class OpenStack_2_NetworkConnection(OpenStackNetworkConnection): 2735 responseCls = OpenStack_1_1_Response 2736 accept_format = 'application/json' 2737 default_content_type = 'application/json; charset=UTF-8' 2738 2739 def encode_data(self, data): 2740 return json.dumps(data) 2741 2742 2743class OpenStack_2_VolumeV2Connection(OpenStackVolumeV2Connection): 2744 responseCls = OpenStack_1_1_Response 2745 accept_format = 'application/json' 2746 default_content_type = 'application/json; charset=UTF-8' 2747 2748 def encode_data(self, data): 2749 return json.dumps(data) 2750 2751 2752class OpenStack_2_VolumeV3Connection(OpenStackVolumeV3Connection): 2753 responseCls = OpenStack_1_1_Response 2754 accept_format = 'application/json' 2755 default_content_type = 'application/json; charset=UTF-8' 2756 2757 def encode_data(self, data): 2758 return json.dumps(data) 2759 2760 2761class OpenStack_2_PortInterfaceState(Type): 2762 """ 2763 Standard states of OpenStack_2_PortInterfaceState 2764 """ 2765 BUILD = 'build' 2766 ACTIVE = 'active' 2767 DOWN = 'down' 2768 UNKNOWN = 'unknown' 2769 2770 2771class OpenStack_2_NodeDriver(OpenStack_1_1_NodeDriver): 2772 """ 2773 OpenStack node driver. 2774 """ 2775 connectionCls = OpenStack_2_Connection 2776 2777 # Previously all image functionality was available through the 2778 # compute API. This deprecated proxied API does not offer all 2779 # functionality that the Glance Image service API offers. 2780 # See https://developer.openstack.org/api-ref/compute/ 2781 # 2782 # > These APIs are proxy calls to the Image service. Nova has deprecated 2783 # > all the proxy APIs and users should use the native APIs instead. These 2784 # > will fail with a 404 starting from microversion 2.36. See: Relevant 2785 # > Image APIs. 2786 # 2787 # For example, managing image visibility and sharing machine 2788 # images across tenants can not be done using the proxied image API in the 2789 # compute endpoint, but it can be done with the Glance Image API. 2790 # See https://developer.openstack.org/api-ref/ 2791 # image/v2/index.html#list-image-members 2792 image_connectionCls = OpenStack_2_ImageConnection 2793 image_connection = None 2794 2795 # Similarly not all node-related operations are exposed through the 2796 # compute API 2797 # See https://developer.openstack.org/api-ref/compute/ 2798 # For example, creating a new node in an OpenStack that is configured to 2799 # create a new port for every new instance will make it so that if that 2800 # port is detached it disappears. But if the port is manually created 2801 # beforehand using the neutron network API and node is booted with that 2802 # port pre-specified, then detaching that port later will result in that 2803 # becoming a re-attachable resource much like a floating ip. So because 2804 # even though this is the compute driver, we do connect to the networking 2805 # API here because some operations relevant for compute can only be 2806 # accessed from there. 2807 network_connectionCls = OpenStack_2_NetworkConnection 2808 network_connection = None 2809 2810 # Similarly all image operations are noe exposed through the block-storage 2811 # API of the cinder service: 2812 # https://developer.openstack.org/api-ref/block-storage/ 2813 volumev2_connectionCls = OpenStack_2_VolumeV2Connection 2814 volumev3_connectionCls = OpenStack_2_VolumeV3Connection 2815 volumev2_connection = None 2816 volumev3_connection = None 2817 volume_connection = None 2818 2819 type = Provider.OPENSTACK 2820 2821 features = {"create_node": ["generates_password"]} 2822 _networks_url_prefix = '/v2.0/networks' 2823 _subnets_url_prefix = '/v2.0/subnets' 2824 2825 PORT_INTERFACE_MAP = { 2826 'BUILD': OpenStack_2_PortInterfaceState.BUILD, 2827 'ACTIVE': OpenStack_2_PortInterfaceState.ACTIVE, 2828 'DOWN': OpenStack_2_PortInterfaceState.DOWN, 2829 'UNKNOWN': OpenStack_2_PortInterfaceState.UNKNOWN 2830 } 2831 2832 def __init__(self, *args, **kwargs): 2833 original_connectionCls = self.connectionCls 2834 self._ex_force_api_version = str(kwargs.pop('ex_force_api_version', 2835 None)) 2836 if 'ex_force_auth_version' not in kwargs: 2837 kwargs['ex_force_auth_version'] = '3.x_password' 2838 2839 original_ex_force_base_url = kwargs.get('ex_force_base_url') 2840 2841 # We run the init once to get the Glance V2 API connection 2842 # and put that on the object under self.image_connection. 2843 if original_ex_force_base_url or kwargs.get('ex_force_image_url'): 2844 kwargs['ex_force_base_url'] = \ 2845 str(kwargs.pop('ex_force_image_url', 2846 original_ex_force_base_url)) 2847 self.connectionCls = self.image_connectionCls 2848 super(OpenStack_2_NodeDriver, self).__init__(*args, **kwargs) 2849 self.image_connection = self.connection 2850 2851 # We run the init once to get the Cinder V2 API connection 2852 # and put that on the object under self.volumev2_connection. 2853 if original_ex_force_base_url or kwargs.get('ex_force_volume_url'): 2854 kwargs['ex_force_base_url'] = \ 2855 str(kwargs.pop('ex_force_volume_url', 2856 original_ex_force_base_url)) 2857 # the V3 API 2858 self.connectionCls = self.volumev3_connectionCls 2859 super(OpenStack_2_NodeDriver, self).__init__(*args, **kwargs) 2860 self.volumev3_connection = self.connection 2861 # the V2 API 2862 self.connectionCls = self.volumev2_connectionCls 2863 super(OpenStack_2_NodeDriver, self).__init__(*args, **kwargs) 2864 self.volumev2_connection = self.connection 2865 2866 # We run the init once to get the Neutron V2 API connection 2867 # and put that on the object under self.network_connection. 2868 if original_ex_force_base_url or kwargs.get('ex_force_network_url'): 2869 kwargs['ex_force_base_url'] = \ 2870 str(kwargs.pop('ex_force_network_url', 2871 original_ex_force_base_url)) 2872 self.connectionCls = self.network_connectionCls 2873 super(OpenStack_2_NodeDriver, self).__init__(*args, **kwargs) 2874 self.network_connection = self.connection 2875 2876 # We run the init once again to get the compute API connection 2877 # and that's put under self.connection as normal. 2878 self._ex_force_base_url = original_ex_force_base_url 2879 if original_ex_force_base_url: 2880 kwargs['ex_force_base_url'] = self._ex_force_base_url 2881 # if ex_force_base_url is not set in original params delete it 2882 elif 'ex_force_base_url' in kwargs: 2883 del kwargs['ex_force_base_url'] 2884 self.connectionCls = original_connectionCls 2885 super(OpenStack_2_NodeDriver, self).__init__(*args, **kwargs) 2886 2887 def _to_port(self, element): 2888 created = element.get('created_at') 2889 updated = element.get('updated_at') 2890 return OpenStack_2_PortInterface( 2891 id=element['id'], 2892 state=self.PORT_INTERFACE_MAP.get( 2893 element.get('status'), OpenStack_2_PortInterfaceState.UNKNOWN 2894 ), 2895 created=created, 2896 driver=self, 2897 extra=dict( 2898 admin_state_up=element['admin_state_up'], 2899 allowed_address_pairs=element['allowed_address_pairs'], 2900 binding_vnic_type=element['binding:vnic_type'], 2901 binding_host_id=element.get('binding:host_id', None), 2902 device_id=element['device_id'], 2903 description=element.get('description', None), 2904 device_owner=element['device_owner'], 2905 fixed_ips=element['fixed_ips'], 2906 mac_address=element['mac_address'], 2907 name=element['name'], 2908 network_id=element['network_id'], 2909 project_id=element.get('project_id', None), 2910 port_security_enabled=element.get('port_security_enabled', 2911 None), 2912 revision_number=element.get('revision_number', None), 2913 security_groups=element['security_groups'], 2914 tags=element.get('tags', None), 2915 tenant_id=element['tenant_id'], 2916 updated=updated, 2917 ) 2918 ) 2919 2920 def list_nodes(self, ex_all_tenants=False): 2921 """ 2922 List the nodes in a tenant 2923 2924 :param ex_all_tenants: List nodes for all the tenants. Note: Your user 2925 must have admin privileges for this 2926 functionality to work. 2927 :type ex_all_tenants: ``bool`` 2928 """ 2929 params = {} 2930 if ex_all_tenants: 2931 params = {'all_tenants': 1} 2932 return self._to_nodes(self._paginated_request( 2933 '/servers/detail', 'servers', self.connection, params=params)) 2934 2935 def get_image(self, image_id): 2936 """ 2937 Get a NodeImage using the V2 Glance API 2938 2939 @inherits: :class:`OpenStack_1_1_NodeDriver.get_image` 2940 2941 :param image_id: ID of the image which should be used 2942 :type image_id: ``str`` 2943 2944 :rtype: :class:`NodeImage` 2945 """ 2946 return self._to_image(self.image_connection.request( 2947 '/v2/images/%s' % (image_id,)).object) 2948 2949 def list_images(self, location=None, ex_only_active=True): 2950 """ 2951 Lists all active images using the V2 Glance API 2952 2953 @inherits: :class:`NodeDriver.list_images` 2954 2955 :param location: Which data center to list the images in. If 2956 empty, undefined behavior will be selected. 2957 (optional) 2958 :type location: :class:`.NodeLocation` 2959 2960 :param ex_only_active: True if list only active (optional) 2961 :type ex_only_active: ``bool`` 2962 """ 2963 if location is not None: 2964 raise NotImplementedError( 2965 "location in list_images is not implemented " 2966 "in the OpenStack_2_NodeDriver") 2967 if not ex_only_active: 2968 raise NotImplementedError( 2969 "ex_only_active in list_images is not implemented " 2970 "in the OpenStack_2_NodeDriver") 2971 2972 result = self._paginated_request_next( 2973 path='/v2/images', 2974 request_method=self.image_connection.request, 2975 response_key='images') 2976 2977 images = [] 2978 for item in result: 2979 images.append(self._to_image(item)) 2980 2981 return images 2982 2983 def ex_update_image(self, image_id, data): 2984 """ 2985 Patch a NodeImage. Can be used to set visibility 2986 2987 :param image_id: ID of the image which should be used 2988 :type image_id: ``str`` 2989 2990 :param data: The data to PATCH, either a dict or a list 2991 for example: [ 2992 {'op': 'replace', 'path': '/visibility', 'value': 'shared'} 2993 ] 2994 :type data: ``dict|list`` 2995 2996 :rtype: :class:`NodeImage` 2997 """ 2998 response = self.image_connection.request( 2999 '/v2/images/%s' % (image_id,), 3000 headers={'Content-type': 'application/' 3001 'openstack-images-' 3002 'v2.1-json-patch'}, 3003 method='PATCH', data=data 3004 ) 3005 return self._to_image(response.object) 3006 3007 def ex_list_image_members(self, image_id): 3008 """ 3009 List all members of an image. See 3010 https://developer.openstack.org/api-ref/image/v2/index.html#sharing 3011 3012 :param image_id: ID of the image of which the members should 3013 be listed 3014 :type image_id: ``str`` 3015 3016 :rtype: ``list`` of :class:`NodeImageMember` 3017 """ 3018 response = self.image_connection.request( 3019 '/v2/images/%s/members' % (image_id,) 3020 ) 3021 image_members = [] 3022 for image_member in response.object['members']: 3023 image_members.append(self._to_image_member(image_member)) 3024 return image_members 3025 3026 def ex_create_image_member(self, image_id, member_id): 3027 """ 3028 Give a project access to an image. 3029 3030 The image should have visibility status 'shared'. 3031 3032 Note that this is not an idempotent operation. If this action is 3033 attempted using a tenant that is already in the image members 3034 group the API will throw a Conflict (409). 3035 See the 'create-image-member' section on 3036 https://developer.openstack.org/api-ref/image/v2/index.html 3037 3038 :param str image_id: The ID of the image to share with the specified 3039 tenant 3040 :param str member_id: The ID of the project / tenant (the image member) 3041 Note that this is the Keystone project ID and not the project name, 3042 so something like e2151b1fe02d4a8a2d1f5fc331522c0a 3043 :return None: 3044 3045 :param image_id: ID of the image to share 3046 :type image_id: ``str`` 3047 3048 :param project: ID of the project to give access to the image 3049 :type image_id: ``str`` 3050 3051 :rtype: ``list`` of :class:`NodeImageMember` 3052 """ 3053 data = {'member': member_id} 3054 response = self.image_connection.request( 3055 '/v2/images/%s/members' % image_id, 3056 method='POST', data=data 3057 ) 3058 return self._to_image_member(response.object) 3059 3060 def ex_get_image_member(self, image_id, member_id): 3061 """ 3062 Get a member of an image by id 3063 3064 :param image_id: ID of the image of which the member should 3065 be listed 3066 :type image_id: ``str`` 3067 3068 :param member_id: ID of the member to list 3069 :type image_id: ``str`` 3070 3071 :rtype: ``list`` of :class:`NodeImageMember` 3072 """ 3073 response = self.image_connection.request( 3074 '/v2/images/%s/members/%s' % (image_id, member_id) 3075 ) 3076 return self._to_image_member(response.object) 3077 3078 def ex_accept_image_member(self, image_id, member_id): 3079 """ 3080 Accept a pending image as a member. 3081 3082 This call is idempotent unlike ex_create_image_member, 3083 you can accept the same image many times. 3084 3085 :param image_id: ID of the image to accept 3086 :type image_id: ``str`` 3087 3088 :param project: ID of the project to accept the image as 3089 :type image_id: ``str`` 3090 3091 :rtype: ``bool`` 3092 """ 3093 data = {'status': 'accepted'} 3094 response = self.image_connection.request( 3095 '/v2/images/%s/members/%s' % (image_id, member_id), 3096 method='PUT', data=data 3097 ) 3098 return self._to_image_member(response.object) 3099 3100 def _to_networks(self, obj): 3101 networks = obj['networks'] 3102 return [self._to_network(network) for network in networks] 3103 3104 def _to_network(self, obj): 3105 extra = {} 3106 if obj.get('router:external', None): 3107 extra['router:external'] = obj.get('router:external') 3108 if obj.get('subnets', None): 3109 extra['subnets'] = obj.get('subnets') 3110 return OpenStackNetwork(id=obj['id'], 3111 name=obj['name'], 3112 cidr=None, 3113 driver=self, 3114 extra=extra) 3115 3116 def ex_list_networks(self): 3117 """ 3118 Get a list of Networks that are available. 3119 3120 :rtype: ``list`` of :class:`OpenStackNetwork` 3121 """ 3122 response = self.network_connection.request( 3123 self._networks_url_prefix).object 3124 return self._to_networks(response) 3125 3126 def ex_get_network(self, network_id): 3127 """ 3128 Retrieve the Network with the given ID 3129 3130 :param networkId: ID of the network 3131 :type networkId: ``str`` 3132 3133 :rtype :class:`OpenStackNetwork` 3134 """ 3135 request_url = "{networks_url_prefix}/{network_id}".format( 3136 networks_url_prefix=self._networks_url_prefix, 3137 network_id=network_id 3138 ) 3139 response = self.network_connection.request(request_url).object 3140 return self._to_network(response['network']) 3141 3142 def ex_create_network(self, name, **kwargs): 3143 """ 3144 Create a new Network 3145 3146 :param name: Name of network which should be used 3147 :type name: ``str`` 3148 3149 :rtype: :class:`OpenStackNetwork` 3150 """ 3151 data = {'network': {'name': name}} 3152 # Add optional values 3153 for key, value in kwargs.items(): 3154 data['network'][key] = value 3155 response = self.network_connection.request(self._networks_url_prefix, 3156 method='POST', 3157 data=data).object 3158 return self._to_network(response['network']) 3159 3160 def ex_delete_network(self, network): 3161 """ 3162 Delete a Network 3163 3164 :param network: Network which should be used 3165 :type network: :class:`OpenStackNetwork` 3166 3167 :rtype: ``bool`` 3168 """ 3169 resp = self.network_connection.request( 3170 '%s/%s' % (self._networks_url_prefix, 3171 network.id), method='DELETE') 3172 return resp.status in (httplib.NO_CONTENT, httplib.ACCEPTED) 3173 3174 def _to_subnets(self, obj): 3175 subnets = obj['subnets'] 3176 return [self._to_subnet(subnet) for subnet in subnets] 3177 3178 def _to_subnet(self, obj): 3179 extra = {} 3180 if obj.get('router:external', None): 3181 extra['router:external'] = obj.get('router:external') 3182 if obj.get('subnets', None): 3183 extra['subnets'] = obj.get('subnets') 3184 return OpenStack_2_SubNet(id=obj['id'], 3185 name=obj['name'], 3186 cidr=obj['cidr'], 3187 network_id=obj['network_id'], 3188 driver=self, 3189 extra=extra) 3190 3191 def ex_list_subnets(self): 3192 """ 3193 Get a list of Subnet that are available. 3194 3195 :rtype: ``list`` of :class:`OpenStack_2_SubNet` 3196 """ 3197 response = self.network_connection.request( 3198 self._subnets_url_prefix).object 3199 return self._to_subnets(response) 3200 3201 def ex_create_subnet(self, name, network, cidr, ip_version=4, 3202 description='', dns_nameservers=None, 3203 host_routes=None): 3204 """ 3205 Create a new Subnet 3206 3207 :param name: Name of subnet which should be used 3208 :type name: ``str`` 3209 3210 :param network: Parent network of the subnet 3211 :type network: ``OpenStackNetwork`` 3212 3213 :param cidr: cidr of network which should be used 3214 :type cidr: ``str`` 3215 3216 :param ip_version: ip_version of subnet which should be used 3217 :type ip_version: ``int`` 3218 3219 :param description: Description for the resource. 3220 :type description: ``str`` 3221 3222 :param dns_nameservers: List of dns name servers. 3223 :type dns_nameservers: ``list`` of ``str`` 3224 3225 :param host_routes: Additional routes for the subnet. 3226 :type host_routes: ``list`` of ``str`` 3227 3228 :rtype: :class:`OpenStack_2_SubNet` 3229 """ 3230 data = { 3231 'subnet': 3232 { 3233 'cidr': cidr, 3234 'network_id': network.id, 3235 'ip_version': ip_version, 3236 'name': name or '', 3237 'description': description or '', 3238 'dns_nameservers': dns_nameservers or [], 3239 'host_routes': host_routes or [] 3240 } 3241 } 3242 response = self.network_connection.request( 3243 self._subnets_url_prefix, method='POST', data=data).object 3244 return self._to_subnet(response['subnet']) 3245 3246 def ex_delete_subnet(self, subnet): 3247 """ 3248 Delete a Subnet 3249 3250 :param subnet: Subnet which should be deleted 3251 :type subnet: :class:`OpenStack_2_SubNet` 3252 3253 :rtype: ``bool`` 3254 """ 3255 resp = self.network_connection.request('%s/%s' % ( 3256 self._subnets_url_prefix, subnet.id), method='DELETE') 3257 return resp.status in (httplib.NO_CONTENT, httplib.ACCEPTED) 3258 3259 def ex_update_subnet(self, subnet, name=None, description=None, 3260 dns_nameservers=None, host_routes=None): 3261 """ 3262 Update data of an existing SubNet 3263 3264 :param subnet: Subnet which should be updated 3265 :type subnet: :class:`OpenStack_2_SubNet` 3266 3267 :param name: Name of subnet which should be used 3268 :type name: ``str`` 3269 3270 :param description: Description for the resource. 3271 :type description: ``str`` 3272 3273 :param dns_nameservers: List of dns name servers. 3274 :type dns_nameservers: ``list`` of ``str`` 3275 3276 :param host_routes: Additional routes for the subnet. 3277 :type host_routes: ``list`` of ``str`` 3278 3279 :rtype: :class:`OpenStack_2_SubNet` 3280 """ 3281 data = {'subnet': {}} 3282 if name is not None: 3283 data['subnet']['name'] = name 3284 if description is not None: 3285 data['subnet']['description'] = description 3286 if dns_nameservers is not None: 3287 data['subnet']['dns_nameservers'] = dns_nameservers 3288 if host_routes is not None: 3289 data['subnet']['host_routes'] = host_routes 3290 response = self.network_connection.request( 3291 "%s/%s" % (self._subnets_url_prefix, subnet.id), 3292 method='PUT', data=data).object 3293 return self._to_subnet(response['subnet']) 3294 3295 def ex_list_ports(self): 3296 """ 3297 List all OpenStack_2_PortInterfaces 3298 3299 https://developer.openstack.org/api-ref/network/v2/#list-ports 3300 3301 :rtype: ``list`` of :class:`OpenStack_2_PortInterface` 3302 """ 3303 response = self._paginated_request( 3304 '/v2.0/ports', 'ports', self.network_connection) 3305 return [self._to_port(port) for port in response['ports']] 3306 3307 def ex_delete_port(self, port): 3308 """ 3309 Delete an OpenStack_2_PortInterface 3310 3311 https://developer.openstack.org/api-ref/network/v2/#delete-port 3312 3313 :param port: port interface to remove 3314 :type port: :class:`OpenStack_2_PortInterface` 3315 3316 :rtype: ``bool`` 3317 """ 3318 response = self.network_connection.request( 3319 '/v2.0/ports/%s' % port.id, method='DELETE' 3320 ) 3321 return response.success() 3322 3323 def ex_detach_port_interface(self, node, port): 3324 """ 3325 Detaches an OpenStack_2_PortInterface interface from a Node. 3326 :param node: node 3327 :type node: :class:`Node` 3328 3329 :param port: port interface to detach 3330 :type port: :class:`OpenStack_2_PortInterface` 3331 3332 :rtype: ``bool`` 3333 """ 3334 return self.connection.request( 3335 '/servers/%s/os-interface/%s' % (node.id, port.id), 3336 method='DELETE' 3337 ).success() 3338 3339 def ex_attach_port_interface(self, node, port): 3340 """ 3341 Attaches an OpenStack_2_PortInterface to a Node. 3342 3343 :param node: node 3344 :type node: :class:`Node` 3345 3346 :param port: port interface to attach 3347 :type port: :class:`OpenStack_2_PortInterface` 3348 3349 :rtype: ``bool`` 3350 """ 3351 data = { 3352 'interfaceAttachment': { 3353 'port_id': port.id 3354 } 3355 } 3356 return self.connection.request( 3357 '/servers/{}/os-interface'.format(node.id), 3358 method='POST', data=data 3359 ).success() 3360 3361 def ex_create_port(self, network, description=None, 3362 admin_state_up=True, name=None): 3363 """ 3364 Creates a new OpenStack_2_PortInterface 3365 3366 :param network: ID of the network where the newly created 3367 port should be attached to 3368 :type network: :class:`OpenStackNetwork` 3369 3370 :param description: Description of the port 3371 :type description: str 3372 3373 :param admin_state_up: The administrative state of the 3374 resource, which is up or down 3375 :type admin_state_up: bool 3376 3377 :param name: Human-readable name of the resource 3378 :type name: str 3379 3380 :rtype: :class:`OpenStack_2_PortInterface` 3381 """ 3382 data = { 3383 'port': 3384 { 3385 'description': description or '', 3386 'admin_state_up': admin_state_up, 3387 'name': name or '', 3388 'network_id': network.id, 3389 } 3390 } 3391 response = self.network_connection.request( 3392 '/v2.0/ports', method='POST', data=data 3393 ) 3394 return self._to_port(response.object['port']) 3395 3396 def ex_get_port(self, port_interface_id): 3397 """ 3398 Retrieve the OpenStack_2_PortInterface with the given ID 3399 3400 :param port_interface_id: ID of the requested port 3401 :type port_interface_id: str 3402 3403 :return: :class:`OpenStack_2_PortInterface` 3404 """ 3405 response = self.network_connection.request( 3406 '/v2.0/ports/{}'.format(port_interface_id), method='GET' 3407 ) 3408 return self._to_port(response.object['port']) 3409 3410 def ex_update_port(self, port, description=None, 3411 admin_state_up=None, name=None, 3412 port_security_enabled=None, 3413 qos_policy_id=None, security_groups=None, 3414 allowed_address_pairs=None): 3415 """ 3416 Update a OpenStack_2_PortInterface 3417 3418 :param port: port interface to update 3419 :type port: :class:`OpenStack_2_PortInterface` 3420 3421 :param description: Description of the port 3422 :type description: ``str`` 3423 3424 :param admin_state_up: The administrative state of the 3425 resource, which is up or down 3426 :type admin_state_up: ``bool`` 3427 3428 :param name: Human-readable name of the resource 3429 :type name: ``str`` 3430 3431 :param port_security_enabled: The port security status 3432 :type port_security_enabled: ``bool`` 3433 3434 :param qos_policy_id: QoS policy associated with the port 3435 :type qos_policy_id: ``str`` 3436 3437 :param security_groups: The IDs of security groups applied 3438 :type security_groups: ``list`` of ``str`` 3439 3440 :param allowed_address_pairs: IP and MAC address that the port 3441 can use when sending packets if port_security_enabled is 3442 true 3443 :type allowed_address_pairs: ``list`` of ``dict`` containing 3444 ip_address and mac_address; mac_address is optional, taken 3445 from the port if not specified 3446 3447 :rtype: :class:`OpenStack_2_PortInterface` 3448 """ 3449 data = {'port': {}} 3450 if description is not None: 3451 data['port']['description'] = description 3452 if admin_state_up is not None: 3453 data['port']['admin_state_up'] = admin_state_up 3454 if name is not None: 3455 data['port']['name'] = name 3456 if port_security_enabled is not None: 3457 data['port']['port_security_enabled'] = port_security_enabled 3458 if qos_policy_id is not None: 3459 data['port']['qos_policy_id'] = qos_policy_id 3460 if security_groups is not None: 3461 data['port']['security_groups'] = security_groups 3462 if allowed_address_pairs is not None: 3463 data['port']['allowed_address_pairs'] = allowed_address_pairs 3464 response = self.network_connection.request( 3465 '/v2.0/ports/{}'.format(port.id), method='PUT', data=data 3466 ) 3467 return self._to_port(response.object['port']) 3468 3469 def _get_volume_connection(self): 3470 """ 3471 Get the correct Volume connection (v3 or v2) 3472 """ 3473 if not self.volume_connection: 3474 try: 3475 # Try to use v3 API first 3476 # if the endpoint is not found 3477 self.volumev3_connection.get_service_catalog() 3478 self.volume_connection = self.volumev3_connection 3479 except LibcloudError: 3480 # then return the v2 conn 3481 self.volume_connection = self.volumev2_connection 3482 return self.volume_connection 3483 3484 def list_volumes(self): 3485 """ 3486 Get a list of Volumes that are available. 3487 3488 :rtype: ``list`` of :class:`StorageVolume` 3489 """ 3490 return self._to_volumes(self._paginated_request( 3491 '/volumes/detail', 'volumes', self._get_volume_connection())) 3492 3493 def ex_get_volume(self, volumeId): 3494 """ 3495 Retrieve the StorageVolume with the given ID 3496 3497 :param volumeId: ID of the volume 3498 :type volumeId: ``string`` 3499 3500 :return: :class:`StorageVolume` 3501 """ 3502 return self._to_volume( 3503 self._get_volume_connection().request('/volumes/%s' % volumeId) 3504 .object) 3505 3506 def create_volume(self, size, name, location=None, snapshot=None, 3507 ex_volume_type=None, ex_image_ref=None): 3508 """ 3509 Create a new volume. 3510 3511 :param size: Size of volume in gigabytes (required) 3512 :type size: ``int`` 3513 3514 :param name: Name of the volume to be created 3515 :type name: ``str`` 3516 3517 :param location: Which data center to create a volume in. If 3518 empty, undefined behavior will be selected. 3519 (optional) 3520 :type location: :class:`.NodeLocation` 3521 3522 :param snapshot: Snapshot from which to create the new 3523 volume. (optional) 3524 :type snapshot: :class:`.VolumeSnapshot` 3525 3526 :param ex_volume_type: What kind of volume to create. 3527 (optional) 3528 :type ex_volume_type: ``str`` 3529 3530 :param ex_image_ref: The image to create the volume from 3531 when creating a bootable volume (optional) 3532 :type ex_image_ref: ``str`` 3533 3534 :return: The newly created volume. 3535 :rtype: :class:`StorageVolume` 3536 """ 3537 volume = { 3538 'name': name, 3539 'description': name, 3540 'size': size, 3541 'metadata': { 3542 'contents': name, 3543 }, 3544 } 3545 3546 if ex_volume_type: 3547 volume['volume_type'] = ex_volume_type 3548 3549 if ex_image_ref: 3550 volume['imageRef'] = ex_image_ref 3551 3552 if location: 3553 volume['availability_zone'] = location 3554 3555 if snapshot: 3556 volume['snapshot_id'] = snapshot.id 3557 3558 resp = self._get_volume_connection().request('/volumes', 3559 method='POST', 3560 data={'volume': volume}) 3561 return self._to_volume(resp.object) 3562 3563 def destroy_volume(self, volume): 3564 """ 3565 Delete a Volume. 3566 3567 :param volume: Volume to be deleted 3568 :type volume: :class:`StorageVolume` 3569 3570 :rtype: ``bool`` 3571 """ 3572 return self._get_volume_connection().request('/volumes/%s' % volume.id, 3573 method='DELETE').success() 3574 3575 def ex_list_snapshots(self): 3576 """ 3577 Get a list of Snapshot that are available. 3578 3579 :rtype: ``list`` of :class:`VolumeSnapshot` 3580 """ 3581 return self._to_snapshots(self._paginated_request( 3582 '/snapshots/detail', 'snapshots', self._get_volume_connection())) 3583 3584 def create_volume_snapshot(self, volume, name=None, ex_description=None, 3585 ex_force=True): 3586 """ 3587 Create snapshot from volume 3588 3589 :param volume: Instance of `StorageVolume` 3590 :type volume: `StorageVolume` 3591 3592 :param name: Name of snapshot (optional) 3593 :type name: `str` | `NoneType` 3594 3595 :param ex_description: Description of the snapshot (optional) 3596 :type ex_description: `str` | `NoneType` 3597 3598 :param ex_force: Specifies if we create a snapshot that is not in 3599 state `available`. For example `in-use`. Defaults 3600 to True. (optional) 3601 :type ex_force: `bool` 3602 3603 :rtype: :class:`VolumeSnapshot` 3604 """ 3605 data = {'snapshot': {'volume_id': volume.id, 'force': ex_force}} 3606 3607 if name is not None: 3608 data['snapshot']['name'] = name 3609 3610 if ex_description is not None: 3611 data['snapshot']['description'] = ex_description 3612 3613 return self._to_snapshot( 3614 self._get_volume_connection().request('/snapshots', method='POST', 3615 data=data).object) 3616 3617 def destroy_volume_snapshot(self, snapshot): 3618 """ 3619 Delete a Volume Snapshot. 3620 3621 :param snapshot: Snapshot to be deleted 3622 :type snapshot: :class:`VolumeSnapshot` 3623 3624 :rtype: ``bool`` 3625 """ 3626 resp = self._get_volume_connection().request( 3627 '/snapshots/%s' % snapshot.id, method='DELETE') 3628 return resp.status in (httplib.NO_CONTENT, httplib.ACCEPTED) 3629 3630 def ex_list_security_groups(self): 3631 """ 3632 Get a list of Security Groups that are available. 3633 3634 :rtype: ``list`` of :class:`OpenStackSecurityGroup` 3635 """ 3636 return self._to_security_groups( 3637 self.network_connection.request('/v2.0/security-groups').object) 3638 3639 def ex_create_security_group(self, name, description): 3640 """ 3641 Create a new Security Group 3642 3643 :param name: Name of the new Security Group 3644 :type name: ``str`` 3645 3646 :param description: Description of the new Security Group 3647 :type description: ``str`` 3648 3649 :rtype: :class:`OpenStackSecurityGroup` 3650 """ 3651 return self._to_security_group(self.network_connection .request( 3652 '/v2.0/security-groups', method='POST', 3653 data={'security_group': {'name': name, 'description': description}} 3654 ).object['security_group']) 3655 3656 def ex_delete_security_group(self, security_group): 3657 """ 3658 Delete a Security Group. 3659 3660 :param security_group: Security Group should be deleted 3661 :type security_group: :class:`OpenStackSecurityGroup` 3662 3663 :rtype: ``bool`` 3664 """ 3665 resp = self.network_connection.request('/v2.0/security-groups/%s' % 3666 (security_group.id), 3667 method='DELETE') 3668 return resp.status == httplib.NO_CONTENT 3669 3670 def _to_security_group_rule(self, obj): 3671 ip_range = group = tenant_id = parent_id = None 3672 protocol = from_port = to_port = direction = None 3673 3674 if 'parent_group_id' in obj: 3675 if obj['group'] == {}: 3676 ip_range = obj['ip_range'].get('cidr', None) 3677 else: 3678 group = obj['group'].get('name', None) 3679 tenant_id = obj['group'].get('tenant_id', None) 3680 3681 parent_id = obj['parent_group_id'] 3682 from_port = obj['from_port'] 3683 to_port = obj['to_port'] 3684 protocol = obj['ip_protocol'] 3685 else: 3686 ip_range = obj.get('remote_ip_prefix', None) 3687 group = obj.get('remote_group_id', None) 3688 tenant_id = obj.get('tenant_id', None) 3689 3690 parent_id = obj['security_group_id'] 3691 from_port = obj['port_range_min'] 3692 to_port = obj['port_range_max'] 3693 protocol = obj['protocol'] 3694 3695 return OpenStackSecurityGroupRule( 3696 id=obj['id'], parent_group_id=parent_id, 3697 ip_protocol=protocol, from_port=from_port, 3698 to_port=to_port, driver=self, ip_range=ip_range, 3699 group=group, tenant_id=tenant_id, direction=direction) 3700 3701 def ex_create_security_group_rule(self, security_group, ip_protocol, 3702 from_port, to_port, cidr=None, 3703 source_security_group=None): 3704 """ 3705 Create a new Rule in a Security Group 3706 3707 :param security_group: Security Group in which to add the rule 3708 :type security_group: :class:`OpenStackSecurityGroup` 3709 3710 :param ip_protocol: Protocol to which this rule applies 3711 Examples: tcp, udp, ... 3712 :type ip_protocol: ``str`` 3713 3714 :param from_port: First port of the port range 3715 :type from_port: ``int`` 3716 3717 :param to_port: Last port of the port range 3718 :type to_port: ``int`` 3719 3720 :param cidr: CIDR notation of the source IP range for this rule 3721 :type cidr: ``str`` 3722 3723 :param source_security_group: Existing Security Group to use as the 3724 source (instead of CIDR) 3725 :type source_security_group: L{OpenStackSecurityGroup 3726 3727 :rtype: :class:`OpenStackSecurityGroupRule` 3728 """ 3729 source_security_group_id = None 3730 if type(source_security_group) == OpenStackSecurityGroup: 3731 source_security_group_id = source_security_group.id 3732 3733 return self._to_security_group_rule(self.network_connection.request( 3734 '/v2.0/security-group-rules', method='POST', 3735 data={'security_group_rule': { 3736 'direction': 'ingress', 3737 'protocol': ip_protocol, 3738 'port_range_min': from_port, 3739 'port_range_max': to_port, 3740 'remote_ip_prefix': cidr, 3741 'remote_group_id': source_security_group_id, 3742 'security_group_id': security_group.id}} 3743 ).object['security_group_rule']) 3744 3745 def ex_delete_security_group_rule(self, rule): 3746 """ 3747 Delete a Rule from a Security Group. 3748 3749 :param rule: Rule should be deleted 3750 :type rule: :class:`OpenStackSecurityGroupRule` 3751 3752 :rtype: ``bool`` 3753 """ 3754 resp = self.network_connection.request( 3755 '/v2.0/security-group-rules/%s' % (rule.id), method='DELETE') 3756 return resp.status == httplib.NO_CONTENT 3757 3758 def ex_remove_security_group_from_node(self, security_group, node): 3759 """ 3760 Remove a Security Group from a node. 3761 3762 :param security_group: Security Group to remove from node. 3763 :type security_group: :class:`OpenStackSecurityGroup` 3764 3765 :param node: Node to remove the Security Group. 3766 :type node: :class:`Node` 3767 3768 :rtype: ``bool`` 3769 """ 3770 server_params = {'name': security_group.name} 3771 resp = self._node_action(node, 'removeSecurityGroup', **server_params) 3772 return resp.status == httplib.ACCEPTED 3773 3774 def _to_floating_ip_pool(self, obj): 3775 return OpenStack_2_FloatingIpPool(obj['id'], obj['name'], 3776 self.network_connection) 3777 3778 def _to_floating_ip_pools(self, obj): 3779 pool_elements = obj['networks'] 3780 return [self._to_floating_ip_pool(pool) for pool in pool_elements] 3781 3782 def ex_list_floating_ip_pools(self): 3783 """ 3784 List available floating IP pools 3785 3786 :rtype: ``list`` of :class:`OpenStack_2_FloatingIpPool` 3787 """ 3788 return self._to_floating_ip_pools( 3789 self.network_connection.request('/v2.0/networks?router:external' 3790 '=True&fields=id&fields=' 3791 'name').object) 3792 3793 def _to_routers(self, obj): 3794 routers = obj['routers'] 3795 return [self._to_router(router) for router in routers] 3796 3797 def _to_router(self, obj): 3798 extra = {} 3799 extra['external_gateway_info'] = obj['external_gateway_info'] 3800 extra['routes'] = obj['routes'] 3801 return OpenStack_2_Router(id=obj['id'], 3802 name=obj['name'], 3803 status=obj['status'], 3804 driver=self, 3805 extra=extra) 3806 3807 def ex_list_routers(self): 3808 """ 3809 Get a list of Routers that are available. 3810 3811 :rtype: ``list`` of :class:`OpenStack_2_Router` 3812 """ 3813 response = self.network_connection.request( 3814 '/v2.0/routers').object 3815 return self._to_routers(response) 3816 3817 def ex_create_router(self, name, description='', admin_state_up=True, 3818 external_gateway_info=None): 3819 """ 3820 Create a new Router 3821 3822 :param name: Name of router which should be used 3823 :type name: ``str`` 3824 3825 :param description: Description of the port 3826 :type description: ``str`` 3827 3828 :param admin_state_up: The administrative state of the 3829 resource, which is up or down 3830 :type admin_state_up: ``bool`` 3831 3832 :param external_gateway_info: The external gateway information 3833 :type external_gateway_info: ``dict`` 3834 3835 :rtype: :class:`OpenStack_2_Router` 3836 """ 3837 data = { 3838 'router': 3839 { 3840 'name': name or '', 3841 'description': description or '', 3842 'admin_state_up': admin_state_up, 3843 } 3844 } 3845 if external_gateway_info: 3846 data['router']['external_gateway_info'] = external_gateway_info 3847 response = self.network_connection.request( 3848 '/v2.0/routers', method='POST', data=data).object 3849 return self._to_router(response['router']) 3850 3851 def ex_delete_router(self, router): 3852 """ 3853 Delete a Router 3854 3855 :param router: Router which should be deleted 3856 :type router: :class:`OpenStack_2_Router` 3857 3858 :rtype: ``bool`` 3859 """ 3860 resp = self.network_connection.request('%s/%s' % ( 3861 '/v2.0/routers', router.id), method='DELETE') 3862 return resp.status in (httplib.NO_CONTENT, httplib.ACCEPTED) 3863 3864 def _manage_router_interface(self, router, op, subnet=None, port=None): 3865 """ 3866 Add/Remove interface to router 3867 3868 :param router: Router to add/remove the interface 3869 :type router: :class:`OpenStack_2_Router` 3870 3871 :param op: Operation to perform: 'add' or 'remove' 3872 :type op: ``str`` 3873 3874 :param subnet: Subnet object to be added to the router 3875 :type subnet: :class:`OpenStack_2_SubNet` 3876 3877 :param port: Port object to be added to the router 3878 :type port: :class:`OpenStack_2_PortInterface` 3879 3880 :rtype: ``bool`` 3881 """ 3882 data = {} 3883 if subnet: 3884 data['subnet_id'] = subnet.id 3885 elif port: 3886 data['port_id'] = port.id 3887 else: 3888 raise OpenStackException("Error in router interface: " 3889 "port or subnet are None.", 500, 3890 self) 3891 3892 resp = self.network_connection.request('%s/%s/%s_router_interface' % ( 3893 '/v2.0/routers', router.id, op), method='PUT', data=data) 3894 return resp.status == httplib.OK 3895 3896 def ex_add_router_port(self, router, port): 3897 """ 3898 Add port to a router 3899 3900 :param router: Router to add the port 3901 :type router: :class:`OpenStack_2_Router` 3902 3903 :param port: Port object to be added to the router 3904 :type port: :class:`OpenStack_2_PortInterface` 3905 3906 :rtype: ``bool`` 3907 """ 3908 return self._manage_router_interface(router, 'add', port=port) 3909 3910 def ex_del_router_port(self, router, port): 3911 """ 3912 Remove port from a router 3913 3914 :param router: Router to remove the port 3915 :type router: :class:`OpenStack_2_Router` 3916 3917 :param port: Port object to be added to the router 3918 :type port: :class:`OpenStack_2_PortInterface` 3919 3920 :rtype: ``bool`` 3921 """ 3922 return self._manage_router_interface(router, 'remove', port=port) 3923 3924 def ex_add_router_subnet(self, router, subnet): 3925 """ 3926 Add subnet to a router 3927 3928 :param router: Router to add the subnet 3929 :type router: :class:`OpenStack_2_Router` 3930 3931 :param subnet: Subnet object to be added to the router 3932 :type subnet: :class:`OpenStack_2_SubNet` 3933 3934 :rtype: ``bool`` 3935 """ 3936 return self._manage_router_interface(router, 'add', subnet=subnet) 3937 3938 def ex_del_router_subnet(self, router, subnet): 3939 """ 3940 Remove subnet to a router 3941 3942 :param router: Router to remove the subnet 3943 :type router: :class:`OpenStack_2_Router` 3944 3945 :param subnet: Subnet object to be added to the router 3946 :type subnet: :class:`OpenStack_2_SubNet` 3947 3948 :rtype: ``bool`` 3949 """ 3950 return self._manage_router_interface(router, 'remove', subnet=subnet) 3951 3952 def _to_quota_set(self, obj): 3953 res = OpenStack_2_QuotaSet( 3954 id=obj['id'], 3955 cores=obj['cores'], 3956 instances=obj['instances'], 3957 key_pairs=obj['key_pairs'], 3958 metadata_items=obj['metadata_items'], 3959 ram=obj['ram'], 3960 server_groups=obj['server_groups'], 3961 server_group_members=obj['server_group_members'], 3962 fixed_ips=obj.get('fixed_ips', None), 3963 floating_ips=obj.get('floating_ips', None), 3964 networks=obj.get('networks', None), 3965 security_group_rules=obj.get('security_group_rules', None), 3966 security_groups=obj.get('security_groups', None), 3967 injected_file_content_bytes=obj.get('injected_file_content_bytes', 3968 None), 3969 injected_file_path_bytes=obj.get('injected_file_path_bytes', None), 3970 injected_files=obj.get('injected_files', None), 3971 driver=self.connection.driver) 3972 3973 return res 3974 3975 def ex_get_quota_set(self, tenant_id, user_id=None): 3976 """ 3977 Get the quota for a project or a project and a user. 3978 3979 :param tenant_id: The UUID of the tenant in a multi-tenancy cloud 3980 :type tenant_id: ``str`` 3981 3982 :param user_id: ID of user to list the quotas for. 3983 :type user_id: ``str`` 3984 3985 :rtype: :class:`OpenStack_2_QuotaSet` 3986 """ 3987 url = '/os-quota-sets/%s/detail' % tenant_id 3988 if user_id: 3989 url += "?user_id=%s" % user_id 3990 return self._to_quota_set( 3991 self.connection.request(url).object['quota_set']) 3992 3993 def _to_network_quota(self, obj): 3994 res = OpenStack_2_NetworkQuota( 3995 floatingip=obj['floatingip'], 3996 network=obj['network'], 3997 port=obj['port'], 3998 rbac_policy=obj['rbac_policy'], 3999 router=obj.get('router', None), 4000 security_group=obj.get('security_group', None), 4001 security_group_rule=obj.get('security_group_rule', None), 4002 subnet=obj.get('subnet', None), 4003 subnetpool=obj.get('subnetpool', None), 4004 driver=self.connection.driver) 4005 4006 return res 4007 4008 def ex_get_network_quotas(self, project_id): 4009 """ 4010 Get the network quotas for a project 4011 4012 :param project_id: The ID of the project. 4013 :type project_id: ``str`` 4014 4015 :rtype: :class:`OpenStack_2_NetworkQuota` 4016 """ 4017 url = '/v2.0/quotas/%s/details.json' % project_id 4018 return self._to_network_quota( 4019 self.network_connection.request(url).object['quota']) 4020 4021 def _to_volume_quota(self, obj): 4022 res = OpenStack_2_VolumeQuota( 4023 backup_gigabytes=obj.get('backup_gigabytes', None), 4024 gigabytes=obj.get('gigabytes', None), 4025 per_volume_gigabytes=obj.get('per_volume_gigabytes', None), 4026 backups=obj.get('backups', None), 4027 snapshots=obj.get('snapshots', None), 4028 volumes=obj.get('volumes', None), 4029 driver=self.connection.driver) 4030 4031 return res 4032 4033 def ex_get_volume_quotas(self, project_id): 4034 """ 4035 Get the volume quotas for a project 4036 4037 :param project_id: The ID of the project. 4038 :type project_id: ``str`` 4039 4040 :rtype: :class:`OpenStack_2_VolumeQuota` 4041 """ 4042 url = '/os-quota-sets/%s?usage=True' % project_id 4043 return self._to_volume_quota( 4044 self._get_volume_connection().request(url).object['quota_set']) 4045 4046 4047class OpenStack_1_1_FloatingIpPool(object): 4048 """ 4049 Floating IP Pool info. 4050 """ 4051 4052 def __init__(self, name, connection): 4053 self.name = name 4054 self.connection = connection 4055 4056 def list_floating_ips(self): 4057 """ 4058 List floating IPs in the pool 4059 4060 :rtype: ``list`` of :class:`OpenStack_1_1_FloatingIpAddress` 4061 """ 4062 return self._to_floating_ips( 4063 self.connection.request('/os-floating-ips').object) 4064 4065 def _to_floating_ips(self, obj): 4066 ip_elements = obj['floating_ips'] 4067 return [self._to_floating_ip(ip) for ip in ip_elements] 4068 4069 def _to_floating_ip(self, obj): 4070 return OpenStack_1_1_FloatingIpAddress(id=obj['id'], 4071 ip_address=obj['ip'], 4072 pool=self, 4073 node_id=obj['instance_id'], 4074 driver=self.connection.driver) 4075 4076 def get_floating_ip(self, ip): 4077 """ 4078 Get specified floating IP from the pool 4079 4080 :param ip: floating IP to get 4081 :type ip: ``str`` 4082 4083 :rtype: :class:`OpenStack_1_1_FloatingIpAddress` 4084 """ 4085 ip_obj, = [x for x in self.list_floating_ips() if x.ip_address == ip] 4086 return ip_obj 4087 4088 def create_floating_ip(self): 4089 """ 4090 Create new floating IP in the pool 4091 4092 :rtype: :class:`OpenStack_1_1_FloatingIpAddress` 4093 """ 4094 resp = self.connection.request('/os-floating-ips', 4095 method='POST', 4096 data={'pool': self.name}) 4097 data = resp.object['floating_ip'] 4098 id = data['id'] 4099 ip_address = data['ip'] 4100 return OpenStack_1_1_FloatingIpAddress(id=id, 4101 ip_address=ip_address, 4102 pool=self, 4103 node_id=None, 4104 driver=self.connection.driver) 4105 4106 def delete_floating_ip(self, ip): 4107 """ 4108 Delete specified floating IP from the pool 4109 4110 :param ip: floating IP to remove 4111 :type ip: :class:`OpenStack_1_1_FloatingIpAddress` 4112 4113 :rtype: ``bool`` 4114 """ 4115 resp = self.connection.request('/os-floating-ips/%s' % ip.id, 4116 method='DELETE') 4117 return resp.status in (httplib.NO_CONTENT, httplib.ACCEPTED) 4118 4119 def __repr__(self): 4120 return ('<OpenStack_1_1_FloatingIpPool: name=%s>' % self.name) 4121 4122 4123class OpenStack_1_1_FloatingIpAddress(object): 4124 """ 4125 Floating IP info. 4126 """ 4127 4128 def __init__(self, id, ip_address, pool, node_id=None, driver=None): 4129 self.id = str(id) 4130 self.ip_address = ip_address 4131 self.pool = pool 4132 self.node_id = node_id 4133 self.driver = driver 4134 4135 def delete(self): 4136 """ 4137 Delete this floating IP 4138 4139 :rtype: ``bool`` 4140 """ 4141 if self.pool is not None: 4142 return self.pool.delete_floating_ip(self) 4143 elif self.driver is not None: 4144 return self.driver.ex_delete_floating_ip(self) 4145 4146 def __repr__(self): 4147 return ('<OpenStack_1_1_FloatingIpAddress: id=%s, ip_addr=%s,' 4148 ' pool=%s, driver=%s>' 4149 % (self.id, self.ip_address, self.pool, self.driver)) 4150 4151 4152class OpenStack_2_FloatingIpPool(object): 4153 """ 4154 Floating IP Pool info. 4155 """ 4156 4157 def __init__(self, id, name, connection): 4158 self.id = id 4159 self.name = name 4160 self.connection = connection 4161 4162 def _to_floating_ips(self, obj): 4163 ip_elements = obj['floatingips'] 4164 return [self._to_floating_ip(ip) for ip in ip_elements] 4165 4166 def _to_floating_ip(self, obj): 4167 instance_id = None 4168 4169 # In neutron version prior to 13.0.0 port_details does not exists 4170 if 'port_details' not in obj and 'port_id' in obj and obj['port_id']: 4171 port = self.connection.driver.ex_get_port(obj['port_id']) 4172 if port: 4173 obj['port_details'] = {"device_id": port.extra["device_id"], 4174 "device_owner": 4175 port.extra["device_owner"], 4176 "mac_address": 4177 port.extra["mac_address"]} 4178 4179 if 'port_details' in obj and obj['port_details']: 4180 dev_owner = obj['port_details']['device_owner'] 4181 if dev_owner and dev_owner.startswith("compute:"): 4182 instance_id = obj['port_details']['device_id'] 4183 4184 ip_address = obj['floating_ip_address'] 4185 return OpenStack_1_1_FloatingIpAddress(id=obj['id'], 4186 ip_address=ip_address, 4187 pool=self, 4188 node_id=instance_id, 4189 driver=self.connection.driver) 4190 4191 def list_floating_ips(self): 4192 """ 4193 List floating IPs in the pool 4194 4195 :rtype: ``list`` of :class:`OpenStack_1_1_FloatingIpAddress` 4196 """ 4197 return self._to_floating_ips( 4198 self.connection.request('/v2.0/floatingips').object) 4199 4200 def get_floating_ip(self, ip): 4201 """ 4202 Get specified floating IP from the pool 4203 4204 :param ip: floating IP to get 4205 :type ip: ``str`` 4206 4207 :rtype: :class:`OpenStack_1_1_FloatingIpAddress` 4208 """ 4209 floating_ips = self._to_floating_ips( 4210 self.connection.request('/v2.0/floatingips?floating_ip_address' 4211 '=%s' % ip).object) 4212 return floating_ips[0] 4213 4214 def create_floating_ip(self): 4215 """ 4216 Create new floating IP in the pool 4217 4218 :rtype: :class:`OpenStack_1_1_FloatingIpAddress` 4219 """ 4220 resp = self.connection.request('/v2.0/floatingips', 4221 method='POST', 4222 data={'floatingip': 4223 {'floating_network_id': self.id}} 4224 ) 4225 data = resp.object['floatingip'] 4226 id = data['id'] 4227 ip_address = data['floating_ip_address'] 4228 return OpenStack_1_1_FloatingIpAddress(id=id, 4229 ip_address=ip_address, 4230 pool=self, 4231 node_id=None, 4232 driver=self.connection.driver) 4233 4234 def delete_floating_ip(self, ip): 4235 """ 4236 Delete specified floating IP from the pool 4237 4238 :param ip: floating IP to remove 4239 :type ip: :class:`OpenStack_1_1_FloatingIpAddress` 4240 4241 :rtype: ``bool`` 4242 """ 4243 resp = self.connection.request('/v2.0/floatingips/%s' % ip.id, 4244 method='DELETE') 4245 return resp.status in (httplib.NO_CONTENT, httplib.ACCEPTED) 4246 4247 def __repr__(self): 4248 return ('<OpenStack_2_FloatingIpPool: name=%s>' % self.name) 4249 4250 4251class OpenStack_2_SubNet(object): 4252 """ 4253 A Virtual SubNet. 4254 """ 4255 4256 def __init__(self, id, name, cidr, network_id, driver, extra=None): 4257 self.id = str(id) 4258 self.name = name 4259 self.cidr = cidr 4260 self.network_id = network_id 4261 self.driver = driver 4262 self.extra = extra or {} 4263 4264 def __repr__(self): 4265 return '<OpenStack_2_SubNet id="%s" name="%s" cidr="%s">' % (self.id, 4266 self.name, 4267 self.cidr) 4268 4269 4270class OpenStack_2_Router(object): 4271 """ 4272 A Virtual Router. 4273 """ 4274 4275 def __init__(self, id, name, status, driver, extra=None): 4276 self.id = str(id) 4277 self.name = name 4278 self.status = status 4279 self.driver = driver 4280 self.extra = extra or {} 4281 4282 def __repr__(self): 4283 return '<OpenStack_2_Router id="%s" name="%s">' % (self.id, 4284 self.name) 4285 4286 4287class OpenStack_2_PortInterface(UuidMixin): 4288 """ 4289 Port Interface info. Similar in functionality to a floating IP (can be 4290 attached / detached from a compute instance) but implementation-wise a 4291 bit different. 4292 4293 > A port is a connection point for attaching a single device, such as the 4294 > NIC of a server, to a network. The port also describes the associated 4295 > network configuration, such as the MAC and IP addresses to be used on 4296 > that port. 4297 https://docs.openstack.org/python-openstackclient/pike/cli/command-objects/port.html 4298 4299 Also see: 4300 https://developer.openstack.org/api-ref/compute/#port-interfaces-servers-os-interface 4301 """ 4302 4303 def __init__(self, id, state, driver, created=None, extra=None): 4304 """ 4305 :param id: Port Interface ID. 4306 :type id: ``str`` 4307 :param state: State of the OpenStack_2_PortInterface. 4308 :type state: :class:`.OpenStack_2_PortInterfaceState` 4309 :param created: A datetime object that represents when the 4310 port interface was created 4311 :type created: ``datetime.datetime`` 4312 :param extra: Optional provided specific attributes associated with 4313 this image. 4314 :type extra: ``dict`` 4315 """ 4316 self.id = str(id) 4317 self.state = state 4318 self.driver = driver 4319 self.created = created 4320 self.extra = extra or {} 4321 UuidMixin.__init__(self) 4322 4323 def delete(self): 4324 """ 4325 Delete this Port Interface 4326 4327 :rtype: ``bool`` 4328 """ 4329 return self.driver.ex_delete_port(self) 4330 4331 def __repr__(self): 4332 return (('<OpenStack_2_PortInterface: id=%s, state=%s, ' 4333 'driver=%s ...>') 4334 % (self.id, self.state, self.driver.name)) 4335 4336 4337class OpenStack_2_QuotaSetItem(object): 4338 """ 4339 Qouta Set Item info. Each item has three attributes: in_use, 4340 limit and reserved. 4341 4342 See: 4343 https://docs.openstack.org/api-ref/compute/?expanded=show-the-detail-of-quota-detail#show-a-quota 4344 """ 4345 4346 def __init__(self, in_use, limit, reserved): 4347 """ 4348 :param in_use: Number of currently used resources. 4349 :type in_use: ``int`` 4350 :param limit: Max number of available resources. 4351 :type limit: ``int`` 4352 :param reserved: Number of reserved resources. 4353 :type reserved: ``int`` 4354 """ 4355 self.in_use = in_use 4356 self.limit = limit 4357 self.reserved = reserved 4358 4359 def __repr__(self): 4360 return ('<OpenStack_2_QuotaSetItem in_use="%s", limit="%s",' 4361 'reserved="%s">' % (self.in_use, self.limit, 4362 self.reserved)) 4363 4364 4365class OpenStack_2_QuotaSet(object): 4366 """ 4367 Quota Set info. To get the informatio about quotas and used resources. 4368 4369 See: 4370 https://docs.openstack.org/api-ref/compute/?expanded=show-the-detail-of-quota-detail#show-a-quota 4371 4372 """ 4373 4374 def __init__(self, id, cores, instances, key_pairs, metadata_items, ram, 4375 server_groups, server_group_members, fixed_ips=None, 4376 floating_ips=None, networks=None, security_group_rules=None, 4377 security_groups=None, injected_file_content_bytes=None, 4378 injected_file_path_bytes=None, injected_files=None, 4379 driver=None): 4380 """ 4381 :param id: Quota Set ID. 4382 :type id: ``str`` 4383 :param cores: Quota Set of cores. 4384 :type cores: :class:`.OpenStack_2_QuotaSetItem` or ``dict`` 4385 :param instances: Quota Set of instances. 4386 :type instances: :class:`.OpenStack_2_QuotaSetItem` or ``dict`` 4387 :param key_pairs: Quota Set of key pairs. 4388 :type key_pairs: :class:`.OpenStack_2_QuotaSetItem` or ``dict`` 4389 :param metadata_items: Quota Set of metadata items. 4390 :type metadata_items: :class:`.OpenStack_2_QuotaSetItem` or ``dict`` 4391 :param ram: Quota Set of RAM. 4392 :type ram: :class:`.OpenStack_2_QuotaSetItem` or ``dict`` 4393 :param server_groups: Quota Set of server groups. 4394 :type server_groups: :class:`.OpenStack_2_QuotaSetItem` or ``dict`` 4395 :param fixed_ips: Quota Set of fixed ips. (optional) 4396 :type fixed_ips: :class:`.OpenStack_2_QuotaSetItem` or ``dict`` 4397 :param floating_ips: Quota Set of floating ips. (optional) 4398 :type floating_ips: :class:`.OpenStack_2_QuotaSetItem` or ``dict`` 4399 :param networks: Quota Set of networks. (optional) 4400 :type networks: :class:`.OpenStack_2_QuotaSetItem` or ``dict`` 4401 :param security_group_rules: Quota Set of security group rules. 4402 (optional) 4403 :type security_group_rules: :class:`.OpenStack_2_QuotaSetItem` 4404 or ``dict`` 4405 :param security_groups: Quota Set of security groups. (optional) 4406 :type security_groups: :class:`.OpenStack_2_QuotaSetItem` or ``dict`` 4407 :param injected_file_content_bytes: Quota Set of injected file content 4408 bytes. (optional) 4409 :type injected_file_content_bytes: :class:`.OpenStack_2_QuotaSetItem` 4410 or ``dict`` 4411 :param injected_file_path_bytes: Quota Set of injected file path bytes. 4412 (optional) 4413 :type injected_file_path_bytes: :class:`.OpenStack_2_QuotaSetItem` 4414 or ``dict`` 4415 :param injected_files: Quota Set of injected files. (optional) 4416 :type injected_files: :class:`.OpenStack_2_QuotaSetItem` or ``dict`` 4417 """ 4418 self.id = str(id) 4419 self.cores = self._to_quota_set_item(cores) 4420 self.instances = self._to_quota_set_item(instances) 4421 self.key_pairs = self._to_quota_set_item(key_pairs) 4422 self.metadata_items = self._to_quota_set_item(metadata_items) 4423 self.ram = self._to_quota_set_item(ram) 4424 self.server_groups = self._to_quota_set_item(server_groups) 4425 self.server_group_members = self._to_quota_set_item( 4426 server_group_members) 4427 self.fixed_ips = self._to_quota_set_item(fixed_ips) 4428 self.floating_ips = self._to_quota_set_item(floating_ips) 4429 self.networks = self._to_quota_set_item(networks) 4430 self.security_group_rules = self._to_quota_set_item( 4431 security_group_rules) 4432 self.security_groups = self._to_quota_set_item(security_groups) 4433 self.injected_file_content_bytes = self._to_quota_set_item( 4434 injected_file_content_bytes) 4435 self.injected_file_path_bytes = self._to_quota_set_item( 4436 injected_file_path_bytes) 4437 self.injected_files = self._to_quota_set_item(injected_files) 4438 self.driver = driver 4439 4440 def _to_quota_set_item(self, obj): 4441 if obj: 4442 if isinstance(obj, OpenStack_2_QuotaSetItem): 4443 return obj 4444 elif isinstance(obj, dict): 4445 return OpenStack_2_QuotaSetItem(obj['in_use'], obj['limit'], 4446 obj['reserved']) 4447 else: 4448 return None 4449 4450 def __repr__(self): 4451 return ('<OpenStack_2_QuotaSet id="%s", cores="%s", ram="%s",' 4452 ' instances="%s">' % (self.id, self.cores, self.ram, 4453 self.instances)) 4454 4455 4456class OpenStack_2_NetworkQuota(object): 4457 """ 4458 Network Quota info. To get the information about quotas and used resources. 4459 4460 See: 4461 https://docs.openstack.org/api-ref/network/v2/?expanded=show-quota-details-for-a-tenant-detail,list-quotas-for-a-project-detail#show-quota-details-for-a-tenant 4462 4463 """ 4464 4465 def __init__(self, floatingip, network, port, rbac_policy, router, 4466 security_group, security_group_rule, subnet, 4467 subnetpool, driver=None): 4468 """ 4469 :param floatingip: Quota of floating ips. 4470 :type floatingip: :class:`.OpenStack_2_QuotaSetItem` or ``dict`` 4471 :param network: Quota of networks. 4472 :type network: :class:`.OpenStack_2_QuotaSetItem` or ``dict`` 4473 :param port: Quota of ports. 4474 :type port: :class:`.OpenStack_2_QuotaSetItem` or ``dict`` 4475 :param rbac_policy: Quota of rbac policies. 4476 :type rbac_policy: :class:`.OpenStack_2_QuotaSetItem` or ``dict`` 4477 :param router: Quota of routers. 4478 :type router: :class:`.OpenStack_2_QuotaSetItem` or ``dict`` 4479 :param security_group: Quota of security groups. 4480 :type security_group: :class:`.OpenStack_2_QuotaSetItem` or ``dict`` 4481 :param security_group_rule: Quota of security group rules. 4482 :type security_group_rule: :class:`.OpenStack_2_QuotaSetItem` 4483 or ``dict`` 4484 :param subnet: Quota of subnets. 4485 :type subnet: :class:`.OpenStack_2_QuotaSetItem` or ``dict`` 4486 :param subnetpool: Quota of subnet pools. 4487 :type subnetpool: :class:`.OpenStack_2_QuotaSetItem` or ``dict`` 4488 """ 4489 self.floatingip = self._to_quota_set_item(floatingip) 4490 self.network = self._to_quota_set_item(network) 4491 self.port = self._to_quota_set_item(port) 4492 self.rbac_policy = self._to_quota_set_item(rbac_policy) 4493 self.router = self._to_quota_set_item(router) 4494 self.security_group = self._to_quota_set_item(security_group) 4495 self.security_group_rule = self._to_quota_set_item(security_group_rule) 4496 self.subnet = self._to_quota_set_item(subnet) 4497 self.subnetpool = self._to_quota_set_item(subnetpool) 4498 self.driver = driver 4499 4500 def _to_quota_set_item(self, obj): 4501 if obj: 4502 if isinstance(obj, OpenStack_2_QuotaSetItem): 4503 return obj 4504 elif isinstance(obj, dict): 4505 return OpenStack_2_QuotaSetItem(obj['used'], obj['limit'], 4506 obj['reserved']) 4507 else: 4508 return None 4509 4510 def __repr__(self): 4511 return ('<OpenStack_2_NetworkQuota Floating IPs="%s", networks="%s",' 4512 ' SGs="%s", SGRs="%s">' % (self.floatingip, self.network, 4513 self.security_group, 4514 self.security_group_rule)) 4515 4516 4517class OpenStack_2_VolumeQuota(object): 4518 """ 4519 Volume Quota info. To get the information about quotas and used resources. 4520 4521 See: 4522 https://docs.openstack.org/api-ref/block-storage/v2/index.html?expanded=show-quotas-detail 4523 https://docs.openstack.org/api-ref/block-storage/v3/index.html?expanded=show-quota-usage-for-a-project-detail 4524 """ 4525 4526 def __init__(self, backup_gigabytes, gigabytes, per_volume_gigabytes, 4527 backups, snapshots, volumes, driver=None): 4528 """ 4529 :param backup_gigabytes: Quota of backup size in gigabytes. 4530 :type backup_gigabytes: :class:`.OpenStack_2_QuotaSetItem` or ``int`` 4531 :param gigabytes: Quota of volume size in gigabytes. 4532 :type gigabytes: :class:`.OpenStack_2_QuotaSetItem` or ``int`` 4533 :param per_volume_gigabytes: Quota of per volume gigabytes. 4534 :type per_volume_gigabytes: :class:`.OpenStack_2_QuotaSetItem` 4535 or ``int`` 4536 :param backups: Quota of backups. 4537 :type backups: :class:`.OpenStack_2_QuotaSetItem` or ``int`` 4538 :param snapshots: Quota of snapshots. 4539 :type snapshots: :class:`.OpenStack_2_QuotaSetItem` or ``int`` 4540 :param volumes: Quota of security volumes. 4541 :type volumes: :class:`.OpenStack_2_QuotaSetItem` or ``int`` 4542 """ 4543 self.backup_gigabytes = self._to_quota_set_item(backup_gigabytes) 4544 self.gigabytes = self._to_quota_set_item(gigabytes) 4545 self.per_volume_gigabytes = self._to_quota_set_item( 4546 per_volume_gigabytes) 4547 self.backups = self._to_quota_set_item(backups) 4548 self.snapshots = self._to_quota_set_item(snapshots) 4549 self.volumes = self._to_quota_set_item(volumes) 4550 self.driver = driver 4551 4552 def _to_quota_set_item(self, obj): 4553 if obj: 4554 if isinstance(obj, OpenStack_2_QuotaSetItem): 4555 return obj 4556 elif isinstance(obj, dict): 4557 return OpenStack_2_QuotaSetItem(obj['in_use'], obj['limit'], 4558 obj['reserved']) 4559 elif isinstance(obj, int): 4560 return OpenStack_2_QuotaSetItem(0, obj, 0) 4561 else: 4562 return None 4563 else: 4564 return None 4565 4566 def __repr__(self): 4567 return ('<OpenStack_2_VolumeQuota Volumes="%s", gigabytes="%s",' 4568 ' snapshots="%s", backups="%s">' % (self.volumes, 4569 self.gigabytes, 4570 self.snapshots, 4571 self.backups)) 4572