-
Notifications
You must be signed in to change notification settings - Fork 83
/
scvmm.py
375 lines (318 loc) · 15.1 KB
/
scvmm.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
# coding: utf-8
"""Backend management system classes
Used to communicate with providers without using CFME facilities
"""
import winrm
from cStringIO import StringIO
from contextlib import contextmanager
from datetime import datetime
from textwrap import dedent
from lxml import etree
from wait_for import wait_for
from base import MgmtSystemAPIBase, VMInfo
class SCVMMSystem(MgmtSystemAPIBase):
"""This class is used to connect to M$ SCVMM
It still has some drawback, the main one is that pywinrm does not support domains with simple
auth mode so I have to do the connection manually in the script which seems to be VERY slow.
"""
STATE_RUNNING = "Running"
STATES_STOPPED = {"PowerOff", "Stopped"} # TODO: "Stopped" when using shutdown. Differ it?
STATE_PAUSED = "Paused"
STATES_STEADY = {STATE_RUNNING, STATE_PAUSED}
STATES_STEADY.update(STATES_STOPPED)
STATES_FAILED = {'Creation Failed'}
_stats_available = {
'num_vm': lambda self: len(self.list_vm()),
'num_template': lambda self: len(self.list_template()),
}
def __init__(self, **kwargs):
super(SCVMMSystem, self).__init__(kwargs)
self.host = kwargs["hostname"]
self.user = kwargs["username"]
self.password = kwargs["password"]
self.domain = kwargs["domain"]
self.api = winrm.Session(self.host, auth=(self.user, self.password))
@property
def pre_script(self):
"""Script that ensures we can access the SCVMM.
Without domain used in login, it is not possible to access the SCVMM environment. Therefore
we need to create our own authentication object (PSCredential) which will provide the
domain. Then it works. Big drawback is speed of this solution.
"""
return dedent("""
$secpasswd = ConvertTo-SecureString "{}" -AsPlainText -Force
$mycreds = New-Object System.Management.Automation.PSCredential ("{}\\{}", $secpasswd)
$scvmm_server = Get-SCVMMServer -Computername localhost -Credential $mycreds
""".format(self.password, self.domain, self.user))
def run_script(self, script):
"""Wrapper for running powershell scripts. Ensures the ``pre_script`` is loaded."""
script = dedent(script)
self.logger.debug(" Running PowerShell script:\n{}\n".format(script))
result = self.api.run_ps("{}\n\n{}".format(self.pre_script, script))
if result.status_code != 0:
raise self.PowerShellScriptError("Script returned {}!: {}"
.format(result.status_code, result.std_err))
return result.std_out.strip()
def _do_vm(self, vm_name, action, params=""):
self.logger.info(" {} {} SCVMM VM `{}`".format(action, params, vm_name))
self.run_script(
"Get-SCVirtualMachine -Name \"{}\" -VMMServer $scvmm_server | {}-SCVirtualMachine {}"
.format(vm_name, action, params).strip())
def start_vm(self, vm_name, force_start=False):
"""Start or resume virtual machine.
Args:
vm_name: Name of the virtual machine
force_start: If we want to use the Start specifically and not Resume
"""
if not force_start and self.is_vm_suspended(vm_name):
# Resume
self._do_vm(vm_name, "Resume")
else:
# Ordinary start
self._do_vm(vm_name, "Start")
def wait_vm_running(self, vm_name, num_sec=300):
wait_for(
lambda: self.is_vm_running(vm_name),
message="SCVMM VM {} be running.".format(vm_name),
num_sec=num_sec)
def stop_vm(self, vm_name, shutdown=False):
self._do_vm(vm_name, "Stop", "-Force" if not shutdown else "")
def wait_vm_stopped(self, vm_name, num_sec=300):
wait_for(
lambda: self.is_vm_stopped(vm_name),
message="SCVMM VM {} be stopped.".format(vm_name),
num_sec=num_sec)
def create_vm(self, vm_name):
raise NotImplementedError('create_vm not implemented.')
def rename_vm(self, vm_name, vm_new_name):
if not self.is_vm_stopped(vm_name) and self.vm_status(vm_name) not in self.STATES_FAILED:
self.stop_vm(vm_name)
self.wait_vm_stopped(vm_name)
script = """
Get-SCVirtualMachine -Name "{vm_name}" | Set-SCVirtualMachine -Name "{vm_new_name}"
""".format(vm_name=vm_name, vm_new_name=vm_new_name)
self.logger.info(" Renaming SCVMM VM `{}` to `{}`"
.format(vm_name, vm_new_name))
self.run_script(script)
def delete_vm(self, vm_name, host):
if not self.is_vm_stopped(vm_name) and self.vm_status(vm_name) not in self.STATES_FAILED:
self.stop_vm(vm_name)
self.wait_vm_stopped(vm_name)
script = """
$VM = Get-SCVirtualMachine -Name \"{vm_name}\" -VMMServer $scvmm_server
Remove-SCVirtualMachine -VM $VM
""".format(vm_name=vm_name, host=host)
self.logger.info(" Deleting SCVMM VM `{}`"
.format(vm_name))
self.run_script(script)
def delete_template(self, template):
script = """
$Template = Get-SCVMTemplate -Name \"{}\" -VMMServer $scvmm_server
Remove-SCVMTemplate -VMTemplate $Template -Force
""".format(template)
self.logger.info(" Removing SCVMM VM `{}`"
.format(template))
self.run_script(script)
def restart_vm(self, vm_name):
self._do_vm(vm_name, "Reset")
def list_vm(self, **kwargs):
data = self.run_script(
"Get-SCVirtualMachine -All -VMMServer $scvmm_server |"
"Select name | ConvertTo-Xml -as String")
return etree.parse(StringIO(data)).getroot().xpath("./Object/Property[@Name='Name']/text()")
def all_vms(self, **kwargs):
vm_list = []
data = self.run_script("""
$outputCollection = @()
$VMs = Get-SCVirtualMachine -All -VMMServer $scvmm_server |
Select VMId, Name, StatusString
$NetAdapter = Get-SCVirtualNetworkAdapter -VMMServer $scvmm_server -All |
Select ID, Name, IPv4Addresses
#Associate objects
$VMs | ForEach-Object{
$vm_object = $_
$ip_object = $NetAdapter | Where-Object {$_.Name -eq $vm_object.Name}
#Make a combined object
$outObj = "" | Select VMId, Name, Status, IPv4
$outObj.VMId = if($vm_object.VMId){$vm_object.VMId} else {"None"}
$outObj.Name = $vm_object.Name
$outObj.Status = $vm_object.StatusString
$outObj.IPv4 = if($ip_object.IPv4Addresses){$ip_object.IPv4Addresses} else {"None"}
#Add the object to the collection
$outputCollection += $outObj
}
$outputCollection | ConvertTo-Xml -as String
""")
vms = etree.parse(StringIO(data)).getroot()
for vm in vms:
VMId = vm.xpath("./Property[@Name='VMId']/text()")[0],
Name = vm.xpath("./Property[@Name='Name']/text()")[0],
Status = vm.xpath("./Property[@Name='Status']/text()")[0],
IPv4 = vm.xpath("./Property[@Name='IPv4']/text()")[0]
vm_data = (
None if VMId == 'None' else VMId[0],
Name[0],
Status[0],
None if IPv4 == 'None' else IPv4)
vm_list.append(VMInfo(*vm_data))
return vm_list
def list_template(self):
data = self.run_script(
"Get-SCVMTemplate -VMMServer $scvmm_server | Select name | ConvertTo-Xml -as String")
return etree.parse(StringIO(data)).getroot().xpath("./Object/Property[@Name='Name']/text()")
def list_flavor(self):
raise NotImplementedError('list_flavor not implemented.')
def list_network(self):
data = self.run_script(
"Get-SCLogicalNetwork -VMMServer $scvmm_server | ConvertTo-Xml -as String")
return etree.parse(StringIO(data)).getroot().xpath(
"./Object/Property[@Name='Name']/text()")
def vm_creation_time(self, vm_name):
xml = self.run_script(
"Get-SCVirtualMachine -Name \"{}\""
" -VMMServer $scvmm_server | ConvertTo-Xml -as String".format(vm_name))
date_time = etree.parse(StringIO(xml)).getroot().xpath(
"./Object/Property[@Name='CreationTime']/text()")[0]
return datetime.strptime(date_time, "%m/%d/%Y %I:%M:%S %p")
def info(self, vm_name):
pass
def disconnect(self):
pass
def vm_status(self, vm_name):
data = self.run_script(
"Get-SCVirtualMachine -Name \"{}\" -VMMServer $scvmm_server | ConvertTo-Xml -as String"
.format(vm_name))
return etree.parse(StringIO(data)).getroot().xpath(
"./Object/Property[@Name='StatusString']/text()")[0]
def is_vm_running(self, vm_name):
return self.vm_status(vm_name) == self.STATE_RUNNING
def is_vm_stopped(self, vm_name):
return self.vm_status(vm_name) in self.STATES_STOPPED
def is_vm_suspended(self, vm_name):
return self.vm_status(vm_name) == self.STATE_PAUSED
def in_steady_state(self, vm_name):
return self.vm_status(vm_name) in self.STATES_STEADY
def suspend_vm(self, vm_name):
self._do_vm(vm_name, "Suspend")
def wait_vm_suspended(self, vm_name, num_sec=300):
wait_for(
lambda: self.is_vm_suspended(vm_name),
message="SCVMM VM {} suspended.".format(vm_name),
num_sec=num_sec)
def clone_vm(self, vm_source, vm_host, path, vm_name):
script = """
$vm_new = Get-SCVirtualMachine -Name "{vm_source}" -VMMServer $scvmm_server
$vm_host = Get-SCVMHost -VMMServer $scvmm_server -ComputerName "{vm_host}"
New-SCVirtualMachine -Name "{vm_name}" -VM $vm_new -VMHost $vm_host -Path "{path}" -StartVM
""".format(vm_name=vm_name, vm_source=vm_source, vm_host=vm_host, path=path)
self.logger.info(" Deploying SCVMM VM `{}` from Clone of `{}`"
.format(vm_name, vm_source))
self.run_script(script)
def does_vm_exist(self, vm_name):
result = self.run_script("Get-SCVirtualMachine -Name \"{}\" -VMMServer $scvmm_server"
.format(vm_name)).strip()
return len(result) > 0
def does_template_exist(self, template):
result = self.run_script("Get-SCVMTemplate -Name \"{}\" -VMMServer $scvmm_server"
.format(template)).strip()
return len(result) > 0
def deploy_template(self, template, host_group, vm_name=None, **kwargs):
timeout = kwargs.pop('timeout', 900)
script = """
$tpl = Get-SCVMTemplate -Name "{template}" -VMMServer $scvmm_server
$vm_host_group = Get-SCVMHostGroup -Name "{host_group}" -VMMServer $scvmm_server
$vmc = New-SCVMConfiguration -VMTemplate $tpl -Name "{vm_name}" -VMHostGroup $vm_host_group
Update-SCVMConfiguration -VMConfiguration $vmc
New-SCVirtualMachine -Name "{vm_name}" -VMConfiguration $vmc
""".format(template=template, vm_name=vm_name, host_group=host_group)
self.logger.info(" Deploying SCVMM VM `{}` from template `{}` on host group `{}`"
.format(vm_name, template, host_group))
self.run_script(script)
self.enable_virtual_services(vm_name)
self.start_vm(vm_name)
self.wait_vm_running(vm_name, num_sec=timeout)
return vm_name
def enable_virtual_services(self, vm_name):
self.run_script(
"Set-SCVirtualMachine -InstallVirtualizationGuestServices 1 -VM '{}' ".format(vm_name))
def mark_as_template(self, vm_name, library, library_share):
# Converts an existing VM into a template. VM no longer exists afterwards.
script = """
$VM = Get-SCVirtualMachine -Name \"{vm_name}\" -VMMServer $scvmm_server
New-SCVMTemplate -Name \"{vm_name}\" -VM $VM -LibraryServer \"{ls}\" -SharePath \"{lp}\"
""".format(vm_name=vm_name, ls=library, lp=library_share)
self.logger.info(" Creating SCVMM Template `{vm_name}` from VM `{vm_name}`-tpl"
.format(vm_name=vm_name))
self.run_script(script)
@contextmanager
def with_vm(self, *args, **kwargs):
"""Context manager for better cleanup"""
name = self.deploy_template(*args, **kwargs)
yield name
self.delete_vm(name)
def current_ip_address(self, vm_name):
data = self.run_script(
"Get-SCVirtualMachine -Name \"{}\" -VMMServer $scvmm_server |"
"Get-SCVirtualNetworkAdapter | Select IPv4Addresses |"
"ft -HideTableHeaders".format(vm_name))
return data.translate(None, '{}')
def get_ip_address(self, vm_name, **kwargs):
return self.current_ip_address(vm_name)
def remove_host_from_cluster(self, hostname):
"""I did not notice any scriptlet that lets you do this."""
def disconnect_dvd_drives(self, vm_name):
number_dvds_disconnected = 0
script = """\
$VM = Get-SCVirtualMachine -Name "{}"
$DVDDrive = Get-SCVirtualDVDDrive -VM $VM
$DVDDrive[0] | Remove-SCVirtualDVDDrive
""".format(vm_name)
while self.data(vm_name).VirtualDVDDrives is not None:
self.run_script(script)
number_dvds_disconnected += 1
return number_dvds_disconnected
def data(self, vm_name):
"""Returns detailed informations about SCVMM VM"""
data = self.run_script(
"Get-SCVirtualMachine -Name \"{}\" -VMMServer $scvmm_server | ConvertTo-Xml -as String"
.format(vm_name))
return self.SCVMMDataHolderDict(etree.parse(StringIO(data)).getroot().xpath("./Object")[0])
##
# Classes and functions used to access detailed SCVMM Data
@staticmethod
def parse_data(t, data):
if data is None:
return None
elif t == "System.Boolean":
return data.lower().strip() == "true"
elif t.startswith("System.Int"):
return int(data)
elif t == "System.String" and data.lower().strip() == "none":
return None
class SCVMMDataHolderDict(object):
def __init__(self, data):
for prop in data.xpath("./Property"):
name = prop.attrib["Name"]
t = prop.attrib["Type"]
children = prop.getchildren()
if children:
if prop.xpath("./Property[@Name]"):
self.__dict__[name] = SCVMMSystem.SCVMMDataHolderDict(prop)
else:
self.__dict__[name] = SCVMMSystem.SCVMMDataHolderList(prop)
else:
data = prop.text
result = SCVMMSystem.parse_data(t, prop.text)
self.__dict__[name] = result
def __repr__(self):
return repr(self.__dict__)
class SCVMMDataHolderList(list):
def __init__(self, data):
super(SCVMMSystem.SCVMMDataHolderList, self).__init__()
for prop in data.xpath("./Property"):
t = prop.attrib["Type"]
data = prop.text
result = SCVMMSystem.parse_data(t, prop.text)
self.append(result)
class PowerShellScriptError(Exception):
pass