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

(cherry picked from commit 271fc68)

Conflicts:

	nova/virt/vmwareapi/fake.py

Change-Id: I48430cb9bc9615e02ca9af235f97853f3f0bdafd
  • Loading branch information
hartsock authored and gkotton committed Jul 25, 2013
1 parent 0b45996 commit 1ef0d96
Show file tree
Hide file tree
Showing 6 changed files with 314 additions and 17 deletions.
8 changes: 2 additions & 6 deletions nova/tests/test_vmwareapi.py
Expand Up @@ -71,7 +71,7 @@ def setUp(self):
vmwareapi_fake.reset()
db_fakes.stub_out_db_instance_api(self.stubs)
stubs.set_stubs(self.stubs)
self.conn = driver.VMwareESXDriver(None, False)
self.conn = driver.VMwareVCDriver(None, False)
# NOTE(vish): none of the network plugging code is actually
# being tested
self.network_info = utils.get_test_network_info(legacy_model=False)
Expand Down Expand Up @@ -379,14 +379,10 @@ def test_get_vnc_console_non_existent(self):
self.instance)

def test_get_vnc_console(self):
vm_ref = fake_vm_ref()
self._create_instance_in_the_db()
self._create_vm()
self.mox.StubOutWithMock(self.conn._vmops, '_get_vnc_port')
self.conn._vmops._get_vnc_port(mox.IgnoreArg()).AndReturn(5910)
self.mox.ReplayAll()
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/test_vmwareapi_vm_util.py
Expand Up @@ -15,6 +15,8 @@
# License for the specific language governing permissions and limitations
# under the License.

import collections

from nova import exception
from nova import test
from nova.virt.vmwareapi import fake
Expand All @@ -32,9 +34,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 @@ -53,3 +57,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 = collections.namedtuple('ObjectContent', ['propSet'])
DynamicProperty = collections.namedtuple('Property', ['name', 'val'])
MoRef = collections.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 @@ -126,6 +126,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 @@ -335,6 +341,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)
if not self._cluster_name:
Expand Down Expand Up @@ -391,6 +405,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
79 changes: 68 additions & 11 deletions nova/virt/vmwareapi/fake.py
Expand Up @@ -20,6 +20,7 @@
A fake VMware VI API implementation.
"""

import collections
import pprint
import uuid

Expand Down Expand Up @@ -81,22 +82,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."""

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
# 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):
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 = str(uuid.uuid4())
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 @@ -116,16 +167,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 @@ -185,7 +240,7 @@ class VirtualMachine(ManagedObject):
"""Virtual Machine class."""

def __init__(self, **kwargs):
super(VirtualMachine, self).__init__("VirtualMachine")
super(VirtualMachine, self).__init__("VirtualMachine", value='vm-10')
self.set("name", kwargs.get("name"))
self.set("runtime.connectionState",
kwargs.get("conn_state", "connected"))
Expand All @@ -203,6 +258,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 @@ -279,8 +336,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 1ef0d96

Please sign in to comment.