Ports consoles API to v3 API
Ports the core consoles API functionality to the V3 API as a plugin along with the corresponding tests Partially implements blueprint v3-api-core-as-extensions Change-Id: Iada86afbfeed055942fef554d12cd36385aa2e1f
This commit is contained in:
parent
5ec5dbbf30
commit
1a3e02f1cd
144
nova/api/openstack/compute/plugins/v3/consoles.py
Normal file
144
nova/api/openstack/compute/plugins/v3/consoles.py
Normal file
@ -0,0 +1,144 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import webob
|
||||
from webob import exc
|
||||
|
||||
from nova.api.openstack import extensions
|
||||
from nova.api.openstack import wsgi
|
||||
from nova.api.openstack import xmlutil
|
||||
from nova.console import api as console_api
|
||||
from nova import exception
|
||||
|
||||
|
||||
def _translate_keys(cons):
|
||||
"""Coerces a console instance into proper dictionary format."""
|
||||
pool = cons['pool']
|
||||
info = {'id': cons['id'],
|
||||
'console_type': pool['console_type']}
|
||||
return dict(console=info)
|
||||
|
||||
|
||||
def _translate_detail_keys(cons):
|
||||
"""Coerces a console instance into proper dictionary format with detail."""
|
||||
pool = cons['pool']
|
||||
info = {'id': cons['id'],
|
||||
'console_type': pool['console_type'],
|
||||
'password': cons['password'],
|
||||
'instance_name': cons['instance_name'],
|
||||
'port': cons['port'],
|
||||
'host': pool['public_hostname']}
|
||||
return dict(console=info)
|
||||
|
||||
|
||||
class ConsoleTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('console', selector='console')
|
||||
|
||||
id_elem = xmlutil.SubTemplateElement(root, 'id', selector='id')
|
||||
id_elem.text = xmlutil.Selector()
|
||||
|
||||
port_elem = xmlutil.SubTemplateElement(root, 'port', selector='port')
|
||||
port_elem.text = xmlutil.Selector()
|
||||
|
||||
host_elem = xmlutil.SubTemplateElement(root, 'host', selector='host')
|
||||
host_elem.text = xmlutil.Selector()
|
||||
|
||||
passwd_elem = xmlutil.SubTemplateElement(root, 'password',
|
||||
selector='password')
|
||||
passwd_elem.text = xmlutil.Selector()
|
||||
|
||||
constype_elem = xmlutil.SubTemplateElement(root, 'console_type',
|
||||
selector='console_type')
|
||||
constype_elem.text = xmlutil.Selector()
|
||||
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class ConsolesTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('consoles')
|
||||
console = xmlutil.SubTemplateElement(root, 'console',
|
||||
selector='consoles')
|
||||
console.append(ConsoleTemplate())
|
||||
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
|
||||
class ConsolesController(object):
|
||||
"""The Consoles controller for the OpenStack API."""
|
||||
|
||||
def __init__(self):
|
||||
self.console_api = console_api.API()
|
||||
|
||||
@wsgi.serializers(xml=ConsolesTemplate)
|
||||
def index(self, req, server_id):
|
||||
"""Returns a list of consoles for this instance."""
|
||||
consoles = self.console_api.get_consoles(
|
||||
req.environ['nova.context'],
|
||||
server_id)
|
||||
return dict(consoles=[_translate_keys(console)
|
||||
for console in consoles])
|
||||
|
||||
def create(self, req, server_id):
|
||||
"""Creates a new console."""
|
||||
self.console_api.create_console(
|
||||
req.environ['nova.context'], server_id)
|
||||
|
||||
@wsgi.serializers(xml=ConsoleTemplate)
|
||||
def show(self, req, server_id, id):
|
||||
"""Shows in-depth information on a specific console."""
|
||||
try:
|
||||
console = self.console_api.get_console(
|
||||
req.environ['nova.context'],
|
||||
server_id,
|
||||
int(id))
|
||||
except exception.NotFound:
|
||||
raise exc.HTTPNotFound()
|
||||
return _translate_detail_keys(console)
|
||||
|
||||
def delete(self, req, server_id, id):
|
||||
"""Deletes a console."""
|
||||
try:
|
||||
self.console_api.delete_console(req.environ['nova.context'],
|
||||
server_id,
|
||||
int(id))
|
||||
except exception.NotFound:
|
||||
raise exc.HTTPNotFound()
|
||||
return webob.Response(status_int=202)
|
||||
|
||||
|
||||
class Consoles(extensions.V3APIExtensionBase):
|
||||
"""Consoles."""
|
||||
|
||||
name = "Consoles"
|
||||
alias = "consoles"
|
||||
namespace = "http://docs.openstack.org/compute/core/consoles/v3"
|
||||
version = 1
|
||||
|
||||
def get_resources(self):
|
||||
parent = {'member_name': 'server',
|
||||
'collection_name': 'servers'}
|
||||
resources = [
|
||||
extensions.ResourceExtension(
|
||||
'consoles', ConsolesController(), parent=parent,
|
||||
member_name='console')]
|
||||
|
||||
return resources
|
||||
|
||||
def get_controller_extensions(self):
|
||||
return []
|
295
nova/tests/api/openstack/compute/plugins/v3/test_consoles.py
Normal file
295
nova/tests/api/openstack/compute/plugins/v3/test_consoles.py
Normal file
@ -0,0 +1,295 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010-2011 OpenStack Foundation
|
||||
# Copyright 2011 Piston Cloud Computing, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import datetime
|
||||
import uuid as stdlib_uuid
|
||||
|
||||
from lxml import etree
|
||||
import webob
|
||||
|
||||
from nova.api.openstack.compute.plugins.v3 import consoles
|
||||
from nova.compute import vm_states
|
||||
from nova import console
|
||||
from nova import db
|
||||
from nova import exception
|
||||
from nova.openstack.common import timeutils
|
||||
from nova import test
|
||||
from nova.tests.api.openstack import fakes
|
||||
from nova.tests import matchers
|
||||
|
||||
|
||||
FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
||||
|
||||
|
||||
class FakeInstanceDB(object):
|
||||
|
||||
def __init__(self):
|
||||
self.instances_by_id = {}
|
||||
self.ids_by_uuid = {}
|
||||
self.max_id = 0
|
||||
|
||||
def return_server_by_id(self, context, id):
|
||||
if id not in self.instances_by_id:
|
||||
self._add_server(id=id)
|
||||
return dict(self.instances_by_id[id])
|
||||
|
||||
def return_server_by_uuid(self, context, uuid):
|
||||
if uuid not in self.ids_by_uuid:
|
||||
self._add_server(uuid=uuid)
|
||||
return dict(self.instances_by_id[self.ids_by_uuid[uuid]])
|
||||
|
||||
def _add_server(self, id=None, uuid=None):
|
||||
if id is None:
|
||||
id = self.max_id + 1
|
||||
if uuid is None:
|
||||
uuid = str(stdlib_uuid.uuid4())
|
||||
instance = stub_instance(id, uuid=uuid)
|
||||
self.instances_by_id[id] = instance
|
||||
self.ids_by_uuid[uuid] = id
|
||||
if id > self.max_id:
|
||||
self.max_id = id
|
||||
|
||||
|
||||
def stub_instance(id, user_id='fake', project_id='fake', host=None,
|
||||
vm_state=None, task_state=None,
|
||||
reservation_id="", uuid=FAKE_UUID, image_ref="10",
|
||||
flavor_id="1", name=None, key_name='',
|
||||
access_ipv4=None, access_ipv6=None, progress=0):
|
||||
|
||||
if host is not None:
|
||||
host = str(host)
|
||||
|
||||
if key_name:
|
||||
key_data = 'FAKE'
|
||||
else:
|
||||
key_data = ''
|
||||
|
||||
# ReservationID isn't sent back, hack it in there.
|
||||
server_name = name or "server%s" % id
|
||||
if reservation_id != "":
|
||||
server_name = "reservation_%s" % (reservation_id, )
|
||||
|
||||
instance = {
|
||||
"id": int(id),
|
||||
"created_at": datetime.datetime(2010, 10, 10, 12, 0, 0),
|
||||
"updated_at": datetime.datetime(2010, 11, 11, 11, 0, 0),
|
||||
"admin_pass": "",
|
||||
"user_id": user_id,
|
||||
"project_id": project_id,
|
||||
"image_ref": image_ref,
|
||||
"kernel_id": "",
|
||||
"ramdisk_id": "",
|
||||
"launch_index": 0,
|
||||
"key_name": key_name,
|
||||
"key_data": key_data,
|
||||
"vm_state": vm_state or vm_states.BUILDING,
|
||||
"task_state": task_state,
|
||||
"memory_mb": 0,
|
||||
"vcpus": 0,
|
||||
"root_gb": 0,
|
||||
"hostname": "",
|
||||
"host": host,
|
||||
"instance_type": {},
|
||||
"user_data": "",
|
||||
"reservation_id": reservation_id,
|
||||
"mac_address": "",
|
||||
"scheduled_at": timeutils.utcnow(),
|
||||
"launched_at": timeutils.utcnow(),
|
||||
"terminated_at": timeutils.utcnow(),
|
||||
"availability_zone": "",
|
||||
"display_name": server_name,
|
||||
"display_description": "",
|
||||
"locked": False,
|
||||
"metadata": [],
|
||||
"access_ip_v4": access_ipv4,
|
||||
"access_ip_v6": access_ipv6,
|
||||
"uuid": uuid,
|
||||
"progress": progress}
|
||||
|
||||
return instance
|
||||
|
||||
|
||||
class ConsolesControllerTest(test.TestCase):
|
||||
def setUp(self):
|
||||
super(ConsolesControllerTest, self).setUp()
|
||||
self.flags(verbose=True)
|
||||
self.instance_db = FakeInstanceDB()
|
||||
self.stubs.Set(db, 'instance_get',
|
||||
self.instance_db.return_server_by_id)
|
||||
self.stubs.Set(db, 'instance_get_by_uuid',
|
||||
self.instance_db.return_server_by_uuid)
|
||||
self.uuid = str(stdlib_uuid.uuid4())
|
||||
self.url = '/v3/fake/servers/%s/consoles' % self.uuid
|
||||
self.controller = consoles.ConsolesController()
|
||||
|
||||
def test_create_console(self):
|
||||
def fake_create_console(cons_self, context, instance_id):
|
||||
self.assertEqual(instance_id, self.uuid)
|
||||
return {}
|
||||
self.stubs.Set(console.api.API, 'create_console', fake_create_console)
|
||||
|
||||
req = fakes.HTTPRequestV3.blank(self.url)
|
||||
self.controller.create(req, self.uuid)
|
||||
|
||||
def test_show_console(self):
|
||||
def fake_get_console(cons_self, context, instance_id, console_id):
|
||||
self.assertEqual(instance_id, self.uuid)
|
||||
self.assertEqual(console_id, 20)
|
||||
pool = dict(console_type='fake_type',
|
||||
public_hostname='fake_hostname')
|
||||
return dict(id=console_id, password='fake_password',
|
||||
port='fake_port', pool=pool, instance_name='inst-0001')
|
||||
|
||||
expected = {'console': {'id': 20,
|
||||
'port': 'fake_port',
|
||||
'host': 'fake_hostname',
|
||||
'password': 'fake_password',
|
||||
'instance_name': 'inst-0001',
|
||||
'console_type': 'fake_type'}}
|
||||
|
||||
self.stubs.Set(console.api.API, 'get_console', fake_get_console)
|
||||
|
||||
req = fakes.HTTPRequestV3.blank(self.url + '/20')
|
||||
res_dict = self.controller.show(req, self.uuid, '20')
|
||||
self.assertThat(res_dict, matchers.DictMatches(expected))
|
||||
|
||||
def test_show_console_unknown_console(self):
|
||||
def fake_get_console(cons_self, context, instance_id, console_id):
|
||||
raise exception.ConsoleNotFound(console_id=console_id)
|
||||
|
||||
self.stubs.Set(console.api.API, 'get_console', fake_get_console)
|
||||
|
||||
req = fakes.HTTPRequestV3.blank(self.url + '/20')
|
||||
self.assertRaises(webob.exc.HTTPNotFound, self.controller.show,
|
||||
req, self.uuid, '20')
|
||||
|
||||
def test_show_console_unknown_instance(self):
|
||||
def fake_get_console(cons_self, context, instance_id, console_id):
|
||||
raise exception.InstanceNotFound(instance_id=instance_id)
|
||||
|
||||
self.stubs.Set(console.api.API, 'get_console', fake_get_console)
|
||||
|
||||
req = fakes.HTTPRequestV3.blank(self.url + '/20')
|
||||
self.assertRaises(webob.exc.HTTPNotFound, self.controller.show,
|
||||
req, self.uuid, '20')
|
||||
|
||||
def test_list_consoles(self):
|
||||
def fake_get_consoles(cons_self, context, instance_id):
|
||||
self.assertEqual(instance_id, self.uuid)
|
||||
|
||||
pool1 = dict(console_type='fake_type',
|
||||
public_hostname='fake_hostname')
|
||||
cons1 = dict(id=10, password='fake_password',
|
||||
port='fake_port', pool=pool1)
|
||||
pool2 = dict(console_type='fake_type2',
|
||||
public_hostname='fake_hostname2')
|
||||
cons2 = dict(id=11, password='fake_password2',
|
||||
port='fake_port2', pool=pool2)
|
||||
return [cons1, cons2]
|
||||
|
||||
expected = {'consoles':
|
||||
[{'console': {'id': 10, 'console_type': 'fake_type'}},
|
||||
{'console': {'id': 11, 'console_type': 'fake_type2'}}]}
|
||||
|
||||
self.stubs.Set(console.api.API, 'get_consoles', fake_get_consoles)
|
||||
|
||||
req = fakes.HTTPRequestV3.blank(self.url)
|
||||
res_dict = self.controller.index(req, self.uuid)
|
||||
self.assertThat(res_dict, matchers.DictMatches(expected))
|
||||
|
||||
def test_delete_console(self):
|
||||
def fake_get_console(cons_self, context, instance_id, console_id):
|
||||
self.assertEqual(instance_id, self.uuid)
|
||||
self.assertEqual(console_id, 20)
|
||||
pool = dict(console_type='fake_type',
|
||||
public_hostname='fake_hostname')
|
||||
return dict(id=console_id, password='fake_password',
|
||||
port='fake_port', pool=pool)
|
||||
|
||||
def fake_delete_console(cons_self, context, instance_id, console_id):
|
||||
self.assertEqual(instance_id, self.uuid)
|
||||
self.assertEqual(console_id, 20)
|
||||
|
||||
self.stubs.Set(console.api.API, 'get_console', fake_get_console)
|
||||
self.stubs.Set(console.api.API, 'delete_console', fake_delete_console)
|
||||
|
||||
req = fakes.HTTPRequestV3.blank(self.url + '/20')
|
||||
self.controller.delete(req, self.uuid, '20')
|
||||
|
||||
def test_delete_console_unknown_console(self):
|
||||
def fake_delete_console(cons_self, context, instance_id, console_id):
|
||||
raise exception.ConsoleNotFound(console_id=console_id)
|
||||
|
||||
self.stubs.Set(console.api.API, 'delete_console', fake_delete_console)
|
||||
|
||||
req = fakes.HTTPRequestV3.blank(self.url + '/20')
|
||||
self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete,
|
||||
req, self.uuid, '20')
|
||||
|
||||
def test_delete_console_unknown_instance(self):
|
||||
def fake_delete_console(cons_self, context, instance_id, console_id):
|
||||
raise exception.InstanceNotFound(instance_id=instance_id)
|
||||
|
||||
self.stubs.Set(console.api.API, 'delete_console', fake_delete_console)
|
||||
|
||||
req = fakes.HTTPRequestV3.blank(self.url + '/20')
|
||||
self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete,
|
||||
req, self.uuid, '20')
|
||||
|
||||
|
||||
class TestConsolesXMLSerializer(test.TestCase):
|
||||
def test_show(self):
|
||||
fixture = {'console': {'id': 20,
|
||||
'password': 'fake_password',
|
||||
'port': 'fake_port',
|
||||
'host': 'fake_hostname',
|
||||
'console_type': 'fake_type'}}
|
||||
|
||||
output = consoles.ConsoleTemplate().serialize(fixture)
|
||||
res_tree = etree.XML(output)
|
||||
|
||||
self.assertEqual(res_tree.tag, 'console')
|
||||
self.assertEqual(res_tree.xpath('id')[0].text, '20')
|
||||
self.assertEqual(res_tree.xpath('port')[0].text, 'fake_port')
|
||||
self.assertEqual(res_tree.xpath('host')[0].text, 'fake_hostname')
|
||||
self.assertEqual(res_tree.xpath('password')[0].text, 'fake_password')
|
||||
self.assertEqual(res_tree.xpath('console_type')[0].text, 'fake_type')
|
||||
|
||||
def test_index(self):
|
||||
fixture = {'consoles': [{'console': {'id': 10,
|
||||
'console_type': 'fake_type'}},
|
||||
{'console': {'id': 11,
|
||||
'console_type': 'fake_type2'}}]}
|
||||
|
||||
output = consoles.ConsolesTemplate().serialize(fixture)
|
||||
res_tree = etree.XML(output)
|
||||
|
||||
self.assertEqual(res_tree.tag, 'consoles')
|
||||
self.assertEqual(len(res_tree), 2)
|
||||
self.assertEqual(res_tree[0].tag, 'console')
|
||||
self.assertEqual(res_tree[1].tag, 'console')
|
||||
self.assertEqual(len(res_tree[0]), 1)
|
||||
self.assertEqual(res_tree[0][0].tag, 'console')
|
||||
self.assertEqual(len(res_tree[1]), 1)
|
||||
self.assertEqual(res_tree[1][0].tag, 'console')
|
||||
self.assertEqual(res_tree[0][0].xpath('id')[0].text, '10')
|
||||
self.assertEqual(res_tree[1][0].xpath('id')[0].text, '11')
|
||||
self.assertEqual(res_tree[0][0].xpath('console_type')[0].text,
|
||||
'fake_type')
|
||||
self.assertEqual(res_tree[1][0].xpath('console_type')[0].text,
|
||||
'fake_type2')
|
@ -59,6 +59,7 @@ nova.api.v3.extensions =
|
||||
servers = nova.api.openstack.compute.plugins.v3.servers:Servers
|
||||
keypairs = nova.api.openstack.compute.plugins.v3.keypairs:Keypairs
|
||||
ips = nova.api.openstack.compute.plugins.v3.ips:IPs
|
||||
consoles = nova.api.openstack.compute.plugins.v3.consoles:Consoles
|
||||
|
||||
nova.api.v3.extensions.server.create =
|
||||
keypairs_create = nova.api.openstack.compute.plugins.v3.keypairs:Keypairs
|
||||
|
Loading…
x
Reference in New Issue
Block a user