You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by to...@apache.org on 2013/01/11 14:22:44 UTC
svn commit: r1432038 - in /libcloud/trunk/libcloud: common/aws.py
compute/drivers/ec2.py dns/drivers/route53.py dns/types.py
loadbalancer/drivers/elb.py
Author: tomaz
Date: Fri Jan 11 13:22:43 2013
New Revision: 1432038
URL: http://svn.apache.org/viewvc?rev=1432038&view=rev
Log:
Remove duplicated authentication code in AWS drivers and refactor common
functionality into a new class.
Part of LIBCLOUD-169.
Modified:
libcloud/trunk/libcloud/common/aws.py
libcloud/trunk/libcloud/compute/drivers/ec2.py
libcloud/trunk/libcloud/dns/drivers/route53.py
libcloud/trunk/libcloud/dns/types.py
libcloud/trunk/libcloud/loadbalancer/drivers/elb.py
Modified: libcloud/trunk/libcloud/common/aws.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/common/aws.py?rev=1432038&r1=1432037&r2=1432038&view=diff
==============================================================================
--- libcloud/trunk/libcloud/common/aws.py (original)
+++ libcloud/trunk/libcloud/common/aws.py Fri Jan 11 13:22:43 2013
@@ -13,8 +13,129 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from libcloud.common.base import XmlResponse
+import base64
+import hmac
+import time
+from hashlib import sha256
+from xml.etree import ElementTree as ET
+
+from libcloud.common.base import ConnectionUserAndKey, XmlResponse
+from libcloud.common.types import InvalidCredsError, MalformedResponseError
+from libcloud.utils.py3 import b, httplib, urlquote
+from libcloud.utils.xml import findtext, findall
class AWSBaseResponse(XmlResponse):
pass
+
+
+class AWSGenericResponse(AWSBaseResponse):
+ # There are multiple error messages in AWS, but they all have an Error node
+ # with Code and Message child nodes. Xpath to select them
+ # None if the root node *is* the Error node
+ xpath = None
+
+ # This dict maps <Error><Code>CodeName</Code></Error> to a specific
+ # exception class that is raised immediately.
+ # If a custom exception class is not defined, errors are accumulated and
+ # returned from the parse_error method.
+ expections = {}
+
+ def success(self):
+ return self.status in [httplib.OK, httplib.CREATED, httplib.ACCEPTED]
+
+ def parse_error(self):
+ context = self.connection.context
+ status = int(self.status)
+
+ # FIXME: Probably ditch this as the forbidden message will have
+ # corresponding XML.
+ if status == httplib.FORBIDDEN:
+ if not self.body:
+ raise InvalidCredsError(str(self.status) + ': ' + self.error)
+ else:
+ raise InvalidCredsError(self.body)
+
+ try:
+ body = ET.XML(self.body)
+ except Exception:
+ raise MalformedResponseError('Failed to parse XML',
+ body=self.body,
+ driver=self.connection.driver)
+
+ if self.xpath:
+ errs = findall(element=body, xpath=self.xpath,
+ namespace=self.namespace)
+ else:
+ errs = [body]
+
+ msgs = []
+ for err in errs:
+ code = findtext(element=err, xpath='Code',
+ namespace=self.namespace)
+ message = findtext(element=err, xpath='Message',
+ namespace=self.namespace)
+
+ exceptionCls = self.exceptions.get(code, None)
+
+ if exceptionCls is None:
+ msgs.append('%s: %s' % (code, message))
+ continue
+
+ # Custom exception class is defined, immediately throw an exception
+ params = {}
+ if hasattr(exceptionCls, 'kwargs'):
+ for key in exceptionCls.kwargs:
+ if key in context:
+ params[key] = context[key]
+
+ raise exceptionCls(value=message, driver=self.connection.driver,
+ **params)
+
+ return "\n".join(msgs)
+
+
+class SignedAWSConnection(ConnectionUserAndKey):
+ def add_default_params(self, params):
+ params['SignatureVersion'] = '2'
+ params['SignatureMethod'] = 'HmacSHA256'
+ params['AWSAccessKeyId'] = self.user_id
+ params['Version'] = self.version
+ params['Timestamp'] = time.strftime('%Y-%m-%dT%H:%M:%SZ',
+ time.gmtime())
+ params['Signature'] = self._get_aws_auth_param(params, self.key,
+ self.action)
+ return params
+
+ def _get_aws_auth_param(self, params, secret_key, path='/'):
+ """
+ Creates the signature required for AWS, per
+ http://bit.ly/aR7GaQ [docs.amazonwebservices.com]:
+
+ StringToSign = HTTPVerb + "\n" +
+ ValueOfHostHeaderInLowercase + "\n" +
+ HTTPRequestURI + "\n" +
+ CanonicalizedQueryString <from the preceding step>
+ """
+ keys = list(params.keys())
+ keys.sort()
+ pairs = []
+ for key in keys:
+ pairs.append(urlquote(key, safe='') + '=' +
+ urlquote(params[key], safe='-_~'))
+
+ qs = '&'.join(pairs)
+
+ hostname = self.host
+ if (self.secure and self.port != 443) or \
+ (not self.secure and self.port != 80):
+ hostname += ":" + str(self.port)
+
+ string_to_sign = '\n'.join(('GET', hostname, path, qs))
+
+ b64_hmac = base64.b64encode(
+ hmac.new(b(secret_key), b(string_to_sign),
+ digestmod=sha256).digest()
+ )
+
+ return b64_hmac.decode('utf-8')
Modified: libcloud/trunk/libcloud/compute/drivers/ec2.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/compute/drivers/ec2.py?rev=1432038&r1=1432037&r2=1432038&view=diff
==============================================================================
--- libcloud/trunk/libcloud/compute/drivers/ec2.py (original)
+++ libcloud/trunk/libcloud/compute/drivers/ec2.py Fri Jan 11 13:22:43 2013
@@ -21,20 +21,15 @@ from __future__ import with_statement
import sys
import base64
-import hmac
import os
-import time
import copy
-from hashlib import sha256
from xml.etree import ElementTree as ET
-from libcloud.utils.py3 import urlquote
from libcloud.utils.py3 import b
from libcloud.utils.xml import fixxpath, findtext, findattr, findall
-from libcloud.common.base import ConnectionUserAndKey
-from libcloud.common.aws import AWSBaseResponse
+from libcloud.common.aws import AWSBaseResponse, SignedAWSConnection
from libcloud.common.types import (InvalidCredsError, MalformedResponseError,
LibcloudError)
from libcloud.compute.providers import Provider
@@ -373,57 +368,15 @@ class EC2Response(AWSBaseResponse):
return "\n".join(err_list)
-class EC2Connection(ConnectionUserAndKey):
+class EC2Connection(SignedAWSConnection):
"""
Represents a single connection to the EC2 Endpoint.
"""
+ version = API_VERSION
host = REGION_DETAILS['us-east-1']['endpoint']
responseCls = EC2Response
- def add_default_params(self, params):
- params['SignatureVersion'] = '2'
- params['SignatureMethod'] = 'HmacSHA256'
- params['AWSAccessKeyId'] = self.user_id
- params['Version'] = API_VERSION
- params['Timestamp'] = time.strftime('%Y-%m-%dT%H:%M:%SZ',
- time.gmtime())
- params['Signature'] = self._get_aws_auth_param(params, self.key,
- self.action)
- return params
-
- def _get_aws_auth_param(self, params, secret_key, path='/'):
- """
- Creates the signature required for AWS, per
- http://bit.ly/aR7GaQ [docs.amazonwebservices.com]:
-
- StringToSign = HTTPVerb + "\n" +
- ValueOfHostHeaderInLowercase + "\n" +
- HTTPRequestURI + "\n" +
- CanonicalizedQueryString <from the preceding step>
- """
- keys = list(params.keys())
- keys.sort()
- pairs = []
- for key in keys:
- pairs.append(urlquote(key, safe='') + '=' +
- urlquote(params[key], safe='-_~'))
-
- qs = '&'.join(pairs)
-
- hostname = self.host
- if (self.secure and self.port != 443) or \
- (not self.secure and self.port != 80):
- hostname += ":" + str(self.port)
-
- string_to_sign = '\n'.join(('GET', hostname, path, qs))
-
- b64_hmac = base64.b64encode(
- hmac.new(b(secret_key), b(string_to_sign),
- digestmod=sha256).digest()
- )
- return b64_hmac.decode('utf-8')
-
class ExEC2AvailabilityZone(object):
"""
Modified: libcloud/trunk/libcloud/dns/drivers/route53.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/dns/drivers/route53.py?rev=1432038&r1=1432037&r2=1432038&view=diff
==============================================================================
--- libcloud/trunk/libcloud/dns/drivers/route53.py (original)
+++ libcloud/trunk/libcloud/dns/drivers/route53.py Fri Jan 11 13:22:43 2013
@@ -32,9 +32,8 @@ from libcloud.utils.xml import findtext,
from libcloud.dns.types import Provider, RecordType
from libcloud.dns.types import ZoneDoesNotExistError, RecordDoesNotExistError
from libcloud.dns.base import DNSDriver, Zone, Record
-from libcloud.common.types import InvalidCredsError
from libcloud.common.types import LibcloudError
-from libcloud.common.aws import AWSBaseResponse
+from libcloud.common.aws import AWSGenericResponse
from libcloud.common.base import ConnectionUserAndKey
@@ -49,44 +48,18 @@ class InvalidChangeBatch(LibcloudError):
pass
-class Route53DNSResponse(AWSBaseResponse):
+class Route53DNSResponse(AWSGenericResponse):
"""
Amazon Route53 response class.
"""
- def success(self):
- return self.status in [httplib.OK, httplib.CREATED, httplib.ACCEPTED]
- def parse_error(self):
- context = self.connection.context
- status = int(self.status)
-
- if status == httplib.FORBIDDEN:
- if not self.body:
- raise InvalidCredsError(str(self.status) + ': ' + self.error)
- else:
- raise InvalidCredsError(self.body)
+ namespace = NAMESPACE
+ xpath = 'Error'
- try:
- body = ET.XML(self.body)
- except Exception:
- raise MalformedResponseError('Failed to parse XML',
- body=self.body, driver=self.driver)
-
- errs = findall(element=body, xpath='Error', namespace=NAMESPACE)
-
- if errs:
- t, code, message = errs[0].getchildren()
-
- if code.text == 'NoSuchHostedZone':
- zone_id = context.get('zone_id', None)
- raise ZoneDoesNotExistError(value=message.text, driver=self,
- zone_id=zone_id)
- elif code.text == 'InvalidChangeBatch':
- raise InvalidChangeBatch(value=message.text)
- else:
- return message.text
-
- return self.body
+ exceptions = {
+ 'NoSuchHostedZone': ZoneDoesNotExistError,
+ 'InvalidChangeBatch': InvalidChangeBatch,
+ }
class Route53Connection(ConnectionUserAndKey):
@@ -268,7 +241,7 @@ class Route53DNSDriver(DNSDriver):
uri = API_ROOT + 'hostedzone/' + zone.id + '/rrset'
data = ET.tostring(changeset)
self.connection.set_context({'zone_id': zone.id})
- rsp = self.connection.request(uri, method='POST', data=data).object
+ self.connection.request(uri, method='POST', data=data)
def _to_zones(self, data):
zones = []
Modified: libcloud/trunk/libcloud/dns/types.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/dns/types.py?rev=1432038&r1=1432037&r2=1432038&view=diff
==============================================================================
--- libcloud/trunk/libcloud/dns/types.py (original)
+++ libcloud/trunk/libcloud/dns/types.py Fri Jan 11 13:22:43 2013
@@ -66,7 +66,8 @@ class RecordType(object):
class ZoneError(LibcloudError):
error_type = 'ZoneError'
-
+ kwargs = ('zone_id', )
+
def __init__(self, value, driver, zone_id):
self.zone_id = zone_id
super(ZoneError, self).__init__(value=value, driver=driver)
Modified: libcloud/trunk/libcloud/loadbalancer/drivers/elb.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/loadbalancer/drivers/elb.py?rev=1432038&r1=1432037&r2=1432038&view=diff
==============================================================================
--- libcloud/trunk/libcloud/loadbalancer/drivers/elb.py (original)
+++ libcloud/trunk/libcloud/loadbalancer/drivers/elb.py Fri Jan 11 13:22:43 2013
@@ -17,19 +17,11 @@ __all__ = [
'ElasticLBDriver'
]
-import base64
-import hmac
-import time
-from hashlib import sha256
-
-from libcloud.utils.py3 import httplib, urlquote, b
from libcloud.utils.xml import findtext, findall
from libcloud.loadbalancer.types import State
from libcloud.loadbalancer.base import Driver, LoadBalancer, Member
-from libcloud.common.types import InvalidCredsError
-from libcloud.common.aws import AWSBaseResponse
-from libcloud.common.base import ConnectionUserAndKey
+from libcloud.common.aws import AWSGenericResponse, SignedAWSConnection
VERSION = '2012-06-01'
@@ -38,70 +30,18 @@ ROOT = '/%s/' % (VERSION)
NS = 'http://elasticloadbalancing.amazonaws.com/doc/%s/' % (VERSION, )
-class ELBResponse(AWSBaseResponse):
+class ELBResponse(AWSGenericResponse):
"""
Amazon ELB response class.
"""
- def success(self):
- return self.status in [httplib.OK, httplib.CREATED, httplib.ACCEPTED]
-
- def parse_error(self):
- status = int(self.status)
-
- if status == httplib.FORBIDDEN:
- if not self.body:
- raise InvalidCredsError(str(self.status) + ': ' + self.error)
- else:
- raise InvalidCredsError(self.body)
+ namespace = NS
-class ELBConnection(ConnectionUserAndKey):
+class ELBConnection(SignedAWSConnection):
+ version = VERSION
host = HOST
responseCls = ELBResponse
- def add_default_params(self, params):
- params['SignatureVersion'] = '2'
- params['SignatureMethod'] = 'HmacSHA256'
- params['AWSAccessKeyId'] = self.user_id
- params['Version'] = VERSION
- params['Timestamp'] = time.strftime('%Y-%m-%dT%H:%M:%SZ',
- time.gmtime())
- params['Signature'] = self._get_aws_auth_param(params, self.key,
- self.action)
- return params
-
- def _get_aws_auth_param(self, params, secret_key, path='/'):
- """
- Creates the signature required for AWS, per
- http://bit.ly/aR7GaQ [docs.amazonwebservices.com]:
-
- StringToSign = HTTPVerb + "\n" +
- ValueOfHostHeaderInLowercase + "\n" +
- HTTPRequestURI + "\n" +
- CanonicalizedQueryString <from the preceding step>
- """
- keys = list(params.keys())
- keys.sort()
- pairs = []
- for key in keys:
- pairs.append(urlquote(key, safe='') + '=' +
- urlquote(params[key], safe='-_~'))
-
- qs = '&'.join(pairs)
-
- hostname = self.host
- if (self.secure and self.port != 443) or \
- (not self.secure and self.port != 80):
- hostname += ":" + str(self.port)
-
- string_to_sign = '\n'.join(('GET', hostname, path, qs))
-
- b64_hmac = base64.b64encode(
- hmac.new(b(secret_key), b(string_to_sign),
- digestmod=sha256).digest()
- )
- return b64_hmac.decode('utf-8')
-
class ElasticLBDriver(Driver):
name = 'ELB'