From 09c515ab68bbb5444554a8035cac93f261570a7d Mon Sep 17 00:00:00 2001 From: sateesh Date: Wed, 2 Mar 2011 00:38:50 +0530 Subject: Support for guest consoles for VMs running on VMware ESX/ESXi servers. Uses vmrc to provide the console access to guests. --- nova/console/vmrc.py | 126 +++++++++++++++++++++++++++++++++++++++ nova/console/vmrc_manager.py | 137 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 263 insertions(+) create mode 100644 nova/console/vmrc.py create mode 100644 nova/console/vmrc_manager.py (limited to 'nova/console') diff --git a/nova/console/vmrc.py b/nova/console/vmrc.py new file mode 100644 index 000000000..09f8067e5 --- /dev/null +++ b/nova/console/vmrc.py @@ -0,0 +1,126 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 Citrix Systems, Inc. +# Copyright 2011 OpenStack LLC. +# +# 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. + +""" +VMRC console drivers. +""" + +from nova import flags +from nova import log as logging +from nova.virt.vmwareapi import vim_util +from nova.virt.vmwareapi_conn import VMWareAPISession + +flags.DEFINE_integer('console_vmrc_port', + 443, + "port for VMware VMRC connections") +flags.DEFINE_integer('console_vmrc_error_retries', + 10, + "number of retries for retrieving VMRC information") + +FLAGS = flags.FLAGS + + +class VMRCConsole(object): + """VMRC console driver with ESX credentials.""" + + def __init__(self): + super(VMRCConsole, self).__init__() + + @property + def console_type(self): + return 'vmrc+credentials' + + def get_port(self, context): + """Get available port for consoles.""" + return FLAGS.console_vmrc_port + + def setup_console(self, context, console): + """Sets up console.""" + pass + + def teardown_console(self, context, console): + """Tears down console.""" + pass + + def init_host(self): + """Perform console initialization.""" + pass + + def fix_pool_password(self, password): + """Encode password.""" + #TODO:Encrypt pool password + return password + + def generate_password(self, address, username, password, instance_name): + """Returns a VMRC Connection credentials + Return string is of the form ':@'. + """ + vim_session = VMWareAPISession(address, + username, + password, + FLAGS.console_vmrc_error_retries) + vms = vim_session._call_method(vim_util, "get_objects", + "VirtualMachine", ["name"]) + vm_ref = None + for vm in vms: + if vm.propSet[0].val == instance_name: + vm_ref = vm.obj + if vm_ref is None: + raise Exception(_("instance - %s not present") % instance_name) + return str(vm_ref) + ":" + username + "@" + password + + def is_otp(self): + """Is one time password.""" + return False + + +class VMRCSessionConsole(VMRCConsole): + """VMRC console driver with VMRC One Time Sessions""" + + def __init__(self): + super(VMRCSessionConsole, self).__init__() + + @property + def console_type(self): + return 'vmrc+session' + + def generate_password(self, address, username, password, instance_name): + """Returns a VMRC Session + Return string is of the form ':'. + """ + vim_session = VMWareAPISession(address, + username, + password, + FLAGS.console_vmrc_error_retries) + vms = vim_session._call_method(vim_util, "get_objects", + "VirtualMachine", ["name"]) + vm_ref = None + for vm in vms: + if vm.propSet[0].val == instance_name: + vm_ref = vm.obj + if vm_ref is None: + raise Exception(_("instance - %s not present") % instance_name) + virtual_machine_ticket = \ + vim_session._call_method( + vim_session._get_vim(), + "AcquireCloneTicket", + vim_session._get_vim().get_service_content().sessionManager) + return str(vm_ref) + ":" + virtual_machine_ticket + + def is_otp(self): + """Is one time password.""" + return True diff --git a/nova/console/vmrc_manager.py b/nova/console/vmrc_manager.py new file mode 100644 index 000000000..24f7f5fe2 --- /dev/null +++ b/nova/console/vmrc_manager.py @@ -0,0 +1,137 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 Citrix Systems, Inc. +# Copyright 2011 OpenStack LLC. +# +# 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. + +""" +VMRC Console Manager +""" + +from nova import exception +from nova import flags +from nova import log as logging +from nova import manager +from nova import rpc +from nova import utils + +LOG = logging.getLogger("nova.console.vmrc_manager") + +FLAGS = flags.FLAGS +flags.DEFINE_string('console_public_hostname', + '', + 'Publicly visible name for this console host') +flags.DEFINE_string('console_driver', + 'nova.console.vmrc.VMRCConsole', + 'Driver to use for the console') + + +class ConsoleVMRCManager(manager.Manager): + + """Manager to handle VMRC connections needed for accessing + instance consoles + """ + + def __init__(self, console_driver=None, *args, **kwargs): + self.driver = utils.import_object(FLAGS.console_driver) + super(ConsoleVMRCManager, self).__init__(*args, **kwargs) + + def init_host(self): + self.driver.init_host() + + def _generate_console(self, context, pool, name, instance_id, instance): + LOG.debug(_("Adding console")) + password = self.driver.generate_password( + pool['address'], + pool['username'], + pool['password'], + instance.name) + console_data = {'instance_name': name, + 'instance_id': instance_id, + 'password': password, + 'pool_id': pool['id']} + console_data['port'] = self.driver.get_port(context) + console = self.db.console_create(context, console_data) + self.driver.setup_console(context, console) + return console + + @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) + if self.driver.is_otp(): + console = self._generate_console( + context, + pool, + name, + instance_id, + instance) + except exception.NotFound: + console = self._generate_console( + context, + pool, + name, + instance_id, + instance) + return console['id'] + + @exception.wrap_exception + def remove_console(self, context, console_id, **_kwargs): + try: + console = self.db.console_get(context, console_id) + except exception.NotFound: + LOG.debug(_("Tried to remove non-existent console " + "%(console_id)s.") % + {'console_id': console_id}) + return + LOG.debug(_("Removing console " + "%(console_id)s.") % + {'console_id': console_id}) + 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: + 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 + #ESX Address or Proxy Address + public_host_name = pool_info['address'] + if FLAGS.console_public_hostname: + public_host_name = FLAGS.console_public_hostname + pool_info['public_hostname'] = public_host_name + pool_info['console_type'] = console_type + pool_info['compute_host'] = instance_host + pool = self.db.console_pool_create(context, pool_info) + return pool -- cgit From 3e97dc47221d19b39aba99f6d389d2ec326e72be Mon Sep 17 00:00:00 2001 From: sateesh Date: Thu, 10 Mar 2011 21:07:44 +0530 Subject: Updated the code to detect the exception by fault type. SOAP faults are embedded in the SOAP response as a property. Certain faults are sent as a part of the SOAP body as property of missingSet. E.g. NotAuthenticated fault. So we examine the response object for missingSet and try to check the property for fault type. --- nova/console/vmrc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/console') diff --git a/nova/console/vmrc.py b/nova/console/vmrc.py index 09f8067e5..9c5e3b444 100644 --- a/nova/console/vmrc.py +++ b/nova/console/vmrc.py @@ -119,7 +119,7 @@ class VMRCSessionConsole(VMRCConsole): vim_session._get_vim(), "AcquireCloneTicket", vim_session._get_vim().get_service_content().sessionManager) - return str(vm_ref) + ":" + virtual_machine_ticket + return str(vm_ref.value) + ":" + virtual_machine_ticket def is_otp(self): """Is one time password.""" -- cgit From 7f28cb611c6bc802ad78242275b18bb0278e43bd Mon Sep 17 00:00:00 2001 From: sateesh Date: Fri, 11 Mar 2011 20:02:21 +0530 Subject: * Modified raise statements to raise nova defined Exceptions. * Fixed Console errors and in network utils using HostSystem instead of Datacenter to fetch network list * Added support for vmwareapi module in nova/virt/connection.py so that vmware hypervisor is supported by nova * Removing self.loop to achieve synchronization --- nova/console/vmrc.py | 50 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 18 deletions(-) (limited to 'nova/console') diff --git a/nova/console/vmrc.py b/nova/console/vmrc.py index 9c5e3b444..f448d094b 100644 --- a/nova/console/vmrc.py +++ b/nova/console/vmrc.py @@ -19,10 +19,13 @@ VMRC console drivers. """ +import base64 +import json + +from nova import exception from nova import flags from nova import log as logging from nova.virt.vmwareapi import vim_util -from nova.virt.vmwareapi_conn import VMWareAPISession flags.DEFINE_integer('console_vmrc_port', 443, @@ -65,23 +68,34 @@ class VMRCConsole(object): #TODO:Encrypt pool password return password - def generate_password(self, address, username, password, instance_name): + def generate_password(self, vim_session, pool, instance_name): """Returns a VMRC Connection credentials - Return string is of the form ':@'. + Return string is of the form ':@'. """ - vim_session = VMWareAPISession(address, - username, - password, - FLAGS.console_vmrc_error_retries) + username, password = pool['username'], pool['password'] vms = vim_session._call_method(vim_util, "get_objects", - "VirtualMachine", ["name"]) + "VirtualMachine", ["name", "config.files.vmPathName"]) + vm_ds_path_name = None vm_ref = None for vm in vms: - if vm.propSet[0].val == instance_name: + vm_name = None + ds_path_name = None + for prop in vm.propSet: + if prop.name == "name": + vm_name = prop.val + elif prop.name == "config.files.vmPathName": + ds_path_name = prop.val + if vm_name == instance_name: vm_ref = vm.obj + vm_ds_path_name = ds_path_name + break if vm_ref is None: - raise Exception(_("instance - %s not present") % instance_name) - return str(vm_ref) + ":" + username + "@" + password + raise exception.NotFound(_("instance - %s not present") % + instance_name) + json_data = json.dumps({"vm_id": vm_ds_path_name, + "username": username, + "password": password}) + return base64.b64encode(json_data) def is_otp(self): """Is one time password.""" @@ -98,14 +112,10 @@ class VMRCSessionConsole(VMRCConsole): def console_type(self): return 'vmrc+session' - def generate_password(self, address, username, password, instance_name): + def generate_password(self, vim_session, pool, instance_name): """Returns a VMRC Session Return string is of the form ':'. """ - vim_session = VMWareAPISession(address, - username, - password, - FLAGS.console_vmrc_error_retries) vms = vim_session._call_method(vim_util, "get_objects", "VirtualMachine", ["name"]) vm_ref = None @@ -113,13 +123,17 @@ class VMRCSessionConsole(VMRCConsole): if vm.propSet[0].val == instance_name: vm_ref = vm.obj if vm_ref is None: - raise Exception(_("instance - %s not present") % instance_name) + raise exception.NotFound(_("instance - %s not present") % + instance_name) virtual_machine_ticket = \ vim_session._call_method( vim_session._get_vim(), "AcquireCloneTicket", vim_session._get_vim().get_service_content().sessionManager) - return str(vm_ref.value) + ":" + virtual_machine_ticket + json_data = json.dumps({"vm_id": str(vm_ref.value), + "username": virtual_machine_ticket, + "password": virtual_machine_ticket}) + return base64.b64encode(json_data) def is_otp(self): """Is one time password.""" -- cgit From 45ca7b71a8e749cbd9b7729b922190e9aaa53744 Mon Sep 17 00:00:00 2001 From: sateesh Date: Wed, 16 Mar 2011 21:54:02 +0530 Subject: * Updated document vmware_readme.rst to mention VLAN networking * Corrected docstrings as per pep0257 recommentations. * Stream-lined the comments. * Updated code with locals() where ever applicable. * VIM : It stands for VMware Virtual Infrastructure Methodology. We have used the terminology from VMware. we have added a question in FAQ inside vmware_readme.rst in doc/source * New fake db: vmwareapi fake module uses a different set of fields and hence the structures required are different. Ex: bridge : 'xenbr0' does not hold good for VMware environment and bridge : 'vmnic0' is used instead. Also return values varies, hence went for implementing separate fake db. * Now using eventlet library instead and removed io_utils.py from branch. * Now using glance.client.Client instead of homegrown code to talk to Glance server to handle images. * Corrected all mis-spelled function names and corresponding calls. Yeah, an auto-complete side-effect! --- nova/console/vmrc.py | 16 ++++++++++------ nova/console/vmrc_manager.py | 35 ++++++++++++++++++++++++++++------- 2 files changed, 38 insertions(+), 13 deletions(-) (limited to 'nova/console') diff --git a/nova/console/vmrc.py b/nova/console/vmrc.py index f448d094b..521da289f 100644 --- a/nova/console/vmrc.py +++ b/nova/console/vmrc.py @@ -65,11 +65,13 @@ class VMRCConsole(object): def fix_pool_password(self, password): """Encode password.""" - #TODO:Encrypt pool password + # TODO(sateesh): Encrypt pool password return password def generate_password(self, vim_session, pool, instance_name): - """Returns a VMRC Connection credentials + """ + Returns VMRC Connection credentials. + Return string is of the form ':@'. """ username, password = pool['username'], pool['password'] @@ -98,12 +100,12 @@ class VMRCConsole(object): return base64.b64encode(json_data) def is_otp(self): - """Is one time password.""" + """Is one time password or not.""" return False class VMRCSessionConsole(VMRCConsole): - """VMRC console driver with VMRC One Time Sessions""" + """VMRC console driver with VMRC One Time Sessions.""" def __init__(self): super(VMRCSessionConsole, self).__init__() @@ -113,7 +115,9 @@ class VMRCSessionConsole(VMRCConsole): return 'vmrc+session' def generate_password(self, vim_session, pool, instance_name): - """Returns a VMRC Session + """ + Returns a VMRC Session. + Return string is of the form ':'. """ vms = vim_session._call_method(vim_util, "get_objects", @@ -136,5 +140,5 @@ class VMRCSessionConsole(VMRCConsole): return base64.b64encode(json_data) def is_otp(self): - """Is one time password.""" + """Is one time password or not.""" return True diff --git a/nova/console/vmrc_manager.py b/nova/console/vmrc_manager.py index 24f7f5fe2..09beac7a0 100644 --- a/nova/console/vmrc_manager.py +++ b/nova/console/vmrc_manager.py @@ -16,7 +16,7 @@ # under the License. """ -VMRC Console Manager +VMRC Console Manager. """ from nova import exception @@ -25,6 +25,7 @@ from nova import log as logging from nova import manager from nova import rpc from nova import utils +from nova.virt.vmwareapi_conn import VMWareAPISession LOG = logging.getLogger("nova.console.vmrc_manager") @@ -39,8 +40,8 @@ flags.DEFINE_string('console_driver', class ConsoleVMRCManager(manager.Manager): - """Manager to handle VMRC connections needed for accessing - instance consoles + """ + Manager to handle VMRC connections needed for accessing instance consoles. """ def __init__(self, console_driver=None, *args, **kwargs): @@ -48,15 +49,29 @@ class ConsoleVMRCManager(manager.Manager): super(ConsoleVMRCManager, self).__init__(*args, **kwargs) def init_host(self): + self.sessions = {} self.driver.init_host() + def _get_vim_session(self, pool): + """Get VIM session for the pool specified.""" + vim_session = None + if pool['id'] not in self.sessions.keys(): + vim_session = VMWareAPISession(pool['address'], + pool['username'], + pool['password'], + FLAGS.console_vmrc_error_retries) + self.sessions[pool['id']] = vim_session + return self.sessions[pool['id']] + def _generate_console(self, context, pool, name, instance_id, instance): + """Sets up console for the instance.""" LOG.debug(_("Adding console")) + password = self.driver.generate_password( - pool['address'], - pool['username'], - pool['password'], + self._get_vim_session(pool), + pool, instance.name) + console_data = {'instance_name': name, 'instance_id': instance_id, 'password': password, @@ -69,6 +84,10 @@ class ConsoleVMRCManager(manager.Manager): @exception.wrap_exception def add_console(self, context, instance_id, password=None, port=None, **kwargs): + """ + Adds a console for the instance. If it is one time password, then we + generate new console credentials. + """ instance = self.db.instance_get(context, instance_id) host = instance['host'] name = instance['name'] @@ -95,6 +114,7 @@ class ConsoleVMRCManager(manager.Manager): @exception.wrap_exception def remove_console(self, context, console_id, **_kwargs): + """Removes a console entry.""" try: console = self.db.console_get(context, console_id) except exception.NotFound: @@ -109,6 +129,7 @@ class ConsoleVMRCManager(manager.Manager): self.driver.teardown_console(context, console) def get_pool_for_instance_host(self, context, instance_host): + """Gets console pool info for the instance.""" context = context.elevated() console_type = self.driver.console_type try: @@ -126,7 +147,7 @@ class ConsoleVMRCManager(manager.Manager): pool_info['password'] = self.driver.fix_pool_password( pool_info['password']) pool_info['host'] = self.host - #ESX Address or Proxy Address + # ESX Address or Proxy Address public_host_name = pool_info['address'] if FLAGS.console_public_hostname: public_host_name = FLAGS.console_public_hostname -- cgit