Add support for SSH key injection.

Change-Id: I64274080402ecc570dd1561a9ee5afdbb7970264
This commit is contained in:
Pablo Orviz 2015-08-21 13:56:33 +02:00
parent 02fbf47a49
commit 7ad885011e
5 changed files with 246 additions and 12 deletions

View File

@ -114,12 +114,24 @@ class Controller(ooi.api.base.Controller):
name = attrs.get("occi.core.title", "OCCI VM")
image = obj["schemes"][templates.OpenStackOSTemplate.scheme][0]
flavor = obj["schemes"][templates.OpenStackResourceTemplate.scheme][0]
user_data = None
user_data, key_name, key_data = None, None, None
if contextualization.user_data.scheme in obj["schemes"]:
user_data = attrs.get("org.openstack.compute.user_data")
if contextualization.public_key.scheme in obj["schemes"]:
key_name = attrs.get("org.openstack.credentials.publickey.name")
key_data = attrs.get("org.openstack.credentials.publickey.data")
if key_name and key_data:
# add keypair: if key_name already exists, a 409 HTTP code
# will be returned by OpenStack
self.os_helper.keypair_create(req, key_name,
public_key=key_data)
elif not key_name and key_data:
raise exception.MissingKeypairName
server = self.os_helper.create_server(req, name, image, flavor,
user_data=user_data)
user_data=user_data,
key_name=key_name)
# The returned JSON does not contain the server name
server["name"] = name
occi_compute_resources = self._get_compute_resources([server])

View File

@ -201,7 +201,9 @@ class OpenStackHelper(BaseHelper):
response = req.get_response(self.app)
return self.get_from_response(response, "server", {})
def _get_create_server_req(self, req, name, image, flavor, user_data=None):
def _get_create_server_req(self, req, name, image, flavor,
user_data=None,
key_name=None):
tenant_id = self.tenant_from_req(req)
path = "/%s/servers" % tenant_id
# TODO(enolfc): add here the correct metadata info
@ -212,15 +214,20 @@ class OpenStackHelper(BaseHelper):
"imageRef": image,
"flavorRef": flavor,
}}
if user_data is not None:
body["server"]["user_data"] = user_data
if key_name is not None:
body["server"]["key_name"] = key_name
return self._get_req(req,
path=path,
content_type="application/json",
body=json.dumps(body),
method="POST")
def create_server(self, req, name, image, flavor, user_data=None):
def create_server(self, req, name, image, flavor,
user_data=None, key_name=None):
"""Create a server.
:param req: the incoming request
@ -228,9 +235,11 @@ class OpenStackHelper(BaseHelper):
:param image: image id for the new server
:param flavor: flavor id for the new server
:param user_data: user data to inject into the server
:param key_name: user public key name
"""
req = self._get_create_server_req(req, name, image, flavor,
user_data=user_data)
user_data=user_data,
key_name=key_name)
response = req.get_response(self.app)
# We only get one server
return self.get_from_response(response, "server", {})
@ -509,3 +518,29 @@ class OpenStackHelper(BaseHelper):
response = req.get_response(self.app)
if response.status_int != 202:
raise exception_from_response(response)
def _get_keypair_create_req(self, req, name, public_key=None):
tenant_id = self.tenant_from_req(req)
path = "/%s/os-keypairs" % tenant_id
body = {"keypair": {
"name": name,
}}
if public_key:
body["keypair"]["public_key"] = public_key
return self._get_req(req,
path=path,
content_type="application/json",
body=json.dumps(body),
method="POST")
def keypair_create(self, req, name, public_key=None):
"""Create a keypair.
:param req: the incoming request
:param name: name for the new keypair
:param public_key: public ssh key to import
"""
req = self._get_keypair_create_req(req, name, public_key=public_key)
response = req.get_response(self.app)
return self.get_from_response(response, "keypair", {})

View File

@ -130,3 +130,8 @@ class NetworkNotFound(NotFound):
class NetworkPoolFound(NotFound):
msg_fmt = "Network Pool Not Found: '%(pool)s'"
class MissingKeypairName(Invalid):
msg_fmt = "Missing Keypair Name"
code = 400

View File

@ -183,7 +183,8 @@ class TestComputeController(base.TestController):
ret = self.controller.create(req, None)
self.assertIsInstance(ret, collection.Collection)
m_create.assert_called_with(mock.ANY, "foo instance", "foo", "bar",
user_data=None)
user_data=None,
key_name=None)
@mock.patch.object(helpers.OpenStackHelper, "create_server")
@mock.patch("ooi.occi.validator.Validator")
@ -193,12 +194,13 @@ class TestComputeController(base.TestController):
obj = {
"attributes": {
"occi.core.title": "foo instance",
"org.openstack.compute.user_data": "bazonk"
"org.openstack.compute.user_data": "bazonk",
},
"schemes": {
templates.OpenStackOSTemplate.scheme: ["foo"],
templates.OpenStackResourceTemplate.scheme: ["bar"],
contextualization.user_data.scheme: None,
contextualization.public_key.scheme: None,
},
}
# NOTE(aloga): the mocked call is
@ -212,4 +214,62 @@ class TestComputeController(base.TestController):
ret = self.controller.create(req, None) # noqa
self.assertIsInstance(ret, collection.Collection)
m_create.assert_called_with(mock.ANY, "foo instance", "foo", "bar",
user_data="bazonk")
user_data="bazonk",
key_name=None)
@mock.patch("ooi.occi.validator.Validator")
def test_create_server_with_sshkeys_invalid(self, m_validator):
tenant = fakes.tenants["foo"]
req = self._build_req(tenant["id"])
obj = {
"attributes": {
"occi.core.title": "foo instance",
"org.openstack.credentials.publickey.name": "",
"org.openstack.credentials.publickey.data": "wtfoodata"
},
"schemes": {
templates.OpenStackOSTemplate.scheme: ["foo"],
templates.OpenStackResourceTemplate.scheme: ["bar"],
contextualization.public_key.scheme: None,
},
}
req.get_parser = mock.MagicMock()
req.get_parser.return_value.return_value.parse.return_value = obj
self.assertRaises(exception.MissingKeypairName,
self.controller.create,
req,
None)
@mock.patch.object(helpers.OpenStackHelper, "keypair_create")
@mock.patch.object(helpers.OpenStackHelper, "create_server")
@mock.patch("ooi.occi.validator.Validator")
def test_create_server_with_sshkeys(self, m_validator, m_server,
m_keypair):
tenant = fakes.tenants["foo"]
req = self._build_req(tenant["id"])
obj = {
"attributes": {
"occi.core.title": "foo instance",
"org.openstack.credentials.publickey.name": "wtfoo",
"org.openstack.credentials.publickey.data": "wtfoodata"
},
"schemes": {
templates.OpenStackOSTemplate.scheme: ["foo"],
templates.OpenStackResourceTemplate.scheme: ["bar"],
contextualization.public_key.scheme: None,
},
}
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_keypair.return_value = None
ret = self.controller.create(req, None) # noqa
self.assertIsInstance(ret, collection.Collection)
m_keypair.assert_called_with(mock.ANY, "wtfoo",
public_key="wtfoodata")
m_server.assert_called_with(mock.ANY, "foo instance", "foo", "bar",
user_data=None,
key_name="wtfoo")

View File

@ -554,10 +554,13 @@ class TestOpenStackHelper(TestBaseHelper):
image = uuid.uuid4().hex
flavor = uuid.uuid4().hex
user_data = "foo"
key_name = "wtfoo"
ret = self.helper.create_server(None, name, image, flavor,
user_data=user_data)
user_data=user_data,
key_name=key_name)
self.assertEqual("FOO", ret)
m.assert_called_with(None, name, image, flavor, user_data=user_data)
m.assert_called_with(None, name, image, flavor, user_data=user_data,
key_name=key_name)
@mock.patch("ooi.api.helpers.exception_from_response")
@mock.patch.object(helpers.OpenStackHelper, "_get_create_server_req")
@ -571,6 +574,7 @@ class TestOpenStackHelper(TestBaseHelper):
image = uuid.uuid4().hex
flavor = uuid.uuid4().hex
user_data = "foo"
key_name = "wtfoo"
m_exc.return_value = webob.exc.HTTPInternalServerError()
self.assertRaises(webob.exc.HTTPInternalServerError,
self.helper.create_server,
@ -578,8 +582,10 @@ class TestOpenStackHelper(TestBaseHelper):
name,
image,
flavor,
user_data=user_data)
m.assert_called_with(None, name, image, flavor, user_data=user_data)
user_data=user_data,
key_name=key_name)
m.assert_called_with(None, name, image, flavor, user_data=user_data,
key_name=key_name)
m_exc.assert_called_with(resp)
@mock.patch.object(helpers.OpenStackHelper, "_get_volume_create_req")
@ -968,6 +974,28 @@ class TestOpenStackHelperReqs(TestBaseHelper):
user_data=user_data)
self.assertExpectedReq("POST", path, body, os_req)
def test_get_os_get_server_create_with_key_name(self):
tenant = fakes.tenants["foo"]
req = self._build_req(tenant["id"])
name = "foo server"
image = "bar image"
flavor = "baz flavor"
key_name = "wtfoo"
body = {
"server": {
"name": name,
"imageRef": image,
"flavorRef": flavor,
"key_name": key_name,
},
}
path = "/%s/servers" % tenant["id"]
os_req = self.helper._get_create_server_req(req, name, image, flavor,
key_name=key_name)
self.assertExpectedReq("POST", path, body, os_req)
def test_get_os_get_volume_create(self):
tenant = fakes.tenants["foo"]
req = self._build_req(tenant["id"])
@ -1021,3 +1049,97 @@ class TestOpenStackHelperReqs(TestBaseHelper):
path = "/%s/servers/%s/action" % (tenant["id"], server)
os_req = self.helper._get_remove_floating_ip_req(req, server, ip)
self.assertExpectedReq("POST", path, body, os_req)
def test_get_os_get_keypair_create(self):
tenant = fakes.tenants["foo"]
req = self._build_req(tenant["id"])
name = "fookey"
body = {
"keypair": {
"name": name,
}
}
path = "/%s/os-keypairs" % tenant["id"]
os_req = self.helper._get_keypair_create_req(req, name)
self.assertExpectedReq("POST", path, body, os_req)
def test_get_os_get_keypair_create_import(self):
tenant = fakes.tenants["foo"]
req = self._build_req(tenant["id"])
name = "fookey"
public_key = "fookeydata"
body = {
"keypair": {
"name": name,
"public_key": public_key
}
}
path = "/%s/os-keypairs" % tenant["id"]
os_req = self.helper._get_keypair_create_req(req, name,
public_key=public_key)
self.assertExpectedReq("POST", path, body, os_req)
@mock.patch.object(helpers.OpenStackHelper, "_get_keypair_create_req")
def test_keypair_create(self, m):
resp = fakes.create_fake_json_resp({"keypair": "FOO"}, 200)
req_mock = mock.MagicMock()
req_mock.get_response.return_value = resp
m.return_value = req_mock
name = uuid.uuid4().hex
public_key = None
ret = self.helper.keypair_create(None, name)
self.assertEqual("FOO", ret)
m.assert_called_with(None, name, public_key=public_key)
@mock.patch.object(helpers.OpenStackHelper, "_get_keypair_create_req")
def test_keypair_create_key_import(self, m):
resp = fakes.create_fake_json_resp({"keypair": "FOO"}, 200)
req_mock = mock.MagicMock()
req_mock.get_response.return_value = resp
m.return_value = req_mock
name = uuid.uuid4().hex
public_key = "fookeydata"
ret = self.helper.keypair_create(None, name, public_key=public_key)
self.assertEqual("FOO", ret)
m.assert_called_with(None, name, public_key=public_key)
@mock.patch("ooi.api.helpers.exception_from_response")
@mock.patch.object(helpers.OpenStackHelper, "_get_keypair_create_req")
def test_keypair_create_with_exception(self, m, m_exc):
fault = {"computeFault": {"message": "bad", "code": 500}}
resp = fakes.create_fake_json_resp(fault, 500)
req_mock = mock.MagicMock()
req_mock.get_response.return_value = resp
m.return_value = req_mock
name = uuid.uuid4().hex
m_exc.return_value = webob.exc.HTTPInternalServerError()
self.assertRaises(webob.exc.HTTPInternalServerError,
self.helper.keypair_create,
None,
name,
None)
m.assert_called_with(None, name, public_key=None)
m_exc.assert_called_with(resp)
@mock.patch("ooi.api.helpers.exception_from_response")
@mock.patch.object(helpers.OpenStackHelper, "_get_keypair_create_req")
def test_keypair_create_key_import_with_exception(self, m, m_exc):
fault = {"computeFault": {"message": "bad", "code": 500}}
resp = fakes.create_fake_json_resp(fault, 500)
req_mock = mock.MagicMock()
req_mock.get_response.return_value = resp
m.return_value = req_mock
name = uuid.uuid4().hex
public_key = "fookeydata"
m_exc.return_value = webob.exc.HTTPInternalServerError()
self.assertRaises(webob.exc.HTTPInternalServerError,
self.helper.keypair_create,
None,
name,
public_key)
m.assert_called_with(None, name, public_key=public_key)
m_exc.assert_called_with(resp)