Add info for Standalone EC2 API to cut access to Nova DB.

This change adds properties to v2.1
os-extended-server-properties and os-extended-volumes nova APIs
in order to expose information which is required for the standalone
EC2 API implementation (resides on stackforge/ec2-api) to use only
public APIs. Right now this information is taken by directly accessing
internal Nova DB.
It adds proposed microversion v2.3.
It implements the spec "Nova changes required for standalone EC2 API
implementation" in this review: https://review.openstack.org/#/c/153636/

APIImpact
Implements: blueprint ec2-api

Change-Id: I850e51e730c82906f68fddb14fb0004d7091eb91
This commit is contained in:
Alexandre Levine
2015-02-19 03:42:42 +04:00
parent e6568b2469
commit abc656d3d5
11 changed files with 412 additions and 21 deletions

View File

@@ -0,0 +1,65 @@
{
"server": {
"addresses": {
"private": [
{
"addr": "192.168.0.3",
"OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
"OS-EXT-IPS:type": "fixed",
"version": 4
}
]
},
"created": "2013-09-16T02:55:07Z",
"flavor": {
"id": "1",
"links": [
{
"href": "http://openstack.example.com/flavors/1",
"rel": "bookmark"
}
]
},
"hostId": "3bf189131c61d0e71b0a8686a897a0f50d1693b48c47b721fe77155b",
"id": "c278163e-36f9-4cf2-b1ac-80db4c63f7a8",
"image": {
"id": "70a599e0-31e7-49b7-b260-868f441e862b",
"links": [
{
"href": "http://openstack.example.com/images/70a599e0-31e7-49b7-b260-868f441e862b",
"rel": "bookmark"
}
]
},
"key_name": null,
"links": [
{
"href": "http://openstack.example.com/v3/servers/c278163e-36f9-4cf2-b1ac-80db4c63f7a8",
"rel": "self"
},
{
"href": "http://openstack.example.com/servers/c278163e-36f9-4cf2-b1ac-80db4c63f7a8",
"rel": "bookmark"
}
],
"metadata": {
"My Server Name": "Apache1"
},
"name": "new-server-test",
"OS-EXT-SRV-ATTR:host": "c5f474bf81474f9dbbc404d5b2e4e9b3",
"OS-EXT-SRV-ATTR:hypervisor_hostname": "fake-mini",
"OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
"OS-EXT-SRV-ATTR:reservation_id": "r-00000001",
"OS-EXT-SRV-ATTR:launch_index": 0,
"OS-EXT-SRV-ATTR:kernel_id": "a5f474bf81474f9dbbc404d5b2e4e9b3",
"OS-EXT-SRV-ATTR:ramdisk_id": "b5f474bf81474f9dbbc404d5b2e4e9b3",
"OS-EXT-SRV-ATTR:hostname": "fake-hostname",
"OS-EXT-SRV-ATTR:root_device_name": "/dev/vda",
"OS-EXT-SRV-ATTR:userdata": "fake",
"progress": 0,
"status": "ACTIVE",
"tenant_id": "openstack",
"updated": "2013-09-16T02:55:08Z",
"user_id": "fake"
}
}

View File

@@ -0,0 +1,67 @@
{
"servers": [
{
"addresses": {
"private": [
{
"addr": "192.168.0.3",
"OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
"OS-EXT-IPS:type": "fixed",
"version": 4
}
]
},
"created": "2013-09-16T02:55:03Z",
"flavor": {
"id": "1",
"links": [
{
"href": "http://openstack.example.com/flavors/1",
"rel": "bookmark"
}
]
},
"hostId": "63cf07a9fd82e1d2294926ec5c0d2e1e0ca449224246df75e16f23dc",
"id": "a8c1c13d-ec7e-47c7-b4ff-077f72c1ca46",
"image": {
"id": "70a599e0-31e7-49b7-b260-868f441e862b",
"links": [
{
"href": "http://openstack.example.com/images/70a599e0-31e7-49b7-b260-868f441e862b",
"rel": "bookmark"
}
]
},
"key_name": null,
"links": [
{
"href": "http://openstack.example.com/v3/servers/a8c1c13d-ec7e-47c7-b4ff-077f72c1ca46",
"rel": "self"
},
{
"href": "http://openstack.example.com/servers/a8c1c13d-ec7e-47c7-b4ff-077f72c1ca46",
"rel": "bookmark"
}
],
"metadata": {
"My Server Name": "Apache1"
},
"name": "new-server-test",
"OS-EXT-SRV-ATTR:host": "bc8efe4fdb7148a4bb921a2b03d17de6",
"OS-EXT-SRV-ATTR:hypervisor_hostname": "fake-mini",
"OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
"OS-EXT-SRV-ATTR:reservation_id": "r-00000001",
"OS-EXT-SRV-ATTR:launch_index": 0,
"OS-EXT-SRV-ATTR:kernel_id": "a5f474bf81474f9dbbc404d5b2e4e9b3",
"OS-EXT-SRV-ATTR:ramdisk_id": "b5f474bf81474f9dbbc404d5b2e4e9b3",
"OS-EXT-SRV-ATTR:hostname": "fake-hostname",
"OS-EXT-SRV-ATTR:root_device_name": "/dev/vda",
"OS-EXT-SRV-ATTR:userdata": "fake",
"progress": 0,
"status": "ACTIVE",
"tenant_id": "openstack",
"updated": "2013-09-16T02:55:05Z",
"user_id": "fake"
}
]
}

View File

@@ -0,0 +1,65 @@
{
"server": {
"addresses": {
"private": [
{
"addr": "192.168.0.3",
"OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
"OS-EXT-IPS:type": "fixed",
"version": 4
}
]
},
"created": "2013-09-16T03:22:28Z",
"flavor": {
"id": "1",
"links": [
{
"href": "http://openstack.example.com/flavors/1",
"rel": "bookmark"
}
]
},
"hostId": "8feef92e2152b9970b51dbdade024afbec7f8f03daf7cb335a3c1cb9",
"id": "7d62983e-23df-4320-bc89-bbc77f2a2e40",
"image": {
"id": "70a599e0-31e7-49b7-b260-868f441e862b",
"links": [
{
"href": "http://openstack.example.com/images/70a599e0-31e7-49b7-b260-868f441e862b",
"rel": "bookmark"
}
]
},
"key_name": null,
"links": [
{
"href": "http://openstack.example.com/v3/servers/7d62983e-23df-4320-bc89-bbc77f2a2e40",
"rel": "self"
},
{
"href": "http://openstack.example.com/servers/7d62983e-23df-4320-bc89-bbc77f2a2e40",
"rel": "bookmark"
}
],
"metadata": {
"My Server Name": "Apache1"
},
"name": "new-server-test",
"os-extended-volumes:volumes_attached": [
{
"id": "volume_id1",
"delete_on_termination": True
},
{
"id": "volume_id2",
"delete_on_termination": False
}
],
"progress": 0,
"status": "ACTIVE",
"tenant_id": "openstack",
"updated": "2013-09-16T03:22:29Z",
"user_id": "fake"
}
}

View File

@@ -0,0 +1,67 @@
{
"servers": [
{
"addresses": {
"private": [
{
"addr": "192.168.0.3",
"OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
"OS-EXT-IPS:type": "fixed",
"version": 4
}
]
},
"created": "2013-09-16T03:22:32Z",
"flavor": {
"id": "1",
"links": [
{
"href": "http://openstack.example.com/flavors/1",
"rel": "bookmark"
}
]
},
"hostId": "f9622ec1b5ab6e3785661ea1c1e0294f95aecbcf27ac4cb60b06bd02",
"id": "8e479732-7701-48cd-af7a-04d84f51b742",
"image": {
"id": "70a599e0-31e7-49b7-b260-868f441e862b",
"links": [
{
"href": "http://openstack.example.com/images/70a599e0-31e7-49b7-b260-868f441e862b",
"rel": "bookmark"
}
]
},
"key_name": null,
"links": [
{
"href": "http://openstack.example.com/v3/servers/8e479732-7701-48cd-af7a-04d84f51b742",
"rel": "self"
},
{
"href": "http://openstack.example.com/servers/8e479732-7701-48cd-af7a-04d84f51b742",
"rel": "bookmark"
}
],
"metadata": {
"My Server Name": "Apache1"
},
"name": "new-server-test",
"os-extended-volumes:volumes_attached": [
{
"id": "volume_id1",
"delete_on_termination": True
},
{
"id": "volume_id2",
"delete_on_termination": False
}
],
"progress": 0,
"status": "ACTIVE",
"tenant_id": "openstack",
"updated": "2013-09-16T03:22:34Z",
"user_id": "fake"
}
]
}

View File

@@ -40,6 +40,8 @@ REST_API_VERSION_HISTORY = """REST API Version History:
* 2.1 - Initial version. Equivalent to v2.0 code
* 2.2 - Adds (keypair) type parameter for os-keypairs plugin
Fixes success status code for create/delete a keypair method
* 2.3 - Exposes additional os-extended-server-attributes
Exposes delete_on_termination for os-extended-volumes
"""
# The minimum and maximum versions of the API supported
@@ -48,7 +50,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
# Note(cyeoh): This only applies for the v2.1 API once microversions
# support is fully merged. It does not affect the V2 API.
_MIN_API_VERSION = "2.1"
_MAX_API_VERSION = "2.2"
_MAX_API_VERSION = "2.3"
DEFAULT_API_VERSION = _MIN_API_VERSION

View File

@@ -14,6 +14,7 @@
"""The Extended Server Attributes API extension."""
from nova.api.openstack import api_version_request
from nova.api.openstack import extensions
from nova.api.openstack import wsgi
@@ -23,11 +24,16 @@ authorize = extensions.soft_extension_authorizer('compute', 'v3:' + ALIAS)
class ExtendedServerAttributesController(wsgi.Controller):
def _extend_server(self, context, server, instance):
def _extend_server(self, context, server, instance, requested_version):
key = "OS-EXT-SRV-ATTR:hypervisor_hostname"
server[key] = instance.node
for attr in ['host', 'name']:
properties = ['host', 'name']
if requested_version >= api_version_request.APIVersionRequest("2.3"):
properties += ['reservation_id', 'launch_index',
'hostname', 'kernel_id', 'ramdisk_id',
'root_device_name', 'user_data']
for attr in properties:
if attr == 'name':
key = "OS-EXT-SRV-ATTR:instance_%s" % attr
else:
@@ -42,7 +48,8 @@ class ExtendedServerAttributesController(wsgi.Controller):
db_instance = req.get_db_instance(server['id'])
# server['id'] is guaranteed to be in the cache due to
# the core API adding it in its 'show' method.
self._extend_server(context, server, db_instance)
self._extend_server(context, server, db_instance,
req.api_version_request)
@wsgi.extends
def detail(self, req, resp_obj):
@@ -53,7 +60,8 @@ class ExtendedServerAttributesController(wsgi.Controller):
db_instance = req.get_db_instance(server['id'])
# server['id'] is guaranteed to be in the cache due to
# the core API adding it in its 'detail' method.
self._extend_server(context, server, db_instance)
self._extend_server(context, server, db_instance,
req.api_version_request)
class ExtendedServerAttributes(extensions.V3APIExtensionBase):

View File

@@ -13,6 +13,7 @@
# under the License.
"""The Extended Volumes API extension."""
from nova.api.openstack import api_version_request
from nova.api.openstack import extensions
from nova.api.openstack import wsgi
from nova import compute
@@ -29,12 +30,20 @@ class ExtendedVolumesController(wsgi.Controller):
self.compute_api = compute.API()
self.volume_api = volume.API()
def _extend_server(self, context, server, instance):
def _extend_server(self, context, server, instance, requested_version):
bdms = objects.BlockDeviceMappingList.get_by_instance_uuid(
context, instance.uuid)
volume_ids = [bdm['volume_id'] for bdm in bdms if bdm['volume_id']]
volumes_attached = []
api_version = api_version_request.APIVersionRequest('2.3')
for bdm in bdms:
if bdm.get('volume_id'):
volume_attached = {'id': bdm['volume_id']}
if requested_version >= api_version:
volume_attached['delete_on_termination'] = (
bdm['delete_on_termination'])
volumes_attached.append(volume_attached)
key = "%s:volumes_attached" % ExtendedVolumes.alias
server[key] = [{'id': volume_id} for volume_id in volume_ids]
server[key] = volumes_attached
@wsgi.extends
def show(self, req, resp_obj, id):
@@ -44,7 +53,8 @@ class ExtendedVolumesController(wsgi.Controller):
db_instance = req.get_db_instance(server['id'])
# server['id'] is guaranteed to be in the cache due to
# the core API adding it in its 'show' method.
self._extend_server(context, server, db_instance)
self._extend_server(context, server, db_instance,
req.api_version_request)
@wsgi.extends
def detail(self, req, resp_obj):
@@ -55,7 +65,8 @@ class ExtendedVolumesController(wsgi.Controller):
db_instance = req.get_db_instance(server['id'])
# server['id'] is guaranteed to be in the cache due to
# the core API adding it in its 'detail' method.
self._extend_server(context, server, db_instance)
self._extend_server(context, server, db_instance,
req.api_version_request)
class ExtendedVolumes(extensions.V3APIExtensionBase):

View File

@@ -35,3 +35,14 @@ user documentation.
Fixes status code for os-keypairs delete method from 202 to 204
- **2.3**
Exposed additional attributes in os-extended-server-attributes:
reservation_id, launch_index, ramdisk_id, kernel_id, hostname,
root_device_name, userdata.
Exposed delete_on_termination for attached_volumes in os-extended-volumes.
This change is required for the extraction of EC2 API into a standalone
service. It exposes necessary properties absent in public nova APIs yet.
Add info for Standalone EC2 API to cut access to Nova DB.

View File

@@ -17,6 +17,7 @@ from oslo_config import cfg
from oslo_serialization import jsonutils
import webob
from nova.api.openstack import wsgi as os_wsgi
from nova import compute
from nova import db
from nova import exception
@@ -30,6 +31,8 @@ NAME_FMT = cfg.CONF.instance_name_template
UUID1 = '00000000-0000-0000-0000-000000000001'
UUID2 = '00000000-0000-0000-0000-000000000002'
UUID3 = '00000000-0000-0000-0000-000000000003'
UUID4 = '00000000-0000-0000-0000-000000000004'
UUID5 = '00000000-0000-0000-0000-000000000005'
def fake_compute_get(*args, **kwargs):
@@ -37,13 +40,28 @@ def fake_compute_get(*args, **kwargs):
return objects.Instance._from_db_object(
args[1], objects.Instance(),
fakes.stub_instance(1, uuid=UUID3, host="host-fake",
node="node-fake"), fields)
node="node-fake",
reservation_id="r-1", launch_index=0,
kernel_id=UUID4, ramdisk_id=UUID5,
display_name="hostname-1",
root_device_name="/dev/vda",
user_data="userdata"), fields)
def fake_compute_get_all(*args, **kwargs):
db_list = [
fakes.stub_instance(1, uuid=UUID1, host="host-1", node="node-1"),
fakes.stub_instance(2, uuid=UUID2, host="host-2", node="node-2")
fakes.stub_instance(1, uuid=UUID1, host="host-1", node="node-1",
reservation_id="r-1", launch_index=0,
kernel_id=UUID4, ramdisk_id=UUID5,
display_name="hostname-1",
root_device_name="/dev/vda",
user_data="userdata"),
fakes.stub_instance(2, uuid=UUID2, host="host-2", node="node-2",
reservation_id="r-2", launch_index=1,
kernel_id=UUID4, ramdisk_id=UUID5,
display_name="hostname-2",
root_device_name="/dev/vda",
user_data="userdata")
]
fields = instance_obj.INSTANCE_DEFAULT_FIELDS
return instance_obj._make_instance_list(args[1],
@@ -55,6 +73,7 @@ class ExtendedServerAttributesTestV21(test.TestCase):
content_type = 'application/json'
prefix = 'OS-EXT-SRV-ATTR:'
fake_url = '/v2/fake'
wsgi_api_version = os_wsgi.DEFAULT_API_VERSION
def setUp(self):
super(ExtendedServerAttributesTestV21, self).setUp()
@@ -64,8 +83,10 @@ class ExtendedServerAttributesTestV21(test.TestCase):
self.stubs.Set(db, 'instance_get_by_uuid', fake_compute_get)
def _make_request(self, url):
req = webob.Request.blank(url)
req = fakes.HTTPRequest.blank(url)
req.headers['Accept'] = self.content_type
req.headers = {os_wsgi.API_VERSION_REQUEST_HEADER:
self.wsgi_api_version}
res = req.get_response(
fakes.wsgi_app_v21(init_only=('servers',
'os-extended-server-attributes')))
@@ -131,3 +152,63 @@ class ExtendedServerAttributesTestV2(ExtendedServerAttributesTestV21):
req.headers['Accept'] = self.content_type
res = req.get_response(fakes.wsgi_app(init_only=('servers',)))
return res
class ExtendedServerAttributesTestV23(ExtendedServerAttributesTestV21):
wsgi_api_version = '2.3'
def assertServerAttributes(self, server, host, node, instance_name,
reservation_id, launch_index, kernel_id,
ramdisk_id, hostname, root_device_name,
user_data):
super(ExtendedServerAttributesTestV23, self).assertServerAttributes(
server, host, node, instance_name)
self.assertEqual(server.get('%sreservation_id' % self.prefix),
reservation_id)
self.assertEqual(server.get('%slaunch_index' % self.prefix),
launch_index)
self.assertEqual(server.get('%skernel_id' % self.prefix),
kernel_id)
self.assertEqual(server.get('%sramdisk_id' % self.prefix),
ramdisk_id)
self.assertEqual(server.get('%shostname' % self.prefix),
hostname)
self.assertEqual(server.get('%sroot_device_name' % self.prefix),
root_device_name)
self.assertEqual(server.get('%suser_data' % self.prefix),
user_data)
def test_show(self):
url = self.fake_url + '/servers/%s' % UUID3
res = self._make_request(url)
self.assertEqual(res.status_int, 200)
self.assertServerAttributes(self._get_server(res.body),
host='host-fake',
node='node-fake',
instance_name=NAME_FMT % 1,
reservation_id="r-1",
launch_index=0,
kernel_id=UUID4,
ramdisk_id=UUID5,
hostname="hostname-1",
root_device_name="/dev/vda",
user_data="userdata")
def test_detail(self):
url = self.fake_url + '/servers/detail'
res = self._make_request(url)
self.assertEqual(res.status_int, 200)
for i, server in enumerate(self._get_servers(res.body)):
self.assertServerAttributes(server,
host='host-%s' % (i + 1),
node='node-%s' % (i + 1),
instance_name=NAME_FMT % (i + 1),
reservation_id="r-%s" % (i + 1),
launch_index=i,
kernel_id=UUID4,
ramdisk_id=UUID5,
hostname="hostname-%s" % (i + 1),
root_device_name="/dev/vda",
user_data="userdata")

View File

@@ -18,6 +18,7 @@ import webob
from nova.api.openstack.compute.plugins.v3 import (extended_volumes
as extended_volumes_v21)
from nova.api.openstack import wsgi as os_wsgi
from nova import compute
from nova import db
from nova import objects
@@ -49,10 +50,12 @@ def fake_compute_get_all(*args, **kwargs):
def fake_bdms_get_all_by_instance(*args, **kwargs):
return [fake_block_device.FakeDbBlockDeviceDict(
{'volume_id': UUID1, 'source_type': 'volume',
'destination_type': 'volume', 'id': 1}),
'destination_type': 'volume', 'id': 1,
'delete_on_termination': True}),
fake_block_device.FakeDbBlockDeviceDict(
{'volume_id': UUID2, 'source_type': 'volume',
'destination_type': 'volume', 'id': 2})]
'destination_type': 'volume', 'id': 2,
'delete_on_termination': False})]
def fake_volume_get(*args, **kwargs):
@@ -63,6 +66,7 @@ class ExtendedVolumesTestV21(test.TestCase):
content_type = 'application/json'
prefix = 'os-extended-volumes:'
exp_volumes = [{'id': UUID1}, {'id': UUID2}]
wsgi_api_version = os_wsgi.DEFAULT_API_VERSION
def setUp(self):
super(ExtendedVolumesTestV21, self).setUp()
@@ -88,6 +92,8 @@ class ExtendedVolumesTestV21(test.TestCase):
def _make_request(self, url, body=None):
req = webob.Request.blank('/v2/fake/servers' + url)
req.headers['Accept'] = self.content_type
req.headers = {os_wsgi.API_VERSION_REQUEST_HEADER:
self.wsgi_api_version}
if body:
req.body = jsonutils.dumps(body)
req.method = 'POST'
@@ -128,3 +134,10 @@ class ExtendedVolumesTestV2(ExtendedVolumesTestV21):
osapi_compute_extension=['nova.api.openstack.compute.'
'contrib.select_extensions'],
osapi_compute_ext_list=['Extended_volumes'])
class ExtendedVolumesTestV23(ExtendedVolumesTestV21):
exp_volumes = [{'id': UUID1, 'delete_on_termination': True},
{'id': UUID2, 'delete_on_termination': False}]
wsgi_api_version = '2.3'

View File

@@ -440,7 +440,8 @@ def stub_instance(id, user_id=None, project_id=None, host=None,
terminated_at=timeutils.utcnow(),
availability_zone='', locked_by=None, cleaned=False,
memory_mb=0, vcpus=0, root_gb=0, ephemeral_gb=0,
instance_type=None):
instance_type=None, launch_index=0, kernel_id="",
ramdisk_id="", user_data=None):
if user_id is None:
user_id = 'fake_user'
if project_id is None:
@@ -495,9 +496,9 @@ def stub_instance(id, user_id=None, project_id=None, host=None,
"user_id": user_id,
"project_id": project_id,
"image_ref": image_ref,
"kernel_id": "",
"ramdisk_id": "",
"launch_index": 0,
"kernel_id": kernel_id,
"ramdisk_id": ramdisk_id,
"launch_index": launch_index,
"key_name": key_name,
"key_data": key_data,
"config_drive": config_drive,
@@ -514,7 +515,7 @@ def stub_instance(id, user_id=None, project_id=None, host=None,
"node": node,
"instance_type_id": 1,
"instance_type": inst_type,
"user_data": "",
"user_data": user_data,
"reservation_id": reservation_id,
"mac_address": "",
"scheduled_at": timeutils.utcnow(),