forked from SeattleTestbed/repy_v2
-
Notifications
You must be signed in to change notification settings - Fork 0
/
resourcemanipulation.py
414 lines (271 loc) · 12.1 KB
/
resourcemanipulation.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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
"""
Author: Justin Cappos
Start Date: 7 Dec 2010 (Derived from restrictions.py and nmresourcemath.py)
Description:
This class handles resource specifications. It used to handle
restricting access to functions, but that has been removed in favor of
security layers.
This module is supposed to be readable and obviously correct.
This is only supposed to specify what resources are assigned to a vessel.
It does not cover tracking resource use over time, etc.
"""
# this provides information about the valid resource names, required
# resources, etc.
import resource_constants
class ResourceParseError(Exception):
"""This exception is thrown if the resource file is invalid"""
class ResourceMathError(Exception):
"""A resource dictionary is missing elements or negative"""
# be sure no resources are negative...
def _assert_resourcedict_doesnt_have_negative_resources(newdict):
for resource in newdict:
if type(newdict[resource]) != set and newdict[resource] < 0.0:
raise ResourceMathError("Insufficient quantity: Resource '"+resource+"' has a negative quantity")
# Helper method to ensure that the given resource dict has all of the resources
# listed as required in nanny.py. -Brent
def _assert_resourcedict_has_required_resources(newdict):
for resource in resource_constants.must_assign_resources:
if resource not in newdict:
raise ResourceMathError("Missing required resource: '"+resource+"'")
############################ Parsing and I/O #############################
"""
The resource file format consists of lines that look like this:
Comment Lines:
# This is a comment
Resource quantities: what the program is allowed to utilize
Usage: resource resourcename limit
Example:
resource CPU 0.1 # Can have %10 CPU utilization
resource memory 33554432 # Can have 32 MB of memory
resource outsockets 1 # Can initiate one outgoing comm
resource insocket 2 # Can listen for 2 incoming comms
resource messport 2023 # Can use messageport 2023
resource messport 2043 # Can use messageport 2043
"""
def read_resourcedict_from_file(filename):
"""
<Purpose>
Reads resource information from a file, returning a dict
<Arguments>
filename: the name of the file to read resource information from.
<Exceptions>
ResourceParseError: if the file does not have the correct format
IOError: if the file cannot be opened.
<Side Effects>
None
<Returns>
A dictionary with the resource information. Resources that are
not specified, but are required will be set to 0.0
"""
filedata = open(filename).read()
return parse_resourcedict_from_string(filedata)
# This is a braindead parser. It's supposed to be readable and obviously
# correct, not "clever"
def parse_resourcedict_from_string(resourcestring):
"""
<Purpose>
Reads resource information from a file, returning a dict
<Arguments>
resourcestring: the string of data to parse
<Exceptions>
ResourceParseError: if the file does not have the correct format
IOError: if the file cannot be opened.
<Side Effects>
None
<Returns>
A dictionary with the resource information. Resources that are
not specified, but are required will be set to 0.0 Also returns
the list of calls that were in the original resource string.
"""
returned_resource_dict = {}
returned_call_list = []
# I must create an empty set for any resource types that are sets.
# (these are things like messports, etc.)
for resourcename in resource_constants.individual_item_resources:
returned_resource_dict[resourcename] = set()
# ensure we don't have problems with windows style newlines... (only LF)
lfresourcestring = resourcestring.replace('\r\n','\n')
for line in lfresourcestring.split('\n'):
# let's get rid of whitespace...
cleanline = line.strip()
# remove any comments
noncommentline = cleanline.split('#')[0]
# the items are all separated by spaces
tokenlist = noncommentline.split()
if len(tokenlist) == 0:
# This was a blank or comment line
continue
linetypestring = tokenlist[0]
# should be either a resource or a call line
if linetypestring != 'resource' and linetypestring != 'call':
raise ResourceParseError("Line '"+line+"' not understood.")
if linetypestring == 'resource':
####### Okay, it's a resource. It must have two other tokens!
if len(tokenlist) != 3:
raise ResourceParseError("Line '"+line+"' has wrong number of items")
# the other tokens are the resource and the resource value
knownresourcename = tokenlist[1]
resourcevaluestring = tokenlist[2]
# and the second token must be a known resource
if knownresourcename not in resource_constants.known_resources:
raise ResourceParseError("Line '"+line+"' has an unknown resource '"+knownresourcename+"'")
# and the last item should be a valid float or int,
# depending on the resource type (#1374)
try:
# Renewable resources should be cast as floats
if knownresourcename in resource_constants.renewable_resources:
resourcevalue = float(resourcevaluestring)
# Quantity resources includes a few renewable ones too,
# but the ``if'' above already caught these. Treat the
# remaining ones as integer.
elif knownresourcename in resource_constants.quantity_resources:
resourcevalue = int(resourcevaluestring)
# Item resources (bytes of space, port numbers) are integers as well.
elif knownresourcename in resource_constants.item_resources:
resourcevalue = int(resourcevaluestring)
else:
raise ResourceParseError("Resource " + knownresourcename +
" in line " + line + " is neither renewable, quantity-based " +
" nor item-based.")
except ValueError:
raise ResourceParseError("Line '"+line+"' has an invalid resource value '"+resourcevaluestring+"'")
# if it's an individual_item_resource, then there can be a list of
# different values for the resource (like ports)
if knownresourcename in resource_constants.individual_item_resources:
# I'm implicitly ignoring duplicates in the file. Is that wise?
returned_resource_dict[knownresourcename].add(resourcevalue)
continue
# other resources should not have been previously assigned
if knownresourcename in returned_resource_dict:
raise ResourceParseError("Line '"+line+"' has a duplicate resource rule for '"+knownresourcename+"'")
# Finally, we assign it to the table
returned_resource_dict[knownresourcename] = resourcevalue
# Let's do the next line!
continue
elif linetypestring == 'call':
returned_call_list.append(cleanline)
# it was a call... I'm going to ignore these because these are obsolete
# This may be an error later
# continue
# MMM: Added back the 'call's to make it compatible with RepyV1
else:
raise ResourceParseError("Internal error for '"+line+"'")
# make sure that if there are required resources, they are defined
_assert_resourcedict_has_required_resources(returned_resource_dict)
# give any remaining resources an entry with 0.0 as the value. This fills
# out the table (preventing key errors later). It won't prevent divide by
# zero for rate based resources though.
for resource in resource_constants.known_resources:
if resource not in returned_resource_dict:
returned_resource_dict[resource] = 0.0
return returned_resource_dict, '\n'.join(returned_call_list)
def write_resourcedict_to_file(resourcedict, filename, call_list=None):
"""
<Purpose>
Writes out a resource dictionary to disk...
<Arguments>
resourcedict: the dictionary to write out
filename: the file to write it to
call_list: if provided is the list of calls that are allowed
(for backward compatibility with Repy V1).
<Exceptions>
IOError: if the filename cannot be opened or is invalid.
<Side Effects>
Creates a file
<Returns>
None
"""
outfo = open(filename,"w")
for resource in resourcedict:
if type(resourcedict[resource]) == set:
for item in resourcedict[resource]:
print >> outfo, "resource "+resource+" "+str(item)
else:
print >> outfo, "resource "+resource+" "+str(resourcedict[resource])
if call_list:
print >> outfo, '\n' + str(call_list)
outfo.close()
############################ Math #############################
# Take two sets of resources and add them...
def add_resourcedicts(dict1, dict2):
"""
<Purpose>
Takes two resource dicts and returns the sum
<Arguments>
dict1,dict2: the resource dictionaries
<Exceptions>
ResourceMathError: if a resource dict is invalid
<Side Effects>
None
<Returns>
The new resource dictionary
"""
# check arguments
_assert_resourcedict_doesnt_have_negative_resources(dict1)
_assert_resourcedict_doesnt_have_negative_resources(dict2)
_assert_resourcedict_has_required_resources(dict1)
_assert_resourcedict_has_required_resources(dict2)
retdict = dict1.copy()
# let's iterate through the second dictionary and add if appropriate. If
# dict2 doesn't have the key, it doesn't matter.
for resource in dict2:
# if this is a set, then get the union
if type(retdict[resource]) == set:
retdict[resource] = retdict[resource].union(dict2[resource])
continue
# empty if not preexisting
if resource not in retdict:
retdict[resource] = 0.0
if type(retdict[resource]) not in [float, int]:
raise ResourceMathError("Resource dictionary contain an element of unknown type '"+str(type(retdict[resource]))+"'")
# ... and add this item to what we have. This is okay for sets or floats
retdict[resource] = retdict[resource] + dict2[resource]
# these should be impossible to trigger
_assert_resourcedict_has_required_resources(retdict)
_assert_resourcedict_doesnt_have_negative_resources(retdict)
return retdict
# remove one quantity of resources from the other. (be sure to check if the
# resulting resources are negative if appropriate)
def subtract_resourcedicts(dict1, dict2):
"""
<Purpose>
Takes resource dict1 and subtracts resource dict2 from it. An
exception is raised if the resulting resource dict is not positive.
<Arguments>
dict1: a resource dict
dict2: a resource dict to remove from dict1
<Exceptions>
ResourceMathError: if the result would be negative or a resource dict
is malformed
<Side Effects>
None
<Returns>
The new resource dictionary
"""
# check arguments
_assert_resourcedict_doesnt_have_negative_resources(dict1)
_assert_resourcedict_doesnt_have_negative_resources(dict2)
_assert_resourcedict_has_required_resources(dict1)
_assert_resourcedict_has_required_resources(dict2)
retdict = dict1.copy()
# then look at resourcefile1
for resource in dict2:
# empty if not preexisting
if resource not in retdict:
retdict[resource] = 0.0
# ... if it's a float, we can just subtract...
if type(retdict[resource]) in [float, int]:
retdict[resource] = retdict[resource] - dict2[resource]
# otherwise we need to be sure we're only subtracting items that exist
elif type(retdict[resource]) == set:
if not retdict[resource].issuperset(dict2[resource]):
raise ResourceMathError('Subtracted resource dictionary does not contain all elements')
# use the set subtraction
retdict[resource] = retdict[resource] - dict2[resource]
# otherwise, WTF is this?
else:
raise ResourceMathError("Resource dictionary contain an element of unknown type '"+str(type(retdict[resource]))+"'")
# this may be negative, but should have all of the required resources
_assert_resourcedict_doesnt_have_negative_resources(retdict)
_assert_resourcedict_has_required_resources(retdict)
return retdict