1
Description: Move console.log to a ringbuffer
2
Author: Robie Basak <robie.basak@canonical.com>
3
Status: https://review.openstack.org/#change,706
4
diff -Naurp nova-2011.3.orig//nova/utils.py nova-2011.3//nova/utils.py
5
--- nova-2011.3.orig//nova/utils.py 2011-09-22 08:02:23.000000000 -0400
6
+++ nova-2011.3//nova/utils.py 2011-09-29 13:57:43.562487261 -0400
7
@@ -26,10 +26,12 @@ import json
20
@@ -49,6 +51,7 @@ from nova import log as logging
21
from nova import version
25
LOG = logging.getLogger("nova.utils")
26
ISO_TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
27
PERFECT_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%f"
28
@@ -910,3 +913,130 @@ def convert_to_list_dict(lst, label):
29
if not isinstance(lst, list):
31
return [{label: x} for x in lst]
34
+class RingBuffer(object):
35
+ """Generic userspace on-disk ringbuffer implementation."""
36
+ _header_max_int = (2 ** (struct.calcsize('I') * BITS_PER_BYTE)) - 1
37
+ _header_format = 'II'
38
+ _header_size = struct.calcsize(_header_format)
40
+ def __init__(self, backing_file, max_size=65536):
41
+ max_size += 1 # we need one extra byte as the buffer is kept with
42
+ # one byte free to avoid the head==tail full/empty
44
+ if not 0 < max_size <= RingBuffer._header_max_int:
45
+ raise ValueError('RingBuffer size out of range')
46
+ had_already_existed = os.path.exists(backing_file)
47
+ self.f = self._open(backing_file)
48
+ if had_already_existed:
49
+ file_size = os.fstat(self.f.fileno()).st_size
51
+ current_size = file_size - self._header_size
52
+ if not 0 < current_size <= RingBuffer._header_max_int:
54
+ raise ValueError('Disk RingBuffer size out of range')
55
+ self.max_size = current_size
57
+ # If the file doesn't contain a header, assume it is corrupt
59
+ if file_size < self._header_size:
60
+ self._write_header(0, 0) # initialise to empty
62
+ # If head or tail point beyond the file then bomb out
63
+ head, tail = self._read_header()
64
+ if head >= current_size or tail >= current_size:
66
+ raise ValueError('RingBuffer %s is corrupt' % backing_file)
68
+ # File is zero bytes: treat as new file
69
+ self.max_size = max_size
70
+ self._initialise_empty_file()
72
+ self.max_size = max_size
73
+ self._initialise_empty_file()
75
+ def _initialise_empty_file(self):
76
+ os.ftruncate(self.f.fileno(), self.max_size + self._header_size)
77
+ self._write_header(0, 0) # head == tail means no data
80
+ def _open(filename):
81
+ """Open a file without truncating it for both reading and writing in
83
+ # Built-in open() cannot open in read/write mode without truncating.
84
+ fd = os.open(filename, os.O_RDWR | os.O_CREAT, 0666)
85
+ return os.fdopen(fd, 'w+')
87
+ def _read_header(self):
89
+ return struct.unpack(self._header_format,
90
+ self.f.read(self._header_size))
92
+ def _write_header(self, head, tail):
94
+ self.f.write(struct.pack(self._header_format, head, tail))
96
+ def _seek(self, pos):
97
+ """Seek to pos in data (ignoring header)."""
98
+ self.f.seek(self._header_size + pos)
100
+ def _read_slice(self, start, end):
105
+ return self.f.read(end - start)
107
+ def _write_slice(self, pos, data):
112
+ """Read the entire ringbuffer without consuming it."""
113
+ head, tail = self._read_header()
116
+ before_wrap = self._read_slice(tail, self.max_size)
117
+ after_wrap = self._read_slice(0, head)
118
+ return before_wrap + after_wrap
120
+ # Just from here to head
121
+ return self._read_slice(tail, head)
123
+ def write(self, data):
124
+ """Write some amount of data to the ringbuffer, discarding the oldest
125
+ data as max_size is exceeded."""
126
+ head, tail = self._read_header()
128
+ # Amount of data to be written on this pass
129
+ len_to_write = min(len(data), self.max_size - head)
131
+ # Where head will be after this write
132
+ new_head = head + len_to_write
134
+ # In the next comparison, new_head may be self.max_size which is
135
+ # logically the same point as tail == 0 and must still be within
136
+ # the range tested.
137
+ unwrapped_tail = tail if tail else self.max_size
139
+ if head < unwrapped_tail <= new_head:
140
+ # Write will go past tail so tail needs to be pushed back
141
+ tail = new_head + 1 # one past head to indicate full
142
+ tail %= self.max_size
143
+ self._write_header(head, tail)
146
+ self._write_slice(head, data[:len_to_write])
147
+ data = data[len_to_write:] # data now left
151
+ head %= self.max_size
152
+ self._write_header(head, tail)
159
diff -Naurp nova-2011.3.orig//nova/virt/libvirt/connection.py nova-2011.3//nova/virt/libvirt/connection.py
160
--- nova-2011.3.orig//nova/virt/libvirt/connection.py 2011-09-22 08:02:23.000000000 -0400
161
+++ nova-2011.3//nova/virt/libvirt/connection.py 2011-09-29 13:40:41.392475523 -0400
162
@@ -37,6 +37,7 @@ Supports KVM, LXC, QEMU, UML, and XEN.
169
import multiprocessing
170
@@ -44,7 +45,9 @@ import netaddr
180
@@ -52,6 +55,7 @@ import uuid
181
from xml.dom import minidom
182
from xml.etree import ElementTree
185
from eventlet import greenthread
186
from eventlet import tpool
188
@@ -141,6 +145,8 @@ flags.DEFINE_string('default_local_forma
189
flags.DEFINE_bool('libvirt_use_virtio_for_bridges',
191
'Use virtio for bridge interfaces')
192
+flags.DEFINE_integer('libvirt_console_log_size', 2**16,
193
+ 'libvirt console log ringbuffer size')
196
def get_connection(read_only):
197
@@ -170,6 +176,55 @@ def _get_eph_disk(ephemeral):
198
return 'disk.eph' + str(ephemeral['num'])
201
+class ConsoleLogger(object):
203
+ def __init__(self, fifo_path, ringbuffer_path):
204
+ self.fifo_path = fifo_path
206
+ self.data_queue = eventlet.queue.LightQueue(0)
207
+ self.ringbuffer = utils.RingBuffer(ringbuffer_path,
208
+ FLAGS.libvirt_console_log_size)
209
+ self.reader_thread = eventlet.spawn(self._reader_thread_func)
210
+ self.writer_thread = eventlet.spawn(self._writer_thread_func)
213
+ if self.fd is not None:
215
+ self.fd = os.open(self.fifo_path, os.O_RDONLY | os.O_NONBLOCK)
217
+ def _reader_thread_func(self):
220
+ select.select([self.fd], [], [])
221
+ data = os.read(self.fd, 1024)
223
+ self.data_queue.put(data)
227
+ def _writer_thread_func(self):
229
+ data = self.data_queue.get()
231
+ self.ringbuffer.write(data)
232
+ data = self.data_queue.get()
234
+ self.ringbuffer.close()
237
+ self.reader_thread.kill()
238
+ self.data_queue.put(None)
240
+ self.writer_thread.wait()
241
+ except eventlet.greenlet.GreenletExit:
243
+ if self.fd is not None:
247
+ return self.ringbuffer.peek()
250
class LibvirtConnection(driver.ComputeDriver):
252
def __init__(self, read_only):
253
@@ -185,6 +240,8 @@ class LibvirtConnection(driver.ComputeDr
254
self.firewall_driver = fw_class(get_connection=self._get_connection)
255
self.vif_driver = utils.import_object(FLAGS.libvirt_vif_driver)
257
+ self.console_loggers = dict()
259
def init_host(self, host):
260
# NOTE(nsokolov): moved instance restarting to ComputeManager
262
@@ -229,6 +286,15 @@ class LibvirtConnection(driver.ComputeDr
264
return libvirt.openAuth(uri, auth, 0)
266
+ def _start_console_logger(self, name, fifo_path, ringbuffer_path):
267
+ self._stop_console_logger(name)
268
+ self.console_loggers[name] = ConsoleLogger(fifo_path, ringbuffer_path)
270
+ def _stop_console_logger(self, name):
271
+ if name in self.console_loggers:
272
+ self.console_loggers[name].close()
273
+ del self.console_loggers[name]
275
def list_instances(self):
276
return [self._conn.lookupByID(x).name()
277
for x in self._conn.listDomainsID()]
278
@@ -333,6 +399,7 @@ class LibvirtConnection(driver.ComputeDr
279
def _cleanup(self, instance):
280
target = os.path.join(FLAGS.instances_path, instance['name'])
281
instance_name = instance['name']
282
+ self._stop_console_logger(instance_name)
283
LOG.info(_('instance %(instance_name)s: deleting instance files'
284
' %(target)s') % locals())
285
if FLAGS.libvirt_type == 'lxc':
286
@@ -652,24 +719,22 @@ class LibvirtConnection(driver.ComputeDr
288
@exception.wrap_exception()
289
def get_console_output(self, instance):
290
- console_log = os.path.join(FLAGS.instances_path, instance['name'],
292
+ console_fifo = os.path.join(FLAGS.instances_path, instance['name'],
293
+ 'console.fifo.out')
295
- utils.execute('chown', os.getuid(), console_log, run_as_root=True)
296
+ utils.execute('chown', os.getuid(), console_fifo, run_as_root=True)
298
if FLAGS.libvirt_type == 'xen':
300
virsh_output = utils.execute('virsh', 'ttyconsole',
302
data = self._flush_xen_console(virsh_output)
303
- fpath = self._append_to_file(data, console_log)
304
+ self._append_to_file(data, console_fifo)
305
elif FLAGS.libvirt_type == 'lxc':
306
# LXC is also special
307
LOG.info(_("Unable to read LXC console"))
309
- fpath = console_log
311
- return self._dump_file(fpath)
312
+ return self.console_loggers[instance['name']].peek()
314
@exception.wrap_exception()
315
def get_ajax_console(self, instance):
316
@@ -816,11 +881,23 @@ class LibvirtConnection(driver.ComputeDr
317
container_dir = '%s/rootfs' % basepath(suffix='')
318
utils.execute('mkdir', '-p', container_dir)
320
- # NOTE(vish): No need add the suffix to console.log
321
- console_log = basepath('console.log', '')
322
- if os.path.exists(console_log):
323
- utils.execute('chown', os.getuid(), console_log, run_as_root=True)
324
- os.close(os.open(console_log, os.O_CREAT | os.O_WRONLY, 0660))
325
+ # NOTE(vish): No need add the suffix
326
+ console_fifo = basepath('console.fifo', '')
327
+ console_ring = basepath('console.ring', '')
329
+ for suffix in [ '.in', '.out' ]:
330
+ console_fifo_suffix = console_fifo + suffix
332
+ console_fifo_stat = os.stat(console_fifo_suffix)
334
+ if e.errno == errno.ENOENT:
335
+ os.mkfifo(console_fifo_suffix, 0660)
339
+ utils.execute('chown', os.getuid(), console_fifo_suffix,
341
+ self._start_console_logger(inst['name'], console_fifo + '.out', console_ring)
344
disk_images = {'image_id': inst['image_ref'],
345
diff -Naurp nova-2011.3.orig//nova/virt/libvirt.xml.template nova-2011.3//nova/virt/libvirt.xml.template
346
--- nova-2011.3.orig//nova/virt/libvirt.xml.template 2011-09-22 08:02:23.000000000 -0400
347
+++ nova-2011.3//nova/virt/libvirt.xml.template 2011-09-29 13:40:41.392475523 -0400
351
<!-- The order is significant here. File must be defined first -->
352
- <serial type="file">
353
- <source path='${basepath}/console.log'/>
354
+ <serial type="pipe">
355
+ <source path='${basepath}/console.fifo'/>