root / trunk / src / haizea / cli / commands.py @ 632
1 |
# -------------------------------------------------------------------------- #
|
---|---|
2 |
# Copyright 2006-2008, University of Chicago #
|
3 |
# Copyright 2008, Distributed Systems Architecture Group, Universidad #
|
4 |
# Complutense de Madrid (dsa-research.org) #
|
5 |
# #
|
6 |
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
|
7 |
# not use this file except in compliance with the License. You may obtain #
|
8 |
# a copy of the License at #
|
9 |
# #
|
10 |
# http://www.apache.org/licenses/LICENSE-2.0 #
|
11 |
# #
|
12 |
# Unless required by applicable law or agreed to in writing, software #
|
13 |
# distributed under the License is distributed on an "AS IS" BASIS, #
|
14 |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
15 |
# See the License for the specific language governing permissions and #
|
16 |
# limitations under the License. #
|
17 |
# -------------------------------------------------------------------------- #
|
18 |
|
19 |
from haizea.core.manager import Manager |
20 |
from haizea.common.utils import generate_config_name, unpickle |
21 |
from haizea.core.configfile import HaizeaConfig, HaizeaMultiConfig |
22 |
from haizea.common.config import ConfigException |
23 |
from haizea.cli.optionparser import OptionParser, Option |
24 |
from haizea.cli import Command |
25 |
from mx.DateTime import TimeDelta |
26 |
import xml.etree.ElementTree as ET |
27 |
import haizea.common.defaults as defaults |
28 |
import sys |
29 |
import os |
30 |
import errno |
31 |
import signal |
32 |
import time |
33 |
|
34 |
|
35 |
class haizea(Command): |
36 |
"""
|
37 |
This is the main Haizea command. By default, it will start Haizea as a daemon, which
|
38 |
can receive requests via RPC or interact with other components such as OpenNebula. It can
|
39 |
also start as a foreground process, and write all log messages to the console. All
|
40 |
Haizea options are specified through the configuration file."""
|
41 |
|
42 |
name = "haizea"
|
43 |
|
44 |
def __init__(self, argv): |
45 |
Command.__init__(self, argv)
|
46 |
|
47 |
self.optparser.add_option(Option("-c", "--conf", action="store", type="string", dest="conf", |
48 |
help = """
|
49 |
The location of the Haizea configuration file. If not
|
50 |
specified, Haizea will first look for it in
|
51 |
/etc/haizea/haizea.conf and then in ~/.haizea/haizea.conf.
|
52 |
"""))
|
53 |
self.optparser.add_option(Option("-f", "--fg", action="store_true", dest="foreground", |
54 |
help = """
|
55 |
Runs Haizea in the foreground.
|
56 |
"""))
|
57 |
self.optparser.add_option(Option("--stop", action="store_true", dest="stop", |
58 |
help = """
|
59 |
Stops the Haizea daemon.
|
60 |
"""))
|
61 |
|
62 |
def run(self): |
63 |
self.parse_options()
|
64 |
|
65 |
pidfile = defaults.DAEMON_PIDFILE # TODO: Make configurable
|
66 |
|
67 |
if self.opt.stop == None: |
68 |
# Start Haizea
|
69 |
|
70 |
# Check if a daemon is already running
|
71 |
if os.path.exists(pidfile):
|
72 |
pf = file(pidfile,'r') |
73 |
pid = int(pf.read().strip())
|
74 |
pf.close() |
75 |
|
76 |
try:
|
77 |
os.kill(pid, signal.SIG_DFL) |
78 |
except OSError, (err, msg): |
79 |
if err == errno.ESRCH:
|
80 |
# Pidfile is stale. Remove it.
|
81 |
os.remove(pidfile) |
82 |
else:
|
83 |
msg = "Unexpected error when checking pid file '%s'.\n%s\n" %(pidfile, msg)
|
84 |
sys.stderr.write(msg) |
85 |
sys.exit(1)
|
86 |
else:
|
87 |
msg = "Haizea seems to be already running (pid %i)\n" % pid
|
88 |
sys.stderr.write(msg) |
89 |
sys.exit(1)
|
90 |
|
91 |
try:
|
92 |
configfile=self.opt.conf
|
93 |
if configfile == None: |
94 |
# Look for config file in default locations
|
95 |
for loc in defaults.CONFIG_LOCATIONS: |
96 |
if os.path.exists(loc):
|
97 |
config = HaizeaConfig.from_file(loc) |
98 |
break
|
99 |
else:
|
100 |
print >> sys.stdout, "No configuration file specified, and none found at default locations." |
101 |
print >> sys.stdout, "Make sure a config file exists at:\n -> %s" % "\n -> ".join(defaults.CONFIG_LOCATIONS) |
102 |
print >> sys.stdout, "Or specify a configuration file with the --conf option." |
103 |
exit(1) |
104 |
else:
|
105 |
config = HaizeaConfig.from_file(configfile) |
106 |
except ConfigException, msg:
|
107 |
print >> sys.stderr, "Error in configuration file:" |
108 |
print >> sys.stderr, msg
|
109 |
exit(1) |
110 |
|
111 |
daemon = not self.opt.foreground |
112 |
|
113 |
manager = Manager(config, daemon, pidfile) |
114 |
|
115 |
manager.start() |
116 |
elif self.opt.stop: # Stop Haizea |
117 |
# Based on code in: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66012
|
118 |
try:
|
119 |
pf = file(pidfile,'r') |
120 |
pid = int(pf.read().strip())
|
121 |
pf.close() |
122 |
except IOError: |
123 |
msg = "Could not stop, pid file '%s' missing.\n"
|
124 |
sys.stderr.write(msg % pidfile) |
125 |
sys.exit(1)
|
126 |
try:
|
127 |
while 1: |
128 |
os.kill(pid, signal.SIGTERM) |
129 |
time.sleep(1)
|
130 |
except OSError, err: |
131 |
err = str(err)
|
132 |
if err.find("No such process") > 0: |
133 |
os.remove(pidfile) |
134 |
else:
|
135 |
print str(err) |
136 |
sys.exit(1)
|
137 |
|
138 |
class haizea_generate_configs(Command): |
139 |
"""
|
140 |
Takes an Haizea multiconfiguration file and generates the individual
|
141 |
configuration files. See the Haizea manual for more details on multiconfiguration
|
142 |
files."""
|
143 |
|
144 |
name = "haizea-generate-configs"
|
145 |
|
146 |
def __init__(self, argv): |
147 |
Command.__init__(self, argv)
|
148 |
|
149 |
self.optparser.add_option(Option("-c", "--conf", action="store", type="string", dest="conf", required=True, |
150 |
help = """
|
151 |
Multiconfiguration file.
|
152 |
"""))
|
153 |
self.optparser.add_option(Option("-d", "--dir", action="store", type="string", dest="dir", required=True, |
154 |
help = """
|
155 |
Directory where the individual configuration files
|
156 |
must be created.
|
157 |
"""))
|
158 |
|
159 |
def run(self): |
160 |
self.parse_options()
|
161 |
|
162 |
configfile=self.opt.conf
|
163 |
multiconfig = HaizeaMultiConfig.from_file(configfile) |
164 |
|
165 |
dir = self.opt.dir
|
166 |
|
167 |
configs = multiconfig.get_configs() |
168 |
|
169 |
etcdir = os.path.abspath(dir)
|
170 |
if not os.path.exists(etcdir): |
171 |
os.makedirs(etcdir) |
172 |
|
173 |
for c in configs: |
174 |
profile = c.get_attr("profile")
|
175 |
tracefile = c.get("tracefile")
|
176 |
injfile = c.get("injectionfile")
|
177 |
configname = generate_config_name(profile, tracefile, injfile) |
178 |
configfile = etcdir + "/%s.conf" % configname
|
179 |
fc = open(configfile, "w") |
180 |
c.config.write(fc) |
181 |
fc.close() |
182 |
|
183 |
class haizea_generate_scripts(Command): |
184 |
"""
|
185 |
Generates a script, based on a script template, to run all the individual
|
186 |
configuration files generated by haizea-generate-configs. This command
|
187 |
requires Mako Templates for Python (http://www.makotemplates.org/)."""
|
188 |
|
189 |
name = "haizea-generate-scripts"
|
190 |
|
191 |
def __init__(self, argv): |
192 |
Command.__init__(self, argv)
|
193 |
|
194 |
self.optparser.add_option(Option("-c", "--conf", action="store", type="string", dest="conf", required=True, |
195 |
help = """
|
196 |
Multiconfiguration file used in haizea-generate-configs.
|
197 |
"""))
|
198 |
self.optparser.add_option(Option("-d", "--confdir", action="store", type="string", dest="confdir", required=True, |
199 |
help = """
|
200 |
Directory containing the individual configuration files.
|
201 |
"""))
|
202 |
self.optparser.add_option(Option("-t", "--template", action="store", type="string", dest="template", required=True, |
203 |
help = """
|
204 |
Script template (sample templates are included in /usr/share/haizea/etc)
|
205 |
"""))
|
206 |
self.optparser.add_option(Option("-m", "--only-missing", action="store_true", dest="onlymissing", |
207 |
help = """
|
208 |
If specified, the generated script will only run the configurations
|
209 |
that have not already produced a datafile. This is useful when some simulations
|
210 |
fail, and you don't want to have to rerun them all.
|
211 |
"""))
|
212 |
|
213 |
def run(self): |
214 |
self.parse_options()
|
215 |
|
216 |
configfile=self.opt.conf
|
217 |
multiconfig = HaizeaMultiConfig.from_file(configfile) |
218 |
|
219 |
try:
|
220 |
from mako.template import Template |
221 |
except:
|
222 |
print "You need Mako Templates for Python to run this command." |
223 |
print "You can download them at http://www.makotemplates.org/" |
224 |
exit(1) |
225 |
|
226 |
configs = multiconfig.get_configs() |
227 |
|
228 |
etcdir = os.path.abspath(self.opt.confdir)
|
229 |
if not os.path.exists(etcdir): |
230 |
os.makedirs(etcdir) |
231 |
|
232 |
templatedata = [] |
233 |
for c in configs: |
234 |
profile = c.get_attr("profile")
|
235 |
tracefile = c.get("tracefile")
|
236 |
injfile = c.get("injectionfile")
|
237 |
datafile = c.get("datafile")
|
238 |
configname = generate_config_name(profile, tracefile, injfile) |
239 |
if not self.opt.onlymissing or not os.path.exists(datafile): |
240 |
configfile = etcdir + "/%s.conf" % configname
|
241 |
templatedata.append((configname, configfile)) |
242 |
|
243 |
template = Template(filename=self.opt.template)
|
244 |
print template.render(configs=templatedata, etcdir=etcdir)
|
245 |
|
246 |
|
247 |
class haizea_convert_data(Command): |
248 |
"""
|
249 |
Converts Haizea datafiles into another (easier to process) format.
|
250 |
"""
|
251 |
|
252 |
name = "haizea-convert-data"
|
253 |
|
254 |
def __init__(self, argv): |
255 |
Command.__init__(self, argv)
|
256 |
|
257 |
self.optparser.add_option(Option("-t", "--type", action="store", dest="type", |
258 |
choices = ["per-experiment", "per-lease"], |
259 |
help = """
|
260 |
Type of data to produce.
|
261 |
"""))
|
262 |
self.optparser.add_option(Option("-f", "--format", action="store", type="string", dest="format", |
263 |
help = """
|
264 |
Output format. Currently supported: csv
|
265 |
"""))
|
266 |
|
267 |
def run(self): |
268 |
self.parse_options()
|
269 |
|
270 |
datafiles=self.args[1:] |
271 |
if len(datafiles) == 0: |
272 |
print "Please specify at least one datafile to convert" |
273 |
exit(1) |
274 |
|
275 |
attr_names = unpickle(datafiles[0]).attrs.keys()
|
276 |
|
277 |
if len(attr_names) == 0: |
278 |
header = ""
|
279 |
else:
|
280 |
header = ",".join(attr_names) + "," |
281 |
|
282 |
if self.opt.type == "per-experiment": |
283 |
header += "all-best-effort"
|
284 |
elif self.opt.type == "per-lease": |
285 |
header += "lease_id,waiting_time,slowdown"
|
286 |
|
287 |
print header
|
288 |
|
289 |
for datafile in datafiles: |
290 |
stats = unpickle(datafile) |
291 |
|
292 |
attrs = ",".join([stats.attrs[attr_name] for attr_name in attr_names]) |
293 |
|
294 |
if self.opt.type == "per-experiment": |
295 |
print attrs + "," + `(stats.get_besteffort_end() - stats.starttime).seconds` |
296 |
elif self.opt.type == "per-lease": |
297 |
waitingtimes = stats.get_waiting_times() |
298 |
slowdowns = stats.get_slowdowns() |
299 |
for lease_id in waitingtimes: |
300 |
print ",".join([attrs, `lease_id`, `waitingtimes[lease_id].seconds`, `slowdowns[lease_id]`]) |
301 |
|
302 |
class haizea_lwf2xml(Command): |
303 |
"""
|
304 |
Converts old Haizea LWF file into new XML-based LWF format
|
305 |
"""
|
306 |
|
307 |
name = "haizea-lwf2xml"
|
308 |
|
309 |
def __init__(self, argv): |
310 |
Command.__init__(self, argv)
|
311 |
|
312 |
self.optparser.add_option(Option("-i", "--in", action="store", type="string", dest="inf", |
313 |
help = """
|
314 |
Input file
|
315 |
"""))
|
316 |
self.optparser.add_option(Option("-o", "--out", action="store", type="string", dest="outf", |
317 |
help = """
|
318 |
Output file
|
319 |
"""))
|
320 |
|
321 |
def run(self): |
322 |
self.parse_options()
|
323 |
|
324 |
infile = self.opt.inf
|
325 |
outfile = self.opt.outf
|
326 |
|
327 |
root = ET.Element("lease-workload")
|
328 |
root.set("name", infile)
|
329 |
description = ET.SubElement(root, "description")
|
330 |
time = TimeDelta(seconds=0)
|
331 |
id = 1
|
332 |
requests = ET.SubElement(root, "lease-requests")
|
333 |
|
334 |
|
335 |
infile = open(infile, "r") |
336 |
for line in infile: |
337 |
if line[0]!='#' and len(line.strip()) != 0: |
338 |
fields = line.split() |
339 |
submit_time = int(fields[0]) |
340 |
start_time = int(fields[1]) |
341 |
duration = int(fields[2]) |
342 |
real_duration = int(fields[3]) |
343 |
num_nodes = int(fields[4]) |
344 |
cpu = int(fields[5]) |
345 |
mem = int(fields[6]) |
346 |
disk = int(fields[7]) |
347 |
vm_image = fields[8]
|
348 |
vm_imagesize = int(fields[9]) |
349 |
|
350 |
|
351 |
|
352 |
lease_request = ET.SubElement(requests, "lease-request")
|
353 |
lease_request.set("arrival", str(TimeDelta(seconds=submit_time))) |
354 |
if real_duration != duration:
|
355 |
realduration = ET.SubElement(lease_request, "realduration")
|
356 |
realduration.set("time", str(TimeDelta(seconds=real_duration))) |
357 |
|
358 |
lease = ET.SubElement(lease_request, "lease")
|
359 |
lease.set("id", `id`) |
360 |
|
361 |
|
362 |
nodes = ET.SubElement(lease, "nodes")
|
363 |
node_set = ET.SubElement(nodes, "node-set")
|
364 |
node_set.set("numnodes", `num_nodes`)
|
365 |
res = ET.SubElement(node_set, "res")
|
366 |
res.set("type", "CPU") |
367 |
if cpu == 1: |
368 |
res.set("amount", "100") |
369 |
else:
|
370 |
pass
|
371 |
res = ET.SubElement(node_set, "res")
|
372 |
res.set("type", "Memory") |
373 |
res.set("amount", `mem`)
|
374 |
|
375 |
start = ET.SubElement(lease, "start")
|
376 |
if start_time == -1: |
377 |
lease.set("preemptible", "true") |
378 |
else:
|
379 |
lease.set("preemptible", "false") |
380 |
exact = ET.SubElement(start, "exact")
|
381 |
exact.set("time", str(TimeDelta(seconds=start_time))) |
382 |
|
383 |
duration_elem = ET.SubElement(lease, "duration")
|
384 |
duration_elem.set("time", str(TimeDelta(seconds=duration))) |
385 |
|
386 |
software = ET.SubElement(lease, "software")
|
387 |
diskimage = ET.SubElement(software, "disk-image")
|
388 |
diskimage.set("id", vm_image)
|
389 |
diskimage.set("size", `vm_imagesize`)
|
390 |
|
391 |
|
392 |
id += 1 |
393 |
tree = ET.ElementTree(root) |
394 |
print ET.tostring(root)
|
395 |
#tree.write("page.xhtml")
|
396 |
#head = ET.SubElement(root, "head")
|
397 |
|
398 |
#title = ET.SubElement(head, "title")
|
399 |
#title.text = "Page Title"
|
400 |
|
401 |
#body = ET.SubElement(root, "body")
|
402 |
#body.set("bgcolor", "#ffffff")
|
403 |
|
404 |
#body.text = "Hello, World!"
|
405 |
|
406 |
# wrap it in an ElementTree instance, and save as XML
|
407 |
#tree = ET.ElementTree(root)
|
408 |
|
409 |
|
410 |
|
411 |
|