Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 36 additions & 2 deletions libcloud/compute/drivers/rackspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from xml.etree import ElementTree as ET
from xml.parsers.expat import ExpatError

from libcloud.pricing import get_pricing
from libcloud.pricing import get_pricing, get_size_price, PRICING_DATA
from libcloud.common.base import Response
from libcloud.common.types import MalformedResponseError
from libcloud.compute.types import NodeState, Provider
Expand All @@ -35,7 +35,6 @@

NAMESPACE='http://docs.rackspacecloud.com/servers/api/v1.0'


class RackspaceResponse(Response):

def success(self):
Expand Down Expand Up @@ -555,8 +554,35 @@ class RackspaceUKNodeDriver(RackspaceNodeDriver):
def list_locations(self):
return [NodeLocation(0, 'Rackspace UK London', 'UK', self)]

class OpenStackResponse(RackspaceResponse):

def has_content_type(self, content_type):
content_type_headers = filter(lambda key: key[0].lower() == 'content-type', self.headers.items())

if not content_type_headers:
return False

content_type_value = content_type_headers[-1][1].lower()

return content_type_value.find(content_type.lower()) > -1

def parse_body(self):
if not self.has_content_type("application/xml") or not self.body:
return self.body

try:
return ET.XML(self.body)
except:
raise MalformedResponseError(
"Failed to parse XML",
body=self.body,
driver=RackspaceNodeDriver)


class OpenStackConnection(RackspaceConnection):

responseCls = OpenStackResponse

def __init__(self, user_id, key, secure, host, port):
super(OpenStackConnection, self).__init__(user_id, key, secure=secure)
self.auth_host = host
Expand All @@ -565,3 +591,11 @@ def __init__(self, user_id, key, secure, host, port):
class OpenStackNodeDriver(RackspaceNodeDriver):
name = 'OpenStack'
connectionCls = OpenStackConnection

def _get_size_price(self, size_id):
if 'openstack' not in PRICING_DATA['compute']:
return 0.0

return get_size_price(driver_type='compute',
driver_name='openstack',
size_id=size_id)
2 changes: 2 additions & 0 deletions libcloud/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,5 @@ def get_driver(drivers, provider):
return getattr(_mod, driver_name)

raise AttributeError('Provider %s does not exist' % (provider))


108 changes: 106 additions & 2 deletions test/compute/test_rackspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@
import httplib

from libcloud.common.types import InvalidCredsError, MalformedResponseError
from libcloud.compute.drivers.rackspace import RackspaceNodeDriver as Rackspace
from libcloud.compute.drivers.rackspace import RackspaceNodeDriver as Rackspace, OpenStackResponse, OpenStackNodeDriver as OpenStack
from libcloud.compute.base import Node, NodeImage, NodeSize
from libcloud.pricing import set_pricing

from test import MockHttpTestCase
from test import MockHttp, MockResponse, MockHttpTestCase
from test.compute import TestCaseMixin
from test.file_fixtures import ComputeFileFixtures

Expand Down Expand Up @@ -305,5 +306,108 @@ def _v1_0_slug_shared_ip_groups_detail(self, method, url, body, headers):
def _v1_0_slug_servers_3445_ips_public_67_23_21_133(self, method, url, body, headers):
return (httplib.ACCEPTED, "", {}, httplib.responses[httplib.ACCEPTED])


#
# OpenStack
#

class OpenStackResponseTestCase(unittest.TestCase):
XML = """<?xml version="1.0" encoding="UTF-8"?><root/>"""

def test_simple_xml_content_type_handling(self):
http_response = MockResponse(200, OpenStackResponseTestCase.XML, headers={'content-type': 'application/xml'})
body = OpenStackResponse(http_response).parse_body()

self.assertTrue(hasattr(body, 'tag'), "Body should be parsed as XML")

def test_extended_xml_content_type_handling(self):
http_response = MockResponse(200,
OpenStackResponseTestCase.XML,
headers={'content-type': 'application/xml; charset=UTF-8'})
body = OpenStackResponse(http_response).parse_body()

self.assertTrue(hasattr(body, 'tag'), "Body should be parsed as XML")

def test_non_xml_content_type_handling(self):
RESPONSE_BODY = "Accepted"

http_response = MockResponse(202, RESPONSE_BODY, headers={'content-type': 'text/html'})
body = OpenStackResponse(http_response).parse_body()

self.assertEqual(body, RESPONSE_BODY, "Non-XML body should be returned as is")


from test.secrets import NOVA_USERNAME, NOVA_API_KEY, NOVA_HOST, NOVA_PORT, NOVA_SECURE


class OpenStackTests(unittest.TestCase):
def setUp(self):
OpenStack.connectionCls.conn_classes = (OpenStackMockHttp, None)
OpenStackMockHttp.type = None
self.driver = OpenStack(NOVA_USERNAME, NOVA_API_KEY, NOVA_SECURE, NOVA_HOST, NOVA_PORT)

def test_destroy_node(self):
node = Node(id=72258, name=None, state=None, public_ip=None, private_ip=None,
driver=self.driver)
ret = node.destroy()
self.assertTrue(ret is True, "Unsuccessful node destroying")

def test_list_sizes(self):
sizes = self.driver.list_sizes()
self.assertEqual(len(sizes), 8, "Wrong sizes count")

for size in sizes:
self.assertTrue(isinstance(size.price, float), "Wrong size price type")
self.assertEqual(size.price, 0, "Size price should be zero by default")

def test_list_sizes_with_specified_pricing(self):
pricing = dict((str(i), i) for i in range(1, 9))

set_pricing(driver_type='compute', driver_name='openstack', pricing=pricing)

sizes = self.driver.list_sizes()
self.assertEqual(len(sizes), 8, "Wrong sizes count")

for size in sizes:
self.assertTrue(isinstance(size.price, float), "Wrong size price type")
self.assertEqual(size.price, pricing[size.id], "Size price should be zero by default")


class OpenStackMockHttp(MockHttp):
def _v1_0(self, method, url, body, headers):
headers = {'x-server-management-url': 'https://servers.api.rackspacecloud.com/v1.0/slug',
'x-auth-token': 'FE011C19-CF86-4F87-BE5D-9229145D7A06',
'x-cdn-management-url': 'https://cdn.clouddrive.com/v1/MossoCloudFS_FE011C19-CF86-4F87-BE5D-9229145D7A06',
'x-storage-token': 'FE011C19-CF86-4F87-BE5D-9229145D7A06',
'x-storage-url': 'https://storage4.clouddrive.com/v1/MossoCloudFS_FE011C19-CF86-4F87-BE5D-9229145D7A06'}
return (httplib.NO_CONTENT, "", headers, httplib.responses[httplib.NO_CONTENT])

def _v1_0_slug_servers_72258(self, method, url, body, headers):
if method != "DELETE":
raise NotImplemented
# only used by destroy node()
return (httplib.ACCEPTED,
"202 Accepted\n\nThe request is accepted for processing.\n\n ",
{'date': 'Thu, 09 Jun 2011 10:51:53 GMT', 'content-length': '58',
'content-type': 'text/html; charset=UTF-8'},
httplib.responses[httplib.ACCEPTED])

def _v1_0_slug_flavors_detail(self, method, url, body, headers):
body = """<flavors xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
<flavor disk="40" id="3" name="m1.medium" ram="4096"/>
<flavor disk="20" id="2" name="m1.small" ram="2048"/>
<flavor disk="80" id="4" name="m1.large" ram="8192"/>
<flavor disk="0" id="6" name="s1" ram="256"/>
<flavor disk="0" id="7" name="s1.swap" ram="256"/>
<flavor disk="0" id="1" name="m1.tiny" ram="512"/>
<flavor disk="10" id="8" name="s1.tiny" ram="512"/>
<flavor disk="160" id="5" name="m1.xlarge" ram="16384"/>
</flavors>
"""
return (httplib.OK, body,
{'date': 'Tue, 14 Jun 2011 09:43:55 GMT', 'content-length': '529', 'content-type': 'application/xml'},
httplib.responses[httplib.OK])


if __name__ == '__main__':
sys.exit(unittest.main())
6 changes: 6 additions & 0 deletions test/secrets.py-dist
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,9 @@ OPENNEBULA_KEY = ''

OPSOURCE_USER=''
OPSOURCE_PASS=''

NOVA_USERNAME=''
NOVA_API_KEY=''
NOVA_HOST=''
NOVA_PORT=8774
NOVA_SECURE=False