virt: setup TCP chardevice in libvirt driver
Setup the tcp chardevice in the libvirt driver based on the configuration (if the serial consoles are enabled) and disable the logging to a file since we have an interactive console. A new image property is introduced: hw_serial_port_count A new flavor extra spec is introduced: hw:serial_port_count Co-Authored-By: Vladan Popovic <vpopovic@redhat.com> Co-Authored-By: Ian Wells <iawells@cisco.com> Co-Authored-By: Sushma Korati <sushma_korati@persistent.co.in> DocImpact Partial-Implements: blueprint serial-ports Change-Id: Ic6d24e27d61f600a90ed8fccb525d8b82725903e
This commit is contained in:
parent
8a23aad0b4
commit
9cf0c413d4
@ -1717,3 +1717,13 @@ class SocketPortRangeExhaustedException(NovaException):
|
||||
|
||||
class SocketPortInUseException(NovaException):
|
||||
msg_fmt = _("Not able to bind %(host)s:%(port)d, %(error)s")
|
||||
|
||||
|
||||
class ImageSerialPortNumberInvalid(Invalid):
|
||||
msg_fmt = _("Number of serial ports '%(num_ports)s' specified in "
|
||||
"'%(property)s' isn't valid.")
|
||||
|
||||
|
||||
class ImageSerialPortNumberExceedFlavorValue(Invalid):
|
||||
msg_fmt = _("Forbidden to exceed flavor value of number of serial "
|
||||
"ports passed in image meta.")
|
||||
|
@ -1529,6 +1529,221 @@ class LibvirtConnTestCase(test.TestCase,
|
||||
self.assertEqual(cfg.devices[5].type, "spice")
|
||||
self.assertEqual(cfg.devices[6].type, "qxl")
|
||||
|
||||
@mock.patch('nova.console.serial.acquire_port')
|
||||
def test_get_guest_config_serial_console(self, acquire_port):
|
||||
self.flags(enabled=True, group='serial_console')
|
||||
|
||||
conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||
instance_ref = db.instance_create(self.context, self.test_instance)
|
||||
|
||||
disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
|
||||
instance_ref)
|
||||
|
||||
acquire_port.return_value = 11111
|
||||
|
||||
cfg = conn._get_guest_config(instance_ref, [], {}, disk_info)
|
||||
self.assertEqual(7, len(cfg.devices))
|
||||
self.assertIsInstance(cfg.devices[0],
|
||||
vconfig.LibvirtConfigGuestDisk)
|
||||
self.assertIsInstance(cfg.devices[1],
|
||||
vconfig.LibvirtConfigGuestDisk)
|
||||
self.assertIsInstance(cfg.devices[2],
|
||||
vconfig.LibvirtConfigGuestSerial)
|
||||
self.assertIsInstance(cfg.devices[3],
|
||||
vconfig.LibvirtConfigGuestSerial)
|
||||
self.assertIsInstance(cfg.devices[4],
|
||||
vconfig.LibvirtConfigGuestInput)
|
||||
self.assertIsInstance(cfg.devices[5],
|
||||
vconfig.LibvirtConfigGuestGraphics)
|
||||
self.assertIsInstance(cfg.devices[6],
|
||||
vconfig.LibvirtConfigGuestVideo)
|
||||
|
||||
self.assertEqual("tcp", cfg.devices[2].type)
|
||||
self.assertEqual(11111, cfg.devices[2].listen_port)
|
||||
|
||||
def test_get_guest_config_serial_console_through_flavor(self):
|
||||
self.flags(enabled=True, group='serial_console')
|
||||
|
||||
conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||
|
||||
fake_flavor = objects.Flavor.get_by_id(
|
||||
self.context, self.test_instance['instance_type_id'])
|
||||
fake_flavor.extra_specs = {'hw:serial_port_count': 3}
|
||||
|
||||
instance_ref = db.instance_create(self.context, self.test_instance)
|
||||
|
||||
disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
|
||||
instance_ref)
|
||||
with mock.patch.object(objects.Flavor, 'get_by_id',
|
||||
return_value=fake_flavor):
|
||||
cfg = conn._get_guest_config(instance_ref, [], {}, disk_info)
|
||||
self.assertEqual(9, len(cfg.devices))
|
||||
self.assertIsInstance(cfg.devices[0],
|
||||
vconfig.LibvirtConfigGuestDisk)
|
||||
self.assertIsInstance(cfg.devices[1],
|
||||
vconfig.LibvirtConfigGuestDisk)
|
||||
self.assertIsInstance(cfg.devices[2],
|
||||
vconfig.LibvirtConfigGuestSerial)
|
||||
self.assertIsInstance(cfg.devices[3],
|
||||
vconfig.LibvirtConfigGuestSerial)
|
||||
self.assertIsInstance(cfg.devices[4],
|
||||
vconfig.LibvirtConfigGuestSerial)
|
||||
self.assertIsInstance(cfg.devices[5],
|
||||
vconfig.LibvirtConfigGuestSerial)
|
||||
self.assertIsInstance(cfg.devices[6],
|
||||
vconfig.LibvirtConfigGuestInput)
|
||||
self.assertIsInstance(cfg.devices[7],
|
||||
vconfig.LibvirtConfigGuestGraphics)
|
||||
self.assertIsInstance(cfg.devices[8],
|
||||
vconfig.LibvirtConfigGuestVideo)
|
||||
|
||||
self.assertEqual("tcp", cfg.devices[2].type)
|
||||
self.assertEqual("tcp", cfg.devices[3].type)
|
||||
self.assertEqual("tcp", cfg.devices[4].type)
|
||||
|
||||
def test_get_guest_config_serial_console_through_invalid_flavor(self):
|
||||
self.flags(enabled=True, group='serial_console')
|
||||
|
||||
conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||
|
||||
fake_flavor = objects.Flavor.get_by_id(
|
||||
self.context, self.test_instance['instance_type_id'])
|
||||
fake_flavor.extra_specs = {'hw:serial_port_count': "a"}
|
||||
|
||||
instance_ref = db.instance_create(self.context, self.test_instance)
|
||||
|
||||
disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
|
||||
instance_ref)
|
||||
with mock.patch.object(objects.Flavor, 'get_by_id',
|
||||
return_value=fake_flavor):
|
||||
self.assertRaises(
|
||||
exception.ImageSerialPortNumberInvalid,
|
||||
conn._get_guest_config, instance_ref, [], {}, disk_info)
|
||||
|
||||
def test_get_guest_config_serial_console_image_meta_and_flavor(self):
|
||||
self.flags(enabled=True, group='serial_console')
|
||||
|
||||
conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||
fake_flavor = objects.Flavor.get_by_id(
|
||||
self.context, self.test_instance['instance_type_id'])
|
||||
fake_flavor.extra_specs = {'hw:serial_port_count': 4}
|
||||
|
||||
image_meta = {"properties": {"hw_serial_port_count": "3"}}
|
||||
instance_ref = db.instance_create(self.context, self.test_instance)
|
||||
|
||||
disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
|
||||
instance_ref)
|
||||
with mock.patch.object(objects.Flavor, 'get_by_id',
|
||||
return_value=fake_flavor):
|
||||
cfg = conn._get_guest_config(instance_ref, [], image_meta,
|
||||
disk_info)
|
||||
self.assertEqual(9, len(cfg.devices), cfg.devices)
|
||||
self.assertIsInstance(cfg.devices[0],
|
||||
vconfig.LibvirtConfigGuestDisk)
|
||||
self.assertIsInstance(cfg.devices[1],
|
||||
vconfig.LibvirtConfigGuestDisk)
|
||||
self.assertIsInstance(cfg.devices[2],
|
||||
vconfig.LibvirtConfigGuestSerial)
|
||||
self.assertIsInstance(cfg.devices[3],
|
||||
vconfig.LibvirtConfigGuestSerial)
|
||||
self.assertIsInstance(cfg.devices[4],
|
||||
vconfig.LibvirtConfigGuestSerial)
|
||||
self.assertIsInstance(cfg.devices[5],
|
||||
vconfig.LibvirtConfigGuestSerial)
|
||||
self.assertIsInstance(cfg.devices[6],
|
||||
vconfig.LibvirtConfigGuestInput)
|
||||
self.assertIsInstance(cfg.devices[7],
|
||||
vconfig.LibvirtConfigGuestGraphics)
|
||||
self.assertIsInstance(cfg.devices[8],
|
||||
vconfig.LibvirtConfigGuestVideo)
|
||||
|
||||
self.assertEqual("tcp", cfg.devices[2].type)
|
||||
self.assertEqual("tcp", cfg.devices[3].type)
|
||||
self.assertEqual("tcp", cfg.devices[4].type)
|
||||
|
||||
def test_get_guest_config_serial_console_through_invalid_img_meta(self):
|
||||
self.flags(enabled=True, group='serial_console')
|
||||
|
||||
conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||
instance_ref = db.instance_create(self.context, self.test_instance)
|
||||
|
||||
disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
|
||||
instance_ref)
|
||||
image_meta = {"properties": {"hw_serial_port_count": "fail"}}
|
||||
self.assertRaises(
|
||||
exception.ImageSerialPortNumberInvalid,
|
||||
conn._get_guest_config, instance_ref, [], image_meta, disk_info)
|
||||
|
||||
@mock.patch('nova.console.serial.acquire_port')
|
||||
def test_get_guest_config_serial_console_through_port_rng_exhausted(
|
||||
self, acquire_port):
|
||||
self.flags(enabled=True, group='serial_console')
|
||||
|
||||
conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||
instance_ref = db.instance_create(self.context, self.test_instance)
|
||||
|
||||
disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
|
||||
instance_ref)
|
||||
|
||||
acquire_port.side_effect = exception.SocketPortRangeExhaustedException(
|
||||
'127.0.0.1')
|
||||
self.assertRaises(
|
||||
exception.SocketPortRangeExhaustedException,
|
||||
conn._get_guest_config, instance_ref, [], {}, disk_info)
|
||||
|
||||
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._lookup_by_name')
|
||||
def test_get_serial_ports_from_instance(self, _lookup_by_name):
|
||||
i = self._test_get_serial_ports_from_instance(_lookup_by_name)
|
||||
self.assertEqual([
|
||||
('127.0.0.1', 100),
|
||||
('127.0.0.1', 101),
|
||||
('127.0.0.2', 100),
|
||||
('127.0.0.2', 101)], list(i))
|
||||
|
||||
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._lookup_by_name')
|
||||
def test_get_serial_ports_from_instance_bind_only(self, _lookup_by_name):
|
||||
i = self._test_get_serial_ports_from_instance(
|
||||
_lookup_by_name, mode='bind')
|
||||
self.assertEqual([
|
||||
('127.0.0.1', 101),
|
||||
('127.0.0.2', 100)], list(i))
|
||||
|
||||
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._lookup_by_name')
|
||||
def test_get_serial_ports_from_instance_connect_only(self,
|
||||
_lookup_by_name):
|
||||
i = self._test_get_serial_ports_from_instance(
|
||||
_lookup_by_name, mode='connect')
|
||||
self.assertEqual([
|
||||
('127.0.0.1', 100),
|
||||
('127.0.0.2', 101)], list(i))
|
||||
|
||||
def _test_get_serial_ports_from_instance(self, _lookup_by_name, mode=None):
|
||||
xml = """
|
||||
<domain type='kvm'>
|
||||
<devices>
|
||||
<serial type="tcp">
|
||||
<source host="127.0.0.1" service="100" mode="connect"/>
|
||||
</serial>
|
||||
<serial type="tcp">
|
||||
<source host="127.0.0.1" service="101" mode="bind"/>
|
||||
</serial>
|
||||
<serial type="tcp">
|
||||
<source host="127.0.0.2" service="100" mode="bind"/>
|
||||
</serial>
|
||||
<serial type="tcp">
|
||||
<source host="127.0.0.2" service="101" mode="connect"/>
|
||||
</serial>
|
||||
</devices>
|
||||
</domain>"""
|
||||
|
||||
dom = mock.MagicMock()
|
||||
dom.XMLDesc.return_value = xml
|
||||
_lookup_by_name.return_value = dom
|
||||
|
||||
conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||
return conn._get_serial_ports_from_instance(
|
||||
{'name': 'fake_instance'}, mode=mode)
|
||||
|
||||
def test_get_guest_config_with_type_xen(self):
|
||||
self.flags(vnc_enabled=True)
|
||||
self.flags(virt_type='xen',
|
||||
|
@ -1077,3 +1077,43 @@ class NUMATopologyTest(test.NoDBTestCase):
|
||||
|
||||
for exp_cell, got_cell in zip(expected.cells, got.cells):
|
||||
self.assertNUMACellMatches(exp_cell, got_cell)
|
||||
|
||||
|
||||
class NumberOfSerialPortsTest(test.NoDBTestCase):
|
||||
def test_flavor(self):
|
||||
flavor = FakeFlavor(8, 2048, {"hw:serial_port_count": 3})
|
||||
num_ports = hw.get_number_of_serial_ports(flavor, None)
|
||||
self.assertEqual(3, num_ports)
|
||||
|
||||
def test_image_meta(self):
|
||||
flavor = FakeFlavor(8, 2048, {})
|
||||
image_meta = {"properties": {"hw_serial_port_count": 2}}
|
||||
num_ports = hw.get_number_of_serial_ports(flavor, image_meta)
|
||||
self.assertEqual(2, num_ports)
|
||||
|
||||
def test_flavor_invalid_value(self):
|
||||
flavor = FakeFlavor(8, 2048, {"hw:serial_port_count": 'foo'})
|
||||
image_meta = {"properties": {}}
|
||||
self.assertRaises(exception.ImageSerialPortNumberInvalid,
|
||||
hw.get_number_of_serial_ports,
|
||||
flavor, image_meta)
|
||||
|
||||
def test_image_meta_invalid_value(self):
|
||||
flavor = FakeFlavor(8, 2048, {})
|
||||
image_meta = {"properties": {"hw_serial_port_count": 'bar'}}
|
||||
self.assertRaises(exception.ImageSerialPortNumberInvalid,
|
||||
hw.get_number_of_serial_ports,
|
||||
flavor, image_meta)
|
||||
|
||||
def test_image_meta_smaller_than_flavor(self):
|
||||
flavor = FakeFlavor(8, 2048, {"hw:serial_port_count": 3})
|
||||
image_meta = {"properties": {"hw_serial_port_count": 2}}
|
||||
num_ports = hw.get_number_of_serial_ports(flavor, image_meta)
|
||||
self.assertEqual(2, num_ports)
|
||||
|
||||
def test_flavor_smaller_than_image_meta(self):
|
||||
flavor = FakeFlavor(8, 2048, {"hw:serial_port_count": 3})
|
||||
image_meta = {"properties": {"hw_serial_port_count": 4}}
|
||||
self.assertRaises(exception.ImageSerialPortNumberExceedFlavorValue,
|
||||
hw.get_number_of_serial_ports,
|
||||
flavor, image_meta)
|
||||
|
@ -140,6 +140,52 @@ def format_cpu_spec(cpuset, allow_ranges=True):
|
||||
return ",".join(str(id) for id in sorted(cpuset))
|
||||
|
||||
|
||||
def get_number_of_serial_ports(flavor, image_meta):
|
||||
"""Get the number of serial consoles from the flavor or image
|
||||
|
||||
:param flavor: Flavor object to read extra specs from
|
||||
:param image_meta: Image object to read image metadata from
|
||||
|
||||
If flavor extra specs is not set, then any image meta value is permitted.
|
||||
If flavour extra specs *is* set, then this provides the default serial
|
||||
port count. The image meta is permitted to override the extra specs, but
|
||||
*only* with a lower value. ie
|
||||
|
||||
- flavor hw:serial_port_count=4
|
||||
VM gets 4 serial ports
|
||||
- flavor hw:serial_port_count=4 and image hw_serial_port_count=2
|
||||
VM gets 2 serial ports
|
||||
- image hw_serial_port_count=6
|
||||
VM gets 6 serial ports
|
||||
- flavor hw:serial_port_count=4 and image hw_serial_port_count=6
|
||||
Abort guest boot - forbidden to exceed flavor value
|
||||
|
||||
:returns: number of serial ports
|
||||
"""
|
||||
|
||||
def get_number(obj, property):
|
||||
num_ports = obj.get(property)
|
||||
if num_ports is not None:
|
||||
try:
|
||||
num_ports = int(num_ports)
|
||||
except ValueError:
|
||||
raise exception.ImageSerialPortNumberInvalid(
|
||||
num_ports=num_ports, property=property)
|
||||
return num_ports
|
||||
|
||||
image_meta_prop = (image_meta or {}).get('properties', {})
|
||||
|
||||
flavor_num_ports = get_number(flavor.extra_specs, "hw:serial_port_count")
|
||||
image_num_ports = get_number(image_meta_prop, "hw_serial_port_count")
|
||||
|
||||
if (flavor_num_ports and image_num_ports) is not None:
|
||||
if image_num_ports > flavor_num_ports:
|
||||
raise exception.ImageSerialPortNumberExceedFlavorValue()
|
||||
return image_num_ports
|
||||
|
||||
return flavor_num_ports or image_num_ports or 1
|
||||
|
||||
|
||||
class VirtCPUTopology(object):
|
||||
|
||||
def __init__(self, sockets, cores, threads):
|
||||
|
@ -55,6 +55,7 @@ from nova.compute import power_state
|
||||
from nova.compute import task_states
|
||||
from nova.compute import utils as compute_utils
|
||||
from nova.compute import vm_mode
|
||||
from nova.console import serial as serial_console
|
||||
from nova import context as nova_context
|
||||
from nova import exception
|
||||
from nova.i18n import _
|
||||
@ -242,6 +243,9 @@ CONF.import_opt('server_proxyclient_address', 'nova.spice', group='spice')
|
||||
CONF.import_opt('vcpu_pin_set', 'nova.virt.hardware')
|
||||
CONF.import_opt('vif_plugging_is_fatal', 'nova.virt.driver')
|
||||
CONF.import_opt('vif_plugging_timeout', 'nova.virt.driver')
|
||||
CONF.import_opt('enabled', 'nova.console.serial', group='serial_console')
|
||||
CONF.import_opt('proxyclient_address', 'nova.console.serial',
|
||||
group='serial_console')
|
||||
|
||||
DEFAULT_FIREWALL_DRIVER = "%s.%s" % (
|
||||
libvirt_firewall.__name__,
|
||||
@ -1120,6 +1124,26 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
if CONF.libvirt.images_type == 'rbd':
|
||||
self._cleanup_rbd(instance)
|
||||
|
||||
if CONF.serial_console.enabled:
|
||||
for host, port in self._get_serial_ports_from_instance(instance):
|
||||
serial_console.release_port(host=host, port=port)
|
||||
|
||||
def _get_serial_ports_from_instance(self, instance, mode=None):
|
||||
"""Returns an iterator over serial port(s) configured on instance.
|
||||
|
||||
:param mode: Should be a value in (None, bind, connect)
|
||||
"""
|
||||
virt_dom = self._lookup_by_name(instance['name'])
|
||||
xml = virt_dom.XMLDesc(0)
|
||||
tree = etree.fromstring(xml)
|
||||
for serial in tree.findall("./devices/serial"):
|
||||
if serial.get("type") == "tcp":
|
||||
source = serial.find("./source")
|
||||
if source is not None:
|
||||
if mode and source.get("mode") != mode:
|
||||
continue
|
||||
yield (source.get("host"), int(source.get("service")))
|
||||
|
||||
@staticmethod
|
||||
def _get_rbd_driver():
|
||||
return rbd_utils.RBDDriver(
|
||||
@ -3637,14 +3661,29 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
|
||||
if ((CONF.libvirt.virt_type == "qemu" or
|
||||
CONF.libvirt.virt_type == "kvm")):
|
||||
# The QEMU 'pty' driver throws away any data if no
|
||||
# client app is connected. Thus we can't get away
|
||||
# with a single type=pty console. Instead we have
|
||||
# to configure two separate consoles.
|
||||
consolelog = vconfig.LibvirtConfigGuestSerial()
|
||||
consolelog.type = "file"
|
||||
consolelog.source_path = self._get_console_log_path(instance)
|
||||
guest.add_device(consolelog)
|
||||
# Create the serial console char devices
|
||||
if CONF.serial_console.enabled:
|
||||
num_ports = hardware.get_number_of_serial_ports(
|
||||
flavor, image_meta)
|
||||
for port in six.moves.range(num_ports):
|
||||
console = vconfig.LibvirtConfigGuestSerial()
|
||||
console.port = port
|
||||
console.type = "tcp"
|
||||
console.listen_host = (
|
||||
CONF.serial_console.proxyclient_address)
|
||||
console.listen_port = (
|
||||
serial_console.acquire_port(
|
||||
console.listen_host))
|
||||
guest.add_device(console)
|
||||
else:
|
||||
# The QEMU 'pty' driver throws away any data if no
|
||||
# client app is connected. Thus we can't get away
|
||||
# with a single type=pty console. Instead we have
|
||||
# to configure two separate consoles.
|
||||
consolelog = vconfig.LibvirtConfigGuestSerial()
|
||||
consolelog.type = "file"
|
||||
consolelog.source_path = self._get_console_log_path(instance)
|
||||
guest.add_device(consolelog)
|
||||
|
||||
consolepty = vconfig.LibvirtConfigGuestSerial()
|
||||
else:
|
||||
|
Loading…
Reference in New Issue
Block a user