Skip to content

Commit

Permalink
VNC console does not work with VCDriver
Browse files Browse the repository at this point in the history
Introduces get_vnc_console_vcenter as a way to get
a modified vnc_console object that connects to the
ESXi host underneath vCenter control.

* adds new fake classes for testing
* documents classes, methods & API logic

fixes bug: #1178369

Change-Id: I48430cb9bc9615e02ca9af235f97853f3f0bdafd
  • Loading branch information
hartsock committed Jun 28, 2013
1 parent dc23e94 commit 271fc68
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 13 deletions.
2 changes: 1 addition & 1 deletion nova/tests/virt/vmwareapi/test_vmwareapi.py
Expand Up @@ -525,7 +525,7 @@ def test_get_vnc_console(self):
self._create_instance_in_the_db()
self._create_vm()
vnc_dict = self.conn.get_vnc_console(self.instance)
self.assertEquals(vnc_dict['host'], "test_url")
self.assertEquals(vnc_dict['host'], "ha-host")
self.assertEquals(vnc_dict['port'], 5910)

def test_host_ip_addr(self):
Expand Down
96 changes: 96 additions & 0 deletions nova/tests/virt/vmwareapi/test_vmwareapi_vm_util.py
Expand Up @@ -16,6 +16,8 @@
# License for the specific language governing permissions and limitations
# under the License.

from collections import namedtuple

from nova import exception
from nova import test
from nova.virt.vmwareapi import fake
Expand All @@ -33,9 +35,11 @@ def _call_method(self, *args):
class VMwareVMUtilTestCase(test.TestCase):
def setUp(self):
super(VMwareVMUtilTestCase, self).setUp()
fake.reset()

def tearDown(self):
super(VMwareVMUtilTestCase, self).tearDown()
fake.reset()

def test_get_datastore_ref_and_name(self):
result = vm_util.get_datastore_ref_and_name(
Expand All @@ -54,3 +58,95 @@ def test_get_datastore_ref_and_name_without_datastore(self):
self.assertRaises(exception.DatastoreNotFound,
vm_util.get_datastore_ref_and_name,
fake_session(), cluster="fake-cluster")

def test_get_host_ref_from_id(self):

fake_host_sys = fake.HostSystem(
fake.ManagedObjectReference("HostSystem", "host-123"))

fake_host_id = fake_host_sys.obj.value
fake_host_name = "ha-host"

ref = vm_util.get_host_ref_from_id(
fake_session([fake_host_sys]), fake_host_id, ['name'])

self.assertIsInstance(ref, fake.HostSystem)
self.assertEqual(fake_host_id, ref.obj.value)

host_name = vm_util.get_host_name_from_host_ref(ref)

self.assertEquals(fake_host_name, host_name)

def test_get_host_name_for_vm(self):

fake_vm = fake.ManagedObject(
"VirtualMachine", fake.ManagedObjectReference(
"vm-123", "VirtualMachine"))
fake_vm.propSet.append(
fake.Property('name', 'vm-123'))

vm_ref = vm_util.get_vm_ref_from_name(
fake_session([fake_vm]), 'vm-123')

self.assertIsNotNone(vm_ref)

fake_results = [
fake.ObjectContent(
None, [
fake.Property('runtime.host',
fake.ManagedObjectReference(
'host-123', 'HostSystem'))
])]

host_id = vm_util.get_host_id_from_vm_ref(
fake_session(fake_results), vm_ref)

self.assertEqual('host-123', host_id)

def test_property_from_property_set(self):

ObjectContent = namedtuple('ObjectContent', ['propSet'])
DynamicProperty = namedtuple('Property', ['name', 'val'])
MoRef = namedtuple('Val', ['value'])

results_good = [
ObjectContent(propSet=[
DynamicProperty(name='name', val=MoRef(value='vm-123'))]),
ObjectContent(propSet=[
DynamicProperty(name='foo', val=MoRef(value='bar1')),
DynamicProperty(
name='runtime.host', val=MoRef(value='host-123')),
DynamicProperty(name='foo', val=MoRef(value='bar2')),
]),
ObjectContent(propSet=[
DynamicProperty(
name='something', val=MoRef(value='thing'))]), ]

results_bad = [
ObjectContent(propSet=[
DynamicProperty(name='name', val=MoRef(value='vm-123'))]),
ObjectContent(propSet=[
DynamicProperty(name='foo', val='bar1'),
DynamicProperty(name='foo', val='bar2'), ]),
ObjectContent(propSet=[
DynamicProperty(
name='something', val=MoRef(value='thing'))]), ]

prop = vm_util.property_from_property_set(
'runtime.host', results_good)
self.assertIsNotNone(prop)
value = prop.val.value
self.assertEqual('host-123', value)

prop2 = vm_util.property_from_property_set(
'runtime.host', results_bad)
self.assertIsNone(prop2)

prop3 = vm_util.property_from_property_set('foo', results_good)
self.assertIsNotNone(prop3)
val3 = prop3.val.value
self.assertEqual('bar1', val3)

prop4 = vm_util.property_from_property_set('foo', results_bad)
self.assertIsNotNone(prop4)
self.assertEqual('bar1', prop4.val)
22 changes: 22 additions & 0 deletions nova/virt/vmwareapi/driver.py
Expand Up @@ -127,6 +127,12 @@ def __str__(self):
class VMwareESXDriver(driver.ComputeDriver):
"""The ESX host connection object."""

# VMwareAPI has both ESXi and vCenter API sets.
# The ESXi API are a proper sub-set of the vCenter API.
# That is to say, nearly all valid ESXi calls are
# valid vCenter calls. There are some small edge-case
# exceptions regarding VNC, CIM, User management & SSO.

def __init__(self, virtapi, read_only=False, scheme="https"):
super(VMwareESXDriver, self).__init__(virtapi)

Expand Down Expand Up @@ -337,6 +343,14 @@ def unplug_vifs(self, instance, network_info):
class VMwareVCDriver(VMwareESXDriver):
"""The ESX host connection object."""

# The vCenter driver includes several additional VMware vSphere
# capabilities that include API that act on hosts or groups of
# hosts in clusters or non-cluster logical-groupings.
#
# vCenter is not a hypervisor itself, it works with multiple
# hypervisor host machines and their guests. This fact can
# subtly alter how vSphere and OpenStack interoperate.

def __init__(self, virtapi, read_only=False, scheme="https"):
super(VMwareVCDriver, self).__init__(virtapi)
self._cluster_name = CONF.vmwareapi_cluster_name
Expand Down Expand Up @@ -397,6 +411,14 @@ def live_migration(self, context, instance_ref, dest,
post_method, recover_method,
block_migration)

def get_vnc_console(self, instance):
"""Return link to instance's VNC console using vCenter logic."""
# In this situation, ESXi and vCenter require different
# API logic to create a valid VNC console connection object.
# In specific, vCenter does not actually run the VNC service
# itself. You must talk to the VNC host underneath vCenter.
return self._vmops.get_vnc_console_vcenter(instance)


class VMwareAPISession(object):
"""
Expand Down
75 changes: 63 additions & 12 deletions nova/virt/vmwareapi/fake.py
Expand Up @@ -21,6 +21,7 @@
A fake VMware VI API implementation.
"""

import collections
import pprint
import uuid

Expand Down Expand Up @@ -83,28 +84,72 @@ def _get_objects(obj_type):
return lst_objs


class Prop(object):
class Property(object):
"""Property Object base class."""

def __init__(self):
self.name = None
self.val = None
def __init__(self, name=None, val=None):
self.name = name
self.val = val


class ManagedObjectReference(object):
"""A managed object reference is a remote identifier."""

class Obj(object):
def __init__(self, name, value):
def __init__(self, value="object-123", _type="ManagedObject"):
super(ManagedObjectReference, self)
# Managed Object Reference value attributes
# typically have values like vm-123 or
# host-232 and not UUID.
self.value = value
self._type = name
# Managed Object Reference _type
# attributes hold the name of the type
# of the vCenter object the value
# attribute is the identifier for
self._type = _type


class ObjectContent(object):
"""ObjectContent array holds dynamic properties."""

# This class is a *fake* of a class sent back to us by
# SOAP. It has its own names. These names are decided
# for us by the API we are *faking* here.
def __init__(self, obj_ref, prop_list=None, missing_list=None):
self.obj = obj_ref

if not isinstance(prop_list, collections.Iterable):
prop_list = []

if not isinstance(missing_list, collections.Iterable):
missing_list = []

# propSet is the name your Python code will need to
# use since this is the name that the API will use
self.propSet = prop_list

# missingSet is the name your python code will
# need to use since this is the name that the
# API we are talking to will use.
self.missingSet = missing_list


class ManagedObject(object):
"""Managed Data Object base class."""
"""Managed Object base class."""

def __init__(self, name="ManagedObject", obj_ref=None, value=None):
"""Sets the obj property which acts as a reference to the object."""
super(ManagedObject, self).__setattr__('objName', name)

# A managed object is a local representation of a
# remote object that you can reference using the
# object reference.
if obj_ref is None:
obj_ref = Obj(name, value)
if value is None:
value = 'obj-123'
obj_ref = ManagedObjectReference(value, name)

# we use __setattr__ here because below the
# default setter has been altered for this class.
object.__setattr__(self, 'obj', obj_ref)
object.__setattr__(self, 'propSet', [])

Expand All @@ -124,16 +169,20 @@ def get(self, attr):
return self.__getattr__(attr)

def __setattr__(self, attr, val):
# TODO(hartsocks): this is adds unnecessary complexity to the class
for prop in self.propSet:
if prop.name == attr:
prop.val = val
return
elem = Prop()
elem = Property()
elem.name = attr
elem.val = val
self.propSet.append(elem)

def __getattr__(self, attr):
# TODO(hartsocks): remove this
# in a real ManagedObject you have to iterate the propSet
# in a real ManagedObject, the propSet is a *set* not a list
for elem in self.propSet:
if elem.name == attr:
return elem.val
Expand Down Expand Up @@ -215,6 +264,8 @@ def __init__(self, **kwargs):
self.set("summary.config.memorySizeMB", kwargs.get("mem", 1))
self.set("config.hardware.device", kwargs.get("virtual_device", None))
self.set("config.extraConfig", kwargs.get("extra_config", None))
self.set('runtime.host',
ManagedObjectReference(value='host-123', _type="HostSystem"))
self.device = kwargs.get("virtual_device")

def reconfig(self, factory, val):
Expand Down Expand Up @@ -310,8 +361,8 @@ def __init__(self):
class HostSystem(ManagedObject):
"""Host System class."""

def __init__(self):
super(HostSystem, self).__init__("HostSystem")
def __init__(self, obj_ref=None, value='host-123'):
super(HostSystem, self).__init__("HostSystem", obj_ref, value)
self.set("name", "ha-host")
if _db_content.get("HostNetworkSystem", None) is None:
create_host_network_system()
Expand Down

0 comments on commit 271fc68

Please sign in to comment.