Commit b2283092 authored by Vangelis Koukis's avatar Vangelis Koukis
Browse files

Change console req/response format, add unit tests

Change the format of JSON requests and replies for getting
a server console over VNC. Fix the cloud command-line tool to
understand the new format.

Add unit tests for api/actions.py:get_console().

Add a quick-n-dirty method of detecting if we're running unit
tests in settings.py.dist and set TEST accordingly. If TEST is
used, the API implementation sets dry_run when creating servers
in the backend and assumes mock replies from it.
parent 1f8fd9b2
......@@ -169,19 +169,18 @@ print out DDL statements. It should not fail.
$ ./bin/python manage.py loaddata db/fixtures/flavors.json
$ ./bin/python manage.py loaddata db/fixtures/images.json
The following fictures can be loaded optionally depending on
The following fixtures can be loaded optionally depending on
testing requirements:
$ ./bin/python manage.py loaddata db/fixtures/vms.json
$ ./bin/python manage.py loaddata db/fixtures/initial_data.json
$ ./bin/python manage.py loaddata db/fixtures/disks.json
7. Set the BACKEND_PREFIX_ID variable to some unique prefix, e.g. your commit
username
8. Start the system
$ ./bin/python db/db_controller.py #DB synch daemon
$ ./bin/python manage.py runserver #Django
$ ./bin/python db/db_controller.py # DB synch daemon
$ ./bin/python manage.py runserver # Django
9. (Hopefully) Done
......
......@@ -38,11 +38,26 @@ def get_console(request, vm, args):
"""Arrange for an OOB console of the specified type
This method arranges for an OOB console of the specified type.
Only "vnc" type consoles are supported for now.
Only consoles of type "vnc" are supported for now.
It uses a running instance of vncauthproxy to setup proper
VNC forwarding with a random password, then returns the necessary
VNC connection info to the caller.
JSON Request: {
"console": {
"type": "vnc"
}
}
JSON Reply: {
"vnc": {
"host": "fqdn_here",
"port": a_port_here,
"password": "a_password_here"
}
}
"""
# Normal Response Code: 200
# Error Response Codes: computeFault (400, 500),
......@@ -55,34 +70,39 @@ def get_console(request, vm, args):
# overLimit (413)
try:
console_type = args.get('type', '')
if console_type != 'VNC':
raise BadRequest(message="type can only be 'VNC'")
if console_type != 'vnc':
raise BadRequest(message="type can only be 'vnc'")
except KeyError:
raise BadRequest()
# Use RAPI to get VNC console information for this instance
if get_rsapi_state(vm) != 'ACTIVE':
raise BadRequest(message="Server not in ACTIVE state")
console_data = rapi.GetInstanceConsole(vm.backend_id)
if settings.TEST:
console_data = { 'kind': 'vnc', 'host': 'ganeti_node', 'port': 1000 }
else:
console_data = rapi.GetInstanceConsole(vm.backend_id)
if console_data['kind'] != 'vnc':
raise ServiceUnavailable()
# Let vncauthproxy decide on the source port.
# The alternative: static allocation, e.g.
# sport = console_data['port'] - 1000]
# sport = console_data['port'] - 1000
sport = 0
daddr = console_data['host']
dport = console_data['port']
passwd = random_password()
try:
fwd = request_vnc_forwarding(sport, daddr, dport, passwd)
if settings.TEST:
fwd = { 'source_port': 1234, 'status': 'OK' }
else:
fwd = request_vnc_forwarding(sport, daddr, dport, passwd)
if fwd['status'] != "OK":
raise ServiceUnavailable("Could not allocate VNC console port")
raise ServiceUnavailable()
vnc = { 'host': getfqdn(), 'port': fwd['source_port'], 'password': passwd }
except Exception:
#raise ServiceUnavailable("Could not allocate VNC console port")
raise
raise ServiceUnavailable("Could not allocate VNC console port")
# Format to be reviewed by [verigak], FIXME
if request.serialization == 'xml':
......@@ -90,7 +110,7 @@ def get_console(request, vm, args):
data = render_to_string('vnc.xml', {'vnc': vnc})
else:
mimetype = 'application/json'
data = json.dumps(vnc)
data = json.dumps({'vnc': vnc})
return HttpResponse(data, mimetype=mimetype, status=200)
......
......@@ -178,17 +178,10 @@ def create_server(request):
# so that it gets a vm.id and vm.backend_id is valid.
vm.save()
if request.META.get('SERVER_NAME', None) == 'testserver':
backend_name = 'test-server'
dry_run = True
else:
backend_name = vm.backend_id
dry_run = False
try:
jobId = rapi.CreateInstance(
mode='create',
name=backend_name,
name=vm.backend_id,
disk_template='plain',
disks=[{"size": 2000}], #FIXME: Always ask for a 2GB disk for now
nics=[{}],
......@@ -196,7 +189,7 @@ def create_server(request):
ip_check=False,
name_check=False,
pnode=rapi.GetNodes()[0], #TODO: verify if this is necessary
dry_run=dry_run,
dry_run=settings.TEST,
beparams=dict(auto_balance=True, vcpus=flavor.cpu, memory=flavor.ram))
except GanetiApiError:
vm.delete()
......
......@@ -694,3 +694,33 @@ class UpdateImageMetadata(BaseTestCase):
data = json.dumps({'metadata': {'Key1': 'A Value'}})
response = self.client.post(path, data, content_type='application/json')
self.assertItemNotFound(response)
class ServerVNCConsole(BaseTestCase):
SERVERS = 1
def test_not_active_server(self):
"""Test console req for server not in ACTIVE state returns badRequest"""
server_id = choice(VirtualMachine.objects.all()).id
path = '/api/v1.1/servers/%d/action' % server_id
data = json.dumps({'console': {'type': 'vnc'}})
response = self.client.post(path, data, content_type='application/json')
self.assertBadRequest(response)
def test_active_server(self):
"""Test console req for ACTIVE server"""
server_id = choice(VirtualMachine.objects.all()).id
# FIXME: Start the server properly, instead of tampering with the DB
vm = choice(VirtualMachine.objects.all())
vm.operstate = 'STARTED'
vm.save()
server_id = vm.id
path = '/api/v1.1/servers/%d/action' % server_id
data = json.dumps({'console': {'type': 'vnc'}})
response = self.client.post(path, data, content_type='application/json')
self.assertEqual(response.status_code, 200)
reply = json.loads(response.content)
self.assertEqual(reply.keys(), ['vnc'])
self.assertEqual(set(reply['vnc'].keys()), set(['host', 'port', 'password']))
......@@ -132,7 +132,7 @@ def update_response_headers(request, response):
else:
response['Content-Type'] = 'application/json'
if request.META.get('SERVER_NAME') == 'testserver':
if settings.TEST:
response['Date'] = format_date_time(time())
def render_metadata(request, metadata, use_values=False, status=200):
......@@ -151,7 +151,7 @@ def render_meta(request, meta, status=200):
return HttpResponse(data, status=status)
def render_fault(request, fault):
if settings.DEBUG or request.META.get('SERVER_NAME') == 'testserver':
if settings.DEBUG or settings.TEST:
fault.details = format_exc(fault)
if request.serialization == 'xml':
......
......@@ -8,6 +8,13 @@ PROJECT_PATH = os.path.dirname(os.path.abspath(__file__)) + '/'
DEBUG = True
TEMPLATE_DEBUG = DEBUG
# A quick-n-dirty way to know if we're running unit tests
import sys
if len(sys.argv) >= 2:
TEST = ('manage.py', 'test') == (os.path.basename(sys.argv[0]), sys.argv[1])
else:
TEST = False
ADMINS = (
# ('Your Name', 'your_email@domain.com'),
)
......
......@@ -82,6 +82,9 @@ class Command(object):
buf = resp.read() or '{}'
reply = json.loads(buf)
# If the response status is not the expected one,
# assume an error has occured and treat the body
# as a cloudfault.
if resp.status != expected_status:
if len(reply) == 1:
key = reply.keys()[0]
......@@ -241,9 +244,9 @@ class ServerConsole(Command):
def execute(self, server_id):
path = '/api/%s/servers/%d/action' % (self.api, int(server_id))
body = json.dumps({'console':{'type':'VNC'}})
reply = self.http_post(path, body)
print_dict(reply)
body = json.dumps({'console':{'type':'vnc'}})
reply = self.http_cmd('POST', path, body, 200)
print_dict(reply['vnc'])
@command_name('lsaddr')
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment