Support linking storage on compute creation

Links to storage on compute creation will use block_device_mapping_v2 to
map the volume(s) on the VM. Parsers and validators are extended to
support links.

Change-Id: I9e52ad231e5a0a62b7522c9e203b95edb1a3170c
This commit is contained in:
Enol Fernandez
2015-11-23 11:42:39 +01:00
parent 189b4ad81b
commit c21637016c
9 changed files with 321 additions and 23 deletions

View File

@@ -26,6 +26,7 @@ from ooi.api import helpers
from ooi import exception
from ooi.occi.core import collection
from ooi.occi.infrastructure import compute as occi_compute
from ooi.occi.infrastructure import storage as occi_storage
from ooi.openstack import contextualization
from ooi.openstack import templates
from ooi.tests import base
@@ -209,7 +210,8 @@ class TestComputeController(base.TestController):
self.assertIsInstance(ret, collection.Collection)
m_create.assert_called_with(mock.ANY, "foo instance", "foo", "bar",
user_data=None,
key_name=None)
key_name=None,
block_device_mapping_v2=[])
@mock.patch.object(helpers.OpenStackHelper, "create_server")
@mock.patch("ooi.occi.validator.Validator")
@@ -240,7 +242,8 @@ class TestComputeController(base.TestController):
self.assertIsInstance(ret, collection.Collection)
m_create.assert_called_with(mock.ANY, "foo instance", "foo", "bar",
user_data="bazonk",
key_name=None)
key_name=None,
block_device_mapping_v2=[])
@mock.patch.object(helpers.OpenStackHelper, "keypair_create")
@mock.patch.object(helpers.OpenStackHelper, "create_server")
@@ -273,7 +276,8 @@ class TestComputeController(base.TestController):
public_key="wtfoodata")
m_server.assert_called_with(mock.ANY, "foo instance", "foo", "bar",
user_data=None,
key_name="wtfoo")
key_name="wtfoo",
block_device_mapping_v2=[])
@mock.patch.object(helpers.OpenStackHelper, "keypair_delete")
@mock.patch.object(helpers.OpenStackHelper, "keypair_create")
@@ -306,5 +310,119 @@ class TestComputeController(base.TestController):
public_key="wtfoodata")
m_server.assert_called_with(mock.ANY, "foo instance", "foo", "bar",
user_data=None,
key_name=mock.ANY)
key_name=mock.ANY,
block_device_mapping_v2=[])
m_keypair_delete.assert_called_with(mock.ANY, mock.ANY)
def test_build_block_mapping_no_links(self):
ret = self.controller._build_block_mapping(None, {})
self.assertEqual([], ret)
def test_build_block_mapping_invalid_rel(self):
obj = {'links': {'foo': {'rel': 'bar'}}}
ret = self.controller._build_block_mapping(None, obj)
self.assertEqual([], ret)
@mock.patch("ooi.api.helpers.get_id_with_kind")
def test_build_block_mapping(self, m_get_id):
vol_id = uuid.uuid4().hex
image_id = uuid.uuid4().hex
obj = {
'links': {
'l1': {
'rel': ('http://schemas.ogf.org/occi/infrastructure#'
'storage'),
'occi.core.target': vol_id,
}
},
"schemes": {
templates.OpenStackOSTemplate.scheme: [image_id],
}
}
m_get_id.return_value = (None, vol_id)
ret = self.controller._build_block_mapping(None, obj)
expected = [
{
"source_type": "image",
"destination_type": "local",
"boot_index": 0,
"delete_on_termination": True,
"uuid": image_id,
},
{
"source_type": "volume",
"uuid": vol_id,
"delete_on_termination": False,
}
]
self.assertEqual(expected, ret)
m_get_id.assert_called_with(None, vol_id,
occi_storage.StorageResource.kind)
@mock.patch("ooi.api.helpers.get_id_with_kind")
def test_build_block_mapping_device_id(self, m_get_id):
vol_id = uuid.uuid4().hex
image_id = uuid.uuid4().hex
obj = {
'links': {
'l1': {
'rel': ('http://schemas.ogf.org/occi/infrastructure#'
'storage'),
'occi.core.target': vol_id,
'occi.storagelink.deviceid': 'baz'
}
},
"schemes": {
templates.OpenStackOSTemplate.scheme: [image_id],
}
}
m_get_id.return_value = (None, vol_id)
ret = self.controller._build_block_mapping(None, obj)
expected = [
{
"source_type": "image",
"destination_type": "local",
"boot_index": 0,
"delete_on_termination": True,
"uuid": image_id,
},
{
"source_type": "volume",
"uuid": vol_id,
"delete_on_termination": False,
"device_name": "baz",
}
]
self.assertEqual(expected, ret)
m_get_id.assert_called_with(None, vol_id,
occi_storage.StorageResource.kind)
@mock.patch.object(helpers.OpenStackHelper, "create_server")
@mock.patch.object(compute.Controller, "_build_block_mapping")
@mock.patch("ooi.occi.validator.Validator")
def test_create_server_with_storage_link(self, m_validator, m_block,
m_server):
tenant = fakes.tenants["foo"]
req = self._build_req(tenant["id"])
obj = {
"attributes": {
"occi.core.title": "foo instance",
},
"schemes": {
templates.OpenStackOSTemplate.scheme: ["foo"],
templates.OpenStackResourceTemplate.scheme: ["bar"],
},
}
req.get_parser = mock.MagicMock()
req.get_parser.return_value.return_value.parse.return_value = obj
m_validator.validate.return_value = True
server = {"id": uuid.uuid4().hex}
m_server.return_value = server
m_block.return_value = "mapping"
ret = self.controller.create(req, None) # noqa
self.assertIsInstance(ret, collection.Collection)
m_server.assert_called_with(mock.ANY, "foo instance", "foo", "bar",
user_data=None,
key_name=mock.ANY,
block_device_mapping_v2="mapping")
m_block.assert_called_with(req, obj)

View File

@@ -615,12 +615,14 @@ class TestOpenStackHelper(TestBaseHelper):
flavor = uuid.uuid4().hex
user_data = "foo"
key_name = "wtfoo"
bdm = []
ret = self.helper.create_server(None, name, image, flavor,
user_data=user_data,
key_name=key_name)
key_name=key_name,
block_device_mapping_v2=bdm)
self.assertEqual("FOO", ret)
m.assert_called_with(None, name, image, flavor, user_data=user_data,
key_name=key_name)
key_name=key_name, block_device_mapping_v2=bdm)
@mock.patch("ooi.api.helpers.exception_from_response")
@mock.patch.object(helpers.OpenStackHelper, "_get_create_server_req")
@@ -635,6 +637,7 @@ class TestOpenStackHelper(TestBaseHelper):
flavor = uuid.uuid4().hex
user_data = "foo"
key_name = "wtfoo"
bdm = []
m_exc.return_value = webob.exc.HTTPInternalServerError()
self.assertRaises(webob.exc.HTTPInternalServerError,
self.helper.create_server,
@@ -643,9 +646,10 @@ class TestOpenStackHelper(TestBaseHelper):
image,
flavor,
user_data=user_data,
key_name=key_name)
key_name=key_name,
block_device_mapping_v2=bdm)
m.assert_called_with(None, name, image, flavor, user_data=user_data,
key_name=key_name)
key_name=key_name, block_device_mapping_v2=bdm)
m_exc.assert_called_with(resp)
@mock.patch.object(helpers.OpenStackHelper, "_get_volume_create_req")

View File

@@ -298,6 +298,38 @@ class TestComputeController(test_middleware.TestMiddleware):
self.assertExpectedResult(expected, resp)
self.assertDefaults(resp)
def test_create_vm_with_storage(self):
tenant = fakes.tenants["foo"]
target = utils.join_url(self.application_url + "/",
"storage/%s" % uuid.uuid4().hex)
app = self.get_app()
headers = {
'Category': (
'compute;'
'scheme="http://schemas.ogf.org/occi/infrastructure#";'
'class="kind",'
'foo;'
'scheme="http://schemas.openstack.org/template/resource#";'
'class="mixin",'
'bar;'
'scheme="http://schemas.openstack.org/template/os#";'
'class="mixin"'),
'Link': (
'</bar>;'
'rel="http://schemas.ogf.org/occi/infrastructure#storage";'
'occi.core.target="%s"') % target
}
req = self._build_req("/compute", tenant["id"], method="POST",
headers=headers)
resp = req.get_response(app)
expected = [("X-OCCI-Location",
utils.join_url(self.application_url + "/",
"compute/%s" % "foo"))]
self.assertEqual(200, resp.status_code)
self.assertExpectedResult(expected, resp)
self.assertDefaults(resp)
def test_vm_links(self):
tenant = fakes.tenants["baz"]

View File

@@ -102,6 +102,29 @@ class TestParserBase(base.TestCase):
expected_attrs = {"foo": "bar", "baz": "1234", "bazonk": "foo=123"}
self.assertEqual(expected_attrs, res["attributes"])
def test_link(self):
headers = {
'Category': ('foo; '
'scheme="http://example.com/scheme#"; '
'class="kind"'),
'Link': ('<bar>; foo="bar"; "bazonk"="foo=123"')
}
parser = self._get_parser(headers, None)
res = parser.parse()
expected_links = {"bar": {"foo": "bar", "bazonk": "foo=123"}}
self.assertEqual(expected_links, res["links"])
def test_invalid_link(self):
headers = {
'Category': ('foo; '
'scheme="http://example.com/scheme#"; '
'class="kind"'),
'Link': ('bar; foo="bar"; "bazonk"="foo=123"')
}
parser = self._get_parser(headers, None)
self.assertRaises(exception.OCCIInvalidSchema,
parser.parse)
class TestTextParser(TestParserBase):
def _get_parser(self, headers, body):

View File

@@ -186,3 +186,37 @@ class TestValidator(base.TestCase):
v = validator.Validator(pobj)
self.assertRaises(exception.OCCISchemaMismatch, v.validate, {})
def test_optional_links(self):
mixins = collections.Counter()
schemes = collections.defaultdict(list)
links = {"foo": {"rel": "http://example.com/scheme#foo"}}
pobj = {
"kind": "compute",
"category": "foo type",
"mixins": mixins,
"schemes": schemes,
"links": links
}
link = mock.MagicMock()
link.type_id = "http://example.com/scheme#foo"
scheme = {"optional_links": [link]}
v = validator.Validator(pobj)
self.assertTrue(v.validate(scheme))
def test_optional_links_invalid(self):
mixins = collections.Counter()
schemes = collections.defaultdict(list)
links = {"foo": {"rel": "http://example.com/scheme#foo"}}
pobj = {
"kind": "compute",
"category": "foo type",
"mixins": mixins,
"schemes": schemes,
"links": links
}
link = mock.MagicMock()
link.type_id = "http://example.com/scheme#foo"
scheme = {"optional_links": []}
v = validator.Validator(pobj)
self.assertRaises(exception.OCCISchemaMismatch, v.validate, scheme)