Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
000c458
Existing l.c.d.openstack to l.c.d.openstack.v1_0.
mikenerone Sep 19, 2011
80135b8
Remove extraneous check for force_base_url.
mikenerone Sep 19, 2011
4eab256
Can't assume a specific class.
mikenerone Sep 19, 2011
fa5c37c
Base version-agnostic OSNodeDriver class.
mikenerone Sep 19, 2011
ff02b27
Add ability to pass auth_url during instantiation.
mikenerone Sep 19, 2011
57acbe4
Implement version-agnostic OSResponse.
mikenerone Sep 19, 2011
2a14a19
Class attrs obsolete __init__().
mikenerone Sep 19, 2011
a7b1183
Fix chicken-and-egg problem.
mikenerone Sep 19, 2011
d8c7db8
Bunch of PEP8 fixes to files I just touched.
mikenerone Sep 19, 2011
513477d
Ver-agnostic base for OS Compute connections.
mikenerone Sep 19, 2011
f75b1df
Let httplib set header.
mikenerone Sep 19, 2011
45e228e
Remove Auth 1.1 support and allow tenant_id.
mikenerone Sep 19, 2011
75bf362
Propogate tenant_id support up the stack.
mikenerone Sep 19, 2011
aa45c31
Make auth URLs fully explicit.
mikenerone Sep 19, 2011
e49c479
Correct the version (and 1.0 doesn't use /auth).
mikenerone Sep 20, 2011
e809600
Remove unused vars & move accept_format baseward.
mikenerone Sep 20, 2011
7f946a4
Handle content-type more consistently.
mikenerone Sep 20, 2011
c5bd7d9
Handle body encoding in one place (v1.0, too).
mikenerone Sep 20, 2011
b8f1b15
Handle no-body-in-response.
mikenerone Sep 20, 2011
955e79f
Hey, an OpenStack Nova 1.1 driver!
mikenerone Sep 20, 2011
c937dee
Methods for get-*, delete image, and get quotas.
mikenerone Sep 20, 2011
12a5013
Turn OS node driver's base class into a factory.
mikenerone Sep 20, 2011
92a3215
Make Provider.OPENSTACK ref the factory.
mikenerone Sep 20, 2011
9cfc9b6
__init__'s obsoleted by base class enhancements.
mikenerone Sep 20, 2011
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions demos/secrets.py-dist
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ ECP_PARAMS = ('user_name', 'password')
GANDI_PARAMS = ('user',)
HOSTINGCOM_PARAMS = ('user', 'secret')
IBM_PARAMS = ('user', 'secret')
# OPENSTACK_PARAMS = ('user_name', 'api_key', secure_bool, 'host', port_int)
OPENSTACK_PARAMS = ('user_name', 'api_key', False, 'host', 8774)
OPENSTACK_PARAMS = ('user_name', 'api_key', 'auth_url')
OPENNEBULA_PARAMS = ('user', 'key')
OPSOURCE_PARAMS = ('user', 'password')
RACKSPACE_PARAMS = ('user', 'key')
Expand Down
26 changes: 11 additions & 15 deletions libcloud/common/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ class Response(object):
status = httplib.OK
headers = {}
error = None
connection = None

def __init__(self, response):
def __init__(self, response, connection=None):
self.body = response.read()
self.status = response.status
self.headers = dict(response.getheaders())
self.error = response.reason
self.connection = connection

if not self.success():
raise Exception(self.parse_error())
Expand Down Expand Up @@ -84,12 +84,13 @@ def success(self):

class RawResponse(Response):

def __init__(self, response=None):
def __init__(self, response=None, connection=None):
self._status = None
self._response = None
self._headers = {}
self._error = None
self._reason = None
self.connection = connection

@property
def response(self):
Expand Down Expand Up @@ -195,7 +196,7 @@ def getresponse(self):
def request(self, method, url, body=None, headers=None):
headers.update({'X-LC-Request-ID': str(id(self))})
if self.log is not None:
pre = "# -------- begin %d request ----------\n" % id(self)
pre = "# -------- begin %d request ----------\n" % id(self)
self.log.write(pre +
self._log_curl(method, url, body, headers) + "\n")
self.log.flush()
Expand All @@ -217,7 +218,7 @@ def getresponse(self):
def request(self, method, url, body=None, headers=None):
headers.update({'X-LC-Request-ID': str(id(self))})
if self.log is not None:
pre = "# -------- begin %d request ----------\n" % id(self)
pre = "# -------- begin %d request ----------\n" % id(self)
self.log.write(pre +
self._log_curl(method, url, body, headers) + "\n")
self.log.flush()
Expand Down Expand Up @@ -285,7 +286,7 @@ def _tuple_from_url(self, url):

return (host, port, secure, request_path)

def connect(self, host=None, port=None, base_url = None):
def connect(self, host=None, port=None, base_url=None):
"""
Establish a connection with the API server.

Expand All @@ -298,7 +299,6 @@ def connect(self, host=None, port=None, base_url = None):
@returns: A connection
"""
# prefer the attribute base_url if its set or sent
connection = None
secure = self.secure

if getattr(self, 'base_url', None) and base_url == None:
Expand Down Expand Up @@ -389,7 +389,6 @@ def request(self,
headers = self.add_default_headers(headers)
# We always send a user-agent header
headers.update({'User-Agent': self._user_agent()})
headers.update({'Host': self.host})
# Encode data if necessary
if data != '' and data != None:
data = self.encode_data(data)
Expand Down Expand Up @@ -424,15 +423,12 @@ def request(self,
raise ssl.SSLError(str(e))

if raw:
response = self.rawResponseCls()
return self.rawResponseCls(connection=self)
else:
response = self.responseCls(self.connection.getresponse())

response.connection = self
return response
return self.responseCls(self.connection.getresponse(), connection=self)

def morph_action_hook(self, action):
return self.request_path + action
return self.request_path + action

def add_default_params(self, params):
"""
Expand Down Expand Up @@ -473,7 +469,7 @@ def encode_data(self, data):
Override in a provider's subclass.
"""
return data

class ConnectionKey(Connection):
"""
A Base Connection class to derive from, which includes a
Expand Down
141 changes: 56 additions & 85 deletions libcloud/common/openstack.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,66 +21,31 @@
from libcloud.common.base import ConnectionUserAndKey, Response
from libcloud.compute.types import LibcloudError, InvalidCredsError, MalformedResponseError

try:
import simplejson as json
except ImportError:
import json

AUTH_API_VERSION = 'v1.0'

__all__ = [
"OpenStackBaseConnection",
"OpenStackAuthConnection",
"OpenStackAuthConnection_v1_0",
]


# @TODO: Refactor for re-use by other openstack drivers
class OpenStackAuthResponse(Response):
class OpenStackAuthResponse_v1_0(Response):
# TODO: Any reason Response's couldn't be this way?
def success(self):
return True

def parse_body(self):
if not self.body:
return None
return 200 <= self.status <= 299

if 'content-type' in self.headers:
key = 'content-type'
elif 'Content-Type' in self.headers:
key = 'Content-Type'
else:
raise LibcloudError('Missing content-type header', driver=OpenStackAuthConnection)

content_type = self.headers[key]
if content_type.find(';') != -1:
content_type = content_type.split(';')[0]

if content_type == 'application/json':
try:
data = json.loads(self.body)
except:
raise MalformedResponseError('Failed to parse JSON',
body=self.body,
driver=OpenStackAuthConnection)
elif content_type == 'text/plain':
data = self.body
else:
data = self.body

return data

class OpenStackAuthConnection(ConnectionUserAndKey):
# @TODO: Refactor for re-use by other openstack drivers
class OpenStackAuthConnection_v1_0(ConnectionUserAndKey):

responseCls = OpenStackAuthResponse
responseCls = OpenStackAuthResponse_v1_0
name = 'OpenStack Auth'

def __init__(self, parent_conn, auth_url, user_id, key):
def __init__(self, parent_conn, auth_url, user_id, key, tenant_id=None):
self.parent_conn = parent_conn
# enable tests to use the same mock connection classes.
self.conn_classes = parent_conn.conn_classes

super(OpenStackAuthConnection, self).__init__(
super(OpenStackAuthConnection_v1_0, self).__init__(
user_id, key, url=auth_url)

self.tenant_id = tenant_id
self.auth_url = auth_url
self.urls = {}
self.driver = self.parent_conn.driver
Expand All @@ -91,51 +56,69 @@ def add_default_headers(self, headers):
return headers

def authenticate(self):
reqbody = json.dumps({'credentials': {'username': self.user_id, 'key': self.key}})
resp = self.request("/auth",
data=reqbody,
headers={
'X-Auth-User': self.user_id,
'X-Auth-Key': self.key,
},
method='POST')
headers = {
'X-Auth-User': self.user_id,
'X-Auth-Key': self.key,
}
if self.tenant_id:
headers['X-Auth-Project-Id'] = self.tenant_id

resp = self.request("/", headers=headers)

if resp.status == httplib.UNAUTHORIZED:
# HTTP UNAUTHORIZED (401): auth failed
raise InvalidCredsError()
elif resp.status != httplib.OK:

elif resp.status in (httplib.OK, httplib.NO_CONTENT):
self.auth_token = resp.headers.get('x-auth-token') or resp.headers.get('X-Auth-Token')
# TODO: Investigate down-casing entire headers dict in Response
self.urls = dict([
(name.lower(), url)
for name, url in resp.headers.items()
if name.lower().endswith('-url')
])

else:
raise MalformedResponseError('Malformed response',
body='code: %s body:%s' % (resp.status, resp.body),
driver=self.driver)
else:
try:
body = json.loads(resp.body)
except Exception, e:
raise MalformedResponseError('Failed to parse JSON', e)
try:
self.auth_token = body['auth']['token']['id']
self.urls = body['auth']['serviceCatalog']
except KeyError, e:
raise MalformedResponseError('Auth JSON response is missing required elements', e)


class OpenStackBaseConnection(ConnectionUserAndKey):

auth_url = None

def __init__(self, user_id, key, secure=True,
host=None, port=None, ex_force_base_url=None):
tenant_id = None
accept_format = 'application/json' # Just a default

def __init__(self,
user_id,
key,
secure=True,
host=None,
port=None,
auth_url=None,
tenant_id=None,
ex_force_base_url=None,
):
self.server_url = None
self.cdn_management_url = None
self.storage_url = None
self.lb_url = None
self.auth_token = None
if auth_url:
self.auth_url = auth_url
if tenant_id:
self.tenant_id = tenant_id
self._force_base_url = ex_force_base_url
super(OpenStackBaseConnection, self).__init__(
user_id, key)

def add_default_headers(self, headers):
headers['X-Auth-Token'] = self.auth_token
headers['Accept'] = self.accept_format
if hasattr(self, 'content_type'):
headers['Content-Type'] = self.content_type

return headers

def morph_action(self, action):
Expand All @@ -158,22 +141,11 @@ def _get_base_url(self, url_key):
if not value:
self._populate_hosts_and_request_paths()
value = getattr(self, url_key, None)
if self._force_base_url != None:
value = self._force_base_url
return value

def _get_default_region(self, arr):
if len(arr):
for i in arr:
if i.get('v1Default', False):
return i['publicURL']
# uber lame
return arr[0]
return None

def request(self, **kwargs):
def request(self, action, **kwargs):
self._populate_hosts_and_request_paths()
return super(OpenStackBaseConnection, self).request(**kwargs)
return super(OpenStackBaseConnection, self).request(action, **kwargs)

def _populate_hosts_and_request_paths(self):
"""
Expand All @@ -185,17 +157,16 @@ def _populate_hosts_and_request_paths(self):
if self.auth_url == None:
raise LibcloudError('OpenStack instance must have auth_url set')

osa = OpenStackAuthConnection(self, self.auth_url, self.user_id, self.key)
osa = OpenStackAuthConnection_v1_0(self, self.auth_url, self.user_id, self.key, tenant_id=self.tenant_id)

# may throw InvalidCreds, etc
osa.authenticate()

self.auth_token = osa.auth_token
self.server_url = osa.urls.get('x-server-management-url', None)
self.cdn_management_url = osa.urls.get('x-cdn-management-url', None)
self.storage_url = osa.urls.get('x-storage-url', None)

# TODO: Multi-region support
self.server_url = self._get_default_region(osa.urls.get('cloudServers', []))
self.cdn_management_url = self._get_default_region(osa.urls.get('cloudFilesCDN', []))
self.storage_url = self._get_default_region(osa.urls.get('cloudFiles', []))
# TODO: this is even more broken, the service catalog does NOT show load
# balanacers :( You must hard code in the Rackspace Load balancer URLs...
self.lb_url = self.server_url.replace("servers", "ord.loadbalancers")
Expand Down
4 changes: 2 additions & 2 deletions libcloud/common/rackspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
Common settings for Rackspace Cloud Servers and Cloud Files
"""

AUTH_URL_US = 'https://auth.api.rackspacecloud.com/v1.1/'
AUTH_URL_UK = 'https://lon.auth.api.rackspacecloud.com/v1.1/'
AUTH_URL_US = 'https://auth.api.rackspacecloud.com/v1.0'
AUTH_URL_UK = 'https://lon.auth.api.rackspacecloud.com/v1.0'

__all__ = [
"AUTH_URL_US",
Expand Down
5 changes: 3 additions & 2 deletions libcloud/compute/drivers/linode.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class LinodeResponse(Response):

libcloud does not take advantage of batching, so a response will always
reflect the above format. A few weird quirks are caught here as well."""
def __init__(self, response):
def __init__(self, response, connection=None):
"""Instantiate a LinodeResponse from the HTTP response

@keyword response: The raw response returned by urllib
Expand All @@ -95,6 +95,7 @@ def __init__(self, response):
self.status = response.status
self.headers = dict(response.getheaders())
self.error = response.reason
self.connection = connection
self.invalid = LinodeException(0xFF,
"Invalid JSON received from server")

Expand Down Expand Up @@ -605,7 +606,7 @@ def _izip_longest(*args, **kwds):
http://docs.python.org/library/itertools.html#itertools.izip
"""
fillvalue = kwds.get('fillvalue')
def sentinel(counter = ([fillvalue]*(len(args)-1)).pop):
def sentinel(counter=([fillvalue]*(len(args) - 1)).pop):
yield counter() # yields the fillvalue, or raises IndexError
fillers = itertools.repeat(fillvalue)
iters = [itertools.chain(it, sentinel(), fillers) for it in args]
Expand Down
Loading