summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRadoslav Gerganov <rgerganov@vmware.com>2013-11-28 13:37:53 +0200
committerGary Kotton <gkotton@vmware.com>2014-03-18 00:16:37 -0700
commit2edf6fffe1e7757dbeaa1a25f75f124687f4b232 (patch)
treee86a250894c89cca5c706a5442da5046b383934e
parent29a9aaab8ec1e49c85cb52be733051de27f10466 (diff)
downloadnova-2edf6fffe1e7757dbeaa1a25f75f124687f4b232.tar.gz
VMware: fix the VNC port allocation
There is small chance for VNC port collisions with the current implementation which choose the port number based on the MoRef id of the VM. This patch fixes this by running a query for all allocated ports and then selects one which is not taken. Closes-Bug: #1255609 (cherry picked from commit 2f49ed4b5dbb5c954fc7a9b42ee7b170c38c775c) Conflicts: nova/exception.py nova/tests/virt/vmwareapi/test_vmwareapi.py nova/tests/virt/vmwareapi/test_vmwareapi_vm_util.py nova/virt/vmwareapi/vm_util.py nova/virt/vmwareapi/vmops.py Change-Id: If7c3b14dd49ed05c5fde819c5a36d5608650cbbc
-rw-r--r--nova/exception.py5
-rwxr-xr-xnova/tests/virt/vmwareapi/test_vmwareapi.py17
-rwxr-xr-xnova/tests/virt/vmwareapi/test_vmwareapi_vm_util.py25
-rwxr-xr-xnova/virt/vmwareapi/vm_util.py46
-rwxr-xr-xnova/virt/vmwareapi/vmops.py20
5 files changed, 99 insertions, 14 deletions
diff --git a/nova/exception.py b/nova/exception.py
index b0b6909124..7c17803541 100644
--- a/nova/exception.py
+++ b/nova/exception.py
@@ -891,6 +891,11 @@ class InstanceTypeNotFoundByName(InstanceTypeNotFound):
"could not be found.")
+class ConsolePortRangeExhausted(NovaException):
+ msg_fmt = _("The console port range %(min_port)d-%(max_port)d is "
+ "exhausted.")
+
+
class FlavorNotFound(NotFound):
msg_fmt = _("Flavor %(flavor_id)s could not be found.")
diff --git a/nova/tests/virt/vmwareapi/test_vmwareapi.py b/nova/tests/virt/vmwareapi/test_vmwareapi.py
index f01439f45c..6950a681ee 100755
--- a/nova/tests/virt/vmwareapi/test_vmwareapi.py
+++ b/nova/tests/virt/vmwareapi/test_vmwareapi.py
@@ -21,6 +21,7 @@
Test suite for VMwareAPI.
"""
+import collections
import contextlib
import copy
@@ -977,11 +978,12 @@ class VMwareAPIVMTestCase(test.NoDBTestCase):
def _test_get_vnc_console(self):
self._create_vm()
fake_vm = vmwareapi_fake._get_objects("VirtualMachine").objects[0]
- fake_vm_id = int(fake_vm.obj.value.replace('vm-', ''))
+ OptionValue = collections.namedtuple('OptionValue', ['key', 'value'])
+ opt_val = OptionValue(key='', value=5906)
+ fake_vm.set(vm_util.VNC_CONFIG_KEY, opt_val)
vnc_dict = self.conn.get_vnc_console(self.instance)
- self.assertEquals(vnc_dict['host'], self.vnc_host)
- self.assertEquals(vnc_dict['port'], cfg.CONF.vmware.vnc_port +
- fake_vm_id % cfg.CONF.vmware.vnc_port_total)
+ self.assertEqual(vnc_dict['host'], self.vnc_host)
+ self.assertEqual(vnc_dict['port'], 5906)
def test_get_vnc_console(self):
self._test_get_vnc_console()
@@ -990,6 +992,13 @@ class VMwareAPIVMTestCase(test.NoDBTestCase):
self.flags(vnc_password='vmware', group='vmware')
self._test_get_vnc_console()
+ def test_get_vnc_console_noport(self):
+ self._create_vm()
+ fake_vm = vmwareapi_fake._get_objects("VirtualMachine").objects[0]
+ self.assertRaises(exception.ConsoleTypeUnavailable,
+ self.conn.get_vnc_console,
+ self.instance)
+
def test_host_ip_addr(self):
self.assertEquals(self.conn.get_host_ip_addr(), "test_url")
diff --git a/nova/tests/virt/vmwareapi/test_vmwareapi_vm_util.py b/nova/tests/virt/vmwareapi/test_vmwareapi_vm_util.py
index a8cdce7bde..ebd495011f 100755
--- a/nova/tests/virt/vmwareapi/test_vmwareapi_vm_util.py
+++ b/nova/tests/virt/vmwareapi/test_vmwareapi_vm_util.py
@@ -373,6 +373,31 @@ class VMwareVMUtilTestCase(test.NoDBTestCase):
result = re.sub(r'\s+', '', repr(result))
self.assertEqual(expected, result)
+ def _create_fake_vms(self):
+ fake_vms = fake.FakeRetrieveResult()
+ OptionValue = collections.namedtuple('OptionValue', ['key', 'value'])
+ for i in range(10):
+ vm = fake.ManagedObject()
+ opt_val = OptionValue(key='', value=5900 + i)
+ vm.set(vm_util.VNC_CONFIG_KEY, opt_val)
+ fake_vms.add_object(vm)
+ return fake_vms
+
+ def test_get_vnc_port(self):
+ fake_vms = self._create_fake_vms()
+ self.flags(vnc_port=5900, group='vmware')
+ self.flags(vnc_port_total=10000, group='vmware')
+ actual = vm_util.get_vnc_port(fake_session(fake_vms))
+ self.assertEqual(actual, 5910)
+
+ def test_get_vnc_port_exhausted(self):
+ fake_vms = self._create_fake_vms()
+ self.flags(vnc_port=5900, group='vmware')
+ self.flags(vnc_port_total=10, group='vmware')
+ self.assertRaises(exception.ConsolePortRangeExhausted,
+ vm_util.get_vnc_port,
+ fake_session(fake_vms))
+
def test_get_all_cluster_refs_by_name_none(self):
fake_objects = fake.FakeRetrieveResult()
refs = vm_util.get_all_cluster_refs_by_name(fake_session(fake_objects),
diff --git a/nova/virt/vmwareapi/vm_util.py b/nova/virt/vmwareapi/vm_util.py
index 981dcdfecd..e7031f5c17 100755
--- a/nova/virt/vmwareapi/vm_util.py
+++ b/nova/virt/vmwareapi/vm_util.py
@@ -23,13 +23,20 @@ The VMware API VM utility module to build SOAP object specs.
import collections
import copy
+from oslo.config import cfg
+
from nova import exception
from nova.openstack.common.gettextutils import _
from nova.openstack.common import log as logging
+from nova import utils
from nova.virt.vmwareapi import vim_util
+CONF = cfg.CONF
LOG = logging.getLogger(__name__)
+# the config key which stores the VNC port
+VNC_CONFIG_KEY = 'config.extraConfig["RemoteDisplay.vnc.port"]'
+
def build_datastore_path(datastore_name, path):
"""Build the datastore compliant path."""
@@ -600,6 +607,45 @@ def get_vnc_config_spec(client_factory, port, password):
return virtual_machine_config_spec
+@utils.synchronized('vmware.get_vnc_port')
+def get_vnc_port(session):
+ """Return VNC port for an VM or None if there is no available port."""
+ min_port = CONF.vmware.vnc_port
+ port_total = CONF.vmware.vnc_port_total
+ allocated_ports = _get_allocated_vnc_ports(session)
+ max_port = min_port + port_total
+ for port in range(min_port, max_port):
+ if port not in allocated_ports:
+ return port
+ raise exception.ConsolePortRangeExhausted(min_port=min_port,
+ max_port=max_port)
+
+
+def _get_allocated_vnc_ports(session):
+ """Return an integer set of all allocated VNC ports."""
+ # TODO(rgerganov): bug #1256944
+ # The VNC port should be unique per host, not per vCenter
+ vnc_ports = set()
+ result = session._call_method(vim_util, "get_objects",
+ "VirtualMachine", [VNC_CONFIG_KEY])
+ while result:
+ for obj in result.objects:
+ if not hasattr(obj, 'propSet'):
+ continue
+ dynamic_prop = obj.propSet[0]
+ option_value = dynamic_prop.val
+ vnc_port = option_value.value
+ vnc_ports.add(int(vnc_port))
+ token = _get_token(result)
+ if token:
+ result = session._call_method(vim_util,
+ "continue_to_get_objects",
+ token)
+ else:
+ break
+ return vnc_ports
+
+
def search_datastore_spec(client_factory, file_name):
"""Builds the datastore search spec."""
search_spec = client_factory.create('ns0:HostDatastoreBrowserSearchSpec')
diff --git a/nova/virt/vmwareapi/vmops.py b/nova/virt/vmwareapi/vmops.py
index 9489cd5a81..c13a0b301c 100755
--- a/nova/virt/vmwareapi/vmops.py
+++ b/nova/virt/vmwareapi/vmops.py
@@ -309,8 +309,8 @@ class VMwareVMOps(object):
# Set the vnc configuration of the instance, vnc port starts from 5900
if CONF.vnc_enabled:
- vnc_port = self._get_vnc_port(vm_ref)
vnc_pass = CONF.vmware.vnc_password or ''
+ vnc_port = vm_util.get_vnc_port(self._session)
self._set_vnc_config(client_factory, instance, vnc_port, vnc_pass)
def _create_virtual_disk():
@@ -1361,9 +1361,17 @@ class VMwareVMOps(object):
def get_vnc_console(self, instance):
"""Return connection info for a vnc console."""
vm_ref = vm_util.get_vm_ref(self._session, instance)
+ opt_value = self._session._call_method(vim_util,
+ 'get_dynamic_property',
+ vm_ref, 'VirtualMachine',
+ vm_util.VNC_CONFIG_KEY)
+ if opt_value:
+ port = int(opt_value.value)
+ else:
+ raise exception.ConsoleTypeUnavailable(console_type='vnc')
return {'host': CONF.vmware.host_ip,
- 'port': self._get_vnc_port(vm_ref),
+ 'port': port,
'internal_access_path': None}
def get_vnc_console_vcenter(self, instance):
@@ -1387,14 +1395,6 @@ class VMwareVMOps(object):
return vnc_console
@staticmethod
- def _get_vnc_port(vm_ref):
- """Return VNC port for an VM."""
- vm_id = int(vm_ref.value.replace('vm-', ''))
- port = CONF.vmware.vnc_port + vm_id % CONF.vmware.vnc_port_total
-
- return port
-
- @staticmethod
def _get_machine_id_str(network_info):
machine_id_str = ''
for vif in network_info: