[Python-modules-commits] [libcloud] 01/05: Imported Upstream version 1.5.0

Hans-Christoph Steiner eighthave at moszumanska.debian.org
Fri Jan 6 22:18:52 UTC 2017


This is an automated email from the git hooks/post-receive script.

eighthave pushed a commit to branch master
in repository libcloud.

commit 3fcb2ab45e14dd0d59a847dd2a9f9147b031f762
Author: Hans-Christoph Steiner <hans at eds.org>
Date:   Fri Jan 6 23:02:11 2017 +0100

    Imported Upstream version 1.5.0
---
 CHANGES.rst                                        |    67 +
 PKG-INFO                                           |     2 +-
 apache_libcloud.egg-info/PKG-INFO                  |     2 +-
 apache_libcloud.egg-info/SOURCES.txt               |    35 +-
 libcloud/__init__.py                               |     2 +-
 libcloud/common/dimensiondata.py                   |    56 +-
 libcloud/common/openstack_identity.py              |   140 +-
 libcloud/compute/drivers/azure_arm.py              |    34 +
 libcloud/compute/drivers/cloudscale.py             |   246 +
 libcloud/compute/drivers/dimensiondata.py          |   610 +-
 libcloud/compute/drivers/ec2.py                    |   102 +-
 libcloud/compute/drivers/ecs.py                    |     3 +
 libcloud/compute/drivers/gce.py                    |    56 +-
 libcloud/compute/providers.py                      |     2 +
 libcloud/compute/types.py                          |     5 +
 libcloud/container/drivers/rancher.py              |    71 +-
 libcloud/loadbalancer/drivers/gce.py               |     5 +-
 ...dimensiondata.py => test_dimensiondata_v2_3.py} |     3 +-
 ...dimensiondata.py => test_dimensiondata_v2_4.py} |    17 +-
 libcloud/test/common/test_openstack_identity.py    |    47 +
 ...9999_providers_Microsoft_Commerce_RateCard.json | 84270 +++++++++++++++++++
 .../compute/fixtures/cloudscale/create_node.json   |    46 +
 .../compute/fixtures/cloudscale/list_images.json   |     1 +
 .../compute/fixtures/cloudscale/list_nodes.json    |    48 +
 .../compute/fixtures/cloudscale/list_sizes.json    |     2 +
 .../2.4/change_nic_networkadapter_response.xml     |     7 +
 .../2.4/exchange_nic_vlans_response.xml            |     8 +
 .../dimensiondata/2.4/image_customerImage.xml      |    57 +
 ...rImage_2ffa36c8_1848_49eb_b4fa_9d908775f68c.xml |    19 +
 ...rImage_5234e5c7_01de_4411_8b6e_baeb8d91cf5d.xml |    19 +
 .../fixtures/dimensiondata/2.4/image_osImage.xml   |    40 +
 ...sImage_6b4fb0c7_a57b_4f58_b59c_9958f94f971a.xml |    13 +
 ...sImage_c14b1a46_2428_44c1_9c1a_b20e6418d08c.xml |    14 +
 .../dimensiondata/2.4/import_image_response.xml    |     8 +
 .../dimensiondata/2.4/server_GetServer.xml         |    42 +
 .../dimensiondata/2.4/server_cleanServer.xml       |     9 +
 .../dimensiondata/2.4/server_clone_response.xml    |     9 +
 .../fixtures/dimensiondata/2.4/server_server.xml   |   192 +
 .../dimensiondata/2.4/server_server_NA3.xml        |    56 +
 ...server_e75ead52_692f_4314_8725_c8a4f4d13a87.xml |    30 +
 .../dimensiondata/2.4/server_server_paginated.xml  |    58 +
 ...x_describe_quota.xml => ex_describe_quotas.xml} |     4 +-
 .../gce/projects_coreos-cloud_global_images.json   |     3 +
 ...ntral1-b_instances_libcloud-lb-nopubip-001.json |    49 +
 libcloud/test/compute/test_azure_arm.py            |     9 +
 libcloud/test/compute/test_cloudscale.py           |   122 +
 ...dimensiondata.py => test_dimensiondata_v2_3.py} |     7 +-
 ...dimensiondata.py => test_dimensiondata_v2_4.py} |   326 +-
 libcloud/test/compute/test_ec2.py                  |    22 +-
 libcloud/test/compute/test_gce.py                  |    18 +-
 libcloud/test/container/test_rancher.py            |   218 +-
 ...dimensiondata.py => test_dimensiondata_v2_3.py} |     3 +-
 ...dimensiondata.py => test_dimensiondata_v2_4.py} |    47 +-
 libcloud/test/loadbalancer/test_gce.py             |     7 +
 libcloud/test/secrets.py-dist                      |     3 +-
 55 files changed, 86943 insertions(+), 348 deletions(-)

diff --git a/CHANGES.rst b/CHANGES.rst
index 8917d83..18df134 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -2,6 +2,69 @@
 =========
 
 
+Changes in current version of Apache Libcloud
+---------------------------------------------
+
+Common
+~~~~~~
+
+- Set Dimension Data compute, backup and load balancer to default to 2.4 API
+  [GITHUB-961]
+  [Samuel Chong]
+
+Compute
+~~~~~~~
+
+- [azure] New method for accessing rate cards
+  [GITHUB-957]
+  (Soren L. Hansen)
+
+- [gce] Allow multiple preemptible instances to be created
+  [GITHUB-954]
+  (John Baublitz)
+
+- [openstack] Add new Connection class to support VOMS proxys to keystone servers
+  [GITHUB-959]
+  (micafer)
+
+- [outscale] Added support for changed API for describing quotas
+  [GITHUB-960]
+  (Javier M. Mellid)
+
+- [ec2] Added m4 instances to us-gov and brazil, added m4.16xlarge to all
+  [GITHUB-964]
+  (Matthew Tyas)
+
+- Added new CloudScale.ch driver
+
+- [google compute] Bug fix for ex_create_multiple_nodes Google Cloud disk auto delete
+  (GITHUB-955)
+  [John Baublitz]
+
+- [google compute] Add "MULTI_IP_SUBNET" guestOsFeatures option.
+  (GITHUB-956)
+  [Max Illfelder]
+
+- [dimensiondata] Added support for 2.4 API, added support for image import, cloning. Add feature for changing NIC VLANs, add feature for changing
+  NIC order for a server.
+  (GITHUB-953)
+  [Samuel Chong]
+
+- [ec2] Add US-EAST2 (Ohio)
+  (GITHUB-946)
+  [Matthew Harris]
+
+- [google compute] Fix to allow multiple node creation with subnets
+  (GITHUB-949)
+  [John Baublitz]
+
+Container
+~~~~~~~~~
+
+- [rancher] The scheme (secure) and port no longer need to be explicitly specified, allowing a user to simply copy in the string provided to them from Rancher.
+  [GITHUB-958]
+  (Matthew Ellison)
+
 Changes in Apache Libcloud 1.4.0
 --------------------------------
 
@@ -90,6 +153,10 @@ Load Balancing
   (LIBCLOUD-869, GITHUB-936)
   [Anton Kozyrev]
 
+- Fix bug where GCE Load balancer supposes that all VMs have public ips
+  (LIBCLOUD-879, GITHUB-952)
+  [Chris Walker]
+
 Storage
 ~~~~~~~
 
diff --git a/PKG-INFO b/PKG-INFO
index 28d0d02..df9284a 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: apache-libcloud
-Version: 1.4.0
+Version: 1.5.0
 Summary: A standard Python library that abstracts away differences among multiple cloud provider APIs. For more information and documentation, please see http://libcloud.apache.org
 Home-page: http://libcloud.apache.org/
 Author: Apache Software Foundation
diff --git a/apache_libcloud.egg-info/PKG-INFO b/apache_libcloud.egg-info/PKG-INFO
index 28d0d02..df9284a 100644
--- a/apache_libcloud.egg-info/PKG-INFO
+++ b/apache_libcloud.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: apache-libcloud
-Version: 1.4.0
+Version: 1.5.0
 Summary: A standard Python library that abstracts away differences among multiple cloud provider APIs. For more information and documentation, please see http://libcloud.apache.org
 Home-page: http://libcloud.apache.org/
 Author: Apache Software Foundation
diff --git a/apache_libcloud.egg-info/SOURCES.txt b/apache_libcloud.egg-info/SOURCES.txt
index ec49ab4..0c576ce 100644
--- a/apache_libcloud.egg-info/SOURCES.txt
+++ b/apache_libcloud.egg-info/SOURCES.txt
@@ -92,6 +92,7 @@ libcloud/compute/drivers/azure_arm.py
 libcloud/compute/drivers/bluebox.py
 libcloud/compute/drivers/brightbox.py
 libcloud/compute/drivers/bsnl.py
+libcloud/compute/drivers/cloudscale.py
 libcloud/compute/drivers/cloudsigma.py
 libcloud/compute/drivers/cloudstack.py
 libcloud/compute/drivers/cloudwatt.py
@@ -231,7 +232,8 @@ libcloud/test/test_types.py
 libcloud/test/test_utils.py
 libcloud/test/backup/__init__.py
 libcloud/test/backup/test_base.py
-libcloud/test/backup/test_dimensiondata.py
+libcloud/test/backup/test_dimensiondata_v2_3.py
+libcloud/test/backup/test_dimensiondata_v2_4.py
 libcloud/test/backup/fixtures/dimensiondata/_backup_DISABLE.xml
 libcloud/test/backup/fixtures/dimensiondata/_backup_ENABLE.xml
 libcloud/test/backup/fixtures/dimensiondata/_backup_EXISTS.xml
@@ -285,12 +287,14 @@ libcloud/test/compute/test_azure_arm.py
 libcloud/test/compute/test_base.py
 libcloud/test/compute/test_bluebox.py
 libcloud/test/compute/test_brightbox.py
+libcloud/test/compute/test_cloudscale.py
 libcloud/test/compute/test_cloudsigma_v1_0.py
 libcloud/test/compute/test_cloudsigma_v2_0.py
 libcloud/test/compute/test_cloudstack.py
 libcloud/test/compute/test_deployment.py
 libcloud/test/compute/test_digitalocean_v2.py
-libcloud/test/compute/test_dimensiondata.py
+libcloud/test/compute/test_dimensiondata_v2_3.py
+libcloud/test/compute/test_dimensiondata_v2_4.py
 libcloud/test/compute/test_ec2.py
 libcloud/test/compute/test_ecp.py
 libcloud/test/compute/test_ecs.py
@@ -383,6 +387,7 @@ libcloud/test/compute/fixtures/azure/_3761b98b_673d_526c_8d55_fee918758e6e_servi
 libcloud/test/compute/fixtures/azure/_3761b98b_673d_526c_8d55_fee918758e6e_services_vmimages.xml
 libcloud/test/compute/fixtures/azure/libcloud.pem
 libcloud/test/compute/fixtures/azure_arm/_77777777_7777_7777_7777_777777777777_oauth2_token.json
+libcloud/test/compute/fixtures/azure_arm/_subscriptions_99999999_9999_9999_9999_999999999999_providers_Microsoft_Commerce_RateCard.json
 libcloud/test/compute/fixtures/azure_arm/_subscriptions_99999999_9999_9999_9999_999999999999_providers_Microsoft_Compute.json
 libcloud/test/compute/fixtures/azure_arm/_subscriptions_99999999_9999_9999_9999_999999999999_providers_Microsoft_Compute_locations_eastus_vmSizes.json
 libcloud/test/compute/fixtures/bluebox/api_block_products_json.json
@@ -402,6 +407,10 @@ libcloud/test/compute/fixtures/brightbox/list_server_types.json
 libcloud/test/compute/fixtures/brightbox/list_servers.json
 libcloud/test/compute/fixtures/brightbox/list_zones.json
 libcloud/test/compute/fixtures/brightbox/token.json
+libcloud/test/compute/fixtures/cloudscale/create_node.json
+libcloud/test/compute/fixtures/cloudscale/list_images.json
+libcloud/test/compute/fixtures/cloudscale/list_nodes.json
+libcloud/test/compute/fixtures/cloudscale/list_sizes.json
 libcloud/test/compute/fixtures/cloudsigma/drives_clone.txt
 libcloud/test/compute/fixtures/cloudsigma/drives_info.txt
 libcloud/test/compute/fixtures/cloudsigma/drives_single_info.txt
@@ -768,6 +777,22 @@ libcloud/test/compute/fixtures/dimensiondata/tag_tagKey_5ab77f5f_5aa9_426f_8459_
 libcloud/test/compute/fixtures/dimensiondata/tag_tagKey_list.xml
 libcloud/test/compute/fixtures/dimensiondata/tag_tagKey_list_SINGLE.xml
 libcloud/test/compute/fixtures/dimensiondata/tag_tag_list.xml
+libcloud/test/compute/fixtures/dimensiondata/2.4/change_nic_networkadapter_response.xml
+libcloud/test/compute/fixtures/dimensiondata/2.4/exchange_nic_vlans_response.xml
+libcloud/test/compute/fixtures/dimensiondata/2.4/image_customerImage.xml
+libcloud/test/compute/fixtures/dimensiondata/2.4/image_customerImage_2ffa36c8_1848_49eb_b4fa_9d908775f68c.xml
+libcloud/test/compute/fixtures/dimensiondata/2.4/image_customerImage_5234e5c7_01de_4411_8b6e_baeb8d91cf5d.xml
+libcloud/test/compute/fixtures/dimensiondata/2.4/image_osImage.xml
+libcloud/test/compute/fixtures/dimensiondata/2.4/image_osImage_6b4fb0c7_a57b_4f58_b59c_9958f94f971a.xml
+libcloud/test/compute/fixtures/dimensiondata/2.4/image_osImage_c14b1a46_2428_44c1_9c1a_b20e6418d08c.xml
+libcloud/test/compute/fixtures/dimensiondata/2.4/import_image_response.xml
+libcloud/test/compute/fixtures/dimensiondata/2.4/server_GetServer.xml
+libcloud/test/compute/fixtures/dimensiondata/2.4/server_cleanServer.xml
+libcloud/test/compute/fixtures/dimensiondata/2.4/server_clone_response.xml
+libcloud/test/compute/fixtures/dimensiondata/2.4/server_server.xml
+libcloud/test/compute/fixtures/dimensiondata/2.4/server_server_NA3.xml
+libcloud/test/compute/fixtures/dimensiondata/2.4/server_server_e75ead52_692f_4314_8725_c8a4f4d13a87.xml
+libcloud/test/compute/fixtures/dimensiondata/2.4/server_server_paginated.xml
 libcloud/test/compute/fixtures/ec2/allocate_address.xml
 libcloud/test/compute/fixtures/ec2/allocate_vpc_address.xml
 libcloud/test/compute/fixtures/ec2/associate_address.xml
@@ -895,7 +920,7 @@ libcloud/test/compute/fixtures/elastichosts/servers_create.json
 libcloud/test/compute/fixtures/elastichosts/servers_info.json
 libcloud/test/compute/fixtures/fcu/ex_describe_instance_types.xml
 libcloud/test/compute/fixtures/fcu/ex_describe_product_types.xml
-libcloud/test/compute/fixtures/fcu/ex_describe_quota.xml
+libcloud/test/compute/fixtures/fcu/ex_describe_quotas.xml
 libcloud/test/compute/fixtures/fcu/ex_get_product_type.xml
 libcloud/test/compute/fixtures/fcu/ex_modify_instance_keypair.xml
 libcloud/test/compute/fixtures/gandi/account_info.xml
@@ -1161,6 +1186,7 @@ libcloud/test/compute/fixtures/gce/zones_us-central1-b_instanceGroup_myinstanceg
 libcloud/test/compute/fixtures/gce/zones_us-central1-b_instances_libcloud-lb-demo-www-000.json
 libcloud/test/compute/fixtures/gce/zones_us-central1-b_instances_libcloud-lb-demo-www-001.json
 libcloud/test/compute/fixtures/gce/zones_us-central1-b_instances_libcloud-lb-demo-www-002.json
+libcloud/test/compute/fixtures/gce/zones_us-central1-b_instances_libcloud-lb-nopubip-001.json
 libcloud/test/compute/fixtures/gce/zones_us-east1-b.json
 libcloud/test/compute/fixtures/gce/zones_us-east1-b_instanceGroupManagers.json
 libcloud/test/compute/fixtures/gce/zones_us-east1-b_instanceGroup_myinstancegroup.json
@@ -1918,7 +1944,8 @@ libcloud/test/loadbalancer/__init__.py
 libcloud/test/loadbalancer/test_alb.py
 libcloud/test/loadbalancer/test_brightbox.py
 libcloud/test/loadbalancer/test_cloudstack.py
-libcloud/test/loadbalancer/test_dimensiondata.py
+libcloud/test/loadbalancer/test_dimensiondata_v2_3.py
+libcloud/test/loadbalancer/test_dimensiondata_v2_4.py
 libcloud/test/loadbalancer/test_elb.py
 libcloud/test/loadbalancer/test_gce.py
 libcloud/test/loadbalancer/test_gogrid.py
diff --git a/libcloud/__init__.py b/libcloud/__init__.py
index 9689bb8..ecb5319 100644
--- a/libcloud/__init__.py
+++ b/libcloud/__init__.py
@@ -36,7 +36,7 @@ __all__ = [
     '__version__',
     'enable_debug'
 ]
-__version__ = '1.4.0'
+__version__ = '1.5.0'
 
 
 def enable_debug(fo):
diff --git a/libcloud/common/dimensiondata.py b/libcloud/common/dimensiondata.py
index bdcde5a..f76d52a 100644
--- a/libcloud/common/dimensiondata.py
+++ b/libcloud/common/dimensiondata.py
@@ -17,13 +17,15 @@ Dimension Data Common Components
 """
 from base64 import b64encode
 from time import sleep
+# TODO: use disutils.version when Travis CI fixed the pylint issue with version
+# from distutils.version import LooseVersion
 from libcloud.utils.py3 import httplib
 from libcloud.utils.py3 import b
 from libcloud.common.base import ConnectionUserAndKey, XmlResponse, RawResponse
-from libcloud.common.types import LibcloudError, InvalidCredsError
 from libcloud.compute.base import Node
 from libcloud.utils.py3 import basestring
 from libcloud.utils.xml import findtext
+from libcloud.compute.types import LibcloudError, InvalidCredsError
 
 # Roadmap / TODO:
 #
@@ -267,7 +269,7 @@ API_ENDPOINTS = {
         'name': 'Africa (AF)',
         'host': 'afapi.bsnlcloud.com',
         'vendor': 'BSNL'
-    },
+    }
 }
 
 # Default API end-point for the base connection class.
@@ -313,6 +315,12 @@ def dd_object_to_id(obj, obj_type, id_value='id'):
         )
 
 
+# TODO: use disutils.version when Travis CI fixed the pylint issue with version
+#       This is a temporary workaround.
+def LooseVersion(version):
+    return float(version)
+
+
 class NetworkDomainServicePlan(object):
     ESSENTIALS = "ESSENTIALS"
     ADVANCED = "ADVANCED"
@@ -372,8 +380,16 @@ class DimensionDataConnection(ConnectionUserAndKey):
 
     api_path_version_1 = '/oec'
     api_path_version_2 = '/caas'
-    api_version_1 = '0.9'
-    api_version_2 = '2.3'
+    api_version_1 = 0.9
+
+    # Earliest version supported
+    oldest_api_version = '2.2'
+
+    # Latest version supported
+    latest_api_version = '2.4'
+
+    # Default api version
+    active_api_version = '2.4'
 
     _orgId = None
     responseCls = DimensionDataResponse
@@ -382,7 +398,8 @@ class DimensionDataConnection(ConnectionUserAndKey):
     allow_insecure = False
 
     def __init__(self, user_id, key, secure=True, host=None, port=None,
-                 url=None, timeout=None, proxy_url=None, **conn_kwargs):
+                 url=None, timeout=None, proxy_url=None,
+                 api_version=None, **conn_kwargs):
         super(DimensionDataConnection, self).__init__(
             user_id=user_id,
             key=key,
@@ -394,6 +411,31 @@ class DimensionDataConnection(ConnectionUserAndKey):
         if conn_kwargs['region']:
             self.host = conn_kwargs['region']['host']
 
+        if api_version:
+            if LooseVersion(api_version) < LooseVersion(
+                    self.oldest_api_version):
+                msg = 'API Version specified is too old. No longer ' \
+                      'supported. Please upgrade to the latest version {}' \
+                    .format(self.active_api_version)
+
+                raise DimensionDataAPIException(code=None,
+                                                msg=msg,
+                                                driver=self.driver)
+            elif LooseVersion(api_version) > LooseVersion(
+                    self.latest_api_version):
+                msg = 'Unsupported API Version. The version specified is ' \
+                      'not release yet. Please use the latest supported ' \
+                      'version {}' \
+                    .format(self.active_api_version)
+
+                raise DimensionDataAPIException(code=None,
+                                                msg=msg,
+                                                driver=self.driver)
+
+            else:
+                # Overwrite default version using the version user specified
+                self.active_api_version = api_version
+
     def add_default_headers(self, headers):
         headers['Authorization'] = \
             ('Basic %s' % b64encode(b('%s:%s' % (self.user_id,
@@ -414,7 +456,7 @@ class DimensionDataConnection(ConnectionUserAndKey):
     def request_api_2(self, path, action, params=None, data='',
                       headers=None, method='GET'):
         action = "%s/%s/%s/%s" % (self.api_path_version_2,
-                                  self.api_version_2, path, action)
+                                  self.active_api_version, path, action)
 
         return super(DimensionDataConnection, self).request(
             action=action,
@@ -515,7 +557,7 @@ class DimensionDataConnection(ConnectionUserAndKey):
         resources that require a full path instead of just an ID, such as
         networks, and customer snapshots.
         """
-        return ("%s/%s/%s" % (self.api_path_version_2, self.api_version_2,
+        return ("%s/%s/%s" % (self.api_path_version_2, self.active_api_version,
                               self._get_orgId()))
 
     def wait_for_state(self, state, func, poll_interval=2, timeout=60, *args,
diff --git a/libcloud/common/openstack_identity.py b/libcloud/common/openstack_identity.py
index 3a81219..da90f0b 100644
--- a/libcloud/common/openstack_identity.py
+++ b/libcloud/common/openstack_identity.py
@@ -24,7 +24,8 @@ import datetime
 from libcloud.utils.py3 import httplib
 from libcloud.utils.iso8601 import parse_date
 
-from libcloud.common.base import ConnectionUserAndKey, Response
+from libcloud.common.base import (ConnectionUserAndKey, Response,
+                                  CertificateConnection)
 from libcloud.compute.types import (LibcloudError, InvalidCredsError,
                                     MalformedResponseError)
 
@@ -41,6 +42,7 @@ AUTH_VERSIONS_WITH_EXPIRES = [
     '2.0',
     '2.0_apikey',
     '2.0_password',
+    '2.0_voms',
     '3.0',
     '3.x_password',
     '3.x_oidc_access_token'
@@ -69,6 +71,7 @@ __all__ = [
     'OpenStackIdentity_1_0_Connection',
     'OpenStackIdentity_1_1_Connection',
     'OpenStackIdentity_2_0_Connection',
+    'OpenStackIdentity_2_0_Connection_VOMS',
     'OpenStackIdentity_3_0_Connection',
     'OpenStackIdentity_3_0_Connection_OIDC_access_token',
 
@@ -1536,6 +1539,139 @@ class OpenStackIdentity_3_0_Connection_OIDC_access_token(
                                          driver=self.driver)
 
 
+class OpenStackIdentity_2_0_Connection_VOMS(OpenStackIdentityConnection,
+                                            CertificateConnection):
+    """
+    Connection class for Keystone API v2.0. with VOMS proxy support
+    In this case the key parameter will be the path of the VOMS proxy file.
+    """
+
+    responseCls = OpenStackAuthResponse
+    name = 'OpenStack Identity API v2.0 VOMS support'
+    auth_version = '2.0'
+
+    def __init__(self, auth_url, user_id, key, tenant_name=None,
+                 domain_name='Default',
+                 token_scope=OpenStackIdentityTokenScope.PROJECT,
+                 timeout=None, parent_conn=None):
+        CertificateConnection.__init__(self, cert_file=key,
+                                       url=auth_url,
+                                       timeout=timeout)
+
+        self.parent_conn = parent_conn
+
+        # enable tests to use the same mock connection classes.
+        if parent_conn:
+            self.conn_classes = parent_conn.conn_classes
+            self.driver = parent_conn.driver
+        else:
+            self.driver = None
+
+        self.auth_url = auth_url
+        self.tenant_name = tenant_name
+        self.domain_name = domain_name
+        self.token_scope = token_scope
+        self.timeout = timeout
+
+        self.urls = {}
+        self.auth_token = None
+        self.auth_token_expires = None
+        self.auth_user_info = None
+
+    def authenticate(self, force=False):
+        if not self._is_authentication_needed(force=force):
+            return self
+
+        tenant = self.tenant_name
+        if not tenant:
+            # if the tenant name is not specified look for it
+            token = self._get_unscoped_token()
+            tenant = self._get_tenant_name(token)
+
+        data = {'auth': {'voms': True, 'tenantName': tenant}}
+
+        reqbody = json.dumps(data)
+        return self._authenticate_2_0_with_body(reqbody)
+
+    def _get_unscoped_token(self):
+        """
+        Get unscoped token from VOMS proxy
+        """
+        data = {'auth': {'voms': True}}
+        reqbody = json.dumps(data)
+
+        response = self.request('/v2.0/tokens', data=reqbody,
+                                headers={'Content-Type': 'application/json'},
+                                method='POST')
+
+        if response.status == httplib.UNAUTHORIZED:
+            # Invalid credentials
+            raise InvalidCredsError()
+        elif response.status in [httplib.OK, httplib.CREATED]:
+            try:
+                body = json.loads(response.body)
+                return body['access']['token']['id']
+            except Exception:
+                e = sys.exc_info()[1]
+                raise MalformedResponseError('Failed to parse JSON', e)
+        else:
+            raise MalformedResponseError('Malformed response',
+                                         driver=self.driver)
+
+    def _get_tenant_name(self, token):
+        """
+        Get the first available tenant name (usually there are only one)
+        """
+        headers = {'Accept': 'application/json',
+                   'Content-Type': 'application/json',
+                   'X-Auth-Token': token}
+        response = self.request('/v2.0/tenants', headers=headers, method='GET')
+
+        if response.status == httplib.UNAUTHORIZED:
+            # Invalid credentials
+            raise InvalidCredsError()
+        elif response.status in [httplib.OK, httplib.CREATED]:
+            try:
+                body = json.loads(response.body)
+                return body["tenants"][0]["name"]
+            except Exception:
+                e = sys.exc_info()[1]
+                raise MalformedResponseError('Failed to parse JSON', e)
+        else:
+            raise MalformedResponseError('Malformed response',
+                                         driver=self.driver)
+
+    def _authenticate_2_0_with_body(self, reqbody):
+        resp = self.request('/v2.0/tokens', data=reqbody,
+                            headers={'Content-Type': 'application/json'},
+                            method='POST')
+
+        if resp.status == httplib.UNAUTHORIZED:
+            raise InvalidCredsError()
+        elif resp.status not in [httplib.OK,
+                                 httplib.NON_AUTHORITATIVE_INFORMATION]:
+            body = 'code: %s body: %s' % (resp.status, resp.body)
+            raise MalformedResponseError('Malformed response', body=body,
+                                         driver=self.driver)
+        else:
+            body = resp.object
+
+            try:
+                access = body['access']
+                expires = access['token']['expires']
+
+                self.auth_token = access['token']['id']
+                self.auth_token_expires = parse_date(expires)
+                self.urls = access['serviceCatalog']
+                self.auth_user_info = access.get('user', {})
+            except KeyError:
+                e = sys.exc_info()[1]
+                raise MalformedResponseError('Auth JSON response is \
+                                             missing required elements', e)
+
+        return self
+
+
 def get_class_for_auth_version(auth_version):
     """
     Retrieve class for the provided auth version.
@@ -1548,6 +1684,8 @@ def get_class_for_auth_version(auth_version):
         cls = OpenStackIdentity_2_0_Connection
     elif auth_version == '2.0_password':
         cls = OpenStackIdentity_2_0_Connection
+    elif auth_version == '2.0_voms':
+        cls = OpenStackIdentity_2_0_Connection_VOMS
     elif auth_version == '3.x_password':
         cls = OpenStackIdentity_3_0_Connection
     elif auth_version == '3.x_oidc_access_token':
diff --git a/libcloud/compute/drivers/azure_arm.py b/libcloud/compute/drivers/azure_arm.py
index c7d0441..44abcdb 100644
--- a/libcloud/compute/drivers/azure_arm.py
+++ b/libcloud/compute/drivers/azure_arm.py
@@ -709,6 +709,40 @@ class AzureNodeDriver(NodeDriver):
 
         return True
 
+    def ex_get_ratecard(self, offer_durable_id, currency='USD',
+                        locale='en-US', region='US'):
+        """
+        Get rate card
+
+        :param offer_durable_id: ID of the offer applicable for this
+        user account. (e.g. "0026P")
+        See http://azure.microsoft.com/en-us/support/legal/offer-details/
+        :type offer_durable_id: str
+
+        :param currency: Desired currency for the response (default: "USD")
+        :type currency: ``str``
+
+        :param locale: Locale (default: "en-US")
+        :type locale: ``str``
+
+        :param region: Region (two-letter code) (default: "US")
+        :type regions: ``str``
+
+        :return: A dictionary of rates whose ID's correspond to nothing at all
+        :rtype: ``dict``
+        """
+
+        action = "/subscriptions/%s/providers/Microsoft.Commerce/" \
+                 "RateCard" % (self.subscription_id,)
+        params = {"api-version": "2016-08-31-preview",
+                  "$filter": "OfferDurableId eq 'MS-AZR-%s' and "
+                             "Currency eq '%s' and "
+                             "Locale eq '%s' and "
+                             "RegionInfo eq '%s'" %
+                             (offer_durable_id, currency, locale, region)}
+        r = self.connection.request(action, params=params)
+        return r.object
+
     def ex_list_publishers(self, location=None):
         """
         List node image publishers.
diff --git a/libcloud/compute/drivers/cloudscale.py b/libcloud/compute/drivers/cloudscale.py
new file mode 100644
index 0000000..98034ec
--- /dev/null
+++ b/libcloud/compute/drivers/cloudscale.py
@@ -0,0 +1,246 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+A driver for cloudscale.ch.
+"""
+
+import json
+
+from libcloud.utils.py3 import httplib
+
+from libcloud.common.base import ConnectionKey, JsonResponse
+from libcloud.compute.types import Provider, NodeState
+from libcloud.common.types import InvalidCredsError
+from libcloud.compute.base import NodeDriver
+from libcloud.compute.base import Node, NodeImage, NodeSize
+
+
+class CloudscaleResponse(JsonResponse):
+    valid_response_codes = [httplib.OK, httplib.ACCEPTED, httplib.CREATED,
+                            httplib.NO_CONTENT]
+
+    def parse_error(self):
+        body = self.parse_body()
+        if self.status == httplib.UNAUTHORIZED:
+            raise InvalidCredsError(body['detail'])
+        else:
+            # We are taking the first issue here. There might be multiple ones,
+            # but that doesn't really matter. It's nicer if the error is just
+            # one error (because it's a Python API and there's only one
+            # exception.
+            return next(iter(body.values()))
+
+    def success(self):
+        return self.status in self.valid_response_codes
+
+
+class CloudscaleConnection(ConnectionKey):
+    """
+    Connection class for the cloudscale.ch driver.
+    """
+    host = 'api.cloudscale.ch'
+    responseCls = CloudscaleResponse
+
+    def add_default_headers(self, headers):
+        """
+        Add headers that are necessary for every request
+
+        This method adds ``token`` to the request.
+        """
+        headers['Authorization'] = 'Bearer %s' % (self.key)
+        headers['Content-Type'] = 'application/json'
+        return headers
+
+
+class CloudscaleNodeDriver(NodeDriver):
+    """
+    Cloudscale's node driver.
+    """
+
+    connectionCls = CloudscaleConnection
+
+    type = Provider.CLOUDSCALE
+    name = 'Cloudscale'
+    website = 'https://www.cloudscale.ch'
+
+    NODE_STATE_MAP = dict(
+        changing=NodeState.PENDING,
+        running=NodeState.RUNNING,
+        stopped=NodeState.STOPPED,
+        paused=NodeState.PAUSED,
+    )
+
+    def __init__(self, key, **kwargs):
+        super(CloudscaleNodeDriver, self).__init__(key, **kwargs)
+
+    def list_nodes(self):
+        '''
+        List all your existing compute nodes.
+        '''
+        return self._list_resources('/v1/servers', self._to_node)
+
+    def list_sizes(self):
+        '''
+        Lists all available sizes. On cloudscale these are known as flavors.
+        '''
+        return self._list_resources('/v1/flavors', self._to_size)
+
+    def list_images(self):
+        '''
+        List all images.
+
+        Images are identified by slugs on cloudscale.ch. This means that minor
+        version upgrades (e.g. Ubuntu 16.04.1 to Ubuntu 16.04.2) will be
+        possible within the same id ``ubuntu-16.04``.
+        '''
+        return self._list_resources('/v1/images', self._to_image)
+
+    def create_node(self, name, size, image, location=None, ex_create_attr={}):
+        """
+        Create a node.
+
+        The `ex_create_attr` parameter can include the following dictionary
+        key and value pairs:
+
+        * `ssh_keys`: ``list`` of ``str`` ssh public keys
+        * `volume_size_gb`: ``int`` defaults to 10.
+        * `bulk_volume_size_gb`: defaults to None.
+        * `use_public_network`: ``bool`` defaults to True
+        * `use_private_network`: ``bool`` defaults to False
+        * `use_ipv6`: ``bool`` defaults to True
+        * `anti_affinity_with`: ``uuid`` of a server to create an anti-affinity
+          group with that server or add it to the same group as that server.
+        * `user_data`: ``str`` for optional cloud-config data
+
+        :keyword ex_create_attr: A dictionary of optional attributes for
+                                 droplet creation
+        :type ex_create_attr: ``dict``
+
+        :return: The newly created node.
+        :rtype: :class:`Node`
+        """
+        attr = dict(ex_create_attr)
+        attr.update(
+            name=name,
+            image=image.id,
+            flavor=size.id,
+        )
+        result = self.connection.request(
+            '/v1/servers',
+            data=json.dumps(attr),
+            method='POST'
+        )
+        return self._to_node(result.object)
+
+    def reboot_node(self, node):
+        '''
+        Reboot a node. It's also possible to use ``node.reboot()``.
+        '''
+        return self._action(node, 'reboot')
+
+    def ex_start_node(self, node):
+        '''
+        Start a node. This is only possible if the node is stopped.
+        '''
+        return self._action(node, 'start')
+
+    def ex_stop_node(self, node):
+        '''
+        Stop a specific node. Similar to ``shutdown -h now``. This is only
+        possible if the node is running.
+        '''
+        return self._action(node, 'stop')
+
+    def ex_node_by_uuid(self, uuid):
+        '''
+        :param str ex_user_data: A valid uuid that references your exisiting
+            cloudscale.ch server.
+        :type       ex_user_data:  ``str``
+
+        :return: The server node you asked for.
+        :rtype: :class:`Node`
+        '''
+        res = self.connection.request(self._get_server_url(uuid))
+        return self._to_node(res.object)
+
+    def destroy_node(self, node):
+        '''
+        Delete a node. It's also possible to use ``node.destroy()``.
+        This will irreversibly delete the cloudscale.ch server and all its
+        volumes. So please be cautious.
+        '''
+        res = self.connection.request(
+            self._get_server_url(node.id),
+            method='DELETE'
+        )
+        return res.status == httplib.NO_CONTENT
+
+    def _get_server_url(self, uuid):
+        return '/v1/servers/%s' % uuid
+
+    def _action(self, node, action_name):
+        response = self.connection.request(
+            self._get_server_url(node.id) + '/' + action_name,
+            method='POST'
+        )
+        return response.status == httplib.OK
+
+    def _list_resources(self, url, tranform_func):
+        data = self.connection.request(url, method='GET').object
+        return [tranform_func(obj) for obj in data]
+
+    def _to_node(self, data):
+        state = self.NODE_STATE_MAP.get(data['status'], NodeState.UNKNOWN)
+        extra_keys = ['volumes', 'interfaces', 'anti_affinity_with']
+        extra = {}
+        for key in extra_keys:
+            if key in data:
+                extra[key] = data[key]
+
+        public_ips = []
+        private_ips = []
+        for interface in data['interfaces']:
+            if interface['type'] == 'public':
+                ips = public_ips
+            else:
+                ips = private_ips
+            for address_obj in interface['addresses']:
+                ips.append(address_obj['address'])
+
+        return Node(
+            id=data['uuid'],
+            name=data['name'],
+            state=state,
+            public_ips=public_ips,
+            private_ips=private_ips,
+            extra=extra,
+            driver=self,
+            image=self._to_image(data['image']),
+            size=self._to_size(data['flavor']),
+        )
+
+    def _to_size(self, data):
+        extra = {'vcpu_count': data['vcpu_count']}
+        ram = data['memory_gb'] * 1024
+
+        return NodeSize(id=data['slug'], name=data['name'],
+                        ram=ram, disk=10,
+                        bandwidth=0, price=0,
+                        extra=extra, driver=self)
+
+    def _to_image(self, data):
+        extra = {'operating_system': data['operating_system']}
+        return NodeImage(id=data['slug'], name=data['name'], extra=extra,
+                         driver=self)
diff --git a/libcloud/compute/drivers/dimensiondata.py b/libcloud/compute/drivers/dimensiondata.py
index 76dfb55..c47eedd 100644
--- a/libcloud/compute/drivers/dimensiondata.py
+++ b/libcloud/compute/drivers/dimensiondata.py
@@ -21,6 +21,7 @@ try:
 except ImportError:
     from xml.etree import ElementTree as ET
 
+from libcloud.common.dimensiondata import LooseVersion
 from libcloud.common.exceptions import BaseHTTPError
 from libcloud.compute.base import NodeDriver, Node, NodeAuthPassword
 from libcloud.compute.base import NodeSize, NodeImage, NodeLocation
@@ -56,15 +57,26 @@ from libcloud.utils.py3 import urlencode, ensure_string
 from libcloud.utils.xml import fixxpath, findtext, findall
 from libcloud.utils.py3 import basestring
 from libcloud.compute.types import NodeState, Provider
+import sys
 
 # Node state map is a dictionary with the keys as tuples
 # These tuples represent:
 # (<state_of_node_from_didata>, <is node started?>, <action happening>)
 NODE_STATE_MAP = {
-    ('NORMAL', 'true', None):
-        NodeState.RUNNING,
     ('NORMAL', 'false', None):
         NodeState.STOPPED,
+    ('PENDING_CHANGE', 'false', None):
+        NodeState.PENDING,
+    ('PENDING_CHANGE', 'false', 'CHANGE_NETWORK_ADAPTER'):
+        NodeState.PENDING,
+    ('PENDING_CHANGE', 'true', 'CHANGE_NETWORK_ADAPTER'):
+        NodeState.PENDING,
+    ('PENDING_CHANGE', 'false', 'EXCHANGE_NIC_VLANS'):
+        NodeState.PENDING,
+    ('PENDING_CHANGE', 'true', 'EXCHANGE_NIC_VLANS'):
+        NodeState.PENDING,
+    ('NORMAL', 'true', None):
+        NodeState.RUNNING,
     ('PENDING_CHANGE', 'true', 'START_SERVER'):
         NodeState.STARTING,
     ('PENDING_ADD', 'true', 'DEPLOY_SERVER'):
@@ -95,6 +107,7 @@ OBJECT_TO_TAGGING_ASSET_TYPE_MAP = {
 class DimensionDataNodeDriver(NodeDriver):
     """
     DimensionData node driver.
+    Default api_version is used unless specified.
     """
 
     selected_region = None
@@ -114,6 +127,9 @@ class DimensionDataNodeDriver(NodeDriver):
         if region is not None:
             self.selected_region = API_ENDPOINTS[region]
 
+        if api_version is not None:
+            self.api_version = api_version
+
         super(DimensionDataNodeDriver, self).__init__(key=key, secret=secret,
                                                       secure=secure, host=host,
                                                       port=port,
@@ -129,6 +145,7 @@ class DimensionDataNodeDriver(NodeDriver):
         kwargs = super(DimensionDataNodeDriver,
                        self)._ex_connection_class_kwargs()
         kwargs['region'] = self.selected_region
+        kwargs['api_version'] = self.api_version
         return kwargs
 
     def _create_node_mcp1(self, name, image, auth, ex_description,
@@ -365,7 +382,6 @@ class DimensionDataNodeDriver(NodeDriver):
         >>> #                           ex_primary_nic_network_adapter='E1000',
         >>> #                           ex_is_started=False)
         >>>
-        >>> pprint(node)
 
         :keyword    name:   (required) String with a name for this new node
         :type       name:   ``str``
@@ -476,6 +492,10 @@ class DimensionDataNodeDriver(NodeDriver):
                              'ex_network_domain '
                              'for MCP2 or ex_network for legacy MCP1')
 
+        # Set ex_is_started to False by default if none bool data type provided
+        if not isinstance(ex_is_started, bool):
+            ex_is_started = True
+
         # Handle MCP1 legacy
         if 'ex_network' in kwargs:
             new_node = self._create_node_mcp1(
@@ -783,7 +803,7 @@ class DimensionDataNodeDriver(NodeDriver):
 
         Note:  Currently only returns the default 'base OS images'
                provided by DimensionData. Customer images (snapshots)
-               are not yet supported.
+               use ex_list_customer_images
 
         :keyword ex_location: Filters the node list to nodes that are
                               located in this location
@@ -864,6 +884,109 @@ class DimensionDataNodeDriver(NodeDriver):
             .request_with_orgId_api_1('networkWithLocation%s' % url_ext)
             .object)
 
+    def import_image(self, ovf_package_name, name,
+                     cluster_id=None, datacenter_id=None, description=None,
+                     is_guest_os_customization=None,
+                     tagkey_name_value_dictionaries=None):
+        """
+        Import image
+
+        :param ovf_package_name: Image OVF package name
+        :type  ovf_package_name: ``str``
+
+        :param name: Image name
... 88224 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/libcloud.git



More information about the Python-modules-commits mailing list