Simply & unify console handling for libvirt drivers

Currently the libvirt.xml.template file contains the following
console definitions

        <!-- The order is significant here.  File must be defined first -->
        <serial type="file">
            <source path='${basepath}/console.log'/>
            <target port='1'/>
        </serial>

        <console type='pty' tty='/dev/pts/2'>
            <source path='/dev/pts/2'/>
            <target port='0'/>
        </console>

        <serial type='pty'>
            <source path='/dev/pts/2'/>
            <target port='0'/>
        </serial>

There are multiple things wrong with this

 - LXC and Xen guests don't honour the <serial> elements
 - The <console> element shouldn't have any <source> element
   of 'tty' attribute set when type=pty, since they are
   dynamically allocated
 - The <console> element will ignored if the <serial>
   element is set and the hypervisor supports this
 - It doesn't say why multiple serial elements are used
   or why the order is important. The reason is that
   the QEMU pty driver throws away data when no client
   is connected. This means we can't use it as a basis
   for the persistent log file. Instead we need two
   separate serial ports, the first of which is used for
   the logfile

In addition in the nova/virt/libvirt/connect.py class the
'get_console_output' method has separate special-case
handling for Xen and LXC despite the fact that both work
in the same way.

All this can be significantly simplified, to unify console
handling across all libvirt drivers. First replacing all
the existing XML with just

  #if $type == 'qemu'
        <serial type='file'>
            <source path='${basepath}/console.log'/>
        </serial>
        <serial type='pty'/>
  #else
        <console type='pty'/>
  #end if

This lets Xen/UML/LXC just use a regular PTY based console,
while special casing QEMU. It is minimal XML, letting
libvirt automatically fill in other attributes

In the code, the get_console_output method can remove the
explicit checks for Xen/LXC and instead be conditionalized
based on what the XML shows.

Finally calling out to 'virsh ttyiname' is pointless since
nova already has a connection to libvirt which can be used
to fetch the XML & extract the TTY path.

Change-Id: I6a966df4ea72e07dbc227683c4225670984fc507
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
This commit is contained in:
Daniel P. Berrange 2012-03-07 06:49:44 -05:00
parent 17396f6a08
commit e3f77127fe
4 changed files with 61 additions and 52 deletions

View File

@ -357,12 +357,17 @@ class Domain(object):
function='0x1'/>
</controller>
%(nics)s
<serial type='pty'>
<source pty='/dev/pts/27'/>
<serial type='file'>
<source path='dummy.log'/>
<target port='0'/>
</serial>
<console type='pty'>
<target type='serial' port='0'/>
<serial type='pty'>
<source pty='/dev/pts/27'/>
<target port='1'/>
</serial>
<console type='file'>
<source path='dummy.log'/>
<target port='0'/>
</console>
<input type='tablet' bus='usb'/>
<input type='mouse' bus='ps2'/>

View File

@ -860,6 +860,21 @@ class LibvirtConnTestCase(test.TestCase):
check = (lambda t: t.find('./os/initrd'), None)
check_list.append(check)
if hypervisor_type in ['qemu', 'kvm']:
check = (lambda t: t.findall('./devices/serial')[0].get(
'type'), 'file')
check_list.append(check)
check = (lambda t: t.findall('./devices/serial')[1].get(
'type'), 'pty')
check_list.append(check)
check = (lambda t: t.findall('./devices/serial/source')[0].get(
'path').split('/')[1], 'console.log')
check_list.append(check)
else:
check = (lambda t: t.find('./devices/console').get(
'type'), 'pty')
check_list.append(check)
parameter = './devices/interface/filterref/parameter'
common_checks = [
(lambda t: t.find('.').tag, 'domain'),
@ -869,8 +884,6 @@ class LibvirtConnTestCase(test.TestCase):
(lambda t: t.findall(parameter)[1].get('name'), 'DHCPSERVER'),
(lambda t: _ipv4_like(t.findall(parameter)[1].get('value'),
'192.168.*.1'), True),
(lambda t: t.find('./devices/serial/source').get(
'path').split('/')[1], 'console.log'),
(lambda t: t.find('./memory').text, '2097152')]
if rescue:
common_checks += [

View File

@ -162,21 +162,20 @@
#end if
#end for
<!-- The order is significant here. File must be defined first -->
<serial type="file">
#if $type == 'qemu' or $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. -->
<serial type='file'>
<source path='${basepath}/console.log'/>
<target port='1'/>
</serial>
<serial type='pty'/>
#else
<console type='pty'/>
#end if
<console type='pty' tty='/dev/pts/2'>
<source path='/dev/pts/2'/>
<target port='0'/>
</console>
<serial type='pty'>
<source path='/dev/pts/2'/>
<target port='0'/>
</serial>
#if $getVar('use_usb_tablet', True) and $type != 'lxc' and $type != 'xen'
<input type='tablet' bus='usb'/>
#end if

View File

@ -875,20 +875,13 @@ class LibvirtConnection(driver.ComputeDriver):
timer = utils.LoopingCall(_wait_for_boot)
return timer.start(interval=0.5, now=True)
def _flush_libvirt_console(self, virsh_output):
LOG.info(_('virsh said: %r'), virsh_output)
virsh_output = virsh_output[0].strip()
if virsh_output.startswith('/dev/'):
LOG.info(_("cool, it's a device"))
out, err = utils.execute('dd',
'if=%s' % virsh_output,
'iflag=nonblock',
run_as_root=True,
check_exit_code=False)
return out
else:
return ''
def _flush_libvirt_console(self, pty):
out, err = utils.execute('dd',
'if=%s' % pty,
'iflag=nonblock',
run_as_root=True,
check_exit_code=False)
return out
def _append_to_file(self, data, fpath):
LOG.info(_('data: %(data)r, fpath: %(fpath)r') % locals())
@ -904,29 +897,28 @@ class LibvirtConnection(driver.ComputeDriver):
@exception.wrap_exception()
def get_console_output(self, instance):
virt_dom = self._lookup_by_name(instance['name'])
xml = virt_dom.XMLDesc(0)
tree = ElementTree.fromstring(xml)
# If the guest has a console logging to a file prefer to use that
node = tree.find("./devices/console[@type='file']/source")
if node is not None:
fpath = node.get("path")
return libvirt_utils.load_file(fpath)
# else if there is a PTY, then try to read latest data from that
node = tree.find("./devices/console[@type='pty']/source")
if node is None:
raise exception.Error(_("Guest does not have a console available"))
pty = node.get("path")
console_log = os.path.join(FLAGS.instances_path, instance['name'],
'console.log')
libvirt_utils.chown(console_log, os.getuid())
if FLAGS.libvirt_type == 'xen':
# Xen is special
virsh_output = utils.execute('virsh',
'ttyconsole',
instance['name'])
data = self._flush_libvirt_console(virsh_output)
fpath = self._append_to_file(data, console_log)
elif FLAGS.libvirt_type == 'lxc':
# LXC is also special
virsh_output = utils.execute('virsh',
'-c',
'lxc:///',
'ttyconsole',
instance['name'])
data = self._flush_libvirt_console(virsh_output)
fpath = self._append_to_file(data, console_log)
else:
fpath = console_log
data = self._flush_libvirt_console(pty)
fpath = self._append_to_file(data, console_log)
return libvirt_utils.load_file(fpath)