~racb/nova/868349

« back to all changes in this revision

Viewing changes to debian/patches/backport-libvirt-console-pipe.patch

  • Committer: Chuck Short
  • Date: 2011-09-29 18:57:57 UTC
  • Revision ID: zulcss@ubuntu.com-20110929185757-mb13lj6ezxdnvl5f
* debian/patches/backport-libvirt-console-pipe.patch:
  Move console.log to a ringbuffer so that the console.log
  keeps filling up. (LP: #832507)
* debian/patches/backport-lxc-container-console-fix.patch:
  Make euca-get-console-output usable for LXC containers.
  (LP: #832159)
* debian/patches/backport-snapshot-cleanup.patch:

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
8
 import lockfile
 
9
 import netaddr
 
10
 import os
 
11
+import os.path
 
12
 import random
 
13
 import re
 
14
 import shlex
 
15
 import socket
 
16
+import stat
 
17
 import struct
 
18
 import sys
 
19
 import time
 
20
@@ -49,6 +51,7 @@ from nova import log as logging
 
21
 from nova import version
 
22
 
 
23
 
 
24
+BITS_PER_BYTE = 8
 
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):
 
30
         lst = [lst]
 
31
     return [{label: x} for x in lst]
 
32
+
 
33
+
 
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)
 
39
+
 
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
 
43
+                      # problem
 
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
 
50
+            if file_size:
 
51
+                current_size = file_size - self._header_size
 
52
+                if not 0 < current_size <= RingBuffer._header_max_int:
 
53
+                    self.f.close()
 
54
+                    raise ValueError('Disk RingBuffer size out of range')
 
55
+                self.max_size = current_size
 
56
+
 
57
+                # If the file doesn't contain a header, assume it is corrupt
 
58
+                # and recreate
 
59
+                if file_size < self._header_size:
 
60
+                    self._write_header(0, 0) # initialise to empty
 
61
+
 
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:
 
65
+                    self.f.close()
 
66
+                    raise ValueError('RingBuffer %s is corrupt' % backing_file)
 
67
+            else:
 
68
+                # File is zero bytes: treat as new file
 
69
+                self.max_size = max_size
 
70
+                self._initialise_empty_file()
 
71
+        else:
 
72
+            self.max_size = max_size
 
73
+            self._initialise_empty_file()
 
74
+
 
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
 
78
+
 
79
+    @staticmethod
 
80
+    def _open(filename):
 
81
+        """Open a file without truncating it for both reading and writing in
 
82
+        binary mode."""
 
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+')
 
86
+
 
87
+    def _read_header(self):
 
88
+        self.f.seek(0)
 
89
+        return struct.unpack(self._header_format,
 
90
+                             self.f.read(self._header_size))
 
91
+
 
92
+    def _write_header(self, head, tail):
 
93
+        self.f.seek(0)
 
94
+        self.f.write(struct.pack(self._header_format, head, tail))
 
95
+
 
96
+    def _seek(self, pos):
 
97
+        """Seek to pos in data (ignoring header)."""
 
98
+        self.f.seek(self._header_size + pos)
 
99
+
 
100
+    def _read_slice(self, start, end):
 
101
+        if start == end:
 
102
+            return ''
 
103
+
 
104
+        self._seek(start)
 
105
+        return self.f.read(end - start)
 
106
+
 
107
+    def _write_slice(self, pos, data):
 
108
+        self._seek(pos)
 
109
+        self.f.write(data)
 
110
+
 
111
+    def peek(self):
 
112
+        """Read the entire ringbuffer without consuming it."""
 
113
+        head, tail = self._read_header()
 
114
+        if head < tail:
 
115
+            # Wraps around
 
116
+            before_wrap = self._read_slice(tail, self.max_size)
 
117
+            after_wrap = self._read_slice(0, head)
 
118
+            return before_wrap + after_wrap
 
119
+        else:
 
120
+            # Just from here to head
 
121
+            return self._read_slice(tail, head)
 
122
+
 
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()
 
127
+        while data:
 
128
+            # Amount of data to be written on this pass
 
129
+            len_to_write = min(len(data), self.max_size - head)
 
130
+
 
131
+            # Where head will be after this write
 
132
+            new_head = head + len_to_write
 
133
+
 
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
 
138
+
 
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)
 
144
+
 
145
+            # Write the data
 
146
+            self._write_slice(head, data[:len_to_write])
 
147
+            data = data[len_to_write:] # data now left
 
148
+
 
149
+            # Push head back
 
150
+            head = new_head
 
151
+            head %= self.max_size
 
152
+            self._write_header(head, tail)
 
153
+
 
154
+    def flush(self):
 
155
+        self.f.flush()
 
156
+
 
157
+    def close(self):
 
158
+        self.f.close()
 
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.
 
163
 
 
164
 """
 
165
 
 
166
+import errno
 
167
 import hashlib
 
168
 import functools
 
169
 import multiprocessing
 
170
@@ -44,7 +45,9 @@ import netaddr
 
171
 import os
 
172
 import random
 
173
 import re
 
174
+import select
 
175
 import shutil
 
176
+import stat
 
177
 import sys
 
178
 import tempfile
 
179
 import time
 
180
@@ -52,6 +55,7 @@ import uuid
 
181
 from xml.dom import minidom
 
182
 from xml.etree import ElementTree
 
183
 
 
184
+import eventlet
 
185
 from eventlet import greenthread
 
186
 from eventlet import tpool
 
187
 
 
188
@@ -141,6 +145,8 @@ flags.DEFINE_string('default_local_forma
 
189
 flags.DEFINE_bool('libvirt_use_virtio_for_bridges',
 
190
                   False,
 
191
                   'Use virtio for bridge interfaces')
 
192
+flags.DEFINE_integer('libvirt_console_log_size', 2**16,
 
193
+                     'libvirt console log ringbuffer size')
 
194
 
 
195
 
 
196
 def get_connection(read_only):
 
197
@@ -170,6 +176,55 @@ def _get_eph_disk(ephemeral):
 
198
     return 'disk.eph' + str(ephemeral['num'])
 
199
 
 
200
 
 
201
+class ConsoleLogger(object):
 
202
+
 
203
+    def __init__(self, fifo_path, ringbuffer_path):
 
204
+        self.fifo_path = fifo_path
 
205
+        self.fd = None
 
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)
 
211
+
 
212
+    def _reopen(self):
 
213
+        if self.fd is not None:
 
214
+            os.close(self.fd)
 
215
+        self.fd = os.open(self.fifo_path, os.O_RDONLY | os.O_NONBLOCK)
 
216
+
 
217
+    def _reader_thread_func(self):
 
218
+        self._reopen()
 
219
+        while True:
 
220
+            select.select([self.fd], [], [])
 
221
+            data = os.read(self.fd, 1024)
 
222
+            if data:
 
223
+                self.data_queue.put(data)
 
224
+            else:
 
225
+                self._reopen()
 
226
+
 
227
+    def _writer_thread_func(self):
 
228
+        try:
 
229
+            data = self.data_queue.get()
 
230
+            while data:
 
231
+                self.ringbuffer.write(data)
 
232
+                data = self.data_queue.get()
 
233
+        finally:
 
234
+            self.ringbuffer.close()
 
235
+
 
236
+    def close(self):
 
237
+        self.reader_thread.kill()
 
238
+        self.data_queue.put(None)
 
239
+        try:
 
240
+            self.writer_thread.wait()
 
241
+        except eventlet.greenlet.GreenletExit:
 
242
+            pass
 
243
+        if self.fd is not None:
 
244
+            os.close(self.fd)
 
245
+
 
246
+    def peek(self):
 
247
+        return self.ringbuffer.peek()
 
248
+
 
249
+
 
250
 class LibvirtConnection(driver.ComputeDriver):
 
251
 
 
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)
 
256
 
 
257
+        self.console_loggers = dict()
 
258
+
 
259
     def init_host(self, host):
 
260
         # NOTE(nsokolov): moved instance restarting to ComputeManager
 
261
         pass
 
262
@@ -229,6 +286,15 @@ class LibvirtConnection(driver.ComputeDr
 
263
         else:
 
264
             return libvirt.openAuth(uri, auth, 0)
 
265
 
 
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)
 
269
+
 
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]
 
274
+
 
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
 
287
 
 
288
     @exception.wrap_exception()
 
289
     def get_console_output(self, instance):
 
290
-        console_log = os.path.join(FLAGS.instances_path, instance['name'],
 
291
-                                   'console.log')
 
292
+        console_fifo = os.path.join(FLAGS.instances_path, instance['name'],
 
293
+                                   'console.fifo.out')
 
294
 
 
295
-        utils.execute('chown', os.getuid(), console_log, run_as_root=True)
 
296
+        utils.execute('chown', os.getuid(), console_fifo, run_as_root=True)
 
297
 
 
298
         if FLAGS.libvirt_type == 'xen':
 
299
             # Xen is special
 
300
             virsh_output = utils.execute('virsh', 'ttyconsole',
 
301
                                          instance['name'])
 
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"))
 
308
-        else:
 
309
-            fpath = console_log
 
310
 
 
311
-        return self._dump_file(fpath)
 
312
+        return self.console_loggers[instance['name']].peek()
 
313
 
 
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)
 
319
 
 
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', '')
 
328
+
 
329
+        for suffix in [ '.in', '.out' ]:
 
330
+            console_fifo_suffix = console_fifo + suffix
 
331
+            try:
 
332
+                console_fifo_stat = os.stat(console_fifo_suffix)
 
333
+            except OSError, e:
 
334
+                if e.errno == errno.ENOENT:
 
335
+                    os.mkfifo(console_fifo_suffix, 0660)
 
336
+                else:
 
337
+                    raise
 
338
+            else:
 
339
+                utils.execute('chown', os.getuid(), console_fifo_suffix,
 
340
+                              run_as_root=True)
 
341
+        self._start_console_logger(inst['name'], console_fifo + '.out', console_ring)
 
342
 
 
343
         if not disk_images:
 
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
 
348
@@ -153,8 +153,8 @@
 
349
 
 
350
 #end for
 
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'/>
 
356
             <target port='1'/>
 
357
         </serial>
 
358