Project

General

Profile

root / branches / 1.1 / src / haizea / lwf / generators.py @ 847

1
# -------------------------------------------------------------------------- #
2
# Copyright 2006-2010, University of Chicago                                 #
3
# Copyright 2008-2010, 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
import haizea.common.stats as stats
20
from haizea.core.leases import LeaseWorkload, LeaseAnnotation, LeaseAnnotations, Timestamp, Lease,\
21
    Capacity, Duration, UnmanagedSoftwareEnvironment,\
22
    DiskImageSoftwareEnvironment, Site
23
from haizea.common.utils import round_datetime_delta
24
from mx.DateTime import DateTimeDelta, TimeDelta, Parser
25
import ConfigParser 
26

    
27
try:
28
    import xml.etree.ElementTree as ET
29
except ImportError:
30
    # Compatibility with Python <=2.4
31
    import elementtree.ElementTree as ET 
32

    
33
class FileGenerator(object):
34
    
35
    GENERAL_SEC = "general"
36
    ATTRIBUTES_OPT = "attributes"
37
    
38
    TYPE_OPT = "type"
39
    DISTRIBUTION_OPT = "distribution"
40
    DISCRETE_OPT = "discrete"
41
    MIN_OPT = "min"
42
    MAX_OPT = "max"
43
    VALUES_OPT = "values"
44
    MEAN_OPT = "mu"
45
    STDEV_OPT = "sigma"
46
    ALPHA_OPT = "alpha"
47
    SCALE_OPT = "scale"
48
    INVERT_OPT = "invert"
49
    SEED_OPT = "seed"
50
    
51
    START_DELAY_SEC = "start-delay"
52
    START_ABSOLUTE = "absolute"
53
    START_DURATION = "multiple-of-duration"
54

    
55
    DEADLINE_STRETCH_SEC = "deadline-stretch"
56
    DEADLINE_DURATION = "multiple-of-duration"
57
    DEADLINE_SLOWDOWN = "original-slowdown"
58
    DEADLINE_ABSOLUTE = "absolute"
59
    
60
    RATE_SEC = "user-rate"
61
    
62
    NODES_SEC = "nodes"
63
    RESOURCES_OPT = "resources"
64

    
65
    DURATION_SEC = "duration"
66
    
67
    SOFTWARE_SEC = "software"
68

    
69
    
70
    def __init__(self, outfile, conffile):
71
        self.outfile = outfile
72
        self.conffile = conffile
73
        
74
        conffile = open(conffile, "r")
75
        self.config = ConfigParser.ConfigParser()
76
        self.config.readfp(conffile)
77
        
78
        self.startdelay_dist = self._get_dist(FileGenerator.START_DELAY_SEC)
79
        if self.startdelay_dist != None:
80
            self.start_type = self.config.get(FileGenerator.START_DELAY_SEC, FileGenerator.TYPE_OPT)
81
        else:
82
            self.start_type = None
83
            
84
        self.deadlinestretch_dist = self._get_dist(FileGenerator.DEADLINE_STRETCH_SEC)
85
        if self.deadlinestretch_dist != None:
86
            self.deadline_type = self.config.get(FileGenerator.DEADLINE_STRETCH_SEC, FileGenerator.TYPE_OPT)
87
        else:
88
            self.deadline_type = None
89

    
90
        self.rate_dist = self._get_dist(FileGenerator.RATE_SEC)
91
        self.duration_dist = self._get_dist(FileGenerator.DURATION_SEC)
92
        self.numnodes_dist = self._get_dist(FileGenerator.NODES_SEC)
93
        self.software_dist = self._get_dist(FileGenerator.SOFTWARE_SEC)
94

    
95
        self.user_rates = {}
96
        
97
        
98
    def _get_start(self, type, lease = None):
99
        if self.startdelay_dist == None:
100
            return None, None
101
        else:
102
            delta = self.startdelay_dist.get()
103
            if type == FileGenerator.START_ABSOLUTE:
104
                start = round_datetime_delta(TimeDelta(seconds=delta))
105
            elif type == FileGenerator.START_DURATION:
106
                start = round_datetime_delta(delta * lease.duration.requested)
107
            return start, delta
108
        
109
    def _get_numnodes(self, lease = None):
110
        if self.numnodes_dist == None:
111
            return None
112
        else:
113
            numnodes = int(self.numnodes_dist.get())
114
            return numnodes      
115
        
116
    def _get_duration(self, lease = None):
117
        if self.duration_dist == None:
118
            return None
119
        else:
120
            numnodes = int(self.duration_dist.get())
121
            return numnodes              
122

    
123
    def _get_deadline(self, type, lease, start):
124
        if self.deadlinestretch_dist == None:
125
            return None, None
126
        else:
127
            if type in (FileGenerator.DEADLINE_DURATION, FileGenerator.DEADLINE_SLOWDOWN):
128
                if type == FileGenerator.DEADLINE_DURATION:
129
                    tau = self.deadlinestretch_dist.get()
130
                    
131
                elif type == FileGenerator.DEADLINE_SLOWDOWN:
132
                    runtime = float(lease.extras["SWF_runtime"])
133
                    waittime = float(lease.extras["SWF_waittime"])
134
                    if runtime < 10: runtime = 10
135
                    slowdown = (waittime + runtime) / runtime
136
    
137
                    min = self.deadlinestretch_dist.min
138
                    max = self.deadlinestretch_dist.max
139
                    tau = self.deadlinestretch_dist.get()
140
                    
141
                    tau = (slowdown - 1)*((tau-min) / (max-min))
142
    
143
                deadline = round_datetime_delta(start + (1 + tau)*lease.duration.requested)                
144
            elif type == FileGenerator.DEADLINE_ABSOLUTE:
145
                wait = self.deadlinestretch_dist.get()
146
                deadline = round_datetime_delta(start + TimeDelta(seconds=wait) + lease.duration.requested)  
147
                
148
                tau = ((deadline - start) / lease.duration.requested) - 1                    
149
                    
150
            return deadline, tau
151
    
152
    def _get_software(self, lease = None):
153
        if self.software_dist == None:
154
            return UnmanagedSoftwareEnvironment()
155
        else:
156
            software = self.software_dist.get()
157
            image_id, image_size = software.split("|")
158
            return DiskImageSoftwareEnvironment(image_id, int(image_size))
159
        
160
    
161
    def _get_rate(self, lease):
162
        if self.rate_dist == None:
163
            return None
164
        else:
165
            if lease.user_id == -1:
166
                return self.rate_dist.get()
167
            else:
168
                if self.user_rates.has_key(lease.user_id):
169
                    return self.user_rates[lease.user_id]
170
                else:
171
                    rate = self.rate_dist.get()
172
                    self.user_rates[lease.user_id] = rate
173
                    return rate
174
    
175
    def _get_attributes(self):
176
        attributes = {}
177
        attrs = self.config.get(FileGenerator.GENERAL_SEC, FileGenerator.ATTRIBUTES_OPT)
178
        attrs = attrs.split(",")
179
        for attr in attrs:
180
            (k,v) = attr.split("=")
181
            attributes[k] = v
182
            
183
        return attributes
184
    
185
    def _get_dist(self, section):
186
        if self.config.has_section(section):
187
            return self.__create_distribution_from_section(section)
188
        else:
189
            return None
190
        
191
    def __create_distribution_from_section(self, section):
192
        dist_type = self.config.get(section, FileGenerator.DISTRIBUTION_OPT)
193
        if self.config.has_option(section, FileGenerator.DISCRETE_OPT):
194
            discrete = self.config.getboolean(section, FileGenerator.DISCRETE_OPT)
195
        else:
196
            discrete = False
197
        
198
        if self.config.has_option(section, FileGenerator.MIN_OPT) and self.config.has_option(section, FileGenerator.MAX_OPT):
199
            min = self.config.get(section, FileGenerator.MIN_OPT)
200
            max = self.config.get(section, FileGenerator.MAX_OPT)
201
            
202
            if min == "unbounded":
203
                min = float("inf")
204
            else:
205
                min = float(min)
206
            if max == "unbounded":
207
                max = float("inf")
208
            else:
209
                max = float(max)
210
        else:
211
            min = max = None
212
  
213
        if discrete:
214
            if min != None:
215
                values = range(int(min), int(max) + 1)
216
            else:
217
                values = self.config.get(section, FileGenerator.VALUES_OPT).split()
218

    
219
        if dist_type == "uniform":
220
            if discrete:
221
                dist = stats.DiscreteUniformDistribution(values)
222
            else:
223
                dist = stats.UniformDistribution(min, max)
224
        elif dist_type == "normal":
225
            mu = self.config.getfloat(section, FileGenerator.MEAN_OPT)
226
            sigma = self.config.getfloat(section, FileGenerator.STDEV_OPT)
227
            dist = stats.BoundedNormalDistribution(min,max,mu,sigma)
228
        elif dist_type == "bounded-pareto" or dist_type == "truncated-pareto":
229
            alpha = self.config.getfloat(section, FileGenerator.ALPHA_OPT)
230
            if self.config.has_option(section, FileGenerator.INVERT_OPT):
231
                invert = self.config.getboolean(section, FileGenerator.INVERT_OPT)
232
            else:
233
                invert = False
234
            if dist_type == "bounded-pareto":
235
                dist = stats.BoundedParetoDistribution(min,max,alpha,invert)
236
            else:
237
                scale = self.config.getfloat(section, FileGenerator.SCALE_OPT)
238
                if discrete:
239
                    dist = stats.DiscreteTruncatedParetoDistribution(values,scale,alpha,invert)
240
                else:
241
                    dist = stats.TruncatedParetoDistribution(min,max,scale,alpha,invert)
242
                
243
            
244
        if self.config.has_option(section, FileGenerator.SEED_OPT):
245
            seed = self.config.getint(section, FileGenerator.SEED_OPT)
246
            dist.seed(seed)
247

    
248
        return dist
249

    
250
class LWFGenerator(FileGenerator):
251
    
252
    SITE_OPT = "site"    
253

    
254
    NUMLEASES_SEC = "numleases"    
255
    
256
    NUMLEASES_TYPE_UTILIZATION = "utilization"    
257
    NUMLEASES_UTILIZATION_OPT = "utilization"    
258
    NUMLEASES_LAST_REQUEST_OPT = "last-request"
259
        
260
    NUMLEASES_TYPE_INTERVAL = "interval"    
261
    NUMLEASES_OPT = "numleases"    
262
    
263
    def __init__(self, outfile, conffile):
264
        FileGenerator.__init__(self, outfile, conffile)
265

    
266
        self.numleases_type = self.config.get(LWFGenerator.NUMLEASES_SEC, LWFGenerator.TYPE_OPT)
267
        
268
        self.interval_dist = self._get_dist(LWFGenerator.NUMLEASES_SEC)
269

    
270
    def _get_interval(self):
271
        if self.interval_dist == None:
272
            return None
273
        else:
274
            interval = int(self.interval_dist.get())
275
            return interval    
276
    
277
    def __gen_lease(self):
278
        submit_time = None
279
        user_id = None
280
        
281
        res = self.config.get(LWFGenerator.NODES_SEC, LWFGenerator.RESOURCES_OPT)
282
        res = Capacity.from_resources_string(res)        
283
        numnodes = self._get_numnodes(None)
284
        requested_resources = dict([(i+1,res) for i in xrange(numnodes)])
285
        
286
        start, delta = self._get_start(self.start_type, None)
287
        start = Timestamp(TimeDelta(seconds=start))
288
        
289
        duration = self._get_duration()
290
        duration = Duration(TimeDelta(seconds=duration))
291
        deadline = None
292
        preemptible = False
293
        software = self._get_software()
294
        
295
        l = Lease.create_new(submit_time, user_id, requested_resources, 
296
                             start, duration, deadline, preemptible, software)
297
        
298
        return l
299
    
300
    def generate(self):
301
        lwf = ET.Element("lease-workload")
302
        lwf.set("name", self.outfile)
303
        description = ET.SubElement(lwf, "description")
304
        description.text = "Created with haizea-generate"
305
        
306
        attributes_elem = ET.SubElement(lwf, "attributes")
307
        attributes = self._get_attributes()
308
        for name, value in attributes.items():
309
            attr_elem = ET.SubElement(attributes_elem, "attr")
310
            attr_elem.set("name", name)
311
            attr_elem.set("value", value)
312
        
313
        site = self.config.get(LWFGenerator.GENERAL_SEC, LWFGenerator.SITE_OPT)
314
        if site.startswith("file:"):
315
            sitefile = site.split(":")
316
            site = Site.from_xml_file(sitefile[1])
317
        else:
318
            site = Site.from_resources_string(site)
319

    
320
        lwf.append(site.to_xml())
321
            
322
        time = TimeDelta(seconds=0)
323
        requests = ET.SubElement(lwf, "lease-requests")   
324
        
325
        if self.numleases_type == LWFGenerator.NUMLEASES_TYPE_INTERVAL:
326
            leases = []            
327
            
328
            numleases = self.config.getint(LWFGenerator.NUMLEASES_SEC, LWFGenerator.NUMLEASES_OPT)
329
            for i in xrange(numleases):
330
                leases.append(self.__gen_lease())
331
    
332
            for l in leases:
333
                interval = TimeDelta(seconds=self._get_interval())
334
                time += interval
335
                l.start.requested += time
336
                lease_request = ET.SubElement(requests, "lease-request")
337
                lease_request.set("arrival", str(time))            
338
                lease_request.append(l.to_xml())
339
        elif self.numleases_type == LWFGenerator.NUMLEASES_TYPE_UTILIZATION:
340
            utilization = self.config.getfloat(LWFGenerator.NUMLEASES_SEC, LWFGenerator.NUMLEASES_UTILIZATION_OPT)
341
            utilization /= 100.0
342
            last_request = self.config.get(LWFGenerator.NUMLEASES_SEC, LWFGenerator.NUMLEASES_LAST_REQUEST_OPT)
343
            last_request = Parser.DateTimeDeltaFromString(last_request)
344
            
345
            max_utilization = 0
346
            for res in site.nodes.get_all_nodes().values():
347
                for i in range(1,res.get_ninstances("CPU") + 1):
348
                    max_utilization += (res.get_quantity_instance("CPU", i)/100.0) * last_request.seconds
349
            target_utilization = int(max_utilization * utilization)
350
            
351
            accum_utilization = 0
352
            
353
            leases = []            
354

    
355
            while accum_utilization < target_utilization:
356
                lease = self.__gen_lease()
357
                leases.append(lease)
358
                duration = lease.duration.requested.seconds
359
                lease_utilization = 0
360
                for res in lease.requested_resources.values():
361
                    for i in range(1,res.get_ninstances("CPU") + 1):
362
                        lease_utilization += (res.get_quantity_instance("CPU", i) / 100.0) * duration                
363
                accum_utilization += lease_utilization
364

    
365
            time = TimeDelta(seconds=0)            
366
            avg_interval = int(last_request.seconds / len(leases))
367
            for l in leases:
368
                interval = avg_interval + TimeDelta(seconds=self._get_interval())
369
                time = max(time + interval, TimeDelta(seconds=0))
370
                arrival = max(time - l.start.requested, TimeDelta(seconds=0))
371
                l.start.requested = time
372
                lease_request = ET.SubElement(requests, "lease-request")
373
                lease_request.set("arrival", str(arrival))            
374
                lease_request.append(l.to_xml())                            
375
        
376
        tree = ET.ElementTree(lwf)
377
        
378
        outfile = open(self.outfile, "w")
379
        tree.write(outfile)
380
        outfile.close()
381
    
382
class LWFAnnotationGenerator(FileGenerator):
383
    
384
    def __init__(self, lwffile, nleases, outfile, conffile):
385
        FileGenerator.__init__(self, outfile, conffile)
386
        self.lwffile = lwffile
387
        self.nleases = nleases
388
        
389
    def __gen_annotation(self, lease = None):    
390
        extra = {}
391
        
392
        start, delta = self._get_start(self.start_type, lease)
393
        if start != None:
394
            start = Timestamp(start)
395
            extra["simul_start_delta"] = "%.2f" % delta
396
            
397
            deadline, tau = self._get_deadline(self.deadline_type, lease, start.requested)
398
            if deadline != None:
399
                extra["simul_deadline_tau"] = "%.2f" % tau
400
        else:
401
            deadline = None
402
        
403
        software = self._get_software(lease)
404
        
405
        rate = self._get_rate(lease)
406

    
407
        if rate != None:
408
            extra["simul_userrate"] = "%.2f" % rate
409
        
410
        if lease == None:
411
            lease_id = None
412
        else:
413
            lease_id = lease.id
414
        
415
        annotation = LeaseAnnotation(lease_id, start, deadline, software, extra)
416
    
417
        return annotation
418
    
419
    def generate(self):
420
        if self.lwffile == None:
421
            annotations = []
422
            for i in xrange(1, self.nleases + 1):
423
                annotation = self.__gen_annotation()
424
                annotations.append(annotation)
425
        else:
426
            annotations = {}
427
            lease_workload = LeaseWorkload.from_xml_file(self.lwffile)
428
            leases = lease_workload.get_leases()
429
            for lease in leases:
430
                annotation = self.__gen_annotation(lease)
431
                annotations[lease.id] = annotation
432
            
433
        attributes = self._get_attributes()
434
        
435
        annotations = LeaseAnnotations(annotations, attributes)
436
        
437
        tree = ET.ElementTree(annotations.to_xml())
438
        outfile = open(self.outfile, "w")
439
        tree.write(outfile)
440
        outfile.close()