diff --git a/nova/exception.py b/nova/exception.py index 0f39b925e75f..4676e6a61a96 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -1970,6 +1970,11 @@ class ImageSerialPortNumberExceedFlavorValue(Invalid): "ports passed in image meta.") +class SerialPortNumberLimitExceeded(Invalid): + msg_fmt = _("Maximum number of serial port exceeds %(allowed)d " + "for %(virt_type)s") + + class InvalidImageConfigDrive(Invalid): msg_fmt = _("Image's config drive option '%(config_drive)s' is invalid") diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index 248529ff67b0..c842a0fd8c8e 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -3308,6 +3308,35 @@ class LibvirtConnTestCase(test.NoDBTestCase): self.assertIsInstance(console_device, device_type) self.assertEqual("tcp", console_device.type) + @mock.patch('nova.virt.hardware.get_number_of_serial_ports', + return_value=4) + @mock.patch.object(libvirt_driver.libvirt_utils, 'get_arch', + side_effect=[arch.X86_64, arch.S390, arch.S390X]) + def test_create_serial_console_devices_with_limit_exceeded_based_on_arch( + self, mock_get_arch, mock_get_port_number): + self.flags(enabled=True, group='serial_console') + self.flags(virt_type="qemu", group='libvirt') + flavor = 'fake_flavor' + image_meta = objects.ImageMeta() + drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) + guest = vconfig.LibvirtConfigGuest() + self.assertRaises(exception.SerialPortNumberLimitExceeded, + drvr._create_serial_console_devices, + guest, None, flavor, image_meta) + mock_get_arch.assert_called_with(image_meta) + mock_get_port_number.assert_called_with(flavor, + image_meta) + + drvr._create_serial_console_devices(guest, None, flavor, image_meta) + mock_get_arch.assert_called_with(image_meta) + mock_get_port_number.assert_called_with(flavor, + image_meta) + + drvr._create_serial_console_devices(guest, None, flavor, image_meta) + mock_get_arch.assert_called_with(image_meta) + mock_get_port_number.assert_called_with(flavor, + image_meta) + @mock.patch('nova.console.serial.acquire_port') def test_get_guest_config_serial_console(self, acquire_port): self.flags(enabled=True, group='serial_console') diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 2f92691635bb..13fd1d073444 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -483,6 +483,12 @@ MIN_QEMU_PPC64_VERSION = (2, 1, 0) # Names of the types that do not get compressed during migration NO_COMPRESSION_TYPES = ('qcow2',) + +# number of serial console limit +QEMU_MAX_SERIAL_PORTS = 4 +# Qemu supports 4 serial consoles, we remove 1 because of the PTY one defined +ALLOWED_QEMU_SERIAL_PORTS = QEMU_MAX_SERIAL_PORTS - 1 + # realtime suppport MIN_LIBVIRT_REALTIME_VERSION = (1, 2, 13) @@ -4191,6 +4197,13 @@ class LibvirtDriver(driver.ComputeDriver): hv.vapic = True guest.features.append(hv) + def _check_number_of_serial_console(self, num_ports): + virt_type = CONF.libvirt.virt_type + if (virt_type in ("kvm", "qemu") and + num_ports > ALLOWED_QEMU_SERIAL_PORTS): + raise exception.SerialPortNumberLimitExceeded( + allowed=ALLOWED_QEMU_SERIAL_PORTS, virt_type=virt_type) + def _create_serial_console_devices(self, guest, instance, flavor, image_meta): guest_arch = libvirt_utils.get_arch(image_meta) @@ -4198,11 +4211,15 @@ class LibvirtDriver(driver.ComputeDriver): if CONF.serial_console.enabled: num_ports = hardware.get_number_of_serial_ports( flavor, image_meta) + + if guest_arch in (arch.S390, arch.S390X): + console_cls = vconfig.LibvirtConfigGuestConsole + else: + console_cls = vconfig.LibvirtConfigGuestSerial + self._check_number_of_serial_console(num_ports) + for port in six.moves.range(num_ports): - if guest_arch in (arch.S390, arch.S390X): - console = vconfig.LibvirtConfigGuestConsole() - else: - console = vconfig.LibvirtConfigGuestSerial() + console = console_cls() console.port = port console.type = "tcp" console.listen_host = (