Project

General

Profile

root / trunk / src / haizea / cli / commands.py @ 641

1
# -------------------------------------------------------------------------- #
2
# Copyright 2006-2009, University of Chicago                                 #
3
# Copyright 2008-2009, 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