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