Project

General

Profile

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

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.core.accounting import AccountingDataCollection
23
from haizea.common.config import ConfigException
24
from haizea.cli.optionparser import Option
25
from haizea.cli import Command
26
from mx.DateTime import TimeDelta
27
import haizea.common.defaults as defaults
28
import sys
29
import os
30
import errno
31
import signal
32
from time import sleep
33

    
34
try:
35
    import xml.etree.ElementTree as ET
36
except ImportError:
37
    # Compatibility with Python <=2.4
38
    import elementtree.ElementTree as ET 
39

    
40

    
41
class haizea(Command):
42
    """
43
    This is the main Haizea command. By default, it will start Haizea as a daemon, which
44
    can receive requests via RPC or interact with other components such as OpenNebula. It can
45
    also start as a foreground process, and write all log messages to the console. All
46
    Haizea options are specified through the configuration file."""
47
    
48
    name = "haizea"
49
    
50
    def __init__(self, argv):
51
        Command.__init__(self, argv)
52
        
53
        self.optparser.add_option(Option("-c", "--conf", action="store", type="string", dest="conf",
54
                                         help = """
55
                                         The location of the Haizea configuration file. If not
56
                                         specified, Haizea will first look for it in
57
                                         /etc/haizea/haizea.conf and then in ~/.haizea/haizea.conf.
58
                                         """))
59
        self.optparser.add_option(Option("-f", "--fg", action="store_true",  dest="foreground",
60
                                         help = """
61
                                         Runs Haizea in the foreground.
62
                                         """))
63
        self.optparser.add_option(Option("--stop", action="store_true",  dest="stop",
64
                                         help = """
65
                                         Stops the Haizea daemon.
66
                                         """))
67
                
68
    def run(self):
69
        self.parse_options()
70

    
71
        pidfile = defaults.DAEMON_PIDFILE # TODO: Make configurable
72

    
73
        if self.opt.stop == None:
74
            # Start Haizea
75
             
76
            # Check if a daemon is already running
77
            if os.path.exists(pidfile):
78
                pf  = file(pidfile,'r')
79
                pid = int(pf.read().strip())
80
                pf.close()
81
     
82
                try:
83
                    os.kill(pid, signal.SIG_DFL)
84
                except OSError, (err, msg):
85
                    if err == errno.ESRCH:
86
                        # Pidfile is stale. Remove it.
87
                        os.remove(pidfile)
88
                    else:
89
                        msg = "Unexpected error when checking pid file '%s'.\n%s\n" %(pidfile, msg)
90
                        sys.stderr.write(msg)
91
                        sys.exit(1)
92
                else:
93
                    msg = "Haizea seems to be already running (pid %i)\n" % pid
94
                    sys.stderr.write(msg)
95
                    sys.exit(1)
96
     
97
            try:
98
                configfile=self.opt.conf
99
                if configfile == None:
100
                    # Look for config file in default locations
101
                    for loc in defaults.CONFIG_LOCATIONS:
102
                        if os.path.exists(loc):
103
                            config = HaizeaConfig.from_file(loc)
104
                            break
105
                    else:
106
                        print >> sys.stdout, "No configuration file specified, and none found at default locations."
107
                        print >> sys.stdout, "Make sure a config file exists at:\n  -> %s" % "\n  -> ".join(defaults.CONFIG_LOCATIONS)
108
                        print >> sys.stdout, "Or specify a configuration file with the --conf option."
109
                        exit(1)
110
                else:
111
                    config = HaizeaConfig.from_file(configfile)
112
            except ConfigException, msg:
113
                print >> sys.stderr, "Error in configuration file:"
114
                print >> sys.stderr, msg
115
                exit(1)
116
                
117
            daemon = not self.opt.foreground
118
        
119
            manager = Manager(config, daemon, pidfile)
120
        
121
            manager.start()
122
        elif self.opt.stop: # Stop Haizea
123
            # Based on code in:  http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66012
124
            try:
125
                pf  = file(pidfile,'r')
126
                pid = int(pf.read().strip())
127
                pf.close()
128
            except IOError:
129
                msg = "Could not stop, pid file '%s' missing.\n"
130
                sys.stderr.write(msg % pidfile)
131
                sys.exit(1)
132
            try:
133
                while 1:
134
                    os.kill(pid, signal.SIGTERM)
135
                    sleep(1)
136
            except OSError, err:
137
                err = str(err)
138
                if err.find("No such process") > 0:
139
                    os.remove(pidfile)
140
                else:
141
                    print str(err)
142
                    sys.exit(1)
143

    
144
class haizea_generate_configs(Command):
145
    """
146
    Takes an Haizea multiconfiguration file and generates the individual
147
    configuration files. See the Haizea manual for more details on multiconfiguration
148
    files."""
149
    
150
    name = "haizea-generate-configs"
151

    
152
    def __init__(self, argv):
153
        Command.__init__(self, argv)
154
        
155
        self.optparser.add_option(Option("-c", "--conf", action="store", type="string", dest="conf", required=True,
156
                                         help = """
157
                                         Multiconfiguration file.
158
                                         """))
159
        self.optparser.add_option(Option("-d", "--dir", action="store", type="string", dest="dir", required=True,
160
                                         help = """
161
                                         Directory where the individual configuration files
162
                                         must be created.
163
                                         """))
164
                
165
    def run(self):    
166
        self.parse_options()
167
        
168
        configfile=self.opt.conf
169
        multiconfig = HaizeaMultiConfig.from_file(configfile)
170
        
171
        etcdir = self.opt.dir
172
        
173
        configs = multiconfig.get_configs()
174
        
175
        etcdir = os.path.abspath(etcdir)    
176
        if not os.path.exists(etcdir):
177
            os.makedirs(etcdir)
178
            
179
        for c in configs:
180
            profile = c.get_attr("profile")
181
            tracefile = c.get("tracefile")
182
            injfile = c.get("injectionfile")
183
            configname = generate_config_name(profile, tracefile, injfile)
184
            configfile = etcdir + "/%s.conf" % configname
185
            fc = open(configfile, "w")
186
            c.config.write(fc)
187
            fc.close()
188

    
189
class haizea_generate_scripts(Command):
190
    """
191
    Generates a script, based on a script template, to run all the individual 
192
    configuration files generated by haizea-generate-configs. This command 
193
    requires Mako Templates for Python (http://www.makotemplates.org/)."""
194
    
195
    name = "haizea-generate-scripts"
196

    
197
    def __init__(self, argv):
198
        Command.__init__(self, argv)
199
        
200
        self.optparser.add_option(Option("-c", "--conf", action="store", type="string", dest="conf", required=True,
201
                                         help = """
202
                                         Multiconfiguration file used in haizea-generate-configs.
203
                                         """))
204
        self.optparser.add_option(Option("-d", "--confdir", action="store", type="string", dest="confdir", required=True,
205
                                         help = """
206
                                         Directory containing the individual configuration files.
207
                                         """))
208
        self.optparser.add_option(Option("-t", "--template", action="store", type="string", dest="template", required=True,
209
                                         help = """
210
                                         Script template (sample templates are included in /usr/share/haizea/etc)
211
                                         """))
212
        self.optparser.add_option(Option("-m", "--only-missing", action="store_true",  dest="onlymissing",
213
                                         help = """
214
                                         If specified, the generated script will only run the configurations
215
                                         that have not already produced a datafile. This is useful when some simulations
216
                                         fail, and you don't want to have to rerun them all.
217
                                         """))
218
                
219
    def run(self):        
220
        self.parse_options()
221
        
222
        configfile=self.opt.conf
223
        multiconfig = HaizeaMultiConfig.from_file(configfile)
224
                
225
        try:
226
            from mako.template import Template
227
        except Exception, e:
228
            print "You need Mako Templates for Python to run this command."
229
            print "You can download them at http://www.makotemplates.org/"
230
            exit(1)
231
    
232
        configs = multiconfig.get_configs()
233
        
234
        etcdir = os.path.abspath(self.opt.confdir)    
235
        if not os.path.exists(etcdir):
236
            os.makedirs(etcdir)
237
            
238
        templatedata = []    
239
        for c in configs:
240
            profile = c.get_attr("profile")
241
            tracefile = c.get("tracefile")
242
            injfile = c.get("injectionfile")
243
            datafile = c.get("datafile")
244
            configname = generate_config_name(profile, tracefile, injfile)
245
            if not self.opt.onlymissing or not os.path.exists(datafile):
246
                configfile = etcdir + "/%s.conf" % configname
247
                templatedata.append((configname, configfile))
248
    
249
        template = Template(filename=self.opt.template)
250
        print template.render(configs=templatedata, etcdir=etcdir)
251

    
252

    
253
class haizea_convert_data(Command):
254
    """
255
    Converts Haizea datafiles into another (easier to process) format.
256
    """
257
    
258
    name = "haizea-convert-data"
259

    
260
    def __init__(self, argv):
261
        Command.__init__(self, argv)
262
        
263
        self.optparser.add_option(Option("-t", "--type", action="store",  dest="type",
264
                                         choices = ["per-run", "per-lease", "counter"],
265
                                         help = """
266
                                         Type of data to produce.
267
                                         """))
268
        self.optparser.add_option(Option("-c", "--counter", action="store",  dest="counter",
269
                                         help = """
270
                                         Counter to print out when using '--type counter'.
271
                                         """))
272
        self.optparser.add_option(Option("-f", "--format", action="store", type="string", dest="format",
273
                                         help = """
274
                                         Output format. Currently supported: csv
275
                                         """))
276
        self.optparser.add_option(Option("-l", "--list-counters", action="store_true",  dest="list_counters",
277
                                         help = """
278
                                         If specified, the command will just print out the names of counters
279
                                         stored in the data file and then exit, regardless of other parameters.
280
                                         """))
281
                
282
    def run(self):            
283
        self.parse_options()
284

    
285
        datafiles=self.args[1:]
286
        if len(datafiles) == 0:
287
            print "Please specify at least one datafile to convert"
288
            exit(1)
289
        
290
        datafile1 = unpickle(datafiles[0])
291
        
292
        counter_names = datafile1.counters.keys()
293
        attr_names = datafile1.attrs.keys()
294
        lease_stats_names = datafile1.lease_stats_names
295
        stats_names = datafile1.stats_names
296

    
297
        if self.opt.list_counters:
298
            for counter in counter_names:
299
                print counter
300
            exit(0)
301
        
302
        if self.opt.type == "per-run":
303
            header_fields = attr_names + stats_names
304
        elif self.opt.type == "per-lease":
305
            header_fields = attr_names + ["lease_id"] + lease_stats_names
306
        elif self.opt.type == "counter":
307
            counter = self.opt.counter
308
            if not datafile1.counters.has_key(counter):
309
                print "The specified datafile does not have a counter called '%s'" % counter
310
                exit(1)
311
            header_fields = attr_names + ["time", "value"]
312
            if datafile1.counter_avg_type[counter] != AccountingDataCollection.AVERAGE_NONE:
313
                header_fields.append("average")                
314

    
315
        header = ",".join(header_fields)
316
            
317
        print header
318
        
319
        for datafile in datafiles:
320
            data = unpickle(datafile)
321
        
322
            attrs = [data.attrs[attr_name] for attr_name in attr_names]
323
                        
324
            if self.opt.type == "per-run":
325
                fields = attrs + [`data.stats[stats_name]` for stats_name in stats_names]
326
                print ",".join(fields)
327
            elif self.opt.type == "per-lease":
328
                leases = data.lease_stats
329
                for lease_id, lease_stat in leases.items():
330
                    fields = attrs + [`lease_id`] + [`lease_stat.get(lease_stat_name,"")` for lease_stat_name in lease_stats_names]
331
                    print ",".join(fields)
332
            elif self.opt.type == "counter":
333
                for (time, lease_id, value, avg) in data.counters[counter]:
334
                    fields = attrs + [`time`, `value`]
335
                    if data.counter_avg_type[counter] != AccountingDataCollection.AVERAGE_NONE:
336
                        fields.append(`avg`)
337
                    print ",".join(fields)
338
                    
339

    
340

    
341
class haizea_lwf2xml(Command):
342
    """
343
    Converts old Haizea LWF file into new XML-based LWF format
344
    """
345
    
346
    name = "haizea-lwf2xml"
347

    
348
    def __init__(self, argv):
349
        Command.__init__(self, argv)
350
        
351
        self.optparser.add_option(Option("-i", "--in", action="store",  type="string", dest="inf",
352
                                         help = """
353
                                         Input file
354
                                         """))
355
        self.optparser.add_option(Option("-o", "--out", action="store", type="string", dest="outf",
356
                                         help = """
357
                                         Output file
358
                                         """))
359
                
360
    def run(self):            
361
        self.parse_options()
362

    
363
        infile = self.opt.inf
364
        outfile = self.opt.outf
365
        
366
        root = ET.Element("lease-workload")
367
        root.set("name", infile)
368
        description = ET.SubElement(root, "description")
369
        time = TimeDelta(seconds=0)
370
        lease_id = 1
371
        requests = ET.SubElement(root, "lease-requests")
372
        
373
        
374
        infile = open(infile, "r")
375
        for line in infile:
376
            if line[0]!='#' and len(line.strip()) != 0:
377
                fields = line.split()
378
                submit_time = int(fields[0])
379
                start_time = int(fields[1])
380
                duration = int(fields[2])
381
                real_duration = int(fields[3])
382
                num_nodes = int(fields[4])
383
                cpu = int(fields[5])
384
                mem = int(fields[6])
385
                disk = int(fields[7])
386
                vm_image = fields[8]
387
                vm_imagesize = int(fields[9])
388
                
389
                
390
        
391
                lease_request = ET.SubElement(requests, "lease-request")
392
                lease_request.set("arrival", str(TimeDelta(seconds=submit_time)))
393
                if real_duration != duration:
394
                    realduration = ET.SubElement(lease_request, "realduration")
395
                    realduration.set("time", str(TimeDelta(seconds=real_duration)))
396
                
397
                lease = ET.SubElement(lease_request, "lease")
398
                lease.set("id", `lease_id`)
399

    
400
                
401
                nodes = ET.SubElement(lease, "nodes")
402
                node_set = ET.SubElement(nodes, "node-set")
403
                node_set.set("numnodes", `num_nodes`)
404
                res = ET.SubElement(node_set, "res")
405
                res.set("type", "CPU")
406
                if cpu == 1:
407
                    res.set("amount", "100")
408
                else:
409
                    pass
410
                res = ET.SubElement(node_set, "res")
411
                res.set("type", "Memory")
412
                res.set("amount", `mem`)
413
                
414
                start = ET.SubElement(lease, "start")
415
                if start_time == -1:
416
                    lease.set("preemptible", "true")
417
                else:
418
                    lease.set("preemptible", "false")
419
                    exact = ET.SubElement(start, "exact")
420
                    exact.set("time", str(TimeDelta(seconds=start_time)))
421

    
422
                duration_elem = ET.SubElement(lease, "duration")
423
                duration_elem.set("time", str(TimeDelta(seconds=duration)))
424

    
425
                software = ET.SubElement(lease, "software")
426
                diskimage = ET.SubElement(software, "disk-image")
427
                diskimage.set("id", vm_image)
428
                diskimage.set("size", `vm_imagesize`)
429
                
430
                    
431
                lease_id += 1
432
        tree = ET.ElementTree(root)
433
        print ET.tostring(root)
434
        #tree.write("page.xhtml")
435

    
436

    
437
        
438

    
439