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:
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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"]
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user