Add --vterm-dump to store terminal dump
[ci.git] / htest / vm / controller.py
blob72232c66c298cc9f0114dd4d0064e95713c7628e
1 #!/usr/bin/env python3
4 # Copyright (c) 2018 Vojtech Horky
5 # All rights reserved.
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions
9 # are met:
11 # - Redistributions of source code must retain the above copyright
12 # notice, this list of conditions and the following disclaimer.
13 # - Redistributions in binary form must reproduce the above copyright
14 # notice, this list of conditions and the following disclaimer in the
15 # documentation and/or other materials provided with the distribution.
16 # - The name of the author may not be used to endorse or promote products
17 # derived from this software without specific prior written permission.
19 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20 # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24 # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 import os
33 class VMManager:
34 """
35 Keeps track of running virtual machines.
36 """
38 def __init__(self, controller, architecture, boot_image):
39 self.controller_class = controller
40 self.architecture = architecture
41 self.boot_image = boot_image
42 self.instances = {}
43 self.last = None
45 def create(self, name):
46 if name in self.instances:
47 raise Exception("Duplicate machine name {}.".format(name))
48 self.instances[name] = self.controller_class(self.architecture, name, self.boot_image)
49 self.last = name
50 return self.instances[name]
52 def get(self, name=None):
53 if name is None:
54 name = self.last
55 if name is None:
56 return None
57 if name in self.instances:
58 self.last = name
59 return self.instances[name]
60 else:
61 return None
63 def terminate(self, vterm_dump_filename):
64 for i in self.instances:
65 self.instances[i].terminate()
66 if vterm_dump_filename is not None:
67 with open(vterm_dump_filename, 'w') as f:
68 for i in self.instances:
69 lines = '\n'.join(self.instances[i].full_vterm)
70 print(lines, file=f)
73 class VMController:
74 """
75 Base class for controllers of specific virtual machine emulators.
76 """
78 def __init__(self, provider):
79 self.provider_name = provider
80 # Patched by VMManager
81 self.name = 'XXX'
82 # All lines seen in the terminal
83 # (do not reset unless you know what you are doing).
84 self.full_vterm = []
85 # Used to keep track of new-lines
86 self.vterm = []
87 pass
89 def boot(self, **kwargs):
90 """
91 Bring the machine up.
92 """
93 pass
95 def terminate(self):
96 """
97 Shutdown the VM.
98 """
99 pass
101 def type(self, what):
103 Type given text into vterm.
105 print("type('{}') @ {}".format(what, self.provider_name))
106 pass
108 def same_vterm_tail(self, lines):
109 lines_count = len(lines)
110 for i in range(-1, -lines_count - 1, -1):
111 if i != -1:
112 if lines[i] != self.full_vterm[i]:
113 return False
114 else:
115 a = lines[-1].replace("_", " ").strip()
116 b = self.full_vterm[-1].replace("_", " ").strip()
117 if not a.startswith(b):
118 return False
119 return True
121 def capture_vterm(self):
123 Capture contents of current terminal window and updates self.vterm
126 # Read everything from the terminal and get rid of completely empty
127 # lines (for first commands when the screen is empty).
128 lines = self.capture_vterm_impl()
129 lines = [l.strip() for l in lines]
130 while (len(lines) > 0) and (lines[-1].strip() == ""):
131 lines = lines[0:-1]
132 if (len(lines) == 0):
133 return
135 # When this is the very first screen, we simply copy it.
136 if len(self.full_vterm) == 0:
137 for l in lines:
138 self.full_vterm.append(l)
139 self.vterm.append(l)
140 else:
141 # Otherwise, we find whether there is some overlap, i.e. whether
142 # we are capturing a rolling screen.
143 lines_count = len(lines)
144 same_lines = 0
145 for i in range(lines_count, 0, -1):
146 if self.same_vterm_tail(lines[0:i]):
147 same_lines = i
148 break
149 # If there is no overlap, we might have missed some lines.
150 if same_lines == 0:
151 self.full_vterm.append("!!!!!! WARNING: probably missed some lines here !!!!!")
152 else:
153 # Otherwise, update the last line (last capture might have
154 # missed some characters).
155 if len(self.full_vterm) > 0:
156 self.full_vterm = self.full_vterm[0:-1]
157 if len(self.vterm) > 0:
158 self.vterm = self.vterm[0:-1]
159 same_lines = same_lines - 1
160 # Add the new lines.
161 for i in range(same_lines, lines_count):
162 self.full_vterm.append(lines[i])
163 self.vterm.append(lines[i])
165 def capture_vterm_impl(self):
167 Do not call but reimplement in subclass.
169 return []
171 def get_temp(self, id):
173 Get temporary file name.
175 os.makedirs('tmp-vm-python', exist_ok=True)
176 return 'tmp-vm-python/tmp-' + self.name + '-' + id