-
Notifications
You must be signed in to change notification settings - Fork 29
/
xenserver_parallel_evacuate.py
executable file
·239 lines (207 loc) · 8.66 KB
/
xenserver_parallel_evacuate.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
#!/usr/bin/python
# Copyright 2015, Schuberg Philis BV
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# Evacuate a XenServer using multiple threads in parallel
# Remi Bergsma, [email protected]
# We depend on these modules
import sys
import os
import getopt
# Argument handling Class
class handleArguments(object):
def handleArguments(self,argv):
self.DEBUG = 0
self.DRYRUN = 1
self.threads = 5
self.skip_checks = False
# Usage message
help = "Usage: " + os.path.basename(__file__) + ' --threads [--debug --exec --skip-checks]'
try:
opts, args = getopt.getopt(argv,"ht:",["threads=","debug","exec","skip-checks"])
except getopt.GetoptError:
print help
sys.exit(2)
for opt, arg in opts:
if opt == '-h':
print help
sys.exit()
elif opt in ("-t","--threads"):
self.threads = arg
elif opt in ("--debug"):
self.DEBUG = 1
elif opt in ("--exec"):
self.DRYRUN = 0
elif opt in ("--skip-checks"):
self.skip_checks = True
# Class to handle XenServer parallel evactation
class xenserver_parallel_evacuation(object):
def __init__(self, arg):
self.DEBUG = arg.DEBUG
self.DRYRUN = arg.DRYRUN
self.threads = arg.threads
self.poolmember = False
self.vmlist = False
self.hvlist = False
self.skip_checks = arg.skip_checks
# Run local command
def run_local_command(self, command):
if self.DEBUG == 1:
print "Debug: Running command:" + command
# XenServer 6.2 runs an ancient python 2.4
# and the subprocess module did not work
# properly. That's why I used ancient os.*
p = os.popen(command)
lines = ""
while 1:
line = p.readline()
if not line: break
lines += line
return lines
# Get overview of free memory on enabled hosts
def get_hypervisor_free_memory(self):
return self.run_local_command("for h in $(xe host-list params=name-label \
enabled=true --minimal | tr ',' ' '); \
do echo -n \"$h,\"; \
xe host-compute-free-memory host=$h;done")
# Get overview of VMs and their memory
def get_vms_with_memory_from_hypervisor(self, grep_for=None):
try:
grep_command = ""
if grep_for is not None:
grep_command = "| grep %s" % grep_for
return self.run_local_command("xe vm-list resident-on=$(xe host-list params=uuid \
name-label=$HOSTNAME --minimal) \
params=name-label,memory-static-max is-control-domain=false |\
tr '\\n' ' ' | sed 's/name-label/\\n/g' | \
awk {'print $4 \",\" $8'} | sed '/^,$/d'" + grep_command)
except:
return False
# Get overview of peer hypervisors and their available memory
def construct_poolmembers(self):
self.hvlist = self.get_hypervisor_free_memory()
# Construct poolmembers
poolmember = {}
hvlist_iter = self.hvlist.split('\n')
for hv in hvlist_iter:
info = hv.split(',')
try:
hv = info[0]
mem = info[1]
except:
continue
poolmember[hv] = {}
poolmember[hv]['memory_free'] = int(mem)
poolmember[hv]['name'] = hv
return poolmember
# Get the hypervisor with the most free memory
def get_hypervisor_with_most_free_memory(self):
if self.poolmember == False:
self.poolmember = self.construct_poolmembers()
return sorted(self.poolmember.items(),key = lambda x :x[1]['memory_free'],reverse = True)[:1][0][1]
# Generate migration plan
def generate_migration_plan(self, grep_for=None):
if self.skip_checks is False:
# Make sure host is disabled
if self.is_host_enabled() is not False:
print "Error: Host should be disabled first."
return False
# Make sure pool HA is turned off
if self.pool_ha_check() is not False:
print "Error: Pool HA should be disabled first."
return False
# Generate migration plan
migration_cmds = ""
if self.vmlist == False:
self.vmlist = self.get_vms_with_memory_from_hypervisor(grep_for)
vmlist_iter = self.vmlist.split('\n')
for vm in vmlist_iter:
info = vm.split(',')
try:
vm = info[0]
mem = int(info[1].strip())
except:
continue
while True:
to_hv = self.get_hypervisor_with_most_free_memory()
# If the hv with the most memory cannot host this vm, we're in trouble
if to_hv['memory_free'] > mem:
# update free_mem
self.poolmember[to_hv['name']]['memory_free'] -= int(mem)
# Prepare migration command
migration_cmds += "xe vm-migrate vm=" + vm + " host=" + to_hv['name'] + ";\n"
print "OK, found migration destination for " + vm
break
else:
# Unable to empty this hv
print "Error: not enough memory (need: " + str(mem) + ") on any hypervisor to migrate vm " + vm + ". This means N+1 rule is not met, please investigate!"
return False
return migration_cmds
# Execute migration plan
def execute_migration_plan(self, grep_for=None):
try:
migration_cmds = self.generate_migration_plan(grep_for)
if migration_cmds == False:
return False
return self.run_local_command("nohup echo \"" + migration_cmds + "\" | \
xargs -n 1 -P " + str(self.threads) + " -I {} bash -c \{\} 2>&1 >/dev/null &")
except:
return False
# Is host enabled?
def is_host_enabled(self):
print "Note: Checking if host is enabled or disabled.."
try:
if self.run_local_command("xe host-list params=enabled name-label=$HOSTNAME --minimal").strip() == "true":
return True
else:
return False
except:
return "Error"
# Check the current state of HA
def pool_ha_check(self):
try:
if self.run_local_command("xe pool-list params=ha-enabled | \
awk {'print $5'} | tr -d '\n'") == "true":
return True
else:
return False
except:
return "Error"
# Main program
if __name__ == "__main__":
arg = handleArguments()
arg.handleArguments(sys.argv[1:])
# Init our class
x = xenserver_parallel_evacuation(arg)
if arg.DRYRUN == 1:
print "Note: Running in DRY-run mode, not executing. Use --exec to execute."
print "Note: Calculating migration plan.."
print "Note: This is the migration plan:"
print "Instances (threads = %s)" % str(x.threads)
print x.generate_migration_plan("i-")
x.threads = 1
x.vmlist = False
print "Routers (threads = %s)" % str(x.threads)
print x.generate_migration_plan("r-")
sys.exit(0)
print "Note: Executing migration plan using " + str(x.threads) + " threads.."
print x.execute_migration_plan("i-")
x.threads = 1
x.vmlist = False
print "Note: Executing migration plan using " + str(x.threads) + " threads.."
print x.execute_migration_plan()