Add trivial scenario for demo.txt
[ci.git] / htest / vm / controller.py
blob5a77627b009f1038fa2d990bca5546f8a4e4faf4
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
32 import subprocess
33 import logging
35 class VMManager:
36 """
37 Keeps track of running virtual machines.
38 """
40 def __init__(self, controller, architecture, boot_image, memory_amount, headless, extra_opts):
41 self.controller_class = controller
42 self.architecture = architecture
43 self.boot_image = boot_image
44 self.memory_amount = memory_amount
45 self.headless = headless
46 self.extra_options = extra_opts
47 self.instances = {}
48 self.last = None
50 def create(self, name):
51 if name in self.instances:
52 raise Exception("Duplicate machine name {}.".format(name))
53 self.instances[name] = self.controller_class(self.architecture, name, self.boot_image)
54 self.instances[name].memory = self.memory_amount
55 self.instances[name].is_headless = self.headless
56 self.instances[name].extra_options = self.extra_options
57 self.last = name
58 return self.instances[name]
60 def get(self, name=None):
61 if name is None:
62 name = self.last
63 if name is None:
64 return None
65 if name in self.instances:
66 self.last = name
67 return self.instances[name]
68 else:
69 return None
71 def terminate(self, vterm_dump_filename, last_screenshot_filename):
72 for i in self.instances:
73 self.instances[i].terminate()
74 if vterm_dump_filename is not None:
75 with open(vterm_dump_filename, 'w') as f:
76 for i in self.instances:
77 lines = '\n'.join(self.instances[i].full_vterm)
78 print(lines, file=f)
79 if last_screenshot_filename is not None:
80 for i in self.instances:
81 filename = self.instances[i].screenshot_filename
82 if filename is not None:
83 proc = subprocess.Popen(['convert', filename, last_screenshot_filename])
84 proc.wait()
85 if proc.returncode != 0:
86 raise Exception("Saving screenshot failed.")
88 class VMController:
89 """
90 Base class for controllers of specific virtual machine emulators.
91 """
93 def __init__(self, provider):
94 self.provider_name = provider
95 self.logger = logging.getLogger(provider)
96 # Patched by VMManager
97 self.name = 'XXX'
98 self.screenshot_filename = None
99 # All lines seen in the terminal
100 # (do not reset unless you know what you are doing).
101 self.full_vterm = []
102 # Used to keep track of new-lines
103 self.vterm = []
104 # Amount of memory (MB) (patched by VMM manager)
105 self.memory = 0
106 # Extra command-line options (patched by VMM manager)
107 self.extra_options = []
108 # Are we headless (patched by VMM manager)
109 self.is_headless = False
110 pass
112 def is_supported(self, arch):
114 Tells whether this controller supports given architecture.
116 return False
118 def boot(self, **kwargs):
120 Bring the machine up.
122 pass
124 def terminate(self):
126 Shutdown the VM.
128 pass
130 def type(self, what):
132 Type given text into vterm.
134 print("type('{}') @ {}".format(what, self.provider_name))
135 pass
137 def same_vterm_tail(self, lines):
138 lines_count = len(lines)
139 for i in range(-1, -lines_count - 1, -1):
140 try:
141 if i != -1:
142 if lines[i] != self.full_vterm[i]:
143 return False
144 else:
145 a = lines[-1].replace("_", " ").strip()
146 b = self.full_vterm[-1].replace("_", " ").strip()
147 if not a.startswith(b):
148 return False
149 except IndexError as e:
150 # FIXME: should not happen but maybe we need to
151 # be more defensive here about what can appear
152 # in self.full_vterm...
153 raise Exception("INTERNAL ERROR: same_vterm_tail(i={}, lines={}, full_vterm={})".format(i, lines, self.full_vterm))
154 return True
156 def capture_vterm(self):
158 Capture contents of current terminal window and updates self.vterm
161 # Read everything from the terminal and get rid of completely empty
162 # lines (for first commands when the screen is empty).
163 lines = self.capture_vterm_impl()
164 lines = [l.strip() for l in lines]
165 self.logger.debug("Captured lines: {}".format(lines))
166 while (len(lines) > 0) and (lines[-1].strip() == ""):
167 lines = lines[0:-1]
168 if (len(lines) == 0):
169 return
171 # When this is the very first screen, we simply copy it.
172 if len(self.full_vterm) == 0:
173 for l in lines:
174 self.full_vterm.append(l)
175 self.vterm.append(l)
176 else:
177 # Otherwise, we find whether there is some overlap, i.e. whether
178 # we are capturing a rolling screen.
179 lines_count = len(lines)
180 same_lines = 0
181 for i in range(lines_count, 0, -1):
182 if self.same_vterm_tail(lines[0:i]):
183 same_lines = i
184 break
185 # If there is no overlap, we might have missed some lines.
186 if same_lines == 0:
187 self.full_vterm.append("!!!!!! WARNING: probably missed some lines here !!!!!")
188 else:
189 # Otherwise, update the last line (last capture might have
190 # missed some characters).
191 if len(self.full_vterm) > 0:
192 self.full_vterm = self.full_vterm[0:-1]
193 if len(self.vterm) > 0:
194 self.vterm = self.vterm[0:-1]
195 same_lines = same_lines - 1
196 # Add the new lines.
197 for i in range(same_lines, lines_count):
198 self.full_vterm.append(lines[i])
199 self.vterm.append(lines[i])
201 def capture_vterm_impl(self):
203 Do not call but reimplement in subclass.
205 return []
207 def get_vterm_cursor_symbol(self):
209 Reimplement if your controller represents cursor in vterm
210 differently than with underscore symbol '_'.
212 return '_'
214 def get_temp(self, id):
216 Get temporary file name.
218 os.makedirs('tmp-vm-python', exist_ok=True)
219 return 'tmp-vm-python/tmp-' + self.name + '-' + id