4 # Copyright (c) 2018 Vojtech Horky
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions
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.
37 Keeps track of running virtual machines.
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
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
58 return self
.instances
[name
]
60 def get(self
, name
=None):
65 if name
in self
.instances
:
67 return self
.instances
[name
]
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
)
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
])
85 if proc
.returncode
!= 0:
86 raise Exception("Saving screenshot failed.")
90 Base class for controllers of specific virtual machine emulators.
93 def __init__(self
, provider
):
94 self
.provider_name
= provider
95 self
.logger
= logging
.getLogger(provider
)
96 # Patched by VMManager
98 self
.screenshot_filename
= None
99 # All lines seen in the terminal
100 # (do not reset unless you know what you are doing).
102 # Used to keep track of new-lines
104 # Amount of memory (MB) (patched by VMM manager)
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
112 def is_supported(self
, arch
):
114 Tells whether this controller supports given architecture.
118 def boot(self
, **kwargs
):
120 Bring the machine up.
130 def type(self
, what
):
132 Type given text into vterm.
134 print("type('{}') @ {}".format(what
, self
.provider_name
))
137 def same_vterm_tail(self
, lines
):
138 lines_count
= len(lines
)
139 for i
in range(-1, -lines_count
- 1, -1):
142 if lines
[i
] != self
.full_vterm
[i
]:
145 a
= lines
[-1].replace("_", " ").strip()
146 b
= self
.full_vterm
[-1].replace("_", " ").strip()
147 if not a
.startswith(b
):
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
))
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() == ""):
168 if (len(lines
) == 0):
171 # When this is the very first screen, we simply copy it.
172 if len(self
.full_vterm
) == 0:
174 self
.full_vterm
.append(l
)
177 # Otherwise, we find whether there is some overlap, i.e. whether
178 # we are capturing a rolling screen.
179 lines_count
= len(lines
)
181 for i
in range(lines_count
, 0, -1):
182 if self
.same_vterm_tail(lines
[0:i
]):
185 # If there is no overlap, we might have missed some lines.
187 self
.full_vterm
.append("!!!!!! WARNING: probably missed some lines here !!!!!")
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
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.
207 def get_vterm_cursor_symbol(self
):
209 Reimplement if your controller represents cursor in vterm
210 differently than with underscore symbol '_'.
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