From 4ee5ef25dcaa9d4235e97972acdbbcbf5067d88c Mon Sep 17 00:00:00 2001 From: root Date: Fri, 17 Sep 2010 19:28:10 -0700 Subject: add in support for ajaxterm console access --- nova/adminclient.py | 22 ++++++++++++++++++++++ nova/endpoint/admin.py | 29 +++++++++++++++++++++++++++++ nova/utils.py | 4 ++++ 3 files changed, 55 insertions(+) (limited to 'nova') diff --git a/nova/adminclient.py b/nova/adminclient.py index 0ca32b1e5..9670b7186 100644 --- a/nova/adminclient.py +++ b/nova/adminclient.py @@ -24,6 +24,19 @@ import base64 import boto from boto.ec2.regioninfo import RegionInfo +class ConsoleInfo(object): + def __init__(self, connection=None, endpoint=None): + self.connection = connection + self.endpoint = endpoint + + def startElement(self, name, attrs, connection): + return None + + def endElement(self, name, value, connection): + if name == 'url': + self.url = str(value) + if name == 'kind': + self.url = str(value) class UserInfo(object): """ @@ -349,3 +362,12 @@ class NovaAdminClient(object): def get_hosts(self): return self.apiconn.get_list('DescribeHosts', {}, [('item', HostInfo)]) + def create_console(self, instance_id, kind='ajax'): + """ + Create a console + """ + console = self.apiconn.get_object('CreateConsole', {'Kind': kind, 'InstanceId': instance_id}, ConsoleInfo) + + if console.url != None: + return console + diff --git a/nova/endpoint/admin.py b/nova/endpoint/admin.py index 3d91c66dc..686e462b5 100644 --- a/nova/endpoint/admin.py +++ b/nova/endpoint/admin.py @@ -21,10 +21,14 @@ Admin API controller, exposed through http via the api worker. """ import base64 +import uuid +import subprocess +import random from nova import db from nova import exception from nova.auth import manager +from utils import novadir def user_dict(user, base64_file=None): @@ -211,3 +215,28 @@ class AdminController(object): def describe_host(self, _context, name, **_kwargs): """Returns status info for single node.""" return host_dict(db.host_get(name)) + + @admin_only + def create_console(self, _context, kind, instance_id, **_kwargs): + """Create a Console""" + #instance = db.instance_get(_context, instance_id) + host = '127.0.0.1' + + def get_port(): + for i in range(0,100): # don't loop forever + port = int(random.uniform(10000, 12000)) + cmd = "netcat 0.0.0.0 " + str(port) + " -w 2 < /dev/null" + # this Popen will exit with 0 only if the port is in use, + # so a nonzero return value implies it is unused + port_is_unused = subprocess.Popen(cmd, shell=True).wait() + if port_is_unused: + return port + raise 'Unable to find an open port' + + port = str(get_port()) + token = str(uuid.uuid4()) + cmd = novadir() + "tools/ajaxterm//ajaxterm.py --command 'ssh root@" + host + "' -t " \ + + token + " -p " + port + port_is_unused = subprocess.Popen(cmd, shell=True) + return {'url': 'http://tonbuntu:' + port + '/?token=' + token } + diff --git a/nova/utils.py b/nova/utils.py index 536d722bb..ed703a9db 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -92,6 +92,10 @@ def abspath(s): return os.path.join(os.path.dirname(__file__), s) +def novadir(s): + return os.path.abspath(nova.__file__).split('nova/__init__.pyc')[0] + + def default_flagfile(filename='nova.conf'): for arg in sys.argv: if arg.find('flagfile') != -1: -- cgit From 4f7bbaa83216dfdb298f460c771806ef1071113b Mon Sep 17 00:00:00 2001 From: root Date: Fri, 17 Sep 2010 20:36:13 -0700 Subject: add in a few comments --- nova/endpoint/admin.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/endpoint/admin.py b/nova/endpoint/admin.py index 686e462b5..8d184f10e 100644 --- a/nova/endpoint/admin.py +++ b/nova/endpoint/admin.py @@ -220,10 +220,9 @@ class AdminController(object): def create_console(self, _context, kind, instance_id, **_kwargs): """Create a Console""" #instance = db.instance_get(_context, instance_id) - host = '127.0.0.1' def get_port(): - for i in range(0,100): # don't loop forever + for i in xrange(0,100): # don't loop forever port = int(random.uniform(10000, 12000)) cmd = "netcat 0.0.0.0 " + str(port) + " -w 2 < /dev/null" # this Popen will exit with 0 only if the port is in use, @@ -235,8 +234,10 @@ class AdminController(object): port = str(get_port()) token = str(uuid.uuid4()) + + host = '127.0.0.1' #TODO add actual host cmd = novadir() + "tools/ajaxterm//ajaxterm.py --command 'ssh root@" + host + "' -t " \ + token + " -p " + port - port_is_unused = subprocess.Popen(cmd, shell=True) - return {'url': 'http://tonbuntu:' + port + '/?token=' + token } + port_is_unused = subprocess.Popen(cmd, shell=True) #TODO error check + return {'url': 'http://tonbuntu:' + port + '/?token=' + token } #TODO - s/tonbuntu/api_server_public_ip -- cgit From 5d0f6ac00633f622d238b49af1a0d7c566057ec5 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Sun, 24 Oct 2010 17:54:52 -0700 Subject: move create_console to cloud.py from admin.py --- nova/api/ec2/admin.py | 30 ------------------------------ nova/api/ec2/cloud.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 30 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index 4281ad055..24ce5ee7c 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -21,15 +21,10 @@ Admin API controller, exposed through http via the api worker. """ import base64 -import uuid -import subprocess -import random from nova import db from nova import exception from nova.auth import manager -from utils import novadir - def user_dict(user, base64_file=None): """Convert the user object to a result dict""" @@ -187,28 +182,3 @@ class AdminController(object): """Returns status info for single node.""" return host_dict(db.host_get(name)) - @admin_only - def create_console(self, _context, kind, instance_id, **_kwargs): - """Create a Console""" - #instance = db.instance_get(_context, instance_id) - - def get_port(): - for i in xrange(0,100): # don't loop forever - port = int(random.uniform(10000, 12000)) - cmd = "netcat 0.0.0.0 " + str(port) + " -w 2 < /dev/null" - # this Popen will exit with 0 only if the port is in use, - # so a nonzero return value implies it is unused - port_is_unused = subprocess.Popen(cmd, shell=True).wait() - if port_is_unused: - return port - raise 'Unable to find an open port' - - port = str(get_port()) - token = str(uuid.uuid4()) - - host = '127.0.0.1' #TODO add actual host - cmd = novadir() + "tools/ajaxterm//ajaxterm.py --command 'ssh root@" + host + "' -t " \ - + token + " -p " + port - port_is_unused = subprocess.Popen(cmd, shell=True) #TODO error check - return {'url': 'http://tonbuntu:' + port + '/?token=' + token } #TODO - s/tonbuntu/api_server_public_ip - diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 784697b01..be537a290 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -26,7 +26,10 @@ import base64 import datetime import logging import os +import random +import subprocess import time +import uuid from nova import context import IPy @@ -436,6 +439,31 @@ class CloudController(object): db.security_group_destroy(context, security_group.id) return True + def create_console(self, context, kind, instance_id, **_kwargs): + """Create a Console""" + + instance_ref = db.instance_get(context, instance_id) + + def get_port(): + for i in xrange(0,100): # don't loop forever + port = random.randint(10000, 12000) + cmd = "netcat 0.0.0.0 %s -w 2 < /dev/null" % (port,) + # this Popen will exit with 0 only if the port is in use, + # so a nonzero return value implies it is unused + port_is_unused = subprocess.Popen(cmd, shell=True).wait() + if port_is_unused: + return port + raise 'Unable to find an open port' + + port = get_port() + token = str(uuid.uuid4()) + + host = instance_ref['host'] + cmd = "%s/tools/ajaxterm/ajaxterm.py --command 'ssh %s' -t %s -p %s" \ + % (utils.novadir(), host, token, port) + port_is_unused = subprocess.Popen(cmd, shell=True) #TODO error check + return {'url': 'http://%s:%s/?token=%s' % (FLAGS.cc_dmz, port, token)} + def get_console_output(self, context, instance_id, **kwargs): # instance_id is passed in as a list of instances ec2_id = instance_id[0] -- cgit From 7bf0f86e5863f4943900a78f9797810b80d171e5 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Sun, 24 Oct 2010 17:56:09 -0700 Subject: whitespace --- nova/api/ec2/admin.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index 24ce5ee7c..23942af6e 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -26,6 +26,7 @@ from nova import db from nova import exception from nova.auth import manager + def user_dict(user, base64_file=None): """Convert the user object to a result dict""" if user: -- cgit From a3077cbb859a9237f9516ed0f073fe00839277c4 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 1 Nov 2010 16:25:56 -0700 Subject: basics to get proxied ajaxterm working with virsh --- nova/api/ec2/cloud.py | 50 ++++++++++++++++++++----------------- nova/boto_extensions.py | 40 +++++++++++++++++++++++++++++ nova/utils.py | 3 ++- nova/virt/libvirt.qemu.xml.template | 9 +++++++ 4 files changed, 78 insertions(+), 24 deletions(-) create mode 100644 nova/boto_extensions.py (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index be537a290..469331a66 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -439,10 +439,27 @@ class CloudController(object): db.security_group_destroy(context, security_group.id) return True - def create_console(self, context, kind, instance_id, **_kwargs): - """Create a Console""" + def get_console_output(self, context, instance_id, **kwargs): + # instance_id is passed in as a list of instances + ec2_id = instance_id[0] + internal_id = ec2_id_to_internal_id(ec2_id) + instance_ref = db.instance_get_by_internal_id(context, internal_id) + output = rpc.call(context, + '%s.%s' % (FLAGS.compute_topic, + instance_ref['host']), + {"method": "get_console_output", + "args": {"instance_id": instance_ref['id']}}) + + now = datetime.datetime.utcnow() + return {"InstanceId": ec2_id, + "Timestamp": now, + "output": base64.b64encode(output)} + def get_ajax_console(self, context, instance_id, **kwargs): + """Create an AJAX Console""" - instance_ref = db.instance_get(context, instance_id) + ec2_id = instance_id[0] + internal_id = ec2_id_to_internal_id(ec2_id) + instance_ref = db.instance_get_by_internal_id(context, internal_id) def get_port(): for i in xrange(0,100): # don't loop forever @@ -450,7 +467,7 @@ class CloudController(object): cmd = "netcat 0.0.0.0 %s -w 2 < /dev/null" % (port,) # this Popen will exit with 0 only if the port is in use, # so a nonzero return value implies it is unused - port_is_unused = subprocess.Popen(cmd, shell=True).wait() + port_is_unused = (subprocess.Popen(cmd, shell=True).wait() != 0) if port_is_unused: return port raise 'Unable to find an open port' @@ -459,26 +476,11 @@ class CloudController(object): token = str(uuid.uuid4()) host = instance_ref['host'] - cmd = "%s/tools/ajaxterm/ajaxterm.py --command 'ssh %s' -t %s -p %s" \ - % (utils.novadir(), host, token, port) + cmd = "%s/tools/ajaxterm/ajaxterm.py --command 'virsh console instance-%d' -t %s -p %s" \ + % (utils.novadir(), internal_id, token, port) port_is_unused = subprocess.Popen(cmd, shell=True) #TODO error check - return {'url': 'http://%s:%s/?token=%s' % (FLAGS.cc_dmz, port, token)} - - def get_console_output(self, context, instance_id, **kwargs): - # instance_id is passed in as a list of instances - ec2_id = instance_id[0] - internal_id = ec2_id_to_internal_id(ec2_id) - instance_ref = db.instance_get_by_internal_id(context, internal_id) - output = rpc.call(context, - '%s.%s' % (FLAGS.compute_topic, - instance_ref['host']), - {"method": "get_console_output", - "args": {"instance_id": instance_ref['id']}}) - - now = datetime.datetime.utcnow() - return {"InstanceId": ec2_id, - "Timestamp": now, - "output": base64.b64encode(output)} + dmz = 'tonbuntu' #TODO put correct value for dmz + return {'url': 'http://%s:%s/?token=%s&host=%s&port=%s' % (dmz, 8000, token, host, port)} def describe_volumes(self, context, **kwargs): if context.user.is_admin(): @@ -896,6 +898,8 @@ class CloudController(object): (context.project.name, context.user.name, inst_id)) return self._format_run_instances(context, reservation_id) + def run_instances2(self, context, **kwargs): + return self.run_instances(context, kwargs) def terminate_instances(self, context, instance_id, **kwargs): """Terminate each instance in instance_id, which is a list of ec2 ids. diff --git a/nova/boto_extensions.py b/nova/boto_extensions.py new file mode 100644 index 000000000..6d55b8012 --- /dev/null +++ b/nova/boto_extensions.py @@ -0,0 +1,40 @@ +import base64 +import boto +from boto.ec2.connection import EC2Connection + +class AjaxConsole: + def __init__(self, parent=None): + self.parent = parent + self.instance_id = None + self.url = None + + def startElement(self, name, attrs, connection): + return None + + def endElement(self, name, value, connection): + if name == 'instanceId': + self.instance_id = value + elif name == 'url': + self.url = value + else: + setattr(self, name, value) + +class NovaEC2Connection(EC2Connection): + def get_ajax_console(self, instance_id): + """ + Retrieves a console connection for the specified instance. + + :type instance_id: string + :param instance_id: The instance ID of a running instance on the cloud. + + :rtype: :class:`AjaxConsole` + """ + params = {} + self.build_list_params(params, [instance_id], 'InstanceId') + return self.get_object('GetAjaxConsole', params, AjaxConsole) + pass + +def override_connect_ec2(aws_access_key_id=None, aws_secret_access_key=None, **kwargs): + return NovaEC2Connection(aws_access_key_id, aws_secret_access_key, **kwargs) + +boto.connect_ec2 = override_connect_ec2 diff --git a/nova/utils.py b/nova/utils.py index ca9a667cf..be61767c7 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -100,7 +100,8 @@ def abspath(s): return os.path.join(os.path.dirname(__file__), s) -def novadir(s): +def novadir(): + import nova return os.path.abspath(nova.__file__).split('nova/__init__.pyc')[0] diff --git a/nova/virt/libvirt.qemu.xml.template b/nova/virt/libvirt.qemu.xml.template index 2538b1ade..d5a249665 100644 --- a/nova/virt/libvirt.qemu.xml.template +++ b/nova/virt/libvirt.qemu.xml.template @@ -4,6 +4,9 @@ hvm %(basepath)s/kernel %(basepath)s/ramdisk + root=/dev/vda1 console=ttyS0 @@ -25,9 +28,15 @@ + + + + + -- cgit From 08963a0df7a6d1c90ba12ce60cbf15c93b0b70e6 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 21 Dec 2010 14:44:53 -0800 Subject: prototype works with kvm. now moving call from api to compute --- nova/api/ec2/cloud.py | 37 ++++++++++++++++++++++++++++++------- nova/compute/instance_types.py | 2 +- nova/compute/manager.py | 9 +++++++++ nova/virt/libvirt.qemu.xml.template | 22 +++++++++++++--------- 4 files changed, 53 insertions(+), 17 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 469331a66..09fdd32da 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -27,6 +27,7 @@ import datetime import logging import os import random +import re import subprocess import time import uuid @@ -44,10 +45,13 @@ from nova import utils from nova.compute.instance_types import INSTANCE_TYPES from nova.api import cloud from nova.api.ec2 import images +from nova.virt import libvirt_conn +from xml.dom import minidom FLAGS = flags.FLAGS flags.DECLARE('storage_availability_zone', 'nova.volume.manager') +flags.DEFINE_string("console_dmz", "tonbuntu:8000", "location of console proxy") InvalidInputException = exception.InvalidInputException @@ -454,6 +458,7 @@ class CloudController(object): return {"InstanceId": ec2_id, "Timestamp": now, "output": base64.b64encode(output)} + def get_ajax_console(self, context, instance_id, **kwargs): """Create an AJAX Console""" @@ -461,7 +466,7 @@ class CloudController(object): internal_id = ec2_id_to_internal_id(ec2_id) instance_ref = db.instance_get_by_internal_id(context, internal_id) - def get_port(): + def get_open_port(): for i in xrange(0,100): # don't loop forever port = random.randint(10000, 12000) cmd = "netcat 0.0.0.0 %s -w 2 < /dev/null" % (port,) @@ -472,15 +477,33 @@ class CloudController(object): return port raise 'Unable to find an open port' - port = get_port() + def get_pty_for_instance(instance_id): + stdout, stderr = utils.execute('virsh dumpxml instance-%d' % int(instance_id)) + dom = minidom.parseString(stdout) + serials = dom.getElementsByTagName('serial') + for serial in serials: + if serial.getAttribute('type') == 'pty': + source = serial.getElementsByTagName('source')[0] + return source.getAttribute('path') + + port = get_open_port() token = str(uuid.uuid4()) host = instance_ref['host'] - cmd = "%s/tools/ajaxterm/ajaxterm.py --command 'virsh console instance-%d' -t %s -p %s" \ - % (utils.novadir(), internal_id, token, port) - port_is_unused = subprocess.Popen(cmd, shell=True) #TODO error check - dmz = 'tonbuntu' #TODO put correct value for dmz - return {'url': 'http://%s:%s/?token=%s&host=%s&port=%s' % (dmz, 8000, token, host, port)} + + if FLAGS.libvirt_type == 'uml': + pass #FIXME + elif FLAGS.libvirt_type == 'xen': + pass #FIXME + else: + ajaxterm_cmd = 'socat - %s' % get_pty_for_instance(internal_id) + + cmd = "%s/tools/ajaxterm/ajaxterm.py --command '%s' -t %s -p %s" \ + % (utils.novadir(), ajaxterm_cmd, token, port) + + subprocess.Popen(cmd, shell=True) + FLAGS.console_dmz = 'tonbuntu:8000' + return {'url': 'http://%s/?token=%s&host=%s&port=%s' % (FLAGS.console_dmz, token, host, port)} def describe_volumes(self, context, **kwargs): if context.user.is_admin(): diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index 67ee8f8a8..bca9839d0 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -22,7 +22,7 @@ The built-in instance properties. """ INSTANCE_TYPES = { - 'm1.tiny': dict(memory_mb=512, vcpus=1, local_gb=0, flavorid=1), + 'm1.tiny': dict(memory_mb=128, vcpus=1, local_gb=0, flavorid=1), 'm1.small': dict(memory_mb=2048, vcpus=1, local_gb=20, flavorid=2), 'm1.medium': dict(memory_mb=4096, vcpus=2, local_gb=40, flavorid=3), 'm1.large': dict(memory_mb=8192, vcpus=4, local_gb=80, flavorid=4), diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 523bb8893..0897eee45 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -154,6 +154,15 @@ class ComputeManager(manager.Manager): return self.driver.get_console_output(instance_ref) + @exception.wrap_exception + def get_ajax_console(self, context, instance_id): + """Send the console output for an instance.""" + context = context.elevated() + logging.debug("instance %s: getting ajax console", instance_id) + instance_ref = self.db.instance_get(context, instance_id) + + return self.driver.get_console_output(instance_ref) + @defer.inlineCallbacks @exception.wrap_exception def attach_volume(self, context, instance_id, volume_id, mountpoint): diff --git a/nova/virt/libvirt.qemu.xml.template b/nova/virt/libvirt.qemu.xml.template index d5a249665..1f0c8a3ff 100644 --- a/nova/virt/libvirt.qemu.xml.template +++ b/nova/virt/libvirt.qemu.xml.template @@ -4,9 +4,6 @@ hvm %(basepath)s/kernel %(basepath)s/ramdisk - root=/dev/vda1 console=ttyS0 @@ -28,15 +25,22 @@ - - - - - ---> + + + + + + + + + + + -- cgit From a84e2b9131e4c8b212c9de0b9ad4931f7743ff75 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 21 Dec 2010 18:20:55 -0800 Subject: move prototype code from api into compute worker --- nova/api/ec2/cloud.py | 49 ++++++----------------------------------------- nova/compute/manager.py | 2 +- nova/virt/libvirt_conn.py | 49 ++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 55 insertions(+), 45 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 09fdd32da..4c9d882f1 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -26,11 +26,8 @@ import base64 import datetime import logging import os -import random import re -import subprocess import time -import uuid from nova import context import IPy @@ -45,13 +42,10 @@ from nova import utils from nova.compute.instance_types import INSTANCE_TYPES from nova.api import cloud from nova.api.ec2 import images -from nova.virt import libvirt_conn -from xml.dom import minidom FLAGS = flags.FLAGS flags.DECLARE('storage_availability_zone', 'nova.volume.manager') -flags.DEFINE_string("console_dmz", "tonbuntu:8000", "location of console proxy") InvalidInputException = exception.InvalidInputException @@ -466,44 +460,13 @@ class CloudController(object): internal_id = ec2_id_to_internal_id(ec2_id) instance_ref = db.instance_get_by_internal_id(context, internal_id) - def get_open_port(): - for i in xrange(0,100): # don't loop forever - port = random.randint(10000, 12000) - cmd = "netcat 0.0.0.0 %s -w 2 < /dev/null" % (port,) - # this Popen will exit with 0 only if the port is in use, - # so a nonzero return value implies it is unused - port_is_unused = (subprocess.Popen(cmd, shell=True).wait() != 0) - if port_is_unused: - return port - raise 'Unable to find an open port' - - def get_pty_for_instance(instance_id): - stdout, stderr = utils.execute('virsh dumpxml instance-%d' % int(instance_id)) - dom = minidom.parseString(stdout) - serials = dom.getElementsByTagName('serial') - for serial in serials: - if serial.getAttribute('type') == 'pty': - source = serial.getElementsByTagName('source')[0] - return source.getAttribute('path') - - port = get_open_port() - token = str(uuid.uuid4()) - - host = instance_ref['host'] - - if FLAGS.libvirt_type == 'uml': - pass #FIXME - elif FLAGS.libvirt_type == 'xen': - pass #FIXME - else: - ajaxterm_cmd = 'socat - %s' % get_pty_for_instance(internal_id) - - cmd = "%s/tools/ajaxterm/ajaxterm.py --command '%s' -t %s -p %s" \ - % (utils.novadir(), ajaxterm_cmd, token, port) + output = rpc.call(context, + '%s.%s' % (FLAGS.compute_topic, + instance_ref['host']), + {"method": "get_ajax_console", + "args": {"instance_id": instance_ref['id']}}) - subprocess.Popen(cmd, shell=True) - FLAGS.console_dmz = 'tonbuntu:8000' - return {'url': 'http://%s/?token=%s&host=%s&port=%s' % (FLAGS.console_dmz, token, host, port)} + return {"url": output } def describe_volumes(self, context, **kwargs): if context.user.is_admin(): diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 0897eee45..1fc71d1e5 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -161,7 +161,7 @@ class ComputeManager(manager.Manager): logging.debug("instance %s: getting ajax console", instance_id) instance_ref = self.db.instance_get(context, instance_id) - return self.driver.get_console_output(instance_ref) + return self.driver.get_ajax_console(instance_ref) @defer.inlineCallbacks @exception.wrap_exception diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 509ed97a0..f2110e4e8 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -42,6 +42,10 @@ from nova.compute import disk from nova.compute import instance_types from nova.compute import power_state from nova.virt import images +import subprocess +import random +import uuid +from xml.dom import minidom libvirt = None libxml2 = None @@ -71,7 +75,9 @@ flags.DEFINE_string('libvirt_uri', flags.DEFINE_bool('allow_project_net_traffic', True, 'Whether to allow in project network traffic') - +flags.DEFINE_string('console_dmz', + 'tonbuntu:8000', + 'location of console proxy') def get_connection(read_only): # These are loaded late so that there's no need to install these @@ -310,6 +316,47 @@ class LibvirtConnection(object): d.addCallback(self._dump_file) return d + @exception.wrap_exception + def get_ajax_console(self, instance): + def get_open_port(): + for i in xrange(0,100): # don't loop forever + port = random.randint(10000, 12000) + cmd = 'netcat 0.0.0.0 %s -w 2 < /dev/null' % (port,) + # this Popen will exit with 0 only if the port is in use, + # so a nonzero return value implies it is unused + port_is_unused = (subprocess.Popen(cmd, shell=True).wait() != 0) + if port_is_unused: + return port + raise 'Unable to find an open port' + + def get_pty_for_instance(instance_name): + stdout, stderr = utils.execute('virsh dumpxml %s' % instance_name) + dom = minidom.parseString(stdout) + serials = dom.getElementsByTagName('serial') + for serial in serials: + if serial.getAttribute('type') == 'pty': + source = serial.getElementsByTagName('source')[0] + return source.getAttribute('path') + + port = get_open_port() + token = str(uuid.uuid4()) + + host = instance['host'] + + if FLAGS.libvirt_type == 'uml': + pass #FIXME + elif FLAGS.libvirt_type == 'xen': + pass #FIXME + else: + ajaxterm_cmd = 'socat - %s' % get_pty_for_instance(instance['name']) + + cmd = '%s/tools/ajaxterm/ajaxterm.py --command "%s" -t %s -p %s' \ + % (utils.novadir(), ajaxterm_cmd, token, port) + + subprocess.Popen(cmd, shell=True) + return 'http://%s/?token=%s&host=%s&port=%s' \ + % (FLAGS.console_dmz, token, host, port) + @defer.inlineCallbacks def _create_image(self, inst, libvirt_xml): # syntactic nicety -- cgit From 28645bec4a6d084f6dc6fa51184061844826cb12 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 21 Dec 2010 23:15:00 -0800 Subject: a few more fixes after merge with trunk --- nova/virt/libvirt_conn.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index d39e3cc5b..b186a7c45 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -42,6 +42,7 @@ import shutil import random import subprocess import uuid +from xml.dom import minidom from eventlet import event @@ -86,6 +87,9 @@ flags.DEFINE_string('libvirt_uri', flags.DEFINE_bool('allow_project_net_traffic', True, 'Whether to allow in project network traffic') +flags.DEFINE_string('console_dmz', + 'tonbuntu:8000', + 'location of console proxy') def get_connection(read_only): @@ -389,13 +393,13 @@ class LibvirtConnection(object): def get_open_port(): for i in xrange(0,100): # don't loop forever port = random.randint(10000, 12000) - cmd = 'netcat 0.0.0.0 %s -w 2 < /dev/null' % (port,) - # this Popen will exit with 0 only if the port is in use, + # netcat will exit with 0 only if the port is in use, # so a nonzero return value implies it is unused - port_is_unused = (subprocess.Popen(cmd, shell=True).wait() != 0) - if port_is_unused: + cmd = 'netcat 0.0.0.0 %s -w 1 < /dev/null || echo free' % (port) + stdout, stderr = utils.execute(cmd) + if stdout.strip() == 'free': return port - raise 'Unable to find an open port' + raise Exception('Unable to find an open port') def get_pty_for_instance(instance_name): stdout, stderr = utils.execute('virsh dumpxml %s' % instance_name) -- cgit From f98bb2b2dee4a0ff67a6548646a852686092c53f Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 22 Dec 2010 02:19:38 -0800 Subject: connecting ajax proxy to rabbit to allow token based security --- nova/api/ec2/cloud.py | 4 ++++ nova/flags.py | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 2ca95c70a..e4ef552b0 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -464,6 +464,10 @@ class CloudController(object): {"method": "get_ajax_console", "args": {"instance_id": instance_ref['id']}}) + rpc.cast(context, '%s' % FLAGS.ajax_proxy_topic, + {"method": "authorize", + "args": {"token": "token", "host": "host", "port":8000}}) + return {"url": output } def describe_volumes(self, context, volume_id=None, **kwargs): diff --git a/nova/flags.py b/nova/flags.py index 8fa0beb7a..53ae9be4f 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -217,7 +217,8 @@ DEFINE_string('scheduler_topic', 'scheduler', 'the topic scheduler nodes listen on') DEFINE_string('volume_topic', 'volume', 'the topic volume nodes listen on') DEFINE_string('network_topic', 'network', 'the topic network nodes listen on') - +DEFINE_string('ajax_proxy_topic', 'ajax_proxy', + 'the topic ajax proxy nodes listen on') DEFINE_bool('verbose', False, 'show debug output') DEFINE_boolean('fake_rabbit', False, 'use a fake rabbit') DEFINE_bool('fake_network', False, -- cgit From dcc58be823aec7725d0b85c443c463124fcdae38 Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Wed, 22 Dec 2010 22:38:50 +0300 Subject: Zone scheduler added --- nova/db/sqlalchemy/models.py | 1 + nova/scheduler/zone.py | 58 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 nova/scheduler/zone.py (limited to 'nova') diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 693db8d23..843675fe6 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -149,6 +149,7 @@ class Service(BASE, NovaBase): topic = Column(String(255)) report_count = Column(Integer, nullable=False, default=0) disabled = Column(Boolean, default=False) + availability_zone = Column(String(255)) class Certificate(BASE, NovaBase): diff --git a/nova/scheduler/zone.py b/nova/scheduler/zone.py new file mode 100644 index 000000000..ec2166adf --- /dev/null +++ b/nova/scheduler/zone.py @@ -0,0 +1,58 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 Openstack, LLC. +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# 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. + +""" +Availability Zone Scheduler implementation +""" + +import random + +from nova.scheduler import driver +from nova import db + + +class ZoneScheduler(driver.Scheduler): + """Implements Scheduler as a random node selector.""" + + def hosts_up_with_zone(self, context, topic, zone): + """Return the list of hosts that have a running service + for topic and availability zone (if defined). + """ + + if zone is None: + return self.hosts_up(context, topic) + + services = db.service_get_all_by_topic(context, topic) + return [service.host + for service in services + if self.service_is_up(service) + and service.availability_zone == zone] + + + def schedule(self, context, topic, *_args, **_kwargs): + """Picks a host that is up at random in selected + availability zone (if defined). + """ + + zone = _kwargs.get('availability_zone') + hosts = self.hosts_up_with_zone(context, topic, zone) + if not hosts: + raise driver.NoValidHost(_("No hosts found")) + return hosts[int(random.random() * len(hosts))] + -- cgit From 19f389b3dcc89f0115dc6fc1a6ca606338ad866a Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 22 Dec 2010 12:36:37 -0800 Subject: working connection security --- nova/api/ec2/cloud.py | 21 ++++++++++++++------- nova/flags.py | 5 ++++- nova/virt/libvirt_conn.py | 8 +++----- 3 files changed, 21 insertions(+), 13 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index e4ef552b0..b3aa83398 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -452,7 +452,11 @@ class CloudController(object): "output": base64.b64encode(output)} def get_ajax_console(self, context, instance_id, **kwargs): - """Create an AJAX Console""" + """Get an AJAX Console + + In order for this to work properly, a ttyS0 must be configured + in the instance + """ ec2_id = instance_id[0] internal_id = ec2_id_to_internal_id(ec2_id) @@ -461,14 +465,17 @@ class CloudController(object): output = rpc.call(context, '%s.%s' % (FLAGS.compute_topic, instance_ref['host']), - {"method": "get_ajax_console", - "args": {"instance_id": instance_ref['id']}}) + {'method': 'get_ajax_console', + 'args': {'instance_id': instance_ref['id']}}) - rpc.cast(context, '%s' % FLAGS.ajax_proxy_topic, - {"method": "authorize", - "args": {"token": "token", "host": "host", "port":8000}}) + # TODO: make this a call + rpc.cast(context, '%s' % FLAGS.ajax_console_proxy_topic, + {'method': 'authorize_ajax_console', + 'args': {'token': output['token'], 'host': output['host'], + 'port':output['port']}}) - return {"url": output } + return {'url': '%s?token=%s' % (FLAGS.ajax_console_proxy_url, + output['token'])} def describe_volumes(self, context, volume_id=None, **kwargs): if context.user.is_admin(): diff --git a/nova/flags.py b/nova/flags.py index 53ae9be4f..c6e56fcc7 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -217,8 +217,11 @@ DEFINE_string('scheduler_topic', 'scheduler', 'the topic scheduler nodes listen on') DEFINE_string('volume_topic', 'volume', 'the topic volume nodes listen on') DEFINE_string('network_topic', 'network', 'the topic network nodes listen on') -DEFINE_string('ajax_proxy_topic', 'ajax_proxy', +DEFINE_string('ajax_console_proxy_topic', 'ajax_proxy', 'the topic ajax proxy nodes listen on') +DEFINE_string('ajax_console_proxy_url', + 'http://tonbuntu:8000', + 'location of ajax console proxy, in the form "http://tonbuntu:8000"') DEFINE_bool('verbose', False, 'show debug output') DEFINE_boolean('fake_rabbit', False, 'use a fake rabbit') DEFINE_bool('fake_network', False, diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index b186a7c45..800312c98 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -87,9 +87,6 @@ flags.DEFINE_string('libvirt_uri', flags.DEFINE_bool('allow_project_net_traffic', True, 'Whether to allow in project network traffic') -flags.DEFINE_string('console_dmz', - 'tonbuntu:8000', - 'location of console proxy') def get_connection(read_only): @@ -426,8 +423,9 @@ class LibvirtConnection(object): % (utils.novadir(), ajaxterm_cmd, token, port) subprocess.Popen(cmd, shell=True) - return 'http://%s/?token=%s&host=%s&port=%s' \ - % (FLAGS.console_dmz, token, host, port) + return {'token': token, 'host': host, 'port': port} + #return 'http://%s/?token=%s&host=%s&port=%s' \ + # % (FLAGS.console_dmz, token, host, port) def _create_image(self, inst, libvirt_xml, prefix='', disk_images=None): # syntactic nicety -- cgit From aa8a6a01bdf8a2f0f732e993a1732993f7328eff Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 22 Dec 2010 13:00:20 -0800 Subject: add in support of openstack api --- nova/api/ec2/cloud.py | 23 +---------------------- nova/api/openstack/servers.py | 9 +++++++++ nova/compute/api.py | 24 ++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 22 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index b3aa83398..11853c8db 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -452,30 +452,9 @@ class CloudController(object): "output": base64.b64encode(output)} def get_ajax_console(self, context, instance_id, **kwargs): - """Get an AJAX Console - - In order for this to work properly, a ttyS0 must be configured - in the instance - """ - ec2_id = instance_id[0] internal_id = ec2_id_to_internal_id(ec2_id) - instance_ref = db.instance_get_by_internal_id(context, internal_id) - - output = rpc.call(context, - '%s.%s' % (FLAGS.compute_topic, - instance_ref['host']), - {'method': 'get_ajax_console', - 'args': {'instance_id': instance_ref['id']}}) - - # TODO: make this a call - rpc.cast(context, '%s' % FLAGS.ajax_console_proxy_topic, - {'method': 'authorize_ajax_console', - 'args': {'token': output['token'], 'host': output['host'], - 'port':output['port']}}) - - return {'url': '%s?token=%s' % (FLAGS.ajax_console_proxy_url, - output['token'])} + return self.compute_api.get_ajax_console(context, internal_id) def describe_volumes(self, context, volume_id=None, **kwargs): if context.user.is_admin(): diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 5c3322f7c..45db89cbf 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -195,3 +195,12 @@ class Controller(wsgi.Controller): logging.error("Compute.api::unpause %s", readable) return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + + def get_ajax_console(self, req, id): + """ Returns a url to and ajaxterm instance console. """ + try: + self.compute_api.get_ajax_console(req.environ['nova.context'], + int(id)) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + return exc.HTTPAccepted() diff --git a/nova/compute/api.py b/nova/compute/api.py index c740814da..1acf320ae 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -316,6 +316,30 @@ class ComputeAPI(base.Base): {"method": "unrescue_instance", "args": {"instance_id": instance['id']}}) + def get_ajax_console(self, context, instance_id): + """Get an AJAX Console + + In order for this to work properly, a ttyS0 must be configured + in the instance + """ + + instance_ref = db.instance_get_by_internal_id(context, instance_id) + + output = rpc.call(context, + '%s.%s' % (FLAGS.compute_topic, + instance_ref['host']), + {'method': 'get_ajax_console', + 'args': {'instance_id': instance_ref['id']}}) + + # TODO: make this a call + rpc.cast(context, '%s' % FLAGS.ajax_console_proxy_topic, + {'method': 'authorize_ajax_console', + 'args': {'token': output['token'], 'host': output['host'], + 'port':output['port']}}) + + return {'url': '%s?token=%s' % (FLAGS.ajax_console_proxy_url, + output['token'])} + def _get_network_topic(self, context): """Retrieves the network host for a project""" network_ref = self.network_manager.get_network(context) -- cgit From 0093342106cc270859df0511dbefad8ec8fc2320 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 22 Dec 2010 13:31:33 -0800 Subject: use libvirt python bindings instead of system call --- nova/virt/libvirt_conn.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 800312c98..658efa8d1 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -392,17 +392,18 @@ class LibvirtConnection(object): port = random.randint(10000, 12000) # netcat will exit with 0 only if the port is in use, # so a nonzero return value implies it is unused - cmd = 'netcat 0.0.0.0 %s -w 1 < /dev/null || echo free' % (port) + cmd = 'netcat 0.0.0.0 %s -w 1 Date: Wed, 22 Dec 2010 18:52:43 -0800 Subject: minor notes, commit before rewriting proxy with eventlet --- nova/compute/api.py | 1 - nova/virt/libvirt_conn.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 1acf320ae..3e9b58db5 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -331,7 +331,6 @@ class ComputeAPI(base.Base): {'method': 'get_ajax_console', 'args': {'instance_id': instance_ref['id']}}) - # TODO: make this a call rpc.cast(context, '%s' % FLAGS.ajax_console_proxy_topic, {'method': 'authorize_ajax_console', 'args': {'token': output['token'], 'host': output['host'], diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 658efa8d1..55754ea48 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -389,7 +389,7 @@ class LibvirtConnection(object): def get_ajax_console(self, instance): def get_open_port(): for i in xrange(0,100): # don't loop forever - port = random.randint(10000, 12000) + port = random.randint(10000, 12000) #TODO - make flag # netcat will exit with 0 only if the port is in use, # so a nonzero return value implies it is unused cmd = 'netcat 0.0.0.0 %s -w 1 Date: Wed, 22 Dec 2010 23:41:07 -0800 Subject: rewrite proxy to not use twisted --- nova/flags.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'nova') diff --git a/nova/flags.py b/nova/flags.py index c6e56fcc7..c4404a120 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -222,6 +222,9 @@ DEFINE_string('ajax_console_proxy_topic', 'ajax_proxy', DEFINE_string('ajax_console_proxy_url', 'http://tonbuntu:8000', 'location of ajax console proxy, in the form "http://tonbuntu:8000"') +DEFINE_string('ajax_console_proxy_port', + 8000, + 'port that ajax_console_proxy binds') DEFINE_bool('verbose', False, 'show debug output') DEFINE_boolean('fake_rabbit', False, 'use a fake rabbit') DEFINE_bool('fake_network', False, -- cgit From 151ffc57a3dd5217981dbaa1754384290d7d73ec Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Thu, 23 Dec 2010 00:23:08 -0800 Subject: move port range for ajaxterm to flag --- nova/virt/libvirt_conn.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 55754ea48..1049eaefa 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -87,6 +87,9 @@ flags.DEFINE_string('libvirt_uri', flags.DEFINE_bool('allow_project_net_traffic', True, 'Whether to allow in project network traffic') +flags.DEFINE_string('ajaxterm_portrange', + '10000-12000', + 'Range of ports that ajaxterm should randomly try to bind') def get_connection(read_only): @@ -388,8 +391,9 @@ class LibvirtConnection(object): @exception.wrap_exception def get_ajax_console(self, instance): def get_open_port(): + start_port, end_port = FLAGS.ajaxterm_portrange.split("-") for i in xrange(0,100): # don't loop forever - port = random.randint(10000, 12000) #TODO - make flag + port = random.randint(int(start_port), int(end_port)) # netcat will exit with 0 only if the port is in use, # so a nonzero return value implies it is unused cmd = 'netcat 0.0.0.0 %s -w 1 Date: Thu, 23 Dec 2010 00:58:15 -0800 Subject: removing xen/uml specific switches. If they need special treatment, we can add it --- nova/virt/libvirt_conn.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index d855770b7..3c0efd18f 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -427,12 +427,7 @@ class LibvirtConnection(object): host = instance['host'] - if FLAGS.libvirt_type == 'uml': - pass #FIXME - elif FLAGS.libvirt_type == 'xen': - pass #FIXME - else: - ajaxterm_cmd = 'socat - %s' % get_pty_for_instance(instance['name']) + ajaxterm_cmd = 'socat - %s' % get_pty_for_instance(instance['name']) cmd = '%s/tools/ajaxterm/ajaxterm.py --command "%s" -t %s -p %s' \ % (utils.novadir(), ajaxterm_cmd, token, port) -- cgit From 43dfae5926bafa1575aee9624651cfcb8f170bb3 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Thu, 23 Dec 2010 01:22:54 -0800 Subject: some pep8 fixes --- nova/flags.py | 2 +- nova/virt/libvirt_conn.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/flags.py b/nova/flags.py index 2d5aec840..406f159e6 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -220,7 +220,7 @@ DEFINE_string('scheduler_topic', 'scheduler', 'the topic scheduler nodes listen on') DEFINE_string('volume_topic', 'volume', 'the topic volume nodes listen on') DEFINE_string('network_topic', 'network', 'the topic network nodes listen on') -DEFINE_string('ajax_console_proxy_topic', 'ajax_proxy', +DEFINE_string('ajax_console_proxy_topic', 'ajax_proxy', 'the topic ajax proxy nodes listen on') DEFINE_string('ajax_console_proxy_url', 'http://tonbuntu:8000', diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 3c0efd18f..40a430d86 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -402,7 +402,7 @@ class LibvirtConnection(object): def get_ajax_console(self, instance): def get_open_port(): start_port, end_port = FLAGS.ajaxterm_portrange.split("-") - for i in xrange(0,100): # don't loop forever + for i in xrange(0,100): # don't loop forever port = random.randint(int(start_port), int(end_port)) # netcat will exit with 0 only if the port is in use, # so a nonzero return value implies it is unused -- cgit From d88817a360676173ac31566e13201d56f1e2b0b0 Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Thu, 23 Dec 2010 22:46:58 +0300 Subject: unit test - should be reworked --- nova/tests/scheduler_unittest.py | 43 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) (limited to 'nova') diff --git a/nova/tests/scheduler_unittest.py b/nova/tests/scheduler_unittest.py index d1756b8fb..dbc195a6e 100644 --- a/nova/tests/scheduler_unittest.py +++ b/nova/tests/scheduler_unittest.py @@ -19,6 +19,9 @@ Tests For Scheduler """ +import datetime + +from mox import IgnoreArg from nova import context from nova import db from nova import flags @@ -72,6 +75,46 @@ class SchedulerTestCase(test.TestCase): self.mox.ReplayAll() scheduler.named_method(ctxt, 'topic', num=7) +class ZoneSchedulerTestCase(test.TestCase): + """Test case for zone scheduler""" + def setUp(self): + super(ZoneSchedulerTestCase, self).setUp() + self.flags(scheduler_driver='nova.scheduler.zone.ZoneScheduler') + + def _create_service_model(self, **kwargs): + service = db.sqlalchemy.models.Service() + service.host = kwargs['host'] + service.disabled = False + service.deleted = False + service.report_count = 0 + service.binary = 'nova-compute' + service.topic = 'compute' + service.id = kwargs['id'] + service.availability_zone = kwargs['zone'] + service.created_at = datetime.datetime.utcnow() + return service + + + def test_with_two_zones(self): + scheduler = manager.SchedulerManager() + ctxt = context.get_admin_context() + service_list = [ + self._create_service_model(id=1, host='host1', zone='zone1'), + self._create_service_model(id=2, host='host2', zone='zone2'), + self._create_service_model(id=3, host='host3', zone='zone2'), + self._create_service_model(id=4, host='host4', zone='zone2'), + self._create_service_model(id=5, host='host5', zone='zone2') + ] + self.mox.StubOutWithMock(db, 'service_get_all_by_topic') + db.service_get_all_by_topic(IgnoreArg(), IgnoreArg()).AndReturn(service_list) + self.mox.StubOutWithMock(rpc, 'cast', use_mock_anything=True) + rpc.cast(ctxt, + 'compute.host1', + {'method': 'create_instance', #TODO: check it + 'args': {'availability_zone': 'zone1'}}) + self.mox.ReplayAll() + scheduler.create_instance(ctxt, 'compute', availability_zone='zone1') + class SimpleDriverTestCase(test.TestCase): """Test case for simple driver""" -- cgit From 43f59fc025b4decd02a78acbfd0cf654bc9cf0db Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Fri, 24 Dec 2010 21:05:45 +0300 Subject: adding zones to api --- nova/api/ec2/cloud.py | 7 ++++++- nova/compute/api.py | 6 ++++-- nova/db/sqlalchemy/models.py | 2 +- nova/service.py | 3 ++- nova/tests/test_scheduler.py | 7 ++++--- nova/tests/test_service.py | 24 ++++++++++++++++-------- 6 files changed, 33 insertions(+), 16 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index e09261f00..66060bbfc 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -753,6 +753,10 @@ class CloudController(object): def run_instances(self, context, **kwargs): max_count = int(kwargs.get('max_count', 1)) + placement = kwargs.get('placement') + avzone = None + if placement is not None: + avzone = placement['availability_zone'] instances = self.compute_api.create_instances(context, instance_types.get_by_type(kwargs.get('instance_type', None)), kwargs['image_id'], @@ -765,7 +769,8 @@ class CloudController(object): key_name=kwargs.get('key_name'), user_data=kwargs.get('user_data'), security_group=kwargs.get('security_group'), - generate_hostname=internal_id_to_ec2_id) + generate_hostname=internal_id_to_ec2_id, + availability_zone=avzone) return self._format_run_instances(context, instances[0]['reservation_id']) diff --git a/nova/compute/api.py b/nova/compute/api.py index 4953fe559..cc377a1e4 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -75,7 +75,8 @@ class ComputeAPI(base.Base): display_name='', description='', key_name=None, key_data=None, security_group='default', user_data=None, - generate_hostname=generate_default_hostname): + generate_hostname=generate_default_hostname, + availability_zone=None): """Create the number of instances requested if quote and other arguments check out ok.""" @@ -175,7 +176,8 @@ class ComputeAPI(base.Base): FLAGS.scheduler_topic, {"method": "run_instance", "args": {"topic": FLAGS.compute_topic, - "instance_id": instance_id}}) + "instance_id": instance_id, + "availability_zone": availability_zone}}) return instances diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 843675fe6..5a020a469 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -149,7 +149,7 @@ class Service(BASE, NovaBase): topic = Column(String(255)) report_count = Column(Integer, nullable=False, default=0) disabled = Column(Boolean, default=False) - availability_zone = Column(String(255)) + availability_zone = Column(String(255), default='nova') class Certificate(BASE, NovaBase): diff --git a/nova/service.py b/nova/service.py index f1f90742f..a612ac592 100644 --- a/nova/service.py +++ b/nova/service.py @@ -114,7 +114,8 @@ class Service(object): {'host': self.host, 'binary': self.binary, 'topic': self.topic, - 'report_count': 0}) + 'report_count': 0, + 'availability_zone': FLAGS.node_availability_zone}) self.service_id = service_ref['id'] def __getattr__(self, key): diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py index 487e7b9e6..188e50aac 100644 --- a/nova/tests/test_scheduler.py +++ b/nova/tests/test_scheduler.py @@ -110,10 +110,11 @@ class ZoneSchedulerTestCase(test.TestCase): self.mox.StubOutWithMock(rpc, 'cast', use_mock_anything=True) rpc.cast(ctxt, 'compute.host1', - {'method': 'create_instance', #TODO: check it - 'args': {'availability_zone': 'zone1'}}) + {'method': 'run_instance', + 'args':{'instance_id': 'i-ffffffff', + 'availability_zone': 'zone1'}}) self.mox.ReplayAll() - scheduler.create_instance(ctxt, 'compute', availability_zone='zone1') + scheduler.run_instance(ctxt, 'compute', instance_id='i-ffffffff', availability_zone='zone1') class SimpleDriverTestCase(test.TestCase): diff --git a/nova/tests/test_service.py b/nova/tests/test_service.py index b30838ad7..1400b88e5 100644 --- a/nova/tests/test_service.py +++ b/nova/tests/test_service.py @@ -107,11 +107,13 @@ class ServiceTestCase(test.TestCase): service_create = {'host': host, 'binary': binary, 'topic': topic, - 'report_count': 0} + 'report_count': 0, + 'availability_zone': 'nova'} service_ref = {'host': host, 'binary': binary, 'report_count': 0, - 'id': 1} + 'id': 1, + 'availability_zone': 'nova'} service.db.service_get_by_args(mox.IgnoreArg(), host, @@ -135,12 +137,14 @@ class ServiceTestCase(test.TestCase): service_create = {'host': host, 'binary': binary, 'topic': topic, - 'report_count': 0} + 'report_count': 0, + 'availability_zone': 'nova'} service_ref = {'host': host, 'binary': binary, 'topic': topic, 'report_count': 0, - 'id': 1} + 'id': 1, + 'availability_zone': 'nova'} service.db.service_get_by_args(mox.IgnoreArg(), host, @@ -167,12 +171,14 @@ class ServiceTestCase(test.TestCase): service_create = {'host': host, 'binary': binary, 'topic': topic, - 'report_count': 0} + 'report_count': 0, + 'availability_zone': 'nova'} service_ref = {'host': host, 'binary': binary, 'topic': topic, 'report_count': 0, - 'id': 1} + 'id': 1, + 'availability_zone': 'nova'} service.db.service_get_by_args(mox.IgnoreArg(), host, @@ -198,12 +204,14 @@ class ServiceTestCase(test.TestCase): service_create = {'host': host, 'binary': binary, 'topic': topic, - 'report_count': 0} + 'report_count': 0, + 'availability_zone': 'nova'} service_ref = {'host': host, 'binary': binary, 'topic': topic, 'report_count': 0, - 'id': 1} + 'id': 1, + 'availability_zone': 'nova'} service.db.service_get_by_args(mox.IgnoreArg(), host, -- cgit From 35638077a186f9315ac6e30cdbe096730a540ed8 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 28 Dec 2010 17:42:33 -0800 Subject: add in unit tests --- nova/tests/cloud_unittest.py | 13 +++++++++++++ nova/tests/compute_unittest.py | 10 ++++++++++ nova/virt/fake.py | 2 ++ 3 files changed, 25 insertions(+) (limited to 'nova') diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py index 70d2c44da..8e3391226 100644 --- a/nova/tests/cloud_unittest.py +++ b/nova/tests/cloud_unittest.py @@ -150,6 +150,19 @@ class CloudTestCase(test.TestCase): greenthread.sleep(0.3) rv = yield self.cloud.terminate_instances(self.context, [instance_id]) + def test_ajax_console(self): + kwargs = {'image_id': image_id } + rv = yield self.cloud.run_instances(self.context, **kwargs) + instance_id = rv['instancesSet'][0]['instanceId'] + output = yield self.cloud.get_console_output(context=self.context, + instance_id=[instance_id]) + self.assertEquals(b64decode(output['output']), + 'http://fakeajaxconsole.com/?token=FAKETOKEN') + # TODO(soren): We need this until we can stop polling in the rpc code + # for unit tests. + greenthread.sleep(0.3) + rv = yield self.cloud.terminate_instances(self.context, [instance_id]) + def test_key_generation(self): result = self._create_key('test') private_key = result['private_key'] diff --git a/nova/tests/compute_unittest.py b/nova/tests/compute_unittest.py index 348bb3351..529847972 100644 --- a/nova/tests/compute_unittest.py +++ b/nova/tests/compute_unittest.py @@ -153,6 +153,16 @@ class ComputeTestCase(test.TestCase): self.assert_(console) self.compute.terminate_instance(self.context, instance_id) + def test_ajax_console(self): + """Make sure we can get console output from instance""" + instance_id = self._create_instance() + self.compute.run_instance(self.context, instance_id) + + console = self.compute.get_ajax_console(self.context, + instance_id) + self.assert_(console) + self.compute.terminate_instance(self.context, instance_id) + def test_run_instance_existing(self): """Ensure failure when running an instance that already exists""" instance_id = self._create_instance() diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 238acf798..b97dbd511 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -260,6 +260,8 @@ class FakeConnection(object): def get_console_output(self, instance): return 'FAKE CONSOLE OUTPUT' + def get_ajax_console(self, instance): + return 'http://fakeajaxconsole.com/?token=FAKETOKEN' class FakeInstance(object): -- cgit From d30ec2b5814480010d1b42ce2e9bed9fbc441fd1 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Wed, 29 Dec 2010 19:51:25 +0300 Subject: Added implementation availability_zones to EC2 API --- nova/api/ec2/cloud.py | 25 +++++++++++++++++++++---- nova/db/api.py | 7 +++++-- nova/db/sqlalchemy/api.py | 14 ++++++++++++-- nova/flags.py | 1 - nova/tests/test_cloud.py | 17 +++++++++++++++++ 5 files changed, 55 insertions(+), 9 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 66060bbfc..0c20ef329 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -138,6 +138,9 @@ class CloudController(object): {"method": "refresh_security_group", "args": {"security_group_id": security_group.id}}) + def _get_availability_zone_by_host(self, context, hostname): + return db.service_get_all_compute_by_host(context, hostname)[0]['availability_zone'] + def get_metadata(self, address): ctxt = context.get_admin_context() instance_ref = db.fixed_ip_get_instance(ctxt, address) @@ -150,6 +153,7 @@ class CloudController(object): else: keys = '' hostname = instance_ref['hostname'] + availability_zone = self._get_availability_zone_by_host(ctxt, hostname) floating_ip = db.instance_get_floating_address(ctxt, instance_ref['id']) ec2_id = internal_id_to_ec2_id(instance_ref['internal_id']) @@ -172,8 +176,7 @@ class CloudController(object): 'local-hostname': hostname, 'local-ipv4': address, 'kernel-id': instance_ref['kernel_id'], - # TODO(vish): real zone - 'placement': {'availability-zone': 'nova'}, + 'placement': {'availability-zone': availability_zone}, 'public-hostname': hostname, 'public-ipv4': floating_ip or '', 'public-keys': keys, @@ -188,8 +191,20 @@ class CloudController(object): return data def describe_availability_zones(self, context, **kwargs): - return {'availabilityZoneInfo': [{'zoneName': 'nova', - 'zoneState': 'available'}]} + enabled_services = db.service_get_all_by_topic(context, 'compute') + disabled_services = db.service_get_all_by_topic(context, 'compute', True) + available_zones = [service.availability_zone for service in enabled_services] + not_available_zones = [service.availability_zone for service in disabled_services + and not service['availability_zone'] in available_zones] + + result = [] + for zone in available_zones: + result.append({'zoneName': zone, + 'zoneState': "available"}) + for zone in not_available_zones: + result.append({'zoneName': zone, + 'zoneState': "not available"}) + return {'availabilityZoneInfo': result} def describe_regions(self, context, region_name=None, **kwargs): if FLAGS.region_list: @@ -660,6 +675,8 @@ class CloudController(object): r['groupSet'] = self._convert_to_set([], 'groups') r['instancesSet'] = [] reservations[instance['reservation_id']] = r + availability_zone = self._get_availability_zone_by_host(ctxt, instance['hostname']) + i['placement'] = {'availabilityZone': availability_zone} reservations[instance['reservation_id']]['instancesSet'].append(i) return list(reservations.values()) diff --git a/nova/db/api.py b/nova/db/api.py index fde3f0852..c0cab1068 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -76,10 +76,13 @@ def service_get(context, service_id): return IMPL.service_get(context, service_id) -def service_get_all_by_topic(context, topic): +def service_get_all_by_topic(context, topic, disabled=False): """Get all compute services for a given topic.""" - return IMPL.service_get_all_by_topic(context, topic) + return IMPL.service_get_all_by_topic(context, topic, disabled) +def service_get_all_compute_by_host(context, host): + """Get all compute service for a given host""" + return IMPL.service_get_all_compute_by_host(context, host) def service_get_all_compute_sorted(context): """Get all compute services sorted by instance count. diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 7e945e4cb..55c3c5594 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -136,11 +136,11 @@ def service_get(context, service_id, session=None): @require_admin_context -def service_get_all_by_topic(context, topic): +def service_get_all_by_topic(context, topic, disabled=False): session = get_session() return session.query(models.Service).\ filter_by(deleted=False).\ - filter_by(disabled=False).\ + filter_by(disabled=disabled).\ filter_by(topic=topic).\ all() @@ -156,6 +156,16 @@ def _service_get_all_topic_subquery(context, session, topic, subq, label): order_by(sort_value).\ all() +@require_admin_context +def service_get_all_compute_by_host(context, host): + session = get_session() + topic = 'compute' + return session.query(models.Service).\ + filter_by(host=host).\ + filter_by(deleted=False).\ + filter_by(topic=topic).\ + all() + @require_admin_context def service_get_all_compute_sorted(context): diff --git a/nova/flags.py b/nova/flags.py index 76a98d35a..b157c9e5d 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -277,6 +277,5 @@ DEFINE_string('image_service', 'nova.image.s3.S3ImageService', DEFINE_string('host', socket.gethostname(), 'name of this node') -# UNUSED DEFINE_string('node_availability_zone', 'nova', 'availability zone of this node') diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 70d2c44da..d7e99b3c8 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -133,6 +133,23 @@ class CloudTestCase(test.TestCase): db.volume_destroy(self.context, vol1['id']) db.volume_destroy(self.context, vol2['id']) + def test_describe_availability_zones(self): + """Makes sure describe_availability_zones works and filters results.""" + service1 = db.service_create(self.context, {'host': 'host1_describe_zones', + 'binary': "nova-compute", + 'topic': 'compute', + 'report_count': 0, + 'availability_zone': "zone1"}) + service2 = db.service_create(self.context, {'host': 'host2_describe_zones', + 'binary': "nova-compute", + 'topic': 'compute', + 'report_count': 0, + 'availability_zone': "zone2"}) + result = self.cloud.describe_availability_zones(self.context) + self.assertEqual(len(result['availabilityZoneInfo']), 3) + db.service_destroy(self.context, service1['id']) + db.service_destroy(self.context, service2['id']) + def test_console_output(self): image_id = FLAGS.default_image instance_type = FLAGS.default_instance_type -- cgit From 579d0e1437efb32ef1a1c50ddbfca9093cfa3d18 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Wed, 29 Dec 2010 23:30:08 +0300 Subject: added tests for EC2 describe_instances --- nova/api/ec2/cloud.py | 7 +++++-- nova/tests/test_cloud.py | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 0c20ef329..9fa422301 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -139,7 +139,10 @@ class CloudController(object): "args": {"security_group_id": security_group.id}}) def _get_availability_zone_by_host(self, context, hostname): - return db.service_get_all_compute_by_host(context, hostname)[0]['availability_zone'] + services = db.service_get_all_compute_by_host(context, hostname) + if len(services) > 0: + return services[0]['availability_zone'] + raise Exception(_('No service with hostname: %s' % hostname)) def get_metadata(self, address): ctxt = context.get_admin_context() @@ -675,7 +678,7 @@ class CloudController(object): r['groupSet'] = self._convert_to_set([], 'groups') r['instancesSet'] = [] reservations[instance['reservation_id']] = r - availability_zone = self._get_availability_zone_by_host(ctxt, instance['hostname']) + availability_zone = self._get_availability_zone_by_host(context, instance['hostname']) i['placement'] = {'availabilityZone': availability_zone} reservations[instance['reservation_id']]['instancesSet'].append(i) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index d7e99b3c8..3adecb729 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -62,7 +62,7 @@ class CloudTestCase(test.TestCase): self.cloud = cloud.CloudController() # set up services - self.compute = service.Service.create(binary='nova-compute') + self.compute = service.Service.create(binary='nova-compute', host='host1') self.compute.start() self.network = service.Service.create(binary='nova-network') self.network.start() @@ -228,6 +228,20 @@ class CloudTestCase(test.TestCase): logging.debug("Terminating instance %s" % instance_id) rv = yield self.compute.terminate_instance(instance_id) + def test_describe_instances(self): + """Makes sure describe_instances works.""" + instance1 = db.instance_create(self.context, {'hostname': 'host2'}) + service1 = db.service_create(self.context, {'host': 'host1', + 'availability_zone': 'zone1', + 'topic': "compute"}) + result = self.cloud.describe_instances(self.context) + self.assertEqual(result['reservationSet'][0]\ + ['instancesSet'][0]\ + ['placement']['availabilityZone'], 'zone1') + db.instance_destroy(self.context, instance1['id']) + db.service_destroy(self.context, service1['id']) + + def test_instance_update_state(self): def instance(num): return { -- cgit From 13dfb66624ca082bd5e83969213c657d2d2d1dff Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 29 Dec 2010 16:11:02 -0800 Subject: pep8 fix, and add in flags that don't refernece my laptop --- nova/flags.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/flags.py b/nova/flags.py index 406f159e6..6f2747fc9 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -223,11 +223,11 @@ DEFINE_string('network_topic', 'network', 'the topic network nodes listen on') DEFINE_string('ajax_console_proxy_topic', 'ajax_proxy', 'the topic ajax proxy nodes listen on') DEFINE_string('ajax_console_proxy_url', - 'http://tonbuntu:8000', - 'location of ajax console proxy, in the form "http://tonbuntu:8000"') + 'http://127.0.0.1:8000', + 'location of ajax console proxy, \ + in the form "http://127.0.0.1:8000"') DEFINE_string('ajax_console_proxy_port', - 8000, - 'port that ajax_console_proxy binds') + 8000, 'port that ajax_console_proxy binds') DEFINE_bool('verbose', False, 'show debug output') DEFINE_boolean('fake_rabbit', False, 'use a fake rabbit') DEFINE_bool('fake_network', False, -- cgit From b2557962b5a365d78346fef727b2fcee75fe3270 Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Thu, 30 Dec 2010 21:27:44 +0000 Subject: Changing DN creation to do searches for entries. This change adds additional interoperability (as many directory servers and LDAP admins use cn, or another attribute, as the naming attribute). DN creation will incur a slight performance penalty for doing so, as DNs must be searched for now. User and project creation skip this performance penalty, as there is no need to search for an entry that is being created. --- nova/auth/ldapdriver.py | 92 ++++++++++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 39 deletions(-) (limited to 'nova') diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index 7616ff112..e139be926 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -117,8 +117,7 @@ class LdapDriver(object): def get_project(self, pid): """Retrieve project by id""" - dn = 'cn=%s,%s' % (pid, - FLAGS.ldap_project_subtree) + dn = self.__project_to_dn(pid) attr = self.__find_object(dn, LdapDriver.project_pattern) return self.__to_project(attr) @@ -226,7 +225,8 @@ class LdapDriver(object): ('description', [description]), (LdapDriver.project_attribute, [manager_dn]), ('member', members)] - self.conn.add_s('cn=%s,%s' % (name, FLAGS.ldap_project_subtree), attr) + dn = self.__project_to_dn(name, search=False) + self.conn.add_s(dn, attr) return self.__to_project(dict(attr)) def modify_project(self, project_id, manager_uid=None, description=None): @@ -244,23 +244,22 @@ class LdapDriver(object): manager_dn)) if description: attr.append((self.ldap.MOD_REPLACE, 'description', description)) - self.conn.modify_s('cn=%s,%s' % (project_id, - FLAGS.ldap_project_subtree), - attr) + dn = self.__project_to_dn(project_id) + self.conn.modify_s(dn, attr) def add_to_project(self, uid, project_id): """Add user to project""" - dn = 'cn=%s,%s' % (project_id, FLAGS.ldap_project_subtree) + dn = self.__project_to_dn(project_id) return self.__add_to_group(uid, dn) def remove_from_project(self, uid, project_id): """Remove user from project""" - dn = 'cn=%s,%s' % (project_id, FLAGS.ldap_project_subtree) + dn = self.__project_to_dn(project_id) return self.__remove_from_group(uid, dn) def is_in_project(self, uid, project_id): """Check if user is in project""" - dn = 'cn=%s,%s' % (project_id, FLAGS.ldap_project_subtree) + dn = self.__project_to_dn(project_id) return self.__is_in_group(uid, dn) def has_role(self, uid, role, project_id=None): @@ -300,7 +299,7 @@ class LdapDriver(object): roles.append(role) return roles else: - project_dn = 'cn=%s,%s' % (project_id, FLAGS.ldap_project_subtree) + project_dn = self.__project_to_dn(project_id) query = ('(&(&(objectclass=groupOfNames)(!%s))(member=%s))' % (LdapDriver.project_pattern, self.__uid_to_dn(uid))) roles = self.__find_objects(project_dn, query) @@ -333,7 +332,7 @@ class LdapDriver(object): def delete_project(self, project_id): """Delete a project""" - project_dn = 'cn=%s,%s' % (project_id, FLAGS.ldap_project_subtree) + project_dn = self.__project_to_dn(project_id) self.__delete_roles(project_dn) self.__delete_group(project_dn) @@ -365,9 +364,10 @@ class LdapDriver(object): def __get_ldap_user(self, uid): """Retrieve LDAP user entry by id""" - attr = self.__find_object(self.__uid_to_dn(uid), - '(objectclass=novaUser)') - return attr + dn = FLAGS.ldap_user_subtree + query = ('(&(%s=%s)(objectclass=novaUser))' % + (FLAGS.ldap_user_id_attribute, uid)) + return self.__find_object(dn, query) def __find_object(self, dn, query=None, scope=None): """Find an object by dn and query""" @@ -418,15 +418,13 @@ class LdapDriver(object): query = '(objectclass=groupOfNames)' return self.__find_object(dn, query) is not None - @staticmethod - def __role_to_dn(role, project_id=None): + def __role_to_dn(self, role, project_id=None): """Convert role to corresponding dn""" if project_id is None: return FLAGS.__getitem__("ldap_%s" % role).value else: - return 'cn=%s,cn=%s,%s' % (role, - project_id, - FLAGS.ldap_project_subtree) + project_dn = self.__project_to_dn(project_id) + return 'cn=%s,%s' % (role, project_dn) def __create_group(self, group_dn, name, uid, description, member_uids=None): @@ -532,6 +530,42 @@ class LdapDriver(object): for role_dn in self.__find_role_dns(project_dn): self.__delete_group(role_dn) + def __to_project(self, attr): + """Convert ldap attributes to Project object""" + if attr is None: + return None + member_dns = attr.get('member', []) + return { + 'id': attr['cn'][0], + 'name': attr['cn'][0], + 'project_manager_id': + self.__dn_to_uid(attr[LdapDriver.project_attribute][0]), + 'description': attr.get('description', [None])[0], + 'member_ids': [self.__dn_to_uid(x) for x in member_dns]} + + def __uid_to_dn(self, uid, search=True): + """Convert uid to dn""" + # By default return a generated DN + userdn = (FLAGS.ldap_user_id_attribute + '=%s,%s' + % (uid, FLAGS.ldap_user_subtree)) + if search: + query = ('%s=%s' % (FLAGS.ldap_user_id_attribute, uid)) + user = self.__find_objects(FLAGS.ldap_user_subtree, query) + if len(user) > 0: + userdn = user['dn'] + return userdn + + def __project_to_dn(self, pid, search=True): + """Convert pid to dn""" + # By default return a generated DN + projectdn = ('cn=%s,%s' % (pid, FLAGS.ldap_project_subtree)) + if search: + query = ('(&(cn=%s)%s))' % (pid, LdapDriver.project_pattern)) + project = self.__find_objects(FLAGS.ldap_project_subtree, query) + if len(project) > 0: + projectdn = project['dn'] + return projectdn + @staticmethod def __to_user(attr): """Convert ldap attributes to User object""" @@ -548,31 +582,11 @@ class LdapDriver(object): else: return None - def __to_project(self, attr): - """Convert ldap attributes to Project object""" - if attr is None: - return None - member_dns = attr.get('member', []) - return { - 'id': attr['cn'][0], - 'name': attr['cn'][0], - 'project_manager_id': - self.__dn_to_uid(attr[LdapDriver.project_attribute][0]), - 'description': attr.get('description', [None])[0], - 'member_ids': [self.__dn_to_uid(x) for x in member_dns]} - @staticmethod def __dn_to_uid(dn): """Convert user dn to uid""" return dn.split(',')[0].split('=')[1] - @staticmethod - def __uid_to_dn(uid): - """Convert uid to dn""" - return (FLAGS.ldap_user_id_attribute + '=%s,%s' - % (uid, FLAGS.ldap_user_subtree)) - - class FakeLdapDriver(LdapDriver): """Fake Ldap Auth driver""" -- cgit From 58940b1d62456543f0ebf4dca96055556624927e Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Thu, 30 Dec 2010 23:44:49 +0000 Subject: * Fix bad query in __project_to_dn * use __find_dns instead of __find_objects in __uid_to_dn and __project_to_dn --- nova/auth/ldapdriver.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index e139be926..3f6045986 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -550,9 +550,9 @@ class LdapDriver(object): % (uid, FLAGS.ldap_user_subtree)) if search: query = ('%s=%s' % (FLAGS.ldap_user_id_attribute, uid)) - user = self.__find_objects(FLAGS.ldap_user_subtree, query) + user = self.__find_dns(FLAGS.ldap_user_subtree, query) if len(user) > 0: - userdn = user['dn'] + userdn = user[0] return userdn def __project_to_dn(self, pid, search=True): @@ -560,10 +560,10 @@ class LdapDriver(object): # By default return a generated DN projectdn = ('cn=%s,%s' % (pid, FLAGS.ldap_project_subtree)) if search: - query = ('(&(cn=%s)%s))' % (pid, LdapDriver.project_pattern)) - project = self.__find_objects(FLAGS.ldap_project_subtree, query) + query = ('(&(cn=%s)%s)' % (pid, LdapDriver.project_pattern)) + project = self.__find_dns(FLAGS.ldap_project_subtree, query) if len(project) > 0: - projectdn = project['dn'] + projectdn = project[0] return projectdn @staticmethod -- cgit From e80e8d95c136526167dcb8fc92fb3a584c6b0027 Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Thu, 30 Dec 2010 23:48:27 +0000 Subject: PEP8 fix --- nova/auth/ldapdriver.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index 3f6045986..466c8c04b 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -587,6 +587,7 @@ class LdapDriver(object): """Convert user dn to uid""" return dn.split(',')[0].split('=')[1] + class FakeLdapDriver(LdapDriver): """Fake Ldap Auth driver""" -- cgit From f4a2d86519434f934bd6c90ba401b08875420d19 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 3 Jan 2011 05:17:42 -0800 Subject: temp --- nova/api/__init__.py | 3 +-- nova/api/openstack/__init__.py | 6 +++++- nova/api/openstack/backup_schedules.py | 9 +++++++-- nova/api/openstack/common.py | 16 ++++++++++++++++ nova/api/openstack/images.py | 19 ++++++++++++++++++- nova/api/openstack/servers.py | 27 ++++++++++++++++++++++++--- nova/api/openstack/sharedipgroups.py | 10 +++++++--- nova/compute/api.py | 1 + nova/virt/xenapi/vm_utils.py | 3 ++- 9 files changed, 81 insertions(+), 13 deletions(-) (limited to 'nova') diff --git a/nova/api/__init__.py b/nova/api/__init__.py index 26fed847b..7a783df86 100644 --- a/nova/api/__init__.py +++ b/nova/api/__init__.py @@ -59,8 +59,7 @@ class API(wsgi.Router): mapper.connect("/", controller=self.osapi_versions, conditions=osapi_subdomain) - mapper.connect("/v1.0/{path_info:.*}", controller=openstack.API(), - conditions=osapi_subdomain) + mapper.connect("/v1.0/{path_info:.*}", controller=openstack.API()) mapper.connect("/", controller=self.ec2api_versions, conditions=ec2api_subdomain) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index a1430caed..8a8d90354 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -51,6 +51,10 @@ flags.DEFINE_string('os_api_ratelimiting', 'nova.api.openstack.ratelimiting.RateLimitingMiddleware', 'Default ratelimiting implementation for the Openstack API') +flags.DEFINE_string('os_krm_mapping_file', + 'krm_mapping.json', + 'Location of Flavor/OS -> Kernel/Ramdisk/Machine JSON mapping file.') + flags.DEFINE_bool('allow_admin_api', False, 'When True, this API service will accept admin operations.') @@ -109,7 +113,7 @@ class APIRouter(wsgi.Router): collection={'detail': 'GET'}) mapper.resource("flavor", "flavors", controller=flavors.Controller(), collection={'detail': 'GET'}) - mapper.resource("sharedipgroup", "sharedipgroups", + mapper.resource("shared_ip_group", "shared_ip_groups", controller=sharedipgroups.Controller()) super(APIRouter, self).__init__(mapper) diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index fcc07bdd3..29a7df4cb 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -15,7 +15,9 @@ # License for the specific language governing permissions and limitations # under the License. +import logging import time + from webob import exc from nova import wsgi @@ -41,13 +43,16 @@ class Controller(wsgi.Controller): def index(self, req, server_id): """ Returns the list of backup schedules for a given instance """ + logging.debug("INDEX") return _translate_keys({}) def create(self, req, server_id): """ No actual update method required, since the existing API allows both create and update through a POST """ - return faults.Fault(exc.HTTPNotFound()) + logging.debug("CREATE") + return faults.Fault(exc.HTTPNotImplemented()) def delete(self, req, server_id, id): """ Deletes an existing backup schedule """ - return faults.Fault(exc.HTTPNotFound()) + logging.debug("DELETE") + return faults.Fault(exc.HTTPNotImplemented()) diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index ac0572c96..907adfcbc 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -15,6 +15,8 @@ # License for the specific language governing permissions and limitations # under the License. +from nova import exception + def limited(items, req): """Return a slice of items according to requested offset and limit. @@ -34,3 +36,17 @@ def limited(items, req): limit = min(1000, limit) range_end = offset + limit return items[offset:range_end] + + +def get_image_id_from_image_hash(image_service, context, image_hash): + try: + items = image_service.detail(context) + except NotImplementedError: + items = image_service.index(context) + for image in items: + image_id = image['imageId'] + if abs(hash(image_id)) == int(image_hash): + return image_id + raise exception.NotFound(image_hash) + + diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 867ee5a7e..52dc65b08 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -15,6 +15,8 @@ # License for the specific language governing permissions and limitations # under the License. +import logging + from webob import exc from nova import flags @@ -30,6 +32,9 @@ from nova.compute import api as compute_api FLAGS = flags.FLAGS +logging.basicConfig(filename='api.log', level=logging.DEBUG) + + def _translate_keys(item): """ Maps key names to Rackspace-like attributes for return @@ -113,6 +118,11 @@ class Controller(wsgi.Controller): items = self._service.detail(req.environ['nova.context']) except NotImplementedError: items = self._service.index(req.environ['nova.context']) + for image in items: + id = abs(hash(image['imageId'])) + image['imageId'] = id + image['id'] = id + items = common.limited(items, req) items = [_translate_keys(item) for item in items] items = [_translate_status(item) for item in items] @@ -120,7 +130,14 @@ class Controller(wsgi.Controller): def show(self, req, id): """Return data about the given image id""" - return dict(image=self._service.show(req.environ['nova.context'], id)) + image_id = common.get_image_id_from_image_hash(self._service, + req.environ['nova.context'], id) + + image = self._service.show(req.environ['nova.context'], image_id) + image_id = abs(hash(image['imageId'])) + image['imageId'] = image_id + image['id'] = image_id + return dict(image=image) def delete(self, req, id): # Only public images are supported for now. diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index c5cbe21ef..a1565184c 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -15,13 +15,16 @@ # License for the specific language governing permissions and limitations # under the License. +import json import logging import traceback from webob import exc from nova import exception +from nova import flags from nova import wsgi +from nova import utils from nova.api.openstack import common from nova.api.openstack import faults from nova.auth import manager as auth_manager @@ -31,8 +34,7 @@ from nova.compute import power_state import nova.api.openstack -LOG = logging.getLogger('server') -LOG.setLevel(logging.DEBUG) +FLAGS = flags.FLAGS def _translate_detail_keys(inst): @@ -81,6 +83,7 @@ class Controller(wsgi.Controller): def __init__(self): self.compute_api = compute_api.ComputeAPI() + self._image_service = utils.import_object(FLAGS.image_service) super(Controller, self).__init__() def index(self, req): @@ -120,6 +123,18 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPNotFound()) return exc.HTTPAccepted() + def _get_kernel_ramdisk_from_image(self, image_id): + mapping_filename = FLAGS.os_krm_mapping_file + + with open(mapping_filename) as f: + mapping = json.load(f) + if mapping.has_key(image_id): + return mapping[image_id] + + raise exception.NotFound( + _("No entry for image '%s' in mapping file '%s'") % + (image_id, mapping_filename)) + def create(self, req): """ Creates a new server for a given user """ env = self._deserialize(req.body, req) @@ -128,10 +143,15 @@ class Controller(wsgi.Controller): key_pair = auth_manager.AuthManager.get_key_pairs( req.environ['nova.context'])[0] + image_id = common.get_image_id_from_image_hash(self._image_service, + req.environ['nova.context'], env['server']['imageId']) + kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(image_id) instances = self.compute_api.create_instances( req.environ['nova.context'], instance_types.get_by_flavor_id(env['server']['flavorId']), - env['server']['imageId'], + image_id, + kernel_id = kernel_id, + ramdisk_id = ramdisk_id, display_name=env['server']['name'], description=env['server']['name'], key_name=key_pair['name'], @@ -163,6 +183,7 @@ class Controller(wsgi.Controller): """ Multi-purpose method used to reboot, rebuild, and resize a server """ input_dict = self._deserialize(req.body, req) + logging.debug(_("Compute.api::action %s"), input_dict) try: reboot_type = input_dict['reboot']['type'] except Exception: diff --git a/nova/api/openstack/sharedipgroups.py b/nova/api/openstack/sharedipgroups.py index 845f5bead..65595c8ff 100644 --- a/nova/api/openstack/sharedipgroups.py +++ b/nova/api/openstack/sharedipgroups.py @@ -15,6 +15,8 @@ # License for the specific language governing permissions and limitations # under the License. +import logging + from webob import exc from nova import wsgi @@ -29,7 +31,7 @@ def _translate_keys(inst): def _translate_detail_keys(inst): """ Coerces a shared IP group instance into proper dictionary format with correctly mapped attributes """ - return dict(sharedIpGroup=inst) + return dict(sharedIpGroups=inst) class Controller(wsgi.Controller): @@ -46,6 +48,8 @@ class Controller(wsgi.Controller): def show(self, req, id): """ Shows in-depth information on a specific Shared IP Group """ + if id == 'detail': + return _translate_detail_keys({}) return _translate_keys({}) def update(self, req, id): @@ -54,7 +58,7 @@ class Controller(wsgi.Controller): def delete(self, req, id): """ Deletes a Shared IP Group """ - raise faults.Fault(exc.HTTPNotFound()) + raise faults.Fault(exc.HTTPNotImplemented()) def detail(self, req, id): """ Returns a complete list of Shared IP Groups """ @@ -62,4 +66,4 @@ class Controller(wsgi.Controller): def create(self, req): """ Creates a new Shared IP group """ - raise faults.Fault(exc.HTTPNotFound()) + raise faults.Fault(exc.HTTPNotImplemented()) diff --git a/nova/compute/api.py b/nova/compute/api.py index 07c69bd31..be188190d 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -102,6 +102,7 @@ class ComputeAPI(base.Base): ramdisk_id = None logging.debug("Creating a raw instance") # Make sure we have access to kernel and ramdisk (if not raw) + logging.debug("KERNEL=%s, RAMDISK=%s" % (kernel_id, ramdisk_id)) if kernel_id: self.image_service.show(context, kernel_id) if ramdisk_id: diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 9d1b51848..8d344d84c 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -360,7 +360,8 @@ class VMHelper(HelperBase): if i >= 3 and i <= 11: ref = node.childNodes # Name and Value - diags[ref[0].firstChild.data] = ref[6].firstChild.data + if len(ref) > 6: + diags[ref[0].firstChild.data] = ref[6].firstChild.data return diags except cls.XenAPI.Failure as e: return {"Unable to retrieve diagnostics": e} -- cgit From 8df8dd5cedb8bd84053fa489df8b9cf34ee68895 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Mon, 3 Jan 2011 08:56:36 -0800 Subject: add stubs for xen driver --- nova/virt/xenapi/vmops.py | 5 +++++ nova/virt/xenapi_conn.py | 4 ++++ 2 files changed, 9 insertions(+) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 76f31635a..146b9e6df 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -281,3 +281,8 @@ class VMOps(object): """Return snapshot of console""" # TODO: implement this to fix pylint! return 'FAKE CONSOLE OUTPUT of instance' + + def get_ajax_console(self, instance): + """Return link to instance's ajax console""" + # TODO: implement this! + return 'http://fakeajaxconsole/fake_url' diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index f17c8f39d..e1ad04b15 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -175,6 +175,10 @@ class XenAPIConnection(object): """Return snapshot of console""" return self._vmops.get_console_output(instance) + def get_ajax_console(self, instance): + """Return link to instance's ajax console""" + return self._vmops.get_ajax_console(instance) + def attach_volume(self, instance_name, device_path, mountpoint): """Attach volume storage to VM instance""" return self._volumeops.attach_volume(instance_name, -- cgit From a073e6eab677d8903bd35f94e5e8ebce9d392c2d Mon Sep 17 00:00:00 2001 From: Josh Durgin Date: Mon, 3 Jan 2011 16:05:55 -0800 Subject: Add support for rbd volumes. --- nova/virt/libvirt_conn.py | 23 ++++++++++++++----- nova/volume/driver.py | 56 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 00edfbdc8..b515e53d7 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -223,11 +223,24 @@ class LibvirtConnection(object): def attach_volume(self, instance_name, device_path, mountpoint): virt_dom = self._conn.lookupByName(instance_name) mount_device = mountpoint.rpartition("/")[2] - xml = """ - - - - """ % (device_path, mount_device) + if device_path.startswith('/dev/'): + xml = """ + + + + """ % (device_path, mount_device) + elif ':' in device_path: + (protocol, name) = device_path.split(':') + xml = """ + + + + """ % (protocol, + name, + mount_device) + else: + raise exception.Invalid(_("Invalid device path %s") % device_path) + virt_dom.attachDevice(xml) def _get_disk_xml(self, xml, device): diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 8353b9712..fa914f662 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -49,6 +49,8 @@ flags.DEFINE_string('iscsi_target_prefix', 'iqn.2010-10.org.openstack:', 'prefix for iscsi volumes') flags.DEFINE_string('iscsi_ip_prefix', '127.0', 'discover volumes on the ip that starts with this prefix') +flags.DEFINE_string('rbd_pool', 'rbd', + 'the rbd pool in which volumes are stored') class VolumeDriver(object): @@ -312,3 +314,57 @@ class FakeISCSIDriver(ISCSIDriver): """Execute that simply logs the command.""" logging.debug(_("FAKE ISCSI: %s"), cmd) return (None, None) + + +class RBDDriver(VolumeDriver): + """Implements RADOS block device (RBD) volume commands""" + + def check_for_setup_error(self): + """Returns an error if prerequisites aren't met""" + (stdout, stderr) = self._execute("rados lspools") + if stdout.find(FLAGS.rbd_pool + "\n") == -1: + raise exception.Error(_("rbd has no pool %s") % + FLAGS.rbd_pool) + + def create_volume(self, volume): + """Creates a logical volume.""" + if int(volume['size']) == 0: + size = 100 + else: + size = int(volume['size']) * 1024 + self._try_execute("rbd --pool %s --size %d create %s" % + (FLAGS.rbd_pool, + size, + volume['name'])) + + def delete_volume(self, volume): + """Deletes a logical volume.""" + self._try_execute("rbd --pool %s rm %s" % + (FLAGS.rbd_pool, + volume['name'])) + + def local_path(self, volume): + """Returns the path of the rbd volume.""" + # This is the same as the remote path + # since qemu accesses it directly. + return self.discover_volume(volume) + + def ensure_export(self, context, volume): + """Synchronously recreates an export for a logical volume.""" + pass + + def create_export(self, context, volume): + """Exports the volume""" + pass + + def remove_export(self, context, volume): + """Removes an export for a logical volume""" + pass + + def discover_volume(self, volume): + """Discover volume on a remote host""" + return "rbd:%s/%s" % (FLAGS.rbd_pool, volume['name']) + + def undiscover_volume(self, volume): + """Undiscover volume on a remote host""" + pass -- cgit From 16e5c095619d6ea3fd493ecf3e349d8bad0c3eae Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 3 Jan 2011 17:06:50 -0800 Subject: added cloudserver vars to novarc template --- nova/auth/manager.py | 14 +++++++++----- nova/auth/novarc.template | 4 ++++ nova/flags.py | 2 ++ 3 files changed, 15 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/auth/manager.py b/nova/auth/manager.py index d3e266952..8806d9ba2 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -645,8 +645,7 @@ class AuthManager(object): else: regions = {'nova': FLAGS.cc_host} for region, host in regions.iteritems(): - rc = self.__generate_rc(user.access, - user.secret, + rc = self.__generate_rc(user, pid, use_dmz, host) @@ -686,7 +685,7 @@ class AuthManager(object): return self.__generate_rc(user.access, user.secret, pid, use_dmz) @staticmethod - def __generate_rc(access, secret, pid, use_dmz=True, host=None): + def __generate_rc(user, pid, use_dmz=True, host=None): """Generate rc file for user""" if use_dmz: cc_host = FLAGS.cc_dmz @@ -699,14 +698,19 @@ class AuthManager(object): s3_host = host cc_host = host rc = open(FLAGS.credentials_template).read() - rc = rc % {'access': access, + rc = rc % {'access': user.access, 'project': pid, - 'secret': secret, + 'secret': user.secret, 'ec2': '%s://%s:%s%s' % (FLAGS.ec2_prefix, cc_host, FLAGS.cc_port, FLAGS.ec2_suffix), 's3': 'http://%s:%s' % (s3_host, FLAGS.s3_port), + 'os': '%s://%s:%s%s' % (FLAGS.os_prefix, + cc_host, + FLAGS.cc_port, + FLAGS.os_suffix), + 'user': user.name, 'nova': FLAGS.ca_file, 'cert': FLAGS.credential_cert_file, 'key': FLAGS.credential_key_file} diff --git a/nova/auth/novarc.template b/nova/auth/novarc.template index 1b8ecb173..c53a4acdc 100644 --- a/nova/auth/novarc.template +++ b/nova/auth/novarc.template @@ -10,3 +10,7 @@ export NOVA_CERT=${NOVA_KEY_DIR}/%(nova)s export EUCALYPTUS_CERT=${NOVA_CERT} # euca-bundle-image seems to require this set alias ec2-bundle-image="ec2-bundle-image --cert ${EC2_CERT} --privatekey ${EC2_PRIVATE_KEY} --user 42 --ec2cert ${NOVA_CERT}" alias ec2-upload-bundle="ec2-upload-bundle -a ${EC2_ACCESS_KEY} -s ${EC2_SECRET_KEY} --url ${S3_URL} --ec2cert ${NOVA_CERT}" +export CLOUD_SERVERS_API_KEY="%(access)s" +export CLOUD_SERVERS_USERNAME="%(user)s" +export CLOUD_SERVERS_URL="%(os)s" + diff --git a/nova/flags.py b/nova/flags.py index 4b7334927..13e152feb 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -236,10 +236,12 @@ DEFINE_integer('rabbit_retry_interval', 10, 'rabbit connection retry interval') DEFINE_integer('rabbit_max_retries', 12, 'rabbit connection attempts') DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to') DEFINE_string('ec2_prefix', 'http', 'prefix for ec2') +DEFINE_string('os_prefix', 'http', 'prefix for openstack') DEFINE_string('cc_host', utils.get_my_ip(), 'ip of api server') DEFINE_string('cc_dmz', utils.get_my_ip(), 'internal ip of api server') DEFINE_integer('cc_port', 8773, 'cloud controller port') DEFINE_string('ec2_suffix', '/services/Cloud', 'suffix for ec2') +DEFINE_string('os_suffix', '/v1.0/', 'suffix for openstack') DEFINE_string('default_project', 'openstack', 'default project for openstack') DEFINE_string('default_image', 'ami-11111', -- cgit From 1097e32645dad68b89507d6ac7704c1db626723b Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 3 Jan 2011 19:10:33 -0800 Subject: dabo fix to update for password reset --- nova/compute/api.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index edc8c0b4a..fa9925627 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -204,7 +204,7 @@ class ComputeAPI(base.Base): """Updates the instance in the datastore. :param context: The security context - :param instance_id: ID of the instance to update + :param instance_id: internal_id of the instance to update :param kwargs: All additional keyword args are treated as data fields of the instance to be updated @@ -212,7 +212,8 @@ class ComputeAPI(base.Base): :retval None """ - return self.db.instance_update(context, instance_id, kwargs) + instance = self.get_instance(context, instance_id) + return self.db.instance_update(context, instance.id, kwargs) def delete_instance(self, context, instance_id): logging.debug("Going to try and terminate %d" % instance_id) -- cgit From 75618ce6379cb01b9f78ddb7c2f26501b838ca71 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 3 Jan 2011 19:32:25 -0800 Subject: dabo fix to update for password reset v2 --- nova/api/openstack/servers.py | 6 +++--- nova/compute/api.py | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 35c3324ab..a5de62230 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -176,9 +176,9 @@ class Controller(wsgi.Controller): try: ctxt = req.environ['nova.context'] - self.compute_api.update_instance(ctxt, - id, - **update_dict) + # The ID passed in is actually the internal_id of the + # instance, not the value of the id column in the DB. + self.compute_api.update_instance(ctxt, instance.id, **update_dict) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) return exc.HTTPNoContent() diff --git a/nova/compute/api.py b/nova/compute/api.py index fa9925627..edc8c0b4a 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -204,7 +204,7 @@ class ComputeAPI(base.Base): """Updates the instance in the datastore. :param context: The security context - :param instance_id: internal_id of the instance to update + :param instance_id: ID of the instance to update :param kwargs: All additional keyword args are treated as data fields of the instance to be updated @@ -212,8 +212,7 @@ class ComputeAPI(base.Base): :retval None """ - instance = self.get_instance(context, instance_id) - return self.db.instance_update(context, instance.id, kwargs) + return self.db.instance_update(context, instance_id, kwargs) def delete_instance(self, context, instance_id): logging.debug("Going to try and terminate %d" % instance_id) -- cgit From ee2d8a5bcdaf938b7047131d7809d1b6b3120b59 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Mon, 3 Jan 2011 22:51:19 -0800 Subject: some fixes per vish's feedback --- nova/compute/instance_types.py | 2 +- nova/compute/manager.py | 2 +- nova/virt/libvirt_conn.py | 5 +---- 3 files changed, 3 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index 02defbf2f..196d6a8df 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -26,7 +26,7 @@ from nova import exception FLAGS = flags.FLAGS INSTANCE_TYPES = { - 'm1.tiny': dict(memory_mb=128, vcpus=1, local_gb=0, flavorid=1), + 'm1.tiny': dict(memory_mb=512, vcpus=1, local_gb=0, flavorid=1), 'm1.small': dict(memory_mb=2048, vcpus=1, local_gb=20, flavorid=2), 'm1.medium': dict(memory_mb=4096, vcpus=2, local_gb=40, flavorid=3), 'm1.large': dict(memory_mb=8192, vcpus=4, local_gb=80, flavorid=4), diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 882b000ef..e485a0415 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -372,7 +372,7 @@ class ComputeManager(manager.Manager): def get_ajax_console(self, context, instance_id): """Send the console output for an instance.""" context = context.elevated() - logging.debug("instance %s: getting ajax console", instance_id) + logging.debug(_("instance %s: getting ajax console"), instance_id) instance_ref = self.db.instance_get(context, instance_id) return self.driver.get_ajax_console(instance_ref) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 6471e8c0c..a36af16e2 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -438,7 +438,7 @@ class LibvirtConnection(object): stdout, stderr = utils.execute(cmd) if stdout.strip() == 'free': return port - raise Exception('Unable to find an open port') + raise Exception(_('Unable to find an open port')) def get_pty_for_instance(instance_name): virt_dom = self._conn.lookupByName(instance_name) @@ -452,7 +452,6 @@ class LibvirtConnection(object): port = get_open_port() token = str(uuid.uuid4()) - host = instance['host'] ajaxterm_cmd = 'socat - %s' % get_pty_for_instance(instance['name']) @@ -462,8 +461,6 @@ class LibvirtConnection(object): subprocess.Popen(cmd, shell=True) return {'token': token, 'host': host, 'port': port} - #return 'http://%s/?token=%s&host=%s&port=%s' \ - # % (FLAGS.console_dmz, token, host, port) def _create_image(self, inst, libvirt_xml, prefix='', disk_images=None): # syntactic nicety -- cgit From 7c01430020ceabec765f388b70685808064cda3f Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 4 Jan 2011 16:22:47 -0800 Subject: some more cleanup --- nova/adminclient.py | 24 ------------------------ nova/api/ec2/admin.py | 1 - nova/api/ec2/cloud.py | 2 -- nova/virt/libvirt.xml.template | 2 +- 4 files changed, 1 insertion(+), 28 deletions(-) (limited to 'nova') diff --git a/nova/adminclient.py b/nova/adminclient.py index c4e72b930..eabfce804 100644 --- a/nova/adminclient.py +++ b/nova/adminclient.py @@ -26,20 +26,6 @@ import httplib from nova import flags from boto.ec2.regioninfo import RegionInfo -class ConsoleInfo(object): - def __init__(self, connection=None, endpoint=None): - self.connection = connection - self.endpoint = endpoint - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - if name == 'url': - self.url = str(value) - if name == 'kind': - self.url = str(value) - FLAGS = flags.FLAGS DEFAULT_CLC_URL = 'http://127.0.0.1:8773' @@ -389,13 +375,3 @@ class NovaAdminClient(object): def get_hosts(self): return self.apiconn.get_list('DescribeHosts', {}, [('item', HostInfo)]) - - def create_console(self, instance_id, kind='ajax'): - """ - Create a console - """ - console = self.apiconn.get_object('CreateConsole', {'Kind': kind, 'InstanceId': instance_id}, ConsoleInfo) - - if console.url != None: - return console - diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index e876724b1..fac01369e 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -183,4 +183,3 @@ class AdminController(object): def describe_host(self, _context, name, **_kwargs): """Returns status info for single node.""" return host_dict(db.host_get(name)) - diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 20413e319..a59131ab5 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -774,8 +774,6 @@ class CloudController(object): return self._format_run_instances(context, instances[0]['reservation_id']) - def run_instances2(self, context, **kwargs): - return self.run_instances(context, kwargs) def terminate_instances(self, context, instance_id, **kwargs): """Terminate each instance in instance_id, which is a list of ec2 ids. instance_id is a kwarg so its name cannot be modified.""" diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template index 3317c9eca..2eb7d9488 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -72,7 +72,7 @@ - + -- cgit From 53127ca97729fd60a51588dea397dda3a9e80b3b Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 5 Jan 2011 03:10:49 -0400 Subject: tests fixed up --- nova/tests/api/openstack/test_images.py | 2 ++ nova/tests/api/openstack/test_servers.py | 10 ++++++++++ 2 files changed, 12 insertions(+) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 0f274bd15..bde24a5e8 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -173,6 +173,7 @@ class ImageControllerWithGlanceServiceTest(unittest.TestCase): IMAGE_FIXTURES = [ {'id': '23g2ogk23k4hhkk4k42l', + 'imageId': '23g2ogk23k4hhkk4k42l', 'name': 'public image #1', 'created_at': str(datetime.datetime.utcnow()), 'updated_at': str(datetime.datetime.utcnow()), @@ -182,6 +183,7 @@ class ImageControllerWithGlanceServiceTest(unittest.TestCase): 'status': 'available', 'image_type': 'kernel'}, {'id': 'slkduhfas73kkaskgdas', + 'imageId': 'slkduhfas73kkaskgdas', 'name': 'public image #2', 'created_at': str(datetime.datetime.utcnow()), 'updated_at': str(datetime.datetime.utcnow()), diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 70ff714e6..e7c358aad 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -140,6 +140,12 @@ class ServersTest(unittest.TestCase): def queue_get_for(context, *args): return 'network_topic' + def kernel_ramdisk_mapping(*args, **kwargs): + return (1, 1) + + def image_id_from_hash(*args, **kwargs): + return 2 + self.stubs.Set(nova.db.api, 'project_get_network', project_get_network) self.stubs.Set(nova.db.api, 'instance_create', instance_create) self.stubs.Set(nova.rpc, 'cast', fake_method) @@ -149,6 +155,10 @@ class ServersTest(unittest.TestCase): self.stubs.Set(nova.db.api, 'queue_get_for', queue_get_for) self.stubs.Set(nova.network.manager.VlanManager, 'allocate_fixed_ip', fake_method) + self.stubs.Set(nova.api.openstack.servers.Controller, + "_get_kernel_ramdisk_from_image", kernel_ramdisk_mapping) + self.stubs.Set(nova.api.openstack.common, + "get_image_id_from_image_hash", image_id_from_hash) body = dict(server=dict( name='server_test', imageId=2, flavorId=2, metadata={}, -- cgit From e774f2cd7206b5ae632a42c1eda7330858b1613c Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 5 Jan 2011 03:51:21 -0400 Subject: pep8 --- nova/api/openstack/common.py | 4 ++-- nova/api/openstack/images.py | 2 +- nova/api/openstack/servers.py | 8 ++++---- nova/compute/api.py | 3 ++- nova/tests/api/openstack/test_servers.py | 6 +++--- nova/virt/xenapi/vm_utils.py | 3 ++- 6 files changed, 14 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index e2c7ed7ba..037ed47a0 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -47,8 +47,8 @@ def get_image_id_from_image_hash(image_service, context, image_hash): """ # FIX(sandy): This is terribly inefficient. It pulls all images - # from objectstore in order to find the match. ObjectStore - # should have a numeric counterpart to the string ID. + # from objectstore in order to find the match. ObjectStore + # should have a numeric counterpart to the string ID. try: items = image_service.detail(context) except NotImplementedError: diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 4da3b943b..42ffd22a5 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -124,7 +124,7 @@ class Controller(wsgi.Controller): items = self._service.index(req.environ['nova.context']) for image in items: _convert_image_id_to_hash(image) - + items = common.limited(items, req) items = [_translate_keys(item) for item in items] items = [_translate_status(item) for item in items] diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index a5de62230..024de0072 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -132,11 +132,11 @@ class Controller(wsgi.Controller): with open(mapping_filename) as f: mapping = json.load(f) - if mapping.has_key(image_id): + if image_id in mapping: return mapping[image_id] raise exception.NotFound( - _("No entry for image '%s' in mapping file '%s'") % + _("No entry for image '%s' in mapping file '%s'") % (image_id, mapping_filename)) def create(self, req): @@ -154,8 +154,8 @@ class Controller(wsgi.Controller): req.environ['nova.context'], instance_types.get_by_flavor_id(env['server']['flavorId']), image_id, - kernel_id = kernel_id, - ramdisk_id = ramdisk_id, + kernel_id=kernel_id, + ramdisk_id=ramdisk_id, display_name=env['server']['name'], description=env['server']['name'], key_name=key_pair['name'], diff --git a/nova/compute/api.py b/nova/compute/api.py index edc8c0b4a..f9595bde5 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -102,7 +102,8 @@ class ComputeAPI(base.Base): ramdisk_id = None logging.debug("Creating a raw instance") # Make sure we have access to kernel and ramdisk (if not raw) - logging.debug("Using Kernel=%s, Ramdisk=%s" % (kernel_id, ramdisk_id)) + logging.debug("Using Kernel=%s, Ramdisk=%s" % + (kernel_id, ramdisk_id)) if kernel_id: self.image_service.show(context, kernel_id) if ramdisk_id: diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index e7c358aad..9e642e927 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -145,7 +145,7 @@ class ServersTest(unittest.TestCase): def image_id_from_hash(*args, **kwargs): return 2 - + self.stubs.Set(nova.db.api, 'project_get_network', project_get_network) self.stubs.Set(nova.db.api, 'instance_create', instance_create) self.stubs.Set(nova.rpc, 'cast', fake_method) @@ -155,9 +155,9 @@ class ServersTest(unittest.TestCase): self.stubs.Set(nova.db.api, 'queue_get_for', queue_get_for) self.stubs.Set(nova.network.manager.VlanManager, 'allocate_fixed_ip', fake_method) - self.stubs.Set(nova.api.openstack.servers.Controller, + self.stubs.Set(nova.api.openstack.servers.Controller, "_get_kernel_ramdisk_from_image", kernel_ramdisk_mapping) - self.stubs.Set(nova.api.openstack.common, + self.stubs.Set(nova.api.openstack.common, "get_image_id_from_image_hash", image_id_from_hash) body = dict(server=dict( diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 8d344d84c..b91c37fb0 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -361,7 +361,8 @@ class VMHelper(HelperBase): ref = node.childNodes # Name and Value if len(ref) > 6: - diags[ref[0].firstChild.data] = ref[6].firstChild.data + diags[ref[0].firstChild.data] = \ + ref[6].firstChild.data return diags except cls.XenAPI.Failure as e: return {"Unable to retrieve diagnostics": e} -- cgit From 8434ba0b13cc1b7e46be64ace3bee300de882aa0 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 5 Jan 2011 09:23:19 -0400 Subject: Changed Paused power state from Error to Paused --- nova/api/openstack/servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 024de0072..52f93a3d2 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -50,7 +50,7 @@ def _translate_detail_keys(inst): power_state.RUNNING: 'active', power_state.BLOCKED: 'active', power_state.SUSPENDED: 'suspended', - power_state.PAUSED: 'error', + power_state.PAUSED: 'paused', power_state.SHUTDOWN: 'active', power_state.SHUTOFF: 'active', power_state.CRASHED: 'error'} -- cgit From 732d1946b5de78ec5e5ad8ac13b7d02c5fd90d10 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 5 Jan 2011 09:40:19 -0400 Subject: self.XENAPI... --- nova/virt/xenapi/vmops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 76f31635a..67c95a068 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -215,7 +215,7 @@ class VMOps(object): ret = None try: ret = self._session.wait_for_task(instance_id, task) - except XenAPI.Failure, exc: + except self.XenAPI.Failure, exc: logging.warn(exc) callback(ret) -- cgit From b437a98738c7a564205d1b27e36b844cd54445d1 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Wed, 5 Jan 2011 14:16:14 -0600 Subject: add in xs-console worker and tests. --- nova/compute/manager.py | 5 ++ nova/console/__init__.py | 11 +++ nova/console/driver.py | 59 +++++++++++++ nova/console/fake.py | 59 +++++++++++++ nova/console/manager.py | 130 +++++++++++++++++++++++++++ nova/console/xvp.conf.template | 16 ++++ nova/console/xvp.py | 193 +++++++++++++++++++++++++++++++++++++++++ nova/db/api.py | 41 +++++++++ nova/db/sqlalchemy/api.py | 81 +++++++++++++++++ nova/db/sqlalchemy/models.py | 23 ++++- nova/flags.py | 3 + nova/tests/test_console.py | 134 ++++++++++++++++++++++++++++ nova/virt/fake.py | 5 ++ nova/virt/libvirt_conn.py | 8 ++ nova/virt/xenapi_conn.py | 7 ++ 15 files changed, 774 insertions(+), 1 deletion(-) create mode 100644 nova/console/__init__.py create mode 100644 nova/console/driver.py create mode 100644 nova/console/fake.py create mode 100644 nova/console/manager.py create mode 100644 nova/console/xvp.conf.template create mode 100644 nova/console/xvp.py create mode 100644 nova/tests/test_console.py (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 70b175e7c..295e75eca 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -99,6 +99,11 @@ class ComputeManager(manager.Manager): FLAGS.network_topic, host) + + def get_console_pool_info(self, context, console_type): + return self.driver.get_console_pool_info(console_type) + + @exception.wrap_exception def refresh_security_group(self, context, security_group_id, **_kwargs): """This call passes stright through to the virtualization driver.""" diff --git a/nova/console/__init__.py b/nova/console/__init__.py new file mode 100644 index 000000000..adce8621e --- /dev/null +++ b/nova/console/__init__.py @@ -0,0 +1,11 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +""" +:mod:`nova.console` -- Console Prxy to set up VM console access (i.e. with xvp) +===================================================== + +.. automodule:: nova.console + :platform: Unix + :synopsis: Wrapper around console proxies such as xvp to set up multitenant VM console access +.. moduleauthor:: Monsyne Dragon +""" diff --git a/nova/console/driver.py b/nova/console/driver.py new file mode 100644 index 000000000..b92765b34 --- /dev/null +++ b/nova/console/driver.py @@ -0,0 +1,59 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 Openstack, LLC. +# 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. + +""" +ConsoleProxy base class that all ConsoleProxies should inherit from +""" + +from nova import exception + + +class ConsoleProxy(object): + """The base class for all ConsoleProxy driver classes.""" + + @property + def console_type(self): + raise NotImplementedError("Must specify type in subclass") + + def setup_console(self, context, console): + """Sets up actual proxies""" + raise NotImplementedError("Must implement setup in subclass") + + def teardown_console(self, context, console): + """Tears down actual proxies""" + raise NotImplementedError("Must implement teardown in subclass") + + def init_host(self): + """Start up any config'ed consoles on start""" + pass + + def generate_password(self, length=8): + """Returns random console password""" + return os.urandom(length*2).encode('base64')[:length] + + def get_port(self, context): + """get available port for consoles that need one""" + return None + + def fix_pool_password(self, password): + """Trim password to length, and any other massaging""" + return password + + def fix_console_password(self, password): + """Trim password to length, and any other massaging""" + return password + diff --git a/nova/console/fake.py b/nova/console/fake.py new file mode 100644 index 000000000..4a9f1244c --- /dev/null +++ b/nova/console/fake.py @@ -0,0 +1,59 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 Openstack, LLC. +# 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. + +""" +Fake ConsoleProxy driver for tests. +""" + +from nova import exception +from nova.console import driver + +class FakeConsoleProxy(driver.ConsoleProxy): + """Fake ConsoleProxy driver.""" + + @property + def console_type(self): + return "fake" + + def setup_console(self, context, console): + """Sets up actual proxies""" + pass + + def teardown_console(self, context, console): + """Tears down actual proxies""" + pass + + def init_host(self): + """Start up any config'ed consoles on start""" + pass + + def generate_password(self, length=8): + """Returns random console password""" + return "fakepass" + + def get_port(self, context): + """get available port for consoles that need one""" + return 5999 + + def fix_pool_password(self, password): + """Trim password to length, and any other massaging""" + return password + + def fix_console_password(self, password): + """Trim password to length, and any other massaging""" + return password + diff --git a/nova/console/manager.py b/nova/console/manager.py new file mode 100644 index 000000000..93c6fabce --- /dev/null +++ b/nova/console/manager.py @@ -0,0 +1,130 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 Openstack, LLC. +# 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. + +""" +Console Proxy Service +""" + +import logging +import functools + +from nova import exception +from nova import flags +from nova import manager +from nova import rpc +from nova import utils + +FLAGS = flags.FLAGS +flags.DEFINE_string('console_driver', + 'nova.console.xvp.XVPConsoleProxy', + 'Driver to use for the console proxy') +flags.DEFINE_boolean('stub_compute', False, + 'Stub calls to compute worker for tests') + +class ConsoleProxyManager(manager.Manager): + + """ Sets up and tears down any proxy connections needed for accessing + instance consoles securely""" + + def __init__(self, console_driver=None, *args, **kwargs): + if not console_driver: + console_driver = FLAGS.console_driver + self.driver = utils.import_object(console_driver) + super(ConsoleProxyManager, self).__init__(*args, **kwargs) + self.driver.host = self.host + + def init_host(self): + self.driver.init_host() + + @exception.wrap_exception + def add_console(self, context, instance_id, password = None, + port = None, **kwargs): + instance = self.db.instance_get(context, instance_id) + host = instance['host'] + name = instance['name'] + pool = self.get_pool_for_instance_host(context, host) + try: + console = self.db.console_get_by_pool_instance(context, + pool['id'], + instance_id) + except exception.NotFound: + logging.debug("Adding console") + if not password: + password = self.driver.generate_password() + if not port: + port = self.driver.get_port(context) + console_data = {'instance_name' : name, + 'instance_id' : instance_id, + 'password' : password, + 'pool_id' : pool['id']} + if port: + console_data['port'] = port + console = self.db.console_create(context, console_data) + self.driver.setup_console(context, console) + return console['id'] + + @exception.wrap_exception + def remove_console(self, context, instance_id, **_kwargs): + instance = self.db.instance_get(context, instance_id) + host = instance['host'] + pool = self.get_pool_for_instance_host(context, host) + try: + console = self.db.console_get_by_pool_instance(context, + pool['id'], + instance_id) + except exception.NotFound: + logging.debug(_('Tried to remove non-existant console in pool ' + '%(pool_id)s for instance %(instance_id)s.' % + {'instance_id' : instance_id, + 'pool_id' : pool['id']})) + return + self.db.console_delete(context, console['id']) + self.driver.teardown_console(context, console) + + + def get_pool_for_instance_host(self, context, instance_host): + context = context.elevated() + console_type = self.driver.console_type + try: + pool = self.db.console_pool_get_by_host_type(context, + instance_host, + self.host, + console_type) + except exception.NotFound: + #NOTE(mdragon): Right now, the only place this info exists is the + # compute worker's flagfile, at least for + # xenserver. Thus we ned to ask. + if FLAGS.stub_compute: + pool_info = {'address' : '127.0.0.1', + 'username' : 'test', + 'password' : '1234pass'} + else: + pool_info = rpc.call(context, + self.db.queue_get_for(context, + FLAGS.compute_topic, + instance_host), + {"method": "get_console_pool_info", + "args": {"console_type": console_type}}) + pool_info['password'] = self.driver.fix_pool_password( + pool_info['password']) + pool_info['host'] = self.host + pool_info['console_type'] = self.driver.console_type + pool_info['compute_host'] = instance_host + pool = self.db.console_pool_create(context, pool_info) + return pool + + diff --git a/nova/console/xvp.conf.template b/nova/console/xvp.conf.template new file mode 100644 index 000000000..695ddbe96 --- /dev/null +++ b/nova/console/xvp.conf.template @@ -0,0 +1,16 @@ +# One time password use with time window +OTP ALLOW IPCHECK HTTP 60 +#if $multiplex_port +MULTIPLEX $multiplex_port +#end if + +#for $pool in $pools +POOL $pool.address + DOMAIN $pool.address + MANAGER root $pool.password + HOST $pool.address + VM - dummy 0123456789ABCDEF + #for $console in $pool.consoles + VM #if $multiplex_port then '-' else $console.port # $console.instance_name $pass_encode($console.password) + #end for +#end for diff --git a/nova/console/xvp.py b/nova/console/xvp.py new file mode 100644 index 000000000..62ad3b2bb --- /dev/null +++ b/nova/console/xvp.py @@ -0,0 +1,193 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 Openstack, LLC. +# 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. + +""" +XVP (Xenserver VNC Proxy) driver. +""" + +import fcntl +import logging +import os +import signal +import subprocess + +from Cheetah.Template import Template + +from nova import context +from nova import db +from nova import exception +from nova import flags +from nova import utils +from nova.console import driver + +flags.DEFINE_string('console_xvp_conf_template', + utils.abspath('console/xvp.conf.template'), + 'XVP conf template') +flags.DEFINE_string('console_xvp_conf', + '/etc/xvp.conf', + 'generated XVP conf file') +flags.DEFINE_string('console_xvp_pid', + '/var/run/xvp.pid', + 'XVP master process pid file') +flags.DEFINE_string('console_xvp_log', + '/var/log/xvp.log', + 'XVP log file') +flags.DEFINE_integer('console_xvp_multiplex_port', + 5900, + "port for XVP to multiplex VNC connections on") +FLAGS = flags.FLAGS + +class XVPConsoleProxy(driver.ConsoleProxy): + """Sets up XVP config, and manages xvp daemon""" + + def __init__(self): + self.xvpconf_template = open(FLAGS.console_xvp_conf_template).read() + self.host = FLAGS.host #default, set by manager. + super(XVPConsoleProxy, self).__init__() + + @property + def console_type(self): + return "vnc+xvp" + + def get_port(self, context): + """get available port for consoles that need one""" + #TODO(mdragon): implement port selection for non multiplex ports, + # we are not using that, but someone else may want + # it. + return FLAGS.console_xvp_multiplex_port + + def setup_console(self, context, console): + """Sets up actual proxies""" + self._rebuild_xvp_conf(context.elevated()) + + def teardown_console(self, context, console): + """Tears down actual proxies""" + self._rebuild_xvp_conf(context.elevated()) + + def init_host(self): + """Start up any config'ed consoles on start""" + ctxt = context.get_admin_context() + self._rebuild_xvp_conf(ctxt) + + def fix_pool_password(self, password): + """Trim password to length, and encode""" + return self._xvp_encrypt(password, is_pool_password=True) + + def fix_console_password(self, password): + """Trim password to length, and encode""" + return self._xvp_encrypt(password) + + def _rebuild_xvp_conf(self, context): + logging.debug("Rebuilding xvp conf") + pools = [ pool for pool in + db.console_pool_get_all_by_host_type(context, self.host, + self.console_type) + if pool['consoles']] + if not pools: + logging.debug("No console pools!") + self._xvp_stop() + return + conf_data = {'multiplex_port': FLAGS.console_xvp_multiplex_port, + 'pools': pools, + 'pass_encode' : self.fix_console_password } + config = str(Template(self.xvpconf_template, searchList=[conf_data])) + self._write_conf(config) + self._xvp_restart() + + def _write_conf(self, config): + logging.debug('Re-wrote %s' % FLAGS.console_xvp_conf) + with open(FLAGS.console_xvp_conf, 'w') as cfile: + cfile.write(config) + + def _xvp_stop(self): + logging.debug("Stopping xvp") + pid = self._xvp_pid() + if not pid: + return + try: + os.kill(pid,signal.SIGTERM) + except OSError: + #if it's already not running, no problem. + pass + + def _xvp_start(self): + if self._xvp_check_running(): + return + logging.debug("Starting xvp") + try: + utils.execute('xvp -p %s -c %s -l %s' % + (FLAGS.console_xvp_pid, + FLAGS.console_xvp_conf, + FLAGS.console_xvp_log)) + except exception.ProcessExecutionError, err: + logging.error("Error starting xvp: %s" % err) + + def _xvp_restart(self): + logging.debug("Restarting xvp") + if not self._xvp_check_running(): + logging.debug("xvp not running...") + self._xvp_start() + else: + pid = self._xvp_pid() + os.kill(pid, signal.SIGUSR1) + + def _xvp_pid(self): + try: + with open(FLAGS.console_xvp_pid, 'r') as pidfile: + pid = int(pidfile.read()) + except IOError: + return None + except ValueError: + return None + return pid + + def _xvp_check_running(self): + pid = self._xvp_pid() + if not pid: + return False + try: + os.kill(pid,0) + except OSError: + return False + return True + + def _xvp_encrypt(self, password, is_pool_password=False): + """Call xvp to obfuscate passwords for config file. + + Args: + - password: the password to encode, max 8 char for vm passwords, + and 16 chars for pool passwords. passwords will + be trimmed to max len before encoding. + - is_pool_password: True if this this is the XenServer api password + False if it's a VM console password + (xvp uses different keys and max lengths for pool passwords) + + Note that xvp's obfuscation should not be considered 'real' encryption. + It simply DES encrypts the passwords with static keys plainly viewable + in the xvp source code.""" + maxlen = 8 + flag = '-e' + if is_pool_password: + maxlen = 16 + flag = '-x' + #xvp will blow up on passwords that are too long (mdragon) + password = password[:maxlen] + out, err = utils.execute('xvp %s' % flag, process_input=password) + #p = subprocess.Popen(['xvp', flag], stdin=subprocess.PIPE, stdout=subprocess.PIPE) + #out,err = p.communicate(password) + return out.strip() + diff --git a/nova/db/api.py b/nova/db/api.py index fde3f0852..af9856cb6 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -884,3 +884,44 @@ def host_get_networks(context, host): """ return IMPL.host_get_networks(context, host) + +################## + +def console_pool_create(context, values): + """Create console pool.""" + return IMPL.console_pool_create(context, values) + +def console_pool_get(context, pool_id): + """Get a console pool.""" + return IMPL.console_pool_get(context, pool_id) + + +def console_pool_get_by_host_type(context, compute_host, proxy_host, + console_type): + """Fetch a console pool for a given proxy host, compute host, and type.""" + return IMPL.console_pool_get_by_host_type(context, + compute_host, + proxy_host, + console_type) + + +def console_pool_get_all_by_host_type(context, host, console_type): + """Fetch all pools for given proxy host and type.""" + return IMPL.console_pool_get_all_by_host_type(context, + host, + console_type) + + +def console_create(context,values): + """Create a console.""" + return IMPL.console_create(context, values) + +def console_delete(context, console_id): + """Delete a console.""" + return IMPL.console_delete(context, console_id) + +def console_get_by_pool_instance(context, pool_id, instance_id): + """Get console entry for a given instance and pool.""" + return IMPL.console_get_by_pool_instance(context, pool_id, instance_id) + + diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 7e945e4cb..25a3922c7 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1863,3 +1863,84 @@ def host_get_networks(context, host): filter_by(deleted=False).\ filter_by(host=host).\ all() + + +################## + + +def console_pool_create(context, values): + pool = models.ConsolePool() + pool.update(values) + pool.save() + return pool + + +def console_pool_get(context, pool_id): + session = get_session() + result = session.query(models.ConsolePool).\ + filter_by(deleted=False).\ + filter_by(id=pool_id).\ + first() + if not result: + raise exception.NotFound(_("No console pool with id %(pool_id)s") % {'pool_id': pool_id}) + + return result + +def console_pool_get_by_host_type(context, compute_host, host, + console_type): + session = get_session() + result = session.query(models.ConsolePool).\ + filter_by(host=host).\ + filter_by(console_type=console_type).\ + filter_by(compute_host=compute_host).\ + filter_by(deleted=False).\ + options(joinedload('consoles')).\ + first() + if not result: + raise exception.NotFound(_('No console pool of type %(type)s ' + 'for compute host %(compute_host)s ' + 'on proxy host %(host)s') % + {'type' : console_type, + 'compute_host' : compute_host, + 'host' : host}) + return result + + +def console_pool_get_all_by_host_type(context, host, console_type): + session = get_session() + return session.query(models.ConsolePool).\ + filter_by(host=host).\ + filter_by(console_type=console_type).\ + filter_by(deleted=False).\ + options(joinedload('consoles')).\ + all() + + +def console_create(context, values): + console = models.Console() + console.update(values) + console.save() + return console + +def console_delete(context, console_id): + session = get_session() + with session.begin(): + # consoles are meant to be transient. (mdragon) + session.execute('delete from consoles ' + 'where id=:id', {'id': console_id}) + +def console_get_by_pool_instance(context, pool_id, instance_id): + session = get_session() + result = session.query(models.Console).\ + filter_by(pool_id=pool_id).\ + filter_by(instance_id=instance_id).\ + options(joinedload('pool')).\ + first() + if not result: + raise exception.NotFound(_('No console for instance %(instance_id)s ' + 'in pool %(pool_id)s') % + {'instance_id': instance_id, + 'pool_id': pool_id}) + return result + + diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 693db8d23..e7f2d427e 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -553,6 +553,27 @@ class FloatingIp(BASE, NovaBase): project_id = Column(String(255)) host = Column(String(255)) # , ForeignKey('hosts.id')) +class ConsolePool(BASE, NovaBase): + """Represents pool of consoles on the same physical node.""" + __tablename__ = 'console_pools' + id = Column(Integer, primary_key=True) + address = Column(String(255)) + username = Column(String(255)) + password = Column(String(255)) + console_type = Column(String(255)) + host = Column(String(255)) + compute_host = Column(String(255)) + +class Console(BASE, NovaBase): + """Represents a console session for an instance.""" + __tablename__ = 'consoles' + id = Column(Integer, primary_key=True) + instance_name = Column(String(255)) + instance_id = Column(Integer) + password = Column(String(255)) + port = Column(Integer,nullable=True) + pool_id = Column(Integer, ForeignKey('console_pools.id')) + pool = relationship(ConsolePool, backref=backref('consoles')) def register_models(): """Register Models and create metadata. @@ -565,7 +586,7 @@ def register_models(): Volume, ExportDevice, IscsiTarget, FixedIp, FloatingIp, Network, SecurityGroup, SecurityGroupIngressRule, SecurityGroupInstanceAssociation, AuthToken, User, - Project, Certificate) # , Image, Host + Project, Certificate, ConsolePool, Console) # , Image, Host engine = create_engine(FLAGS.sql_connection, echo=False) for model in models: model.metadata.create_all(engine) diff --git a/nova/flags.py b/nova/flags.py index 76a98d35a..447cc6c6c 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -216,6 +216,7 @@ DEFINE_integer('s3_port', 3333, 's3 port') DEFINE_string('s3_host', utils.get_my_ip(), 's3 host (for infrastructure)') DEFINE_string('s3_dmz', utils.get_my_ip(), 's3 dmz ip (for instances)') DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on') +DEFINE_string('console_topic', 'console', 'the topic console proxy nodes listen on') DEFINE_string('scheduler_topic', 'scheduler', 'the topic scheduler nodes listen on') DEFINE_string('volume_topic', 'volume', 'the topic volume nodes listen on') @@ -263,6 +264,8 @@ DEFINE_string('sql_connection', DEFINE_string('compute_manager', 'nova.compute.manager.ComputeManager', 'Manager for compute') +DEFINE_string('console_manager', 'nova.console.manager.ConsoleProxyManager', + 'Manager for console proxy') DEFINE_string('network_manager', 'nova.network.manager.VlanManager', 'Manager for network') DEFINE_string('volume_manager', 'nova.volume.manager.VolumeManager', diff --git a/nova/tests/test_console.py b/nova/tests/test_console.py new file mode 100644 index 000000000..9f06a1771 --- /dev/null +++ b/nova/tests/test_console.py @@ -0,0 +1,134 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 Openstack, LLC. +# Administrator of the National Aeronautics and Space Administration. +# 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. + +""" +Tests For Console proxy. +""" + +import datetime +import logging + +from nova import context +from nova import db +from nova import exception +from nova import flags +from nova import test +from nova import utils +from nova.auth import manager +from nova.console import manager as console_manager + +FLAGS = flags.FLAGS + + +class ConsoleTestCase(test.TestCase): + """Test case for console proxy""" + def setUp(self): + logging.getLogger().setLevel(logging.DEBUG) + super(ConsoleTestCase, self).setUp() + self.flags(console_driver='nova.console.fake.FakeConsoleProxy', + stub_compute=True) + self.console = utils.import_object(FLAGS.console_manager) + self.manager = manager.AuthManager() + self.user = self.manager.create_user('fake', 'fake', 'fake') + self.project = self.manager.create_project('fake', 'fake', 'fake') + self.context = context.get_admin_context() + self.host = 'test_compute_host' + + def tearDown(self): + self.manager.delete_user(self.user) + self.manager.delete_project(self.project) + super(ConsoleTestCase, self).tearDown() + + def _create_instance(self): + """Create a test instance""" + inst = {} + #inst['host'] = self.host + #inst['name'] = 'instance-1234' + inst['image_id'] = 'ami-test' + inst['reservation_id'] = 'r-fakeres' + inst['launch_time'] = '10' + inst['user_id'] = self.user.id + inst['project_id'] = self.project.id + inst['instance_type'] = 'm1.tiny' + inst['mac_address'] = utils.generate_mac() + inst['ami_launch_index'] = 0 + return db.instance_create(self.context, inst)['id'] + + def test_get_pool_for_instance_host(self): + pool = self.console.get_pool_for_instance_host(self.context, self.host) + self.assertEqual(pool['compute_host'], self.host) + + def test_get_pool_creates_new_pool_if_needed(self): + self.assertRaises(exception.NotFound, + db.console_pool_get_by_host_type, + self.context, + self.host, + self.console.host, + self.console.driver.console_type) + pool = self.console.get_pool_for_instance_host(self.context, + self.host) + pool2 = db.console_pool_get_by_host_type(self.context, + self.host, + self.console.host, + self.console.driver.console_type) + self.assertEqual(pool['id'], pool2['id']) + + def test_get_pool_does_not_create_new_pool_if_exists(self): + pool_info = {'address' : '127.0.0.1', + 'username' : 'test', + 'password' : '1234pass', + 'host' : self.console.host, + 'console_type' : self.console.driver.console_type, + 'compute_host' : 'sometesthostname' } + new_pool = db.console_pool_create(self.context, pool_info) + pool = self.console.get_pool_for_instance_host(self.context, + 'sometesthostname') + self.assertEqual(pool['id'], new_pool['id']) + + def test_add_console(self): + instance_id = self._create_instance() + self.console.add_console(self.context, instance_id) + instance = db.instance_get(self.context, instance_id) + pool = db.console_pool_get_by_host_type(self.context, + instance['host'], + self.console.host, + self.console.driver.console_type) + + console_instances = [con['instance_id'] for con in pool.consoles] + self.assert_(instance_id in console_instances) + + def test_add_console_does_not_duplicate(self): + instance_id = self._create_instance() + cons1 = self.console.add_console(self.context, instance_id) + cons2 = self.console.add_console(self.context, instance_id) + self.assertEqual(cons1,cons2) + + def test_remove_console(self): + instance_id = self._create_instance() + self.console.add_console(self.context, instance_id) + self.console.remove_console(self.context, instance_id) + + instance = db.instance_get(self.context, instance_id) + pool = db.console_pool_get_by_host_type(self.context, + instance['host'], + self.console.host, + self.console.driver.console_type) + + console_instances = [con['instance_id'] for con in pool.consoles] + self.assert_(instance_id not in console_instances) + diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 706888b0d..acabb8034 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -272,6 +272,11 @@ class FakeConnection(object): def get_console_output(self, instance): return 'FAKE CONSOLE OUTPUT' + def get_console_pool_info(self, console_type): + return {'address' : '127.0.0.1', + 'username' : 'fakeuser', + 'password' : 'fakepassword'} + class FakeInstance(object): diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 65cf65098..51353147f 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -671,6 +671,14 @@ class LibvirtConnection(object): fw = NWFilterFirewall(self._conn) fw.ensure_security_group_filter(security_group_id) + def get_console_pool_info(self, console_type): + #TODO(mdragon): console proxy should be implemented for libvirt, + # in case someone wants to use it with kvm or + # such. For now return fake data. + return {'address' : '127.0.0.1', + 'username' : 'fakeuser', + 'password' : 'fakepassword'} + class NWFilterFirewall(object): """ diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 7f03d6c2b..abad0a08a 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -52,6 +52,7 @@ reactor thread if the VM.get_by_name_label or VM.get_record calls block. import logging import sys +import urlparse import xmlrpclib from eventlet import event @@ -177,6 +178,12 @@ class XenAPIConnection(object): """Detach volume storage to VM instance""" return self._volumeops.detach_volume(instance_name, mountpoint) + def get_console_pool_info(self, console_type): + xs_url = urlparse.urlparse(FLAGS.xenapi_connection_url) + return {'address' : xs_url.netloc, + 'username' : FLAGS.xenapi_connection_username, + 'password' : FLAGS.xenapi_connection_password} + class XenAPISession(object): """The session to invoke XenAPI SDK calls""" -- cgit From 9b99e385967c4ba21d94d82aa62115fc11634118 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 5 Jan 2011 14:57:31 -0800 Subject: socat will need to be added to our nova sudoers --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index a36af16e2..d83c57741 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -454,7 +454,7 @@ class LibvirtConnection(object): token = str(uuid.uuid4()) host = instance['host'] - ajaxterm_cmd = 'socat - %s' % get_pty_for_instance(instance['name']) + ajaxterm_cmd = 'sudo socat - %s' % get_pty_for_instance(instance['name']) cmd = '%s/tools/ajaxterm/ajaxterm.py --command "%s" -t %s -p %s' \ % (utils.novadir(), ajaxterm_cmd, token, port) -- cgit From f21f078113fc81c1dcee4f3a077bd555c0cf85f6 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Wed, 5 Jan 2011 19:45:46 -0600 Subject: Fix a bunch of pep8 stuff --- nova/api/openstack/consoles.py | 5 ----- nova/compute/manager.py | 2 -- nova/console/__init__.py | 7 ++++--- nova/console/api.py | 2 +- nova/console/driver.py | 3 +-- nova/console/fake.py | 2 +- nova/console/manager.py | 26 ++++++++++++-------------- nova/console/xvp.py | 18 ++++++++---------- nova/db/api.py | 11 ++++++++--- nova/db/sqlalchemy/api.py | 22 +++++++++++++--------- nova/db/sqlalchemy/models.py | 5 ++++- nova/flags.py | 3 ++- nova/tests/test_console.py | 19 +++++++++---------- nova/virt/fake.py | 6 +++--- nova/virt/libvirt_conn.py | 8 ++++---- nova/virt/xenapi_conn.py | 6 +++--- 16 files changed, 73 insertions(+), 72 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index bf3403655..49eefe09d 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -85,8 +85,3 @@ class Controller(wsgi.Controller): except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) return exc.HTTPAccepted() - -# def detail(self, req, id): -# """ Returns a complete list of consoles for this instance""" -# return _translate_detail_keys({}) - diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 3e73c351c..403b46b2a 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -112,11 +112,9 @@ class ComputeManager(manager.Manager): FLAGS.network_topic, host) - def get_console_pool_info(self, context, console_type): return self.driver.get_console_pool_info(console_type) - @exception.wrap_exception def refresh_security_group(self, context, security_group_id, **_kwargs): """This call passes stright through to the virtualization driver.""" diff --git a/nova/console/__init__.py b/nova/console/__init__.py index adce8621e..491df075b 100644 --- a/nova/console/__init__.py +++ b/nova/console/__init__.py @@ -1,11 +1,12 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 """ -:mod:`nova.console` -- Console Prxy to set up VM console access (i.e. with xvp) +:mod:`nova.console` -- Console Prxy to set up VM console access (i.e. with xvp) ===================================================== .. automodule:: nova.console :platform: Unix - :synopsis: Wrapper around console proxies such as xvp to set up multitenant VM console access -.. moduleauthor:: Monsyne Dragon + :synopsis: Wrapper around console proxies such as xvp to set up + multitenant VM console access +.. moduleauthor:: Monsyne Dragon """ diff --git a/nova/console/api.py b/nova/console/api.py index 78bfe636b..54317e3b3 100644 --- a/nova/console/api.py +++ b/nova/console/api.py @@ -29,6 +29,7 @@ from nova import rpc FLAGS = flags.FLAGS + class ConsoleAPI(base.Base): """API for spining up or down console proxy connections""" @@ -70,7 +71,6 @@ class ConsoleAPI(base.Base): {"method": "add_console", "args": {"instance_id": instance['id']}}) - def _get_console_topic(self, context, instance_host): topic = self.db.queue_get_for(context, FLAGS.compute_topic, diff --git a/nova/console/driver.py b/nova/console/driver.py index b92765b34..d2dafb1f3 100644 --- a/nova/console/driver.py +++ b/nova/console/driver.py @@ -43,7 +43,7 @@ class ConsoleProxy(object): def generate_password(self, length=8): """Returns random console password""" - return os.urandom(length*2).encode('base64')[:length] + return os.urandom(length * 2).encode('base64')[:length] def get_port(self, context): """get available port for consoles that need one""" @@ -56,4 +56,3 @@ class ConsoleProxy(object): def fix_console_password(self, password): """Trim password to length, and any other massaging""" return password - diff --git a/nova/console/fake.py b/nova/console/fake.py index 4a9f1244c..46782bdb3 100644 --- a/nova/console/fake.py +++ b/nova/console/fake.py @@ -22,6 +22,7 @@ Fake ConsoleProxy driver for tests. from nova import exception from nova.console import driver + class FakeConsoleProxy(driver.ConsoleProxy): """Fake ConsoleProxy driver.""" @@ -56,4 +57,3 @@ class FakeConsoleProxy(driver.ConsoleProxy): def fix_console_password(self, password): """Trim password to length, and any other massaging""" return password - diff --git a/nova/console/manager.py b/nova/console/manager.py index e3cbdae0e..b743e55b5 100644 --- a/nova/console/manager.py +++ b/nova/console/manager.py @@ -35,9 +35,10 @@ flags.DEFINE_string('console_driver', flags.DEFINE_boolean('stub_compute', False, 'Stub calls to compute worker for tests') + class ConsoleProxyManager(manager.Manager): - """ Sets up and tears down any proxy connections needed for accessing + """ Sets up and tears down any proxy connections needed for accessing instance consoles securely""" def __init__(self, console_driver=None, *args, **kwargs): @@ -51,8 +52,8 @@ class ConsoleProxyManager(manager.Manager): self.driver.init_host() @exception.wrap_exception - def add_console(self, context, instance_id, password = None, - port = None, **kwargs): + def add_console(self, context, instance_id, password=None, + port=None, **kwargs): instance = self.db.instance_get(context, instance_id) host = instance['host'] name = instance['name'] @@ -67,10 +68,10 @@ class ConsoleProxyManager(manager.Manager): password = self.driver.generate_password() if not port: port = self.driver.get_port(context) - console_data = {'instance_name' : name, - 'instance_id' : instance_id, - 'password' : password, - 'pool_id' : pool['id']} + console_data = {'instance_name': name, + 'instance_id': instance_id, + 'password': password, + 'pool_id': pool['id']} if port: console_data['port'] = port console = self.db.console_create(context, console_data) @@ -84,12 +85,11 @@ class ConsoleProxyManager(manager.Manager): except exception.NotFound: logging.debug(_('Tried to remove non-existant console ' '%(console_id)s.') % - {'console_id' : console_id}) + {'console_id': console_id}) return self.db.console_delete(context, console_id) self.driver.teardown_console(context, console) - def get_pool_for_instance_host(self, context, instance_host): context = context.elevated() console_type = self.driver.console_type @@ -103,9 +103,9 @@ class ConsoleProxyManager(manager.Manager): # compute worker's flagfile, at least for # xenserver. Thus we ned to ask. if FLAGS.stub_compute: - pool_info = {'address' : '127.0.0.1', - 'username' : 'test', - 'password' : '1234pass'} + pool_info = {'address': '127.0.0.1', + 'username': 'test', + 'password': '1234pass'} else: pool_info = rpc.call(context, self.db.queue_get_for(context, @@ -120,5 +120,3 @@ class ConsoleProxyManager(manager.Manager): pool_info['compute_host'] = instance_host pool = self.db.console_pool_create(context, pool_info) return pool - - diff --git a/nova/console/xvp.py b/nova/console/xvp.py index 62ad3b2bb..161b5ce20 100644 --- a/nova/console/xvp.py +++ b/nova/console/xvp.py @@ -51,12 +51,13 @@ flags.DEFINE_integer('console_xvp_multiplex_port', "port for XVP to multiplex VNC connections on") FLAGS = flags.FLAGS + class XVPConsoleProxy(driver.ConsoleProxy): """Sets up XVP config, and manages xvp daemon""" def __init__(self): self.xvpconf_template = open(FLAGS.console_xvp_conf_template).read() - self.host = FLAGS.host #default, set by manager. + self.host = FLAGS.host # default, set by manager. super(XVPConsoleProxy, self).__init__() @property @@ -93,8 +94,8 @@ class XVPConsoleProxy(driver.ConsoleProxy): def _rebuild_xvp_conf(self, context): logging.debug("Rebuilding xvp conf") - pools = [ pool for pool in - db.console_pool_get_all_by_host_type(context, self.host, + pools = [pool for pool in + db.console_pool_get_all_by_host_type(context, self.host, self.console_type) if pool['consoles']] if not pools: @@ -103,7 +104,7 @@ class XVPConsoleProxy(driver.ConsoleProxy): return conf_data = {'multiplex_port': FLAGS.console_xvp_multiplex_port, 'pools': pools, - 'pass_encode' : self.fix_console_password } + 'pass_encode': self.fix_console_password} config = str(Template(self.xvpconf_template, searchList=[conf_data])) self._write_conf(config) self._xvp_restart() @@ -119,7 +120,7 @@ class XVPConsoleProxy(driver.ConsoleProxy): if not pid: return try: - os.kill(pid,signal.SIGTERM) + os.kill(pid, signal.SIGTERM) except OSError: #if it's already not running, no problem. pass @@ -129,7 +130,7 @@ class XVPConsoleProxy(driver.ConsoleProxy): return logging.debug("Starting xvp") try: - utils.execute('xvp -p %s -c %s -l %s' % + utils.execute('xvp -p %s -c %s -l %s' % (FLAGS.console_xvp_pid, FLAGS.console_xvp_conf, FLAGS.console_xvp_log)) @@ -160,7 +161,7 @@ class XVPConsoleProxy(driver.ConsoleProxy): if not pid: return False try: - os.kill(pid,0) + os.kill(pid, 0) except OSError: return False return True @@ -187,7 +188,4 @@ class XVPConsoleProxy(driver.ConsoleProxy): #xvp will blow up on passwords that are too long (mdragon) password = password[:maxlen] out, err = utils.execute('xvp %s' % flag, process_input=password) - #p = subprocess.Popen(['xvp', flag], stdin=subprocess.PIPE, stdout=subprocess.PIPE) - #out,err = p.communicate(password) return out.strip() - diff --git a/nova/db/api.py b/nova/db/api.py index 15b87520b..2c53fbeef 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -895,12 +895,15 @@ def host_get_networks(context, host): """ return IMPL.host_get_networks(context, host) + ################## + def console_pool_create(context, values): """Create console pool.""" return IMPL.console_pool_create(context, values) + def console_pool_get(context, pool_id): """Get a console pool.""" return IMPL.console_pool_get(context, pool_id) @@ -922,24 +925,26 @@ def console_pool_get_all_by_host_type(context, host, console_type): console_type) -def console_create(context,values): +def console_create(context, values): """Create a console.""" return IMPL.console_create(context, values) + def console_delete(context, console_id): """Delete a console.""" return IMPL.console_delete(context, console_id) + def console_get_by_pool_instance(context, pool_id, instance_id): """Get console entry for a given instance and pool.""" return IMPL.console_get_by_pool_instance(context, pool_id, instance_id) + def console_get_all_by_instance(context, instance_id): """Get consoles for a given instance.""" return IMPL.console_get_all_by_instance(context, instance_id) + def console_get(context, console_id, instance_id=None): """Get a specific console (possibly on a given instance).""" return IMPL.console_get(context, console_id, instance_id) - - diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 77dfe4a28..7b70566eb 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1835,10 +1835,12 @@ def console_pool_get(context, pool_id): filter_by(id=pool_id).\ first() if not result: - raise exception.NotFound(_("No console pool with id %(pool_id)s") % {'pool_id': pool_id}) + raise exception.NotFound(_("No console pool with id %(pool_id)s") % + {'pool_id': pool_id}) return result + def console_pool_get_by_host_type(context, compute_host, host, console_type): session = get_session() @@ -1853,9 +1855,9 @@ def console_pool_get_by_host_type(context, compute_host, host, raise exception.NotFound(_('No console pool of type %(type)s ' 'for compute host %(compute_host)s ' 'on proxy host %(host)s') % - {'type' : console_type, - 'compute_host' : compute_host, - 'host' : host}) + {'type': console_type, + 'compute_host': compute_host, + 'host': host}) return result @@ -1875,13 +1877,15 @@ def console_create(context, values): console.save() return console + def console_delete(context, console_id): session = get_session() with session.begin(): - # consoles are meant to be transient. (mdragon) + # consoles are meant to be transient. (mdragon) session.execute('delete from consoles ' 'where id=:id', {'id': console_id}) + def console_get_by_pool_instance(context, pool_id, instance_id): session = get_session() result = session.query(models.Console).\ @@ -1891,11 +1895,12 @@ def console_get_by_pool_instance(context, pool_id, instance_id): first() if not result: raise exception.NotFound(_('No console for instance %(instance_id)s ' - 'in pool %(pool_id)s') % + 'in pool %(pool_id)s') % {'instance_id': instance_id, 'pool_id': pool_id}) return result + def console_get_all_by_instance(context, instance_id): session = get_session() results = session.query(models.Console).\ @@ -1904,6 +1909,7 @@ def console_get_all_by_instance(context, instance_id): all() return results + def console_get(context, console_id, instance_id=None): session = get_session() query = session.query(models.Console).\ @@ -1914,7 +1920,5 @@ def console_get(context, console_id, instance_id=None): if not result: idesc = _(" on instance %(instance_id)s") if instance_id else "" raise exception.NotFound(_("No console with id %(instance)s") % - {'instance' : idesc}) + {'instance': idesc}) return result - - diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 58cf21b0d..dd75927d0 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -537,6 +537,7 @@ class FloatingIp(BASE, NovaBase): project_id = Column(String(255)) host = Column(String(255)) # , ForeignKey('hosts.id')) + class ConsolePool(BASE, NovaBase): """Represents pool of consoles on the same physical node.""" __tablename__ = 'console_pools' @@ -548,6 +549,7 @@ class ConsolePool(BASE, NovaBase): host = Column(String(255)) compute_host = Column(String(255)) + class Console(BASE, NovaBase): """Represents a console session for an instance.""" __tablename__ = 'consoles' @@ -555,10 +557,11 @@ class Console(BASE, NovaBase): instance_name = Column(String(255)) instance_id = Column(Integer) password = Column(String(255)) - port = Column(Integer,nullable=True) + port = Column(Integer, nullable=True) pool_id = Column(Integer, ForeignKey('console_pools.id')) pool = relationship(ConsolePool, backref=backref('consoles')) + def register_models(): """Register Models and create metadata. diff --git a/nova/flags.py b/nova/flags.py index 58ba4d16d..f3a19c0c8 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -218,7 +218,8 @@ DEFINE_integer('s3_port', 3333, 's3 port') DEFINE_string('s3_host', utils.get_my_ip(), 's3 host (for infrastructure)') DEFINE_string('s3_dmz', utils.get_my_ip(), 's3 dmz ip (for instances)') DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on') -DEFINE_string('console_topic', 'console', 'the topic console proxy nodes listen on') +DEFINE_string('console_topic', 'console', + 'the topic console proxy nodes listen on') DEFINE_string('scheduler_topic', 'scheduler', 'the topic scheduler nodes listen on') DEFINE_string('volume_topic', 'volume', 'the topic volume nodes listen on') diff --git a/nova/tests/test_console.py b/nova/tests/test_console.py index b23b1499b..31b5ca79c 100644 --- a/nova/tests/test_console.py +++ b/nova/tests/test_console.py @@ -89,12 +89,12 @@ class ConsoleTestCase(test.TestCase): self.assertEqual(pool['id'], pool2['id']) def test_get_pool_does_not_create_new_pool_if_exists(self): - pool_info = {'address' : '127.0.0.1', - 'username' : 'test', - 'password' : '1234pass', - 'host' : self.console.host, - 'console_type' : self.console.driver.console_type, - 'compute_host' : 'sometesthostname' } + pool_info = {'address': '127.0.0.1', + 'username': 'test', + 'password': '1234pass', + 'host': self.console.host, + 'console_type': self.console.driver.console_type, + 'compute_host': 'sometesthostname'} new_pool = db.console_pool_create(self.context, pool_info) pool = self.console.get_pool_for_instance_host(self.context, 'sometesthostname') @@ -107,16 +107,16 @@ class ConsoleTestCase(test.TestCase): pool = db.console_pool_get_by_host_type(self.context, instance['host'], self.console.host, - self.console.driver.console_type) + self.console.driver.console_type) - console_instances = [con['instance_id'] for con in pool.consoles] + console_instances = [con['instance_id'] for con in pool.consoles] self.assert_(instance_id in console_instances) def test_add_console_does_not_duplicate(self): instance_id = self._create_instance() cons1 = self.console.add_console(self.context, instance_id) cons2 = self.console.add_console(self.context, instance_id) - self.assertEqual(cons1,cons2) + self.assertEqual(cons1, cons2) def test_remove_console(self): instance_id = self._create_instance() @@ -127,4 +127,3 @@ class ConsoleTestCase(test.TestCase): db.console_get, self.context, console_id) - diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 13490b12e..849261f07 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -290,9 +290,9 @@ class FakeConnection(object): return 'FAKE CONSOLE OUTPUT' def get_console_pool_info(self, console_type): - return {'address' : '127.0.0.1', - 'username' : 'fakeuser', - 'password' : 'fakepassword'} + return {'address': '127.0.0.1', + 'username': 'fakeuser', + 'password': 'fakepassword'} class FakeInstance(object): diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index ded1004cd..0e3b6dff6 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -697,10 +697,10 @@ class LibvirtConnection(object): def get_console_pool_info(self, console_type): #TODO(mdragon): console proxy should be implemented for libvirt, # in case someone wants to use it with kvm or - # such. For now return fake data. - return {'address' : '127.0.0.1', - 'username' : 'fakeuser', - 'password' : 'fakepassword'} + # such. For now return fake data. + return {'address': '127.0.0.1', + 'username': 'fakeuser', + 'password': 'fakepassword'} class NWFilterFirewall(object): diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index c702059f7..86efb6b07 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -189,9 +189,9 @@ class XenAPIConnection(object): def get_console_pool_info(self, console_type): xs_url = urlparse.urlparse(FLAGS.xenapi_connection_url) - return {'address' : xs_url.netloc, - 'username' : FLAGS.xenapi_connection_username, - 'password' : FLAGS.xenapi_connection_password} + return {'address': xs_url.netloc, + 'username': FLAGS.xenapi_connection_username, + 'password': FLAGS.xenapi_connection_password} class XenAPISession(object): -- cgit From f9fa25f9a873c1e4831c342689f7b5adc8f41013 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Wed, 5 Jan 2011 20:14:36 -0600 Subject: add in separate public hostname for console hosts. flesh out console api data. --- nova/api/openstack/consoles.py | 17 +++++++++++++---- nova/console/manager.py | 7 ++++++- nova/db/sqlalchemy/models.py | 1 + 3 files changed, 20 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index 49eefe09d..e108bab86 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -23,15 +23,24 @@ from nova.console import api as console_api from nova.api.openstack import faults -def _translate_keys(inst): +def _translate_keys(cons): """Coerces a console instance into proper dictionary format """ - return dict(console=inst) + pool = cons['pool'] + info = {'id': cons['id'], + 'console_type': pool['console_type']} + return dict(console=info) -def _translate_detail_keys(inst): +def _translate_detail_keys(cons): """Coerces a console instance into proper dictionary format with correctly mapped attributes """ - return dict(console=inst) + pool = cons['pool'] + info = {'id': cons['id'], + 'console_type': pool['console_type'], + 'password': cons['password'], + 'port': cons['port'], + 'host': pool['public_hostname']} + return dict(console=info) class Controller(wsgi.Controller): diff --git a/nova/console/manager.py b/nova/console/manager.py index b743e55b5..c55ca8e8f 100644 --- a/nova/console/manager.py +++ b/nova/console/manager.py @@ -19,8 +19,9 @@ Console Proxy Service """ -import logging import functools +import logging +import socket from nova import exception from nova import flags @@ -34,6 +35,9 @@ flags.DEFINE_string('console_driver', 'Driver to use for the console proxy') flags.DEFINE_boolean('stub_compute', False, 'Stub calls to compute worker for tests') +flags.DEFINE_string('console_public_hostname', + socket.gethostname(), + 'Publicly visable name for this console host') class ConsoleProxyManager(manager.Manager): @@ -116,6 +120,7 @@ class ConsoleProxyManager(manager.Manager): pool_info['password'] = self.driver.fix_pool_password( pool_info['password']) pool_info['host'] = self.host + pool_info['public_hostname'] = FLAGS.console_public_hostname pool_info['console_type'] = self.driver.console_type pool_info['compute_host'] = instance_host pool = self.db.console_pool_create(context, pool_info) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index dd75927d0..0c55ee75e 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -546,6 +546,7 @@ class ConsolePool(BASE, NovaBase): username = Column(String(255)) password = Column(String(255)) console_type = Column(String(255)) + public_hostname = Column(String(255)) host = Column(String(255)) compute_host = Column(String(255)) -- cgit From b55940e8e3d977960ff60f4cb7cff4b6ea2e8fb8 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Wed, 5 Jan 2011 22:11:05 -0600 Subject: fix some glitches due to someone removing instanc.internal_id (not that I mind) remove accidental change to nova-combined script --- nova/console/api.py | 23 +++++++++-------------- nova/console/driver.py | 2 ++ nova/db/sqlalchemy/api.py | 6 +++--- 3 files changed, 14 insertions(+), 17 deletions(-) (limited to 'nova') diff --git a/nova/console/api.py b/nova/console/api.py index 54317e3b3..93e28ad64 100644 --- a/nova/console/api.py +++ b/nova/console/api.py @@ -36,20 +36,16 @@ class ConsoleAPI(base.Base): def __init__(self, **kwargs): super(ConsoleAPI, self).__init__(**kwargs) - def get_consoles(self, context, instance_internal_id): - instance = self.db.instance_get_by_internal_id(context, - instance_internal_id) - return self.db.console_get_all_by_instance(context, instance['id']) + def get_consoles(self, context, instance_id): + return self.db.console_get_all_by_instance(context, instance_id) - def get_console(self, context, instance_internal_id, console_id): - return self.db.console_get(context, console_id, instance_internal_id) + def get_console(self, context, instance_id, console_id): + return self.db.console_get(context, console_id, instance_id) - def delete_console(self, context, instance_internal_id, console_id): - instance = self.db.instance_get_by_internal_id(context, - instance_internal_id) + def delete_console(self, context, instance_id, console_id): console = self.db.console_get(context, console_id, - instance['id']) + instance_id) pool = console['pool'] rpc.cast(context, self.db.queue_get_for(context, @@ -58,9 +54,8 @@ class ConsoleAPI(base.Base): {"method": "remove_console", "args": {"console_id": console['id']}}) - def create_console(self, context, instance_internal_id): - instance = self.db.instance_get_by_internal_id(context, - instance_internal_id) + def create_console(self, context, instance_id): + instance = self.db.instance_get(context, instance_id) #NOTE(mdragon): If we wanted to return this the console info # here, as we would need to do a call. # They can just do an index later to fetch @@ -69,7 +64,7 @@ class ConsoleAPI(base.Base): rpc.cast(context, self._get_console_topic(context, instance['host']), {"method": "add_console", - "args": {"instance_id": instance['id']}}) + "args": {"instance_id": instance_id}}) def _get_console_topic(self, context, instance_host): topic = self.db.queue_get_for(context, diff --git a/nova/console/driver.py b/nova/console/driver.py index d2dafb1f3..c4cf5e5e9 100644 --- a/nova/console/driver.py +++ b/nova/console/driver.py @@ -19,6 +19,8 @@ ConsoleProxy base class that all ConsoleProxies should inherit from """ +import os + from nova import exception diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 7b70566eb..6f4d068f1 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1918,7 +1918,7 @@ def console_get(context, console_id, instance_id=None): query = query.filter_by(instance_id=instance_id) result = query.options(joinedload('pool')).first() if not result: - idesc = _(" on instance %(instance_id)s") if instance_id else "" - raise exception.NotFound(_("No console with id %(instance)s") % - {'instance': idesc}) + idesc = (_("on instance %s") % instance_id) if instance_id else "" + raise exception.NotFound(_("No console with id %(console_id)s %(instance)s") % + {'instance': idesc, 'console_id': console_id}) return result -- cgit From 114577d4f4ed1ca173dadf47d4bf3a5a05c449a2 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 6 Jan 2011 03:05:59 -0400 Subject: Fixed display_name on create_instance --- nova/compute/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 7ab7eb6ad..252f88003 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -171,7 +171,7 @@ class API(base.Base): # Set sane defaults if not specified updates = dict(hostname=generate_hostname(instance_id)) - if 'display_name' not in instance: + if not hasattr(instance, 'display_name'): updates['display_name'] = "Server %s" % instance_id instance = self.update(context, instance_id, **updates) -- cgit From 457e19826cfdb7f8f324180e42d8df79da48cfc6 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 6 Jan 2011 04:55:16 -0400 Subject: renamed sharedipgroups to shared_ip_groups and fixed tests for display_name --- nova/api/openstack/__init__.py | 4 +- nova/api/openstack/shared_ip_groups.py | 69 +++++++++++++++++++++++ nova/api/openstack/sharedipgroups.py | 69 ----------------------- nova/compute/api.py | 3 +- nova/tests/api/openstack/test_shared_ip_groups.py | 39 +++++++++++++ nova/tests/api/openstack/test_sharedipgroups.py | 39 ------------- 6 files changed, 112 insertions(+), 111 deletions(-) create mode 100644 nova/api/openstack/shared_ip_groups.py delete mode 100644 nova/api/openstack/sharedipgroups.py create mode 100644 nova/tests/api/openstack/test_shared_ip_groups.py delete mode 100644 nova/tests/api/openstack/test_sharedipgroups.py (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 0b54c7233..33eac001a 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -39,7 +39,7 @@ from nova.api.openstack import flavors from nova.api.openstack import images from nova.api.openstack import ratelimiting from nova.api.openstack import servers -from nova.api.openstack import sharedipgroups +from nova.api.openstack import shared_ip_groups FLAGS = flags.FLAGS @@ -114,7 +114,7 @@ class APIRouter(wsgi.Router): mapper.resource("flavor", "flavors", controller=flavors.Controller(), collection={'detail': 'GET'}) mapper.resource("shared_ip_group", "shared_ip_groups", - controller=sharedipgroups.Controller()) + controller=shared_ip_groups.Controller()) super(APIRouter, self).__init__(mapper) diff --git a/nova/api/openstack/shared_ip_groups.py b/nova/api/openstack/shared_ip_groups.py new file mode 100644 index 000000000..65595c8ff --- /dev/null +++ b/nova/api/openstack/shared_ip_groups.py @@ -0,0 +1,69 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack LLC. +# 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 logging + +from webob import exc + +from nova import wsgi +from nova.api.openstack import faults + + +def _translate_keys(inst): + """ Coerces a shared IP group instance into proper dictionary format """ + return dict(sharedIpGroup=inst) + + +def _translate_detail_keys(inst): + """ Coerces a shared IP group instance into proper dictionary format with + correctly mapped attributes """ + return dict(sharedIpGroups=inst) + + +class Controller(wsgi.Controller): + """ The Shared IP Groups Controller for the Openstack API """ + + _serialization_metadata = { + 'application/xml': { + 'attributes': { + 'sharedIpGroup': []}}} + + def index(self, req): + """ Returns a list of Shared IP Groups for the user """ + return dict(sharedIpGroups=[]) + + def show(self, req, id): + """ Shows in-depth information on a specific Shared IP Group """ + if id == 'detail': + return _translate_detail_keys({}) + return _translate_keys({}) + + def update(self, req, id): + """ You can't update a Shared IP Group """ + raise faults.Fault(exc.HTTPNotImplemented()) + + def delete(self, req, id): + """ Deletes a Shared IP Group """ + raise faults.Fault(exc.HTTPNotImplemented()) + + def detail(self, req, id): + """ Returns a complete list of Shared IP Groups """ + return _translate_detail_keys({}) + + def create(self, req): + """ Creates a new Shared IP group """ + raise faults.Fault(exc.HTTPNotImplemented()) diff --git a/nova/api/openstack/sharedipgroups.py b/nova/api/openstack/sharedipgroups.py deleted file mode 100644 index 65595c8ff..000000000 --- a/nova/api/openstack/sharedipgroups.py +++ /dev/null @@ -1,69 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 OpenStack LLC. -# 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 logging - -from webob import exc - -from nova import wsgi -from nova.api.openstack import faults - - -def _translate_keys(inst): - """ Coerces a shared IP group instance into proper dictionary format """ - return dict(sharedIpGroup=inst) - - -def _translate_detail_keys(inst): - """ Coerces a shared IP group instance into proper dictionary format with - correctly mapped attributes """ - return dict(sharedIpGroups=inst) - - -class Controller(wsgi.Controller): - """ The Shared IP Groups Controller for the Openstack API """ - - _serialization_metadata = { - 'application/xml': { - 'attributes': { - 'sharedIpGroup': []}}} - - def index(self, req): - """ Returns a list of Shared IP Groups for the user """ - return dict(sharedIpGroups=[]) - - def show(self, req, id): - """ Shows in-depth information on a specific Shared IP Group """ - if id == 'detail': - return _translate_detail_keys({}) - return _translate_keys({}) - - def update(self, req, id): - """ You can't update a Shared IP Group """ - raise faults.Fault(exc.HTTPNotImplemented()) - - def delete(self, req, id): - """ Deletes a Shared IP Group """ - raise faults.Fault(exc.HTTPNotImplemented()) - - def detail(self, req, id): - """ Returns a complete list of Shared IP Groups """ - return _translate_detail_keys({}) - - def create(self, req): - """ Creates a new Shared IP group """ - raise faults.Fault(exc.HTTPNotImplemented()) diff --git a/nova/compute/api.py b/nova/compute/api.py index 252f88003..912c7d93f 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -171,7 +171,8 @@ class API(base.Base): # Set sane defaults if not specified updates = dict(hostname=generate_hostname(instance_id)) - if not hasattr(instance, 'display_name'): + if (not hasattr(instance, 'display_name')) or \ + instance.display_name == None: updates['display_name'] = "Server %s" % instance_id instance = self.update(context, instance_id, **updates) diff --git a/nova/tests/api/openstack/test_shared_ip_groups.py b/nova/tests/api/openstack/test_shared_ip_groups.py new file mode 100644 index 000000000..c2fc3a203 --- /dev/null +++ b/nova/tests/api/openstack/test_shared_ip_groups.py @@ -0,0 +1,39 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack LLC. +# 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 unittest + +import stubout + +from nova.api.openstack import shared_ip_groups + + +class SharedIpGroupsTest(unittest.TestCase): + def setUp(self): + self.stubs = stubout.StubOutForTesting() + + def tearDown(self): + self.stubs.UnsetAll() + + def test_get_shared_ip_groups(self): + pass + + def test_create_shared_ip_group(self): + pass + + def test_delete_shared_ip_group(self): + pass diff --git a/nova/tests/api/openstack/test_sharedipgroups.py b/nova/tests/api/openstack/test_sharedipgroups.py deleted file mode 100644 index d199951d8..000000000 --- a/nova/tests/api/openstack/test_sharedipgroups.py +++ /dev/null @@ -1,39 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 OpenStack LLC. -# 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 unittest - -import stubout - -from nova.api.openstack import sharedipgroups - - -class SharedIpGroupsTest(unittest.TestCase): - def setUp(self): - self.stubs = stubout.StubOutForTesting() - - def tearDown(self): - self.stubs.UnsetAll() - - def test_get_shared_ip_groups(self): - pass - - def test_create_shared_ip_group(self): - pass - - def test_delete_shared_ip_group(self): - pass -- cgit From 1ce25cab7f1818aababb18d60959f44602f2e17c Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Thu, 6 Jan 2011 13:19:58 -0600 Subject: pep8 fix --- nova/db/sqlalchemy/api.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 6f4d068f1..c3dee4329 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1919,6 +1919,8 @@ def console_get(context, console_id, instance_id=None): result = query.options(joinedload('pool')).first() if not result: idesc = (_("on instance %s") % instance_id) if instance_id else "" - raise exception.NotFound(_("No console with id %(console_id)s %(instance)s") % - {'instance': idesc, 'console_id': console_id}) + raise exception.NotFound(_("No console with id %(console_id)s" + " %(instance)s") % + {'instance': idesc, + 'console_id': console_id}) return result -- cgit From 3478e90442ad7a22497b53153ae893df96e55b4e Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Fri, 7 Jan 2011 05:59:30 +0300 Subject: merge --- nova/api/ec2/cloud.py | 34 ++++++++++++++++++++++++++++++---- nova/compute/api.py | 3 ++- nova/db/api.py | 13 ++++++++++++- nova/db/sqlalchemy/api.py | 17 +++++++++++++++++ nova/db/sqlalchemy/models.py | 2 +- nova/flags.py | 1 - nova/service.py | 3 ++- nova/tests/test_cloud.py | 35 +++++++++++++++++++++++++++++++++++ nova/tests/test_scheduler.py | 43 +++++++++++++++++++++++++++++++++++++++++++ nova/tests/test_service.py | 15 +++++++++++---- 10 files changed, 153 insertions(+), 13 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 6619b5452..b6966e605 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -140,6 +140,12 @@ class CloudController(object): {"method": "refresh_security_group", "args": {"security_group_id": security_group.id}}) + def _get_availability_zone_by_host(self, context, host): + services = db.service_get_all_by_host(context, host) + if len(services) > 0: + return services[0]['availability_zone'] + return 'unknown zone' + def get_metadata(self, address): ctxt = context.get_admin_context() instance_ref = self.compute_api.get_all(ctxt, fixed_ip=address) @@ -152,6 +158,8 @@ class CloudController(object): else: keys = '' hostname = instance_ref['hostname'] + availability_zone = self._get_availability_zone_by_host(ctxt, + instance_ref['host']) floating_ip = db.instance_get_floating_address(ctxt, instance_ref['id']) ec2_id = id_to_ec2_id(instance_ref['id']) @@ -174,8 +182,7 @@ class CloudController(object): 'local-hostname': hostname, 'local-ipv4': address, 'kernel-id': instance_ref['kernel_id'], - # TODO(vish): real zone - 'placement': {'availability-zone': 'nova'}, + 'placement': {'availability-zone': availability_zone}, 'public-hostname': hostname, 'public-ipv4': floating_ip or '', 'public-keys': keys, @@ -199,8 +206,25 @@ class CloudController(object): return self._describe_availability_zones(context, **kwargs) def _describe_availability_zones(self, context, **kwargs): - return {'availabilityZoneInfo': [{'zoneName': 'nova', - 'zoneState': 'available'}]} + enabled_services = db.service_get_all(context) + disabled_services = db.service_get_all(context, True) + available_zones = [] + for zone in [service.availability_zone for service in enabled_services]: + if not zone in available_zones: + available_zones.append(zone) + not_available_zones = [] + for zone in [service.availability_zone for service in disabled_services + and not service['availability_zone'] in available_zones]: + if not zone in not_available_zones: + not_available_zones.append(zone) + result = [] + for zone in available_zones: + result.append({'zoneName': zone, + 'zoneState': "available"}) + for zone in not_available_zones: + result.append({'zoneName': zone, + 'zoneState': "not available"}) + return {'availabilityZoneInfo': result} def _describe_availability_zones_verbose(self, context, **kwargs): rv = {'availabilityZoneInfo': [{'zoneName': 'nova', @@ -632,6 +656,8 @@ class CloudController(object): i['amiLaunchIndex'] = instance['launch_index'] i['displayName'] = instance['display_name'] i['displayDescription'] = instance['display_description'] + availability_zone = self._get_availability_zone_by_host(context, instance['host']) + i['placement'] = {'availabilityZone': availability_zone} if instance['reservation_id'] not in reservations: r = {} r['reservationId'] = instance['reservation_id'] diff --git a/nova/compute/api.py b/nova/compute/api.py index 64d47b1ce..3ba91fe05 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -181,7 +181,8 @@ class API(base.Base): FLAGS.scheduler_topic, {"method": "run_instance", "args": {"topic": FLAGS.compute_topic, - "instance_id": instance_id}}) + "instance_id": instance_id, + "availability_zone": availability_zone}}) return instances diff --git a/nova/db/api.py b/nova/db/api.py index 0fa5eb1e8..ee4c521a0 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -81,11 +81,22 @@ def service_get(context, service_id): return IMPL.service_get(context, service_id) +def service_get_all(context, disabled=False): + """Get all service.""" + return IMPL.service_get_all(context, disabled) + + def service_get_all_by_topic(context, topic): - """Get all compute services for a given topic.""" + """Get all services for a given topic.""" return IMPL.service_get_all_by_topic(context, topic) +def service_get_all_by_host(context, host): + """Get all services for a given host.""" + return IMPL.service_get_all_by_host(context, host) + + + def service_get_all_compute_sorted(context): """Get all compute services sorted by instance count. diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index aaa07e3c9..aa0306eb4 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -134,6 +134,14 @@ def service_get(context, service_id, session=None): return result +@require_admin_context +def service_get_all(context, disabled=False): + session = get_session() + return session.query(models.Service).\ + filter_by(deleted=False).\ + filter_by(disabled=disabled).\ + all() + @require_admin_context def service_get_all_by_topic(context, topic): session = get_session() @@ -144,6 +152,15 @@ def service_get_all_by_topic(context, topic): all() +@require_admin_context +def service_get_all_by_host(context, host): + session = get_session() + return session.query(models.Service).\ + filter_by(deleted=False).\ + filter_by(host=host).\ + all() + + @require_admin_context def _service_get_all_topic_subquery(context, session, topic, subq, label): sort_value = getattr(subq.c, label) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 62bb1780d..1ffb9298f 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -149,7 +149,7 @@ class Service(BASE, NovaBase): topic = Column(String(255)) report_count = Column(Integer, nullable=False, default=0) disabled = Column(Boolean, default=False) - + availability_zone = Column(String(255), default='nova') class Certificate(BASE, NovaBase): """Represents a an x509 certificate""" diff --git a/nova/flags.py b/nova/flags.py index 4b7334927..4e71d2152 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -285,6 +285,5 @@ DEFINE_string('image_service', 'nova.image.s3.S3ImageService', DEFINE_string('host', socket.gethostname(), 'name of this node') -# UNUSED DEFINE_string('node_availability_zone', 'nova', 'availability zone of this node') diff --git a/nova/service.py b/nova/service.py index 7203430c6..d4a6f3839 100644 --- a/nova/service.py +++ b/nova/service.py @@ -118,7 +118,8 @@ class Service(object): {'host': self.host, 'binary': self.binary, 'topic': self.topic, - 'report_count': 0}) + 'report_count': 0, + 'availability_zone': FLAGS.node_availability_zone}) self.service_id = service_ref['id'] def __getattr__(self, key): diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index ba58fab59..21d212df7 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -133,6 +133,25 @@ class CloudTestCase(test.TestCase): db.volume_destroy(self.context, vol1['id']) db.volume_destroy(self.context, vol2['id']) + + def test_describe_availability_zones(self): + """Makes sure describe_availability_zones works and filters results.""" + service1 = db.service_create(self.context, {'host': 'host1_describe_zones', + 'binary': "nova-compute", + 'topic': 'compute', + 'report_count': 0, + 'availability_zone': "zone1"}) + service2 = db.service_create(self.context, {'host': 'host2_describe_zones', + 'binary': "nova-compute", + 'topic': 'compute', + 'report_count': 0, + 'availability_zone': "zone2"}) + result = self.cloud.describe_availability_zones(self.context) + self.assertEqual(len(result['availabilityZoneInfo']), 3) + db.service_destroy(self.context, service1['id']) + db.service_destroy(self.context, service2['id']) + + def test_console_output(self): image_id = FLAGS.default_image instance_type = FLAGS.default_instance_type @@ -212,6 +231,21 @@ class CloudTestCase(test.TestCase): logging.debug("Terminating instance %s" % instance_id) rv = self.compute.terminate_instance(instance_id) + + def test_describe_instances(self): + """Makes sure describe_instances works.""" + instance1 = db.instance_create(self.context, {'host': 'host2'}) + service1 = db.service_create(self.context, {'host': 'host2', + 'availability_zone': 'zone1', + 'topic': "compute"}) + result = self.cloud.describe_instances(self.context) + self.assertEqual(result['reservationSet'][0]\ + ['instancesSet'][0]\ + ['placement']['availabilityZone'], 'zone1') + db.instance_destroy(self.context, instance1['id']) + db.service_destroy(self.context, service1['id']) + + def test_instance_update_state(self): def instance(num): return { @@ -261,6 +295,7 @@ class CloudTestCase(test.TestCase): # data = self.cloud.get_metadata(instance(i)['private_dns_name']) # self.assert_(data['meta-data']['ami-id'] == 'ami-%s' % i) + @staticmethod def _fake_set_image_description(ctxt, image_id, description): from nova.objectstore import handler diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py index a9937d797..99f62f136 100644 --- a/nova/tests/test_scheduler.py +++ b/nova/tests/test_scheduler.py @@ -21,6 +21,7 @@ Tests For Scheduler import datetime +from mox import IgnoreArg from nova import context from nova import db from nova import flags @@ -76,6 +77,48 @@ class SchedulerTestCase(test.TestCase): scheduler.named_method(ctxt, 'topic', num=7) +class ZoneSchedulerTestCase(test.TestCase): + """Test case for zone scheduler""" + def setUp(self): + super(ZoneSchedulerTestCase, self).setUp() + self.flags(scheduler_driver='nova.scheduler.zone.ZoneScheduler') + + def _create_service_model(self, **kwargs): + service = db.sqlalchemy.models.Service() + service.host = kwargs['host'] + service.disabled = False + service.deleted = False + service.report_count = 0 + service.binary = 'nova-compute' + service.topic = 'compute' + service.id = kwargs['id'] + service.availability_zone = kwargs['zone'] + service.created_at = datetime.datetime.utcnow() + return service + + + def test_with_two_zones(self): + scheduler = manager.SchedulerManager() + ctxt = context.get_admin_context() + service_list = [ + self._create_service_model(id=1, host='host1', zone='zone1'), + self._create_service_model(id=2, host='host2', zone='zone2'), + self._create_service_model(id=3, host='host3', zone='zone2'), + self._create_service_model(id=4, host='host4', zone='zone2'), + self._create_service_model(id=5, host='host5', zone='zone2') + ] + self.mox.StubOutWithMock(db, 'service_get_all_by_topic') + db.service_get_all_by_topic(IgnoreArg(), IgnoreArg()).AndReturn(service_list) + self.mox.StubOutWithMock(rpc, 'cast', use_mock_anything=True) + rpc.cast(ctxt, + 'compute.host1', + {'method': 'run_instance', + 'args':{'instance_id': 'i-ffffffff', + 'availability_zone': 'zone1'}}) + self.mox.ReplayAll() + scheduler.run_instance(ctxt, 'compute', instance_id='i-ffffffff', availability_zone='zone1') + + class SimpleDriverTestCase(test.TestCase): """Test case for simple driver""" def setUp(self): diff --git a/nova/tests/test_service.py b/nova/tests/test_service.py index 9f1a181a0..a67c8d1e8 100644 --- a/nova/tests/test_service.py +++ b/nova/tests/test_service.py @@ -133,7 +133,8 @@ class ServiceTestCase(test.TestCase): service_create = {'host': host, 'binary': binary, 'topic': topic, - 'report_count': 0} + 'report_count': 0, + 'availability_zone': 'nova'} service_ref = {'host': host, 'binary': binary, 'report_count': 0, @@ -161,11 +162,13 @@ class ServiceTestCase(test.TestCase): service_create = {'host': host, 'binary': binary, 'topic': topic, - 'report_count': 0} + 'report_count': 0, + 'availability_zone': 'nova'} service_ref = {'host': host, 'binary': binary, 'topic': topic, 'report_count': 0, + 'availability_zone': 'nova', 'id': 1} service.db.service_get_by_args(mox.IgnoreArg(), @@ -193,11 +196,13 @@ class ServiceTestCase(test.TestCase): service_create = {'host': host, 'binary': binary, 'topic': topic, - 'report_count': 0} + 'report_count': 0, + 'availability_zone': 'nova'} service_ref = {'host': host, 'binary': binary, 'topic': topic, 'report_count': 0, + 'availability_zone': 'nova', 'id': 1} service.db.service_get_by_args(mox.IgnoreArg(), @@ -224,11 +229,13 @@ class ServiceTestCase(test.TestCase): service_create = {'host': host, 'binary': binary, 'topic': topic, - 'report_count': 0} + 'report_count': 0, + 'availability_zone': 'nova'} service_ref = {'host': host, 'binary': binary, 'topic': topic, 'report_count': 0, + 'availability_zone': 'nova', 'id': 1} service.db.service_get_by_args(mox.IgnoreArg(), -- cgit From b2d6bb841857599096467470ec704e6696317829 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Fri, 7 Jan 2011 19:04:22 -0600 Subject: change API classname to match the way other API's are done. --- nova/api/openstack/consoles.py | 4 ++-- nova/console/__init__.py | 1 + nova/console/api.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index e108bab86..9ebdbe710 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -17,9 +17,9 @@ from webob import exc +from nova import console from nova import exception from nova import wsgi -from nova.console import api as console_api from nova.api.openstack import faults @@ -52,7 +52,7 @@ class Controller(wsgi.Controller): 'console': []}}} def __init__(self): - self.console_api = console_api.ConsoleAPI() + self.console_api = console.API() super(Controller, self).__init__() def index(self, req, server_id): diff --git a/nova/console/__init__.py b/nova/console/__init__.py index 491df075b..dfc72cd61 100644 --- a/nova/console/__init__.py +++ b/nova/console/__init__.py @@ -10,3 +10,4 @@ multitenant VM console access .. moduleauthor:: Monsyne Dragon """ +from nova.console.api import API diff --git a/nova/console/api.py b/nova/console/api.py index 93e28ad64..3850d2c44 100644 --- a/nova/console/api.py +++ b/nova/console/api.py @@ -30,11 +30,11 @@ from nova import rpc FLAGS = flags.FLAGS -class ConsoleAPI(base.Base): +class API(base.Base): """API for spining up or down console proxy connections""" def __init__(self, **kwargs): - super(ConsoleAPI, self).__init__(**kwargs) + super(API, self).__init__(**kwargs) def get_consoles(self, context, instance_id): return self.db.console_get_all_by_instance(context, instance_id) -- cgit From 94f3782eb27fd63c64845f9ab59039d07ac7ba8c Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Mon, 10 Jan 2011 14:59:32 -0600 Subject: remove uneeded superclass --- nova/console/driver.py | 60 -------------------------------------------------- nova/console/fake.py | 3 +-- nova/console/xvp.py | 7 ++++-- 3 files changed, 6 insertions(+), 64 deletions(-) delete mode 100644 nova/console/driver.py (limited to 'nova') diff --git a/nova/console/driver.py b/nova/console/driver.py deleted file mode 100644 index c4cf5e5e9..000000000 --- a/nova/console/driver.py +++ /dev/null @@ -1,60 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2010 Openstack, LLC. -# 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. - -""" -ConsoleProxy base class that all ConsoleProxies should inherit from -""" - -import os - -from nova import exception - - -class ConsoleProxy(object): - """The base class for all ConsoleProxy driver classes.""" - - @property - def console_type(self): - raise NotImplementedError("Must specify type in subclass") - - def setup_console(self, context, console): - """Sets up actual proxies""" - raise NotImplementedError("Must implement setup in subclass") - - def teardown_console(self, context, console): - """Tears down actual proxies""" - raise NotImplementedError("Must implement teardown in subclass") - - def init_host(self): - """Start up any config'ed consoles on start""" - pass - - def generate_password(self, length=8): - """Returns random console password""" - return os.urandom(length * 2).encode('base64')[:length] - - def get_port(self, context): - """get available port for consoles that need one""" - return None - - def fix_pool_password(self, password): - """Trim password to length, and any other massaging""" - return password - - def fix_console_password(self, password): - """Trim password to length, and any other massaging""" - return password diff --git a/nova/console/fake.py b/nova/console/fake.py index 46782bdb3..7a90d5221 100644 --- a/nova/console/fake.py +++ b/nova/console/fake.py @@ -20,10 +20,9 @@ Fake ConsoleProxy driver for tests. """ from nova import exception -from nova.console import driver -class FakeConsoleProxy(driver.ConsoleProxy): +class FakeConsoleProxy(object): """Fake ConsoleProxy driver.""" @property diff --git a/nova/console/xvp.py b/nova/console/xvp.py index 161b5ce20..2a76223da 100644 --- a/nova/console/xvp.py +++ b/nova/console/xvp.py @@ -32,7 +32,6 @@ from nova import db from nova import exception from nova import flags from nova import utils -from nova.console import driver flags.DEFINE_string('console_xvp_conf_template', utils.abspath('console/xvp.conf.template'), @@ -52,7 +51,7 @@ flags.DEFINE_integer('console_xvp_multiplex_port', FLAGS = flags.FLAGS -class XVPConsoleProxy(driver.ConsoleProxy): +class XVPConsoleProxy(object): """Sets up XVP config, and manages xvp daemon""" def __init__(self): @@ -92,6 +91,10 @@ class XVPConsoleProxy(driver.ConsoleProxy): """Trim password to length, and encode""" return self._xvp_encrypt(password) + def generate_password(self, length=8): + """Returns random console password""" + return os.urandom(length * 2).encode('base64')[:length] + def _rebuild_xvp_conf(self, context): logging.debug("Rebuilding xvp conf") pools = [pool for pool in -- cgit From 4edfa8ea26f8e820674e8bebbe34b6ed5885a69b Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Mon, 10 Jan 2011 13:44:45 -0800 Subject: consolidate boto_extensions.py and euca-get-ajax-console, fix bugs from previous trunk merge --- nova/api/ec2/cloud.py | 2 +- nova/boto_extensions.py | 40 ---------------------------------------- nova/compute/api.py | 6 +++--- 3 files changed, 4 insertions(+), 44 deletions(-) delete mode 100644 nova/boto_extensions.py (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 17b9a14fb..b426710bc 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -500,7 +500,7 @@ class CloudController(object): def get_ajax_console(self, context, instance_id, **kwargs): ec2_id = instance_id[0] - internal_id = ec2_id_to_internal_id(ec2_id) + internal_id = ec2_id_to_id(ec2_id) return self.compute_api.get_ajax_console(context, internal_id) def describe_volumes(self, context, volume_id=None, **kwargs): diff --git a/nova/boto_extensions.py b/nova/boto_extensions.py deleted file mode 100644 index 6d55b8012..000000000 --- a/nova/boto_extensions.py +++ /dev/null @@ -1,40 +0,0 @@ -import base64 -import boto -from boto.ec2.connection import EC2Connection - -class AjaxConsole: - def __init__(self, parent=None): - self.parent = parent - self.instance_id = None - self.url = None - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - if name == 'instanceId': - self.instance_id = value - elif name == 'url': - self.url = value - else: - setattr(self, name, value) - -class NovaEC2Connection(EC2Connection): - def get_ajax_console(self, instance_id): - """ - Retrieves a console connection for the specified instance. - - :type instance_id: string - :param instance_id: The instance ID of a running instance on the cloud. - - :rtype: :class:`AjaxConsole` - """ - params = {} - self.build_list_params(params, [instance_id], 'InstanceId') - return self.get_object('GetAjaxConsole', params, AjaxConsole) - pass - -def override_connect_ec2(aws_access_key_id=None, aws_secret_access_key=None, **kwargs): - return NovaEC2Connection(aws_access_key_id, aws_secret_access_key, **kwargs) - -boto.connect_ec2 = override_connect_ec2 diff --git a/nova/compute/api.py b/nova/compute/api.py index adf4dbe43..4d25bd705 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -416,13 +416,13 @@ class API(base.Base): def get_ajax_console(self, context, instance_id): """Get a url to an AJAX Console""" - instance_ref = db.instance_get_by_internal_id(context, instance_id) + instance = self.get(context, instance_id) output = rpc.call(context, '%s.%s' % (FLAGS.compute_topic, - instance_ref['host']), + instance['host']), {'method': 'get_ajax_console', - 'args': {'instance_id': instance_ref['id']}}) + 'args': {'instance_id': instance['id']}}) rpc.cast(context, '%s' % FLAGS.ajax_console_proxy_topic, {'method': 'authorize_ajax_console', -- cgit From 77e75fefc7c9c4085a64eabb5ef44ffd5fff3229 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Tue, 11 Jan 2011 03:38:40 -0400 Subject: Changed shared_ip_group detail routing --- nova/api/openstack/shared_ip_groups.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/shared_ip_groups.py b/nova/api/openstack/shared_ip_groups.py index ec399db93..bd3cc23a8 100644 --- a/nova/api/openstack/shared_ip_groups.py +++ b/nova/api/openstack/shared_ip_groups.py @@ -48,8 +48,6 @@ class Controller(wsgi.Controller): def show(self, req, id): """ Shows in-depth information on a specific Shared IP Group """ - #if id == 'detail': - # return _translate_detail_keys({}) return _translate_keys({}) def update(self, req, id): @@ -60,7 +58,7 @@ class Controller(wsgi.Controller): """ Deletes a Shared IP Group """ raise faults.Fault(exc.HTTPNotImplemented()) - def detail(self, req, id): + def detail(self, req): """ Returns a complete list of Shared IP Groups """ return _translate_detail_keys({}) -- cgit From ea4cde387c04e450c7bea9407772ca4276ea54f4 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 11 Jan 2011 11:06:16 +0100 Subject: Fixed a number of issues with the iptables firewall backend: * Port specifications for firewalls come back from the data store as integers, but were compared as strings. * --icmp-type was misspelled as --icmp_type (underscore vs dash) * There weren't any unit tests for these issues. --- nova/tests/test_virt.py | 44 +++++++++++++++++++++++++++++++++++++++++++- nova/virt/libvirt_conn.py | 6 +++--- 2 files changed, 46 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 59053f4d0..c69dbaab3 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -276,6 +276,20 @@ class IptablesFirewallTestCase(test.TestCase): 'name': 'testgroup', 'description': 'test group'}) + db.security_group_rule_create(admin_ctxt, + {'parent_group_id': secgroup['id'], + 'protocol': 'icmp', + 'from_port': -1, + 'to_port': -1, + 'cidr': '192.168.11.0/24'}) + + db.security_group_rule_create(admin_ctxt, + {'parent_group_id': secgroup['id'], + 'protocol': 'icmp', + 'from_port': 8, + 'to_port': -1, + 'cidr': '192.168.11.0/24'}) + db.security_group_rule_create(admin_ctxt, {'parent_group_id': secgroup['id'], 'protocol': 'tcp', @@ -297,7 +311,35 @@ class IptablesFirewallTestCase(test.TestCase): self.assertTrue(rule in out_rules, 'Rule went missing: %s' % rule) - print '\n'.join(out_rules) + instance_chain = None + for rule in out_rules: + # This is pretty crude, but it'll do for now + if '-d 10.11.12.13 -j' in rule: + instance_chain = rule.split(' ')[-1] + break + self.assertTrue(instance_chain, "The instance chain wasn't added") + + security_group_chain = None + for rule in out_rules: + # This is pretty crude, but it'll do for now + if '-A %s -j' % instance_chain in rule: + security_group_chain = rule.split(' ')[-1] + break + self.assertTrue(security_group_chain, + "The security group chain wasn't added") + + self.assertTrue('-A %s -p icmp -s 192.168.11.0/24 -j ACCEPT' % \ + security_group_chain in out_rules, + "ICMP acceptance rule wasn't added") + + self.assertTrue('-A %s -p icmp -s 192.168.11.0/24 -m icmp --icmp-type' + ' 8 -j ACCEPT' % security_group_chain in out_rules, + "ICMP Echo Request acceptance rule wasn't added") + + self.assertTrue('-A %s -p tcp -s 192.168.10.0/24 -m multiport ' + '--dports 80:81 -j ACCEPT' % security_group_chain \ + in out_rules, + "TCP port 80/81 acceptance rule wasn't added") class NWFilterTestCase(test.TestCase): diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 3a4b6d469..759ef62ab 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1105,15 +1105,15 @@ class IptablesFirewallDriver(FirewallDriver): icmp_type = rule.from_port icmp_code = rule.to_port - if icmp_type == '-1': + if icmp_type == -1: icmp_type_arg = None else: icmp_type_arg = '%s' % icmp_type - if not icmp_code == '-1': + if not icmp_code == -1: icmp_type_arg += '/%s' % icmp_code if icmp_type_arg: - args += ['-m', 'icmp', '--icmp_type', icmp_type_arg] + args += ['-m', 'icmp', '--icmp-type', icmp_type_arg] args += ['-j ACCEPT'] our_rules += [' '.join(args)] -- cgit From d01b546ae574f74b9c4c07a039c2c52cf0ed3bfb Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Wed, 12 Jan 2011 01:27:36 +0300 Subject: resolve pylint warnings --- nova/api/ec2/cloud.py | 14 ++++---- nova/db/api.py | 1 - nova/db/sqlalchemy/models.py | 1 + nova/scheduler/zone.py | 8 ++--- nova/service.py | 3 +- nova/tests/test_cloud.py | 45 ++++++++++++------------- nova/tests/test_scheduler.py | 78 ++++++++++++++------------------------------ 7 files changed, 59 insertions(+), 91 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index b6748e608..42245fa54 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -159,8 +159,8 @@ class CloudController(object): else: keys = '' hostname = instance_ref['hostname'] - availability_zone = self._get_availability_zone_by_host(ctxt, - instance_ref['host']) + host = instance_ref['host'] + availability_zone = self._get_availability_zone_by_host(ctxt, host) floating_ip = db.instance_get_floating_address(ctxt, instance_ref['id']) ec2_id = id_to_ec2_id(instance_ref['id']) @@ -210,12 +210,13 @@ class CloudController(object): enabled_services = db.service_get_all(context) disabled_services = db.service_get_all(context, True) available_zones = [] - for zone in [service.availability_zone for service in enabled_services]: + for zone in [service.availability_zone for service + in enabled_services]: if not zone in available_zones: available_zones.append(zone) not_available_zones = [] for zone in [service.availability_zone for service in disabled_services - and not service['availability_zone'] in available_zones]: + if not service['availability_zone'] in available_zones]: if not zone in not_available_zones: not_available_zones.append(zone) result = [] @@ -679,8 +680,9 @@ class CloudController(object): i['amiLaunchIndex'] = instance['launch_index'] i['displayName'] = instance['display_name'] i['displayDescription'] = instance['display_description'] - availability_zone = self._get_availability_zone_by_host(context, instance['host']) - i['placement'] = {'availabilityZone': availability_zone} + host = instance['host'] + zone = self._get_availability_zone_by_host(context, host) + i['placement'] = {'availabilityZone': zone} if instance['reservation_id'] not in reservations: r = {} r['reservationId'] = instance['reservation_id'] diff --git a/nova/db/api.py b/nova/db/api.py index 64fee590b..4577d97d2 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -96,7 +96,6 @@ def service_get_all_by_host(context, host): return IMPL.service_get_all_by_host(context, host) - def service_get_all_compute_sorted(context): """Get all compute services sorted by instance count. diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 05ddbeb72..8e5b6b3ac 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -151,6 +151,7 @@ class Service(BASE, NovaBase): disabled = Column(Boolean, default=False) availability_zone = Column(String(255), default='nova') + class Certificate(BASE, NovaBase): """Represents a an x509 certificate""" __tablename__ = 'certificates' diff --git a/nova/scheduler/zone.py b/nova/scheduler/zone.py index ec2166adf..49786cd32 100644 --- a/nova/scheduler/zone.py +++ b/nova/scheduler/zone.py @@ -31,8 +31,8 @@ class ZoneScheduler(driver.Scheduler): """Implements Scheduler as a random node selector.""" def hosts_up_with_zone(self, context, topic, zone): - """Return the list of hosts that have a running service - for topic and availability zone (if defined). + """Return the list of hosts that have a running service + for topic and availability zone (if defined). """ if zone is None: @@ -44,9 +44,8 @@ class ZoneScheduler(driver.Scheduler): if self.service_is_up(service) and service.availability_zone == zone] - def schedule(self, context, topic, *_args, **_kwargs): - """Picks a host that is up at random in selected + """Picks a host that is up at random in selected availability zone (if defined). """ @@ -55,4 +54,3 @@ class ZoneScheduler(driver.Scheduler): if not hosts: raise driver.NoValidHost(_("No hosts found")) return hosts[int(random.random() * len(hosts))] - diff --git a/nova/service.py b/nova/service.py index 2998ed3e5..8b2a22ce0 100644 --- a/nova/service.py +++ b/nova/service.py @@ -113,12 +113,13 @@ class Service(object): self.timers.append(periodic) def _create_service_ref(self, context): + zone = FLAGS.node_availability_zone service_ref = db.service_create(context, {'host': self.host, 'binary': self.binary, 'topic': self.topic, 'report_count': 0, - 'availability_zone': FLAGS.node_availability_zone}) + 'availability_zone': zone}) self.service_id = service_ref['id'] def __getattr__(self, key): diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index afb6a8f1b..afa6e9140 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -135,12 +135,12 @@ class CloudTestCase(test.TestCase): def test_describe_availability_zones(self): """Makes sure describe_availability_zones works and filters results.""" - service1 = db.service_create(self.context, {'host': 'host1_describe_zones', + service1 = db.service_create(self.context, {'host': 'host1_zones', 'binary': "nova-compute", 'topic': 'compute', 'report_count': 0, 'availability_zone': "zone1"}) - service2 = db.service_create(self.context, {'host': 'host2_describe_zones', + service2 = db.service_create(self.context, {'host': 'host2_zones', 'binary': "nova-compute", 'topic': 'compute', 'report_count': 0, @@ -150,17 +150,18 @@ class CloudTestCase(test.TestCase): db.service_destroy(self.context, service1['id']) db.service_destroy(self.context, service2['id']) - def test_describe_instances(self): """Makes sure describe_instances works and filters results.""" - inst1 = db.instance_create(self.context, {'reservation_id': 'a', 'host': 'host1'}) - inst2 = db.instance_create(self.context, {'reservation_id': 'a', 'host': 'host2'}) - compute1 = db.service_create(self.context, {'host': 'host1', - 'availability_zone': 'zone1', - 'topic': "compute"}) - compute2 = db.service_create(self.context, {'host': 'host2', - 'availability_zone': 'zone2', - 'topic': "compute"}) + inst1 = db.instance_create(self.context, {'reservation_id': 'a', + 'host': 'host1'}) + inst2 = db.instance_create(self.context, {'reservation_id': 'a', + 'host': 'host2'}) + comp1 = db.service_create(self.context, {'host': 'host1', + 'availability_zone': 'zone1', + 'topic': "compute"}) + comp2 = db.service_create(self.context, {'host': 'host2', + 'availability_zone': 'zone2', + 'topic': "compute"}) result = self.cloud.describe_instances(self.context) result = result['reservationSet'][0] self.assertEqual(len(result['instancesSet']), 2) @@ -171,13 +172,12 @@ class CloudTestCase(test.TestCase): self.assertEqual(len(result['instancesSet']), 1) self.assertEqual(result['instancesSet'][0]['instanceId'], instance_id) - self.assertEqual(result['instancesSet'][0]\ + self.assertEqual(result['instancesSet'][0] ['placement']['availabilityZone'], 'zone2') db.instance_destroy(self.context, inst1['id']) db.instance_destroy(self.context, inst2['id']) - db.service_destroy(self.context, compute1['id']) - db.service_destroy(self.context, compute2['id']) - + db.service_destroy(self.context, comp1['id']) + db.service_destroy(self.context, comp2['id']) def test_console_output(self): image_id = FLAGS.default_image @@ -257,21 +257,19 @@ class CloudTestCase(test.TestCase): LOG.debug(_("Terminating instance %s"), instance_id) rv = self.compute.terminate_instance(instance_id) - def test_describe_instances(self): """Makes sure describe_instances works.""" instance1 = db.instance_create(self.context, {'host': 'host2'}) - service1 = db.service_create(self.context, {'host': 'host2', - 'availability_zone': 'zone1', - 'topic': "compute"}) + comp1 = db.service_create(self.context, {'host': 'host2', + 'availability_zone': 'zone1', + 'topic': "compute"}) result = self.cloud.describe_instances(self.context) - self.assertEqual(result['reservationSet'][0]\ - ['instancesSet'][0]\ + self.assertEqual(result['reservationSet'][0] + ['instancesSet'][0] ['placement']['availabilityZone'], 'zone1') db.instance_destroy(self.context, instance1['id']) - db.service_destroy(self.context, service1['id']) + db.service_destroy(self.context, comp1['id']) - def test_instance_update_state(self): def instance(num): return { @@ -321,7 +319,6 @@ class CloudTestCase(test.TestCase): # data = self.cloud.get_metadata(instance(i)['private_dns_name']) # self.assert_(data['meta-data']['ami-id'] == 'ami-%s' % i) - @staticmethod def _fake_set_image_description(ctxt, image_id, description): from nova.objectstore import handler diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py index 127d666e4..ce4262ccf 100644 --- a/nova/tests/test_scheduler.py +++ b/nova/tests/test_scheduler.py @@ -76,6 +76,7 @@ class SchedulerTestCase(test.TestCase): self.mox.ReplayAll() scheduler.named_method(ctxt, 'topic', num=7) + class ZoneSchedulerTestCase(test.TestCase): """Test case for zone scheduler""" def setUp(self): @@ -95,70 +96,39 @@ class ZoneSchedulerTestCase(test.TestCase): service.created_at = datetime.datetime.utcnow() return service - def test_with_two_zones(self): scheduler = manager.SchedulerManager() ctxt = context.get_admin_context() - service_list = [ - self._create_service_model(id=1, host='host1', zone='zone1'), - self._create_service_model(id=2, host='host2', zone='zone2'), - self._create_service_model(id=3, host='host3', zone='zone2'), - self._create_service_model(id=4, host='host4', zone='zone2'), - self._create_service_model(id=5, host='host5', zone='zone2') - ] + service_list = [self._create_service_model(id=1, + host='host1', + zone='zone1'), + self._create_service_model(id=2, + host='host2', + zone='zone2'), + self._create_service_model(id=3, + host='host3', + zone='zone2'), + self._create_service_model(id=4, + host='host4', + zone='zone2'), + self._create_service_model(id=5, + host='host5', + zone='zone2')] self.mox.StubOutWithMock(db, 'service_get_all_by_topic') - db.service_get_all_by_topic(IgnoreArg(), IgnoreArg()).AndReturn(service_list) + arg = IgnoreArg() + db.service_get_all_by_topic(arg, arg).AndReturn(service_list) self.mox.StubOutWithMock(rpc, 'cast', use_mock_anything=True) rpc.cast(ctxt, 'compute.host1', {'method': 'run_instance', - 'args':{'instance_id': 'i-ffffffff', - 'availability_zone': 'zone1'}}) + 'args': {'instance_id': 'i-ffffffff', + 'availability_zone': 'zone1'}}) self.mox.ReplayAll() - scheduler.run_instance(ctxt, 'compute', instance_id='i-ffffffff', availability_zone='zone1') - - -class ZoneSchedulerTestCase(test.TestCase): - """Test case for zone scheduler""" - def setUp(self): - super(ZoneSchedulerTestCase, self).setUp() - self.flags(scheduler_driver='nova.scheduler.zone.ZoneScheduler') + scheduler.run_instance(ctxt, + 'compute', + instance_id='i-ffffffff', + availability_zone='zone1') - def _create_service_model(self, **kwargs): - service = db.sqlalchemy.models.Service() - service.host = kwargs['host'] - service.disabled = False - service.deleted = False - service.report_count = 0 - service.binary = 'nova-compute' - service.topic = 'compute' - service.id = kwargs['id'] - service.availability_zone = kwargs['zone'] - service.created_at = datetime.datetime.utcnow() - return service - - - def test_with_two_zones(self): - scheduler = manager.SchedulerManager() - ctxt = context.get_admin_context() - service_list = [ - self._create_service_model(id=1, host='host1', zone='zone1'), - self._create_service_model(id=2, host='host2', zone='zone2'), - self._create_service_model(id=3, host='host3', zone='zone2'), - self._create_service_model(id=4, host='host4', zone='zone2'), - self._create_service_model(id=5, host='host5', zone='zone2') - ] - self.mox.StubOutWithMock(db, 'service_get_all_by_topic') - db.service_get_all_by_topic(IgnoreArg(), IgnoreArg()).AndReturn(service_list) - self.mox.StubOutWithMock(rpc, 'cast', use_mock_anything=True) - rpc.cast(ctxt, - 'compute.host1', - {'method': 'run_instance', - 'args':{'instance_id': 'i-ffffffff', - 'availability_zone': 'zone1'}}) - self.mox.ReplayAll() - scheduler.run_instance(ctxt, 'compute', instance_id='i-ffffffff', availability_zone='zone1') - class SimpleDriverTestCase(test.TestCase): """Test case for simple driver""" -- cgit From eb48bdce5ad131245977dff50030f5561b8809c1 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 11 Jan 2011 14:33:20 -0800 Subject: bah - pep8 errors --- nova/compute/api.py | 4 ++-- nova/tests/test_cloud.py | 2 +- nova/virt/fake.py | 1 + nova/virt/libvirt_conn.py | 7 ++++--- 4 files changed, 8 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 4d25bd705..632ce0efb 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -426,8 +426,8 @@ class API(base.Base): rpc.cast(context, '%s' % FLAGS.ajax_console_proxy_topic, {'method': 'authorize_ajax_console', - 'args': {'token': output['token'], 'host': output['host'], - 'port':output['port']}}) + 'args': {'token': output['token'], 'host': output['host'], + 'port': output['port']}}) return {'url': '%s?token=%s' % (FLAGS.ajax_console_proxy_url, output['token'])} diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 76a620406..8e43eec00 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -168,7 +168,7 @@ class CloudTestCase(test.TestCase): rv = self.cloud.terminate_instances(self.context, [instance_id]) def test_ajax_console(self): - kwargs = {'image_id': image_id } + kwargs = {'image_id': image_id} rv = yield self.cloud.run_instances(self.context, **kwargs) instance_id = rv['instancesSet'][0]['instanceId'] output = yield self.cloud.get_console_output(context=self.context, diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 925c32e4d..8abe08e41 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -292,6 +292,7 @@ class FakeConnection(object): def get_ajax_console(self, instance): return 'http://fakeajaxconsole.com/?token=FAKETOKEN' + class FakeInstance(object): def __init__(self): diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index dc31d8357..4a907c88f 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -445,7 +445,7 @@ class LibvirtConnection(object): def get_ajax_console(self, instance): def get_open_port(): start_port, end_port = FLAGS.ajaxterm_portrange.split("-") - for i in xrange(0,100): # don't loop forever + for i in xrange(0, 100): # don't loop forever port = random.randint(int(start_port), int(end_port)) # netcat will exit with 0 only if the port is in use, # so a nonzero return value implies it is unused @@ -469,10 +469,11 @@ class LibvirtConnection(object): token = str(uuid.uuid4()) host = instance['host'] - ajaxterm_cmd = 'sudo socat - %s' % get_pty_for_instance(instance['name']) + ajaxterm_cmd = 'sudo socat - %s' \ + % get_pty_for_instance(instance['name']) cmd = '%s/tools/ajaxterm/ajaxterm.py --command "%s" -t %s -p %s' \ - % (utils.novadir(), ajaxterm_cmd, token, port) + % (utils.novadir(), ajaxterm_cmd, token, port) subprocess.Popen(cmd, shell=True) return {'token': token, 'host': host, 'port': port} -- cgit From 0ac0cd5976ad6b053aa011071194614ee4f70c48 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 11 Jan 2011 18:03:15 -0500 Subject: Raise meaningful exception when there aren't enough params for a sec group rule. --- nova/api/ec2/cloud.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 99a9677c4..135836348 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -399,8 +399,8 @@ class CloudController(object): criteria = self._revoke_rule_args_to_dict(context, **kwargs) if criteria == None: - raise exception.ApiError(_("No rule for the specified " - "parameters.")) + raise exception.ApiError(_("Not enough parameters to build a " + "valid rule.")) for rule in security_group.rules: match = True @@ -427,6 +427,9 @@ class CloudController(object): group_name) values = self._revoke_rule_args_to_dict(context, **kwargs) + if values is None: + raise exception.ApiError(_("Not enough parameters to build a " + "valid rule.")) values['parent_group_id'] = security_group.id if self._security_group_rule_exists(security_group, values): -- cgit From b94f3a6cce3a49853c2426b87740fc467a4a787b Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Wed, 12 Jan 2011 02:19:05 +0300 Subject: remove extra whitspaces --- nova/db/sqlalchemy/models.py | 2 +- nova/tests/test_scheduler.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 8e5b6b3ac..e7e984c18 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -151,7 +151,7 @@ class Service(BASE, NovaBase): disabled = Column(Boolean, default=False) availability_zone = Column(String(255), default='nova') - + class Certificate(BASE, NovaBase): """Represents a an x509 certificate""" __tablename__ = 'certificates' diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py index ce4262ccf..9d458244b 100644 --- a/nova/tests/test_scheduler.py +++ b/nova/tests/test_scheduler.py @@ -76,7 +76,7 @@ class SchedulerTestCase(test.TestCase): self.mox.ReplayAll() scheduler.named_method(ctxt, 'topic', num=7) - + class ZoneSchedulerTestCase(test.TestCase): """Test case for zone scheduler""" def setUp(self): -- cgit From ef86d16f15276581932ab50029e895c9cbf655af Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Wed, 12 Jan 2011 12:29:28 +0100 Subject: Eagerly load fixed_ip property of instances. --- nova/db/sqlalchemy/api.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 4561fa219..cee6121a9 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -756,12 +756,14 @@ def instance_get_by_id(context, instance_id): if is_admin_context(context): result = session.query(models.Instance).\ options(joinedload('security_groups')).\ + options(joinedload('fixed_ip')).\ filter_by(id=instance_id).\ filter_by(deleted=can_read_deleted(context)).\ first() elif is_user_context(context): result = session.query(models.Instance).\ options(joinedload('security_groups')).\ + options(joinedload('fixed_ip')).\ filter_by(project_id=context.project_id).\ filter_by(id=instance_id).\ filter_by(deleted=False).\ -- cgit From c29fe496c1124369a8b9b77aeee84e8296f964f9 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 12 Jan 2011 11:28:05 -0600 Subject: fixed pause and resume --- nova/virt/xenapi/vmops.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index e20930fe8..5ca127f36 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -252,7 +252,7 @@ class VMOps(object): raise Exception(_("suspend: instance not present %s") % instance_name) task = self._session.call_xenapi('Async.VM.suspend', vm) - self._wait_with_callback(task, callback) + self._wait_with_callback(instance.id, task, callback) def resume(self, instance, callback): """resume the specified instance""" @@ -262,7 +262,7 @@ class VMOps(object): raise Exception(_("resume: instance not present %s") % instance_name) task = self._session.call_xenapi('Async.VM.resume', vm, False, True) - self._wait_with_callback(task, callback) + self._wait_with_callback(instance.id, task, callback) def get_info(self, instance_id): """Return data about VM instance""" -- cgit From 3e9d08bded2f504a5dd03712c82e981f73ae16ed Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 12 Jan 2011 14:16:51 -0400 Subject: change novarc template from cc_port to osapi_port. Removed osapi_port from bin scripts. --- nova/auth/manager.py | 2 +- nova/flags.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 89f02998d..6fb9b522f 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -747,7 +747,7 @@ class AuthManager(object): 's3': 'http://%s:%s' % (s3_host, FLAGS.s3_port), 'os': '%s://%s:%s%s' % (FLAGS.os_prefix, cc_host, - FLAGS.cc_port, + FLAGS.osapi_port, FLAGS.os_suffix), 'user': user.name, 'nova': FLAGS.ca_file, diff --git a/nova/flags.py b/nova/flags.py index fdcba6c72..ef66c3f3a 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -259,6 +259,7 @@ DEFINE_string('os_prefix', 'http', 'prefix for openstack') DEFINE_string('cc_host', '$my_ip', 'ip of api server') DEFINE_string('cc_dmz', '$my_ip', 'internal ip of api server') DEFINE_integer('cc_port', 8773, 'cloud controller port') +DEFINE_integer('osapi_port', 8774, 'OpenStack API port') DEFINE_string('ec2_suffix', '/services/Cloud', 'suffix for ec2') DEFINE_string('os_suffix', '/v1.0/', 'suffix for openstack') -- cgit From 70ac0dfea7a55c3580d4a9cd65752f894dfaa222 Mon Sep 17 00:00:00 2001 From: Josh Durgin Date: Wed, 12 Jan 2011 10:17:48 -0800 Subject: Check for whole pool name in check_for_setup_error --- nova/volume/driver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/volume/driver.py b/nova/volume/driver.py index fa914f662..609a00310 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -322,7 +322,8 @@ class RBDDriver(VolumeDriver): def check_for_setup_error(self): """Returns an error if prerequisites aren't met""" (stdout, stderr) = self._execute("rados lspools") - if stdout.find(FLAGS.rbd_pool + "\n") == -1: + pools = stdout.split("\n") + if not FLAGS.rbd_pool in pools: raise exception.Error(_("rbd has no pool %s") % FLAGS.rbd_pool) -- cgit From a58fe1849ad7473f7e437e07611aa9c9611cf5e6 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Wed, 12 Jan 2011 22:45:44 +0100 Subject: Do joinedload_all('fixed_ip.floating_ips') instead of joinedload('fixed_ip') --- nova/db/sqlalchemy/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index cee6121a9..e00f31cbe 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -756,14 +756,14 @@ def instance_get_by_id(context, instance_id): if is_admin_context(context): result = session.query(models.Instance).\ options(joinedload('security_groups')).\ - options(joinedload('fixed_ip')).\ + options(joinedload_all('fixed_ip.floating_ips')).\ filter_by(id=instance_id).\ filter_by(deleted=can_read_deleted(context)).\ first() elif is_user_context(context): result = session.query(models.Instance).\ options(joinedload('security_groups')).\ - options(joinedload('fixed_ip')).\ + options(joinedload_all('fixed_ip.floating_ips')).\ filter_by(project_id=context.project_id).\ filter_by(id=instance_id).\ filter_by(deleted=False).\ -- cgit From c966028d5123940aecf4710a15082ae10fcc76e6 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 12 Jan 2011 14:56:26 -0800 Subject: correct formatting for volume ids --- nova/api/ec2/cloud.py | 12 ++++++------ nova/tests/test_cloud.py | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 832426b94..eb6f5548d 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -77,13 +77,13 @@ def ec2_id_to_id(ec2_id): return int(ec2_id[2:], 36) -def id_to_ec2_id(instance_id): +def id_to_ec2_id(instance_id, template='i-%s'): """Convert an instance ID (int) to an ec2 ID (i-[base 36 number])""" digits = [] while instance_id != 0: instance_id, remainder = divmod(instance_id, 36) digits.append('0123456789abcdefghijklmnopqrstuvwxyz'[remainder]) - return "i-%s" % ''.join(reversed(digits)) + return template % (''.join(reversed(digits)).zfill(8)) class CloudController(object): @@ -556,7 +556,7 @@ class CloudController(object): instance_data = '%s[%s]' % (instance_ec2_id, volume['instance']['host']) v = {} - v['volumeId'] = volume['id'] + v['volumeId'] = id_to_ec2_id(volume['id'], 'vol-%s') v['status'] = volume['status'] v['size'] = volume['size'] v['availabilityZone'] = volume['availability_zone'] @@ -574,7 +574,7 @@ class CloudController(object): 'device': volume['mountpoint'], 'instanceId': instance_ec2_id, 'status': 'attached', - 'volume_id': volume['ec2_id']}] + 'volumeId': id_to_ec2_id(volume['id'], 'vol-%s')}] else: v['attachmentSet'] = [{}] @@ -616,7 +616,7 @@ class CloudController(object): 'instanceId': instance_id, 'requestId': context.request_id, 'status': volume['attach_status'], - 'volumeId': volume_id} + 'volumeId': id_to_ec2_id(volume_id, 'vol-%s')} def detach_volume(self, context, volume_id, **kwargs): LOG.audit(_("Detach volume %s"), volume_id, context=context) @@ -627,7 +627,7 @@ class CloudController(object): 'instanceId': id_to_ec2_id(instance['id']), 'requestId': context.request_id, 'status': volume['attach_status'], - 'volumeId': volume_id} + 'volumeId': id_to_ec2_id(volume_id, 'vol-%s')} def _convert_to_set(self, lst, label): if lst == None or lst == []: diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index fdacb04f6..1d59d4e6a 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -127,7 +127,7 @@ class CloudTestCase(test.TestCase): result = self.cloud.describe_volumes(self.context) self.assertEqual(len(result['volumeSet']), 2) result = self.cloud.describe_volumes(self.context, - volume_id=[vol2['id']]) + volume_id=[cloud.id_to_ec2_id(vol2['id'], 'vol-%s')]) self.assertEqual(len(result['volumeSet']), 1) self.assertEqual(result['volumeSet'][0]['volumeId'], vol2['id']) db.volume_destroy(self.context, vol1['id']) @@ -385,7 +385,7 @@ class CloudTestCase(test.TestCase): def test_update_of_volume_display_fields(self): vol = db.volume_create(self.context, {}) - self.cloud.update_volume(self.context, vol['id'], + self.cloud.update_volume(self.context, id_to_ec2_id(vol['id'], 'vol-%s'), display_name='c00l v0lum3') vol = db.volume_get(self.context, vol['id']) self.assertEqual('c00l v0lum3', vol['display_name']) @@ -393,8 +393,8 @@ class CloudTestCase(test.TestCase): def test_update_of_volume_wont_update_private_fields(self): vol = db.volume_create(self.context, {}) - self.cloud.update_volume(self.context, vol['id'], - mountpoint='/not/here') + self.cloud.update_volume(self.context, id_to_ec2_id(vol['id'], 'vol-%s'), + mountpoint='/not/here') vol = db.volume_get(self.context, vol['id']) self.assertEqual(None, vol['mountpoint']) db.volume_destroy(self.context, vol['id']) -- cgit From 1fa45c2ce52612455d88d1fdabec38d4bcc01ca7 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 12 Jan 2011 15:01:23 -0800 Subject: correct volume ids for ec2 --- nova/api/ec2/cloud.py | 3 ++- nova/tests/test_cloud.py | 11 +++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index eb6f5548d..7df2feb98 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -574,7 +574,8 @@ class CloudController(object): 'device': volume['mountpoint'], 'instanceId': instance_ec2_id, 'status': 'attached', - 'volumeId': id_to_ec2_id(volume['id'], 'vol-%s')}] + 'volumeId': id_to_ec2_id(volume['id'], + 'vol-%s')}] else: v['attachmentSet'] = [{}] diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 1d59d4e6a..f5ab2330f 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -126,8 +126,9 @@ class CloudTestCase(test.TestCase): vol2 = db.volume_create(self.context, {}) result = self.cloud.describe_volumes(self.context) self.assertEqual(len(result['volumeSet']), 2) - result = self.cloud.describe_volumes(self.context, - volume_id=[cloud.id_to_ec2_id(vol2['id'], 'vol-%s')]) + result = self.cloud.describe_volumes( + self.context, + volume_id=[cloud.id_to_ec2_id(vol2['id'], 'vol-%s')]) self.assertEqual(len(result['volumeSet']), 1) self.assertEqual(result['volumeSet'][0]['volumeId'], vol2['id']) db.volume_destroy(self.context, vol1['id']) @@ -385,7 +386,8 @@ class CloudTestCase(test.TestCase): def test_update_of_volume_display_fields(self): vol = db.volume_create(self.context, {}) - self.cloud.update_volume(self.context, id_to_ec2_id(vol['id'], 'vol-%s'), + self.cloud.update_volume(self.context, + id_to_ec2_id(vol['id'], 'vol-%s'), display_name='c00l v0lum3') vol = db.volume_get(self.context, vol['id']) self.assertEqual('c00l v0lum3', vol['display_name']) @@ -393,7 +395,8 @@ class CloudTestCase(test.TestCase): def test_update_of_volume_wont_update_private_fields(self): vol = db.volume_create(self.context, {}) - self.cloud.update_volume(self.context, id_to_ec2_id(vol['id'], 'vol-%s'), + self.cloud.update_volume(self.context, + id_to_ec2_id(vol['id'], 'vol-%s'), mountpoint='/not/here') vol = db.volume_get(self.context, vol['id']) self.assertEqual(None, vol['mountpoint']) -- cgit From 5fbc74784918abb509aba88400e6ed9a1d01deb9 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 12 Jan 2011 15:03:08 -0800 Subject: standardize on hex for ids, allow configurable instance names --- nova/api/ec2/cloud.py | 14 +++++--------- nova/db/api.py | 4 ++++ nova/db/sqlalchemy/models.py | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 7df2feb98..c68a1f4bc 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -73,17 +73,13 @@ def _gen_key(context, user_id, key_name): def ec2_id_to_id(ec2_id): - """Convert an ec2 ID (i-[base 36 number]) to an instance id (int)""" - return int(ec2_id[2:], 36) + """Convert an ec2 ID (i-[base 16 number]) to an instance id (int)""" + return int(ec2_id.split('-')[-1], 16) -def id_to_ec2_id(instance_id, template='i-%s'): - """Convert an instance ID (int) to an ec2 ID (i-[base 36 number])""" - digits = [] - while instance_id != 0: - instance_id, remainder = divmod(instance_id, 36) - digits.append('0123456789abcdefghijklmnopqrstuvwxyz'[remainder]) - return template % (''.join(reversed(digits)).zfill(8)) +def id_to_ec2_id(instance_id, template='i-%08x'): + """Convert an instance ID (int) to an ec2 ID (i-[base 16 number])""" + return template % instance_id class CloudController(object): diff --git a/nova/db/api.py b/nova/db/api.py index 1f81ef145..e57766b5c 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -42,6 +42,10 @@ flags.DEFINE_string('db_backend', 'sqlalchemy', 'The backend to use for db') flags.DEFINE_boolean('enable_new_services', True, 'Services to be added to the available pool on create') +flags.DEFINE_string('instance_name_template', 'instance-%08x', + 'Template string to be used to generate instance names') +flags.DEFINE_string('volume_name_template', 'volume-%08x', + 'Template string to be used to generate instance names') IMPL = utils.LazyPluggable(FLAGS['db_backend'], diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 1dc46fe78..bbc89e573 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -169,7 +169,7 @@ class Instance(BASE, NovaBase): @property def name(self): - return "instance-%08x" % self.id + return FLAGS.instance_name_template % self.id admin_pass = Column(String(255)) user_id = Column(String(255)) @@ -256,7 +256,7 @@ class Volume(BASE, NovaBase): @property def name(self): - return "volume-%08x" % self.id + return FLAGS.volume_name_template % self.id user_id = Column(String(255)) project_id = Column(String(255)) -- cgit From 1f59fcb405e16869c9cc94f54cda6e6aae23fc40 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 12 Jan 2011 15:20:13 -0800 Subject: standardize volume ids --- nova/api/ec2/cloud.py | 17 ++++++++++++----- nova/tests/test_cloud.py | 11 +++++------ 2 files changed, 17 insertions(+), 11 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index c68a1f4bc..38d47a0a7 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -537,6 +537,8 @@ class CloudController(object): return self.compute_api.get_ajax_console(context, internal_id) def describe_volumes(self, context, volume_id=None, **kwargs): + if volume_id: + volume_id = [ec2_id_to_id(x) for x in volume_id] volumes = self.volume_api.get_all(context) # NOTE(vish): volume_id is an optional list of volume ids to filter by. volumes = [self._format_volume(context, v) for v in volumes @@ -552,7 +554,7 @@ class CloudController(object): instance_data = '%s[%s]' % (instance_ec2_id, volume['instance']['host']) v = {} - v['volumeId'] = id_to_ec2_id(volume['id'], 'vol-%s') + v['volumeId'] = id_to_ec2_id(volume['id'], 'vol-%08x') v['status'] = volume['status'] v['size'] = volume['size'] v['availabilityZone'] = volume['availability_zone'] @@ -571,7 +573,7 @@ class CloudController(object): 'instanceId': instance_ec2_id, 'status': 'attached', 'volumeId': id_to_ec2_id(volume['id'], - 'vol-%s')}] + 'vol-%08x')}] else: v['attachmentSet'] = [{}] @@ -590,10 +592,12 @@ class CloudController(object): return {'volumeSet': [self._format_volume(context, dict(volume_ref))]} def delete_volume(self, context, volume_id, **kwargs): + volume_id = ec2_id_to_id(volume_id) self.volume_api.delete(context, volume_id) return True def update_volume(self, context, volume_id, **kwargs): + volume_id = ec2_id_to_id(volume_id) updatable_fields = ['display_name', 'display_description'] changes = {} for field in updatable_fields: @@ -604,18 +608,21 @@ class CloudController(object): return True def attach_volume(self, context, volume_id, instance_id, device, **kwargs): + volume_id = ec2_id_to_id(volume_id) + instance_id = ec2_id_to_id(instance_id) LOG.audit(_("Attach volume %s to instacne %s at %s"), volume_id, instance_id, device, context=context) self.compute_api.attach_volume(context, instance_id, volume_id, device) volume = self.volume_api.get(context, volume_id) return {'attachTime': volume['attach_time'], 'device': volume['mountpoint'], - 'instanceId': instance_id, + 'instanceId': id_to_ec2_id(instance_id), 'requestId': context.request_id, 'status': volume['attach_status'], - 'volumeId': id_to_ec2_id(volume_id, 'vol-%s')} + 'volumeId': id_to_ec2_id(volume_id, 'vol-%08x')} def detach_volume(self, context, volume_id, **kwargs): + volume_id = ec2_id_to_id(volume_id) LOG.audit(_("Detach volume %s"), volume_id, context=context) volume = self.volume_api.get(context, volume_id) instance = self.compute_api.detach_volume(context, volume_id) @@ -624,7 +631,7 @@ class CloudController(object): 'instanceId': id_to_ec2_id(instance['id']), 'requestId': context.request_id, 'status': volume['attach_status'], - 'volumeId': id_to_ec2_id(volume_id, 'vol-%s')} + 'volumeId': id_to_ec2_id(volume_id, 'vol-%08x')} def _convert_to_set(self, lst, label): if lst == None or lst == []: diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index f5ab2330f..c751832ec 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -126,11 +126,10 @@ class CloudTestCase(test.TestCase): vol2 = db.volume_create(self.context, {}) result = self.cloud.describe_volumes(self.context) self.assertEqual(len(result['volumeSet']), 2) - result = self.cloud.describe_volumes( - self.context, - volume_id=[cloud.id_to_ec2_id(vol2['id'], 'vol-%s')]) + volume_id = cloud.id_to_ec2_id(vol2['id'], 'vol-%08x') + result = self.cloud.describe_volumes(self.context, volume_id=[volume_id]) self.assertEqual(len(result['volumeSet']), 1) - self.assertEqual(result['volumeSet'][0]['volumeId'], vol2['id']) + self.assertEqual(cloud.ec2_id_to_id(result['volumeSet'][0]['volumeId']), vol2['id']) db.volume_destroy(self.context, vol1['id']) db.volume_destroy(self.context, vol2['id']) @@ -387,7 +386,7 @@ class CloudTestCase(test.TestCase): def test_update_of_volume_display_fields(self): vol = db.volume_create(self.context, {}) self.cloud.update_volume(self.context, - id_to_ec2_id(vol['id'], 'vol-%s'), + cloud.id_to_ec2_id(vol['id'], 'vol-%08x'), display_name='c00l v0lum3') vol = db.volume_get(self.context, vol['id']) self.assertEqual('c00l v0lum3', vol['display_name']) @@ -396,7 +395,7 @@ class CloudTestCase(test.TestCase): def test_update_of_volume_wont_update_private_fields(self): vol = db.volume_create(self.context, {}) self.cloud.update_volume(self.context, - id_to_ec2_id(vol['id'], 'vol-%s'), + cloud.id_to_ec2_id(vol['id'], 'vol-%08x'), mountpoint='/not/here') vol = db.volume_get(self.context, vol['id']) self.assertEqual(None, vol['mountpoint']) -- cgit From 3df3c2359369fc74d78cb566545fbc54d0627c7e Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 12 Jan 2011 15:21:18 -0800 Subject: standardize volume ids --- nova/tests/test_cloud.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index c751832ec..2e350cd5a 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -127,9 +127,12 @@ class CloudTestCase(test.TestCase): result = self.cloud.describe_volumes(self.context) self.assertEqual(len(result['volumeSet']), 2) volume_id = cloud.id_to_ec2_id(vol2['id'], 'vol-%08x') - result = self.cloud.describe_volumes(self.context, volume_id=[volume_id]) + result = self.cloud.describe_volumes(self.context, + volume_id=[volume_id]) self.assertEqual(len(result['volumeSet']), 1) - self.assertEqual(cloud.ec2_id_to_id(result['volumeSet'][0]['volumeId']), vol2['id']) + self.assertEqual( + cloud.ec2_id_to_id(result['volumeSet'][0]['volumeId']), + vol2['id']) db.volume_destroy(self.context, vol1['id']) db.volume_destroy(self.context, vol2['id']) -- cgit From a46c753d8f65e948bd67f70a13544763c91645c4 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 12 Jan 2011 16:56:21 -0800 Subject: fix changed call to generate_rc --- nova/auth/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 6fb9b522f..de18a3838 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -721,7 +721,7 @@ class AuthManager(object): if project is None: project = user.id pid = Project.safe_id(project) - return self.__generate_rc(user.access, user.secret, pid, use_dmz) + return self.__generate_rc(user, pid, use_dmz) @staticmethod def __generate_rc(user, pid, use_dmz=True, host=None): -- cgit From 4eb2e469fc3780ff1399bd610a308bbdebdcfd1d Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 12 Jan 2011 17:12:20 -0800 Subject: fix invalid variable reference in cloud api --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 832426b94..5c25aa076 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -590,7 +590,7 @@ class CloudController(object): # TODO(vish): Instance should be None at db layer instead of # trying to lazy load, but for now we turn it into # a dict to avoid an error. - return {'volumeSet': [self._format_volume(context, dict(volume_ref))]} + return {'volumeSet': [self._format_volume(context, dict(volume))]} def delete_volume(self, context, volume_id, **kwargs): self.volume_api.delete(context, volume_id) -- cgit From f3332a1a63db657b84b52cf17ff46a853dfd063c Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Thu, 13 Jan 2011 01:25:08 +0000 Subject: Fixes #701055. Move instance termination code inline to prevent manager from prematurely marking it as destroyed. --- nova/virt/libvirt_conn.py | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index bd863b3a2..c03046703 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -197,40 +197,27 @@ class LibvirtConnection(object): pass # If the instance is already terminated, we're still happy - done = event.Event() - # We'll save this for when we do shutdown, # instead of destroy - but destroy returns immediately timer = utils.LoopingCall(f=None) - def _wait_for_shutdown(): + while True: try: state = self.get_info(instance['name'])['state'] db.instance_set_state(context.get_admin_context(), instance['id'], state) if state == power_state.SHUTDOWN: - timer.stop() + break except Exception: db.instance_set_state(context.get_admin_context(), instance['id'], power_state.SHUTDOWN) - timer.stop() + break + + if cleanup: + self._cleanup(instance) - timer.f = _wait_for_shutdown - timer_done = timer.start(interval=0.5, now=True) - - # NOTE(termie): this is strictly superfluous (we could put the - # cleanup code in the timer), but this emulates the - # previous model so I am keeping it around until - # everything has been vetted a bit - def _wait_for_timer(): - timer_done.wait() - if cleanup: - self._cleanup(instance) - done.send() - - greenthread.spawn(_wait_for_timer) - return done + return True def _cleanup(self, instance): target = os.path.join(FLAGS.instances_path, instance['name']) -- cgit