| 1 | #!/usr/bin/python -- |
|---|
| 2 | # |
|---|
| 3 | # Copyright (C) 2005 Kevin Lai |
|---|
| 4 | # |
|---|
| 5 | # This program is free software; you can redistribute it and/or modify |
|---|
| 6 | # it under the terms of the GNU General Public License as published by |
|---|
| 7 | # the Free Software Foundation; either version 2 of the License, or |
|---|
| 8 | # (at your option) any later version. |
|---|
| 9 | # |
|---|
| 10 | # This program is distributed in the hope that it will be useful, |
|---|
| 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 13 | # GNU General Public License for more details. |
|---|
| 14 | # |
|---|
| 15 | # You should have received a copy of the GNU General Public License |
|---|
| 16 | # along with this program; if not, write to the Free Software |
|---|
| 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|---|
| 18 | # |
|---|
| 19 | import datetime, re, time, xmlrpclib |
|---|
| 20 | from decimal import Decimal, InvalidOperation |
|---|
| 21 | import KL.Measurement.SI |
|---|
| 22 | from Virtualization.Resources import Resources |
|---|
| 23 | from KL.Utility.Command import ArgumentError |
|---|
| 24 | |
|---|
| 25 | class Preference(object): |
|---|
| 26 | |
|---|
| 27 | __pref_re = None |
|---|
| 28 | |
|---|
| 29 | def __init__( |
|---|
| 30 | self, |
|---|
| 31 | # Relative minimum bids for the resources |
|---|
| 32 | weight=Decimal(1), |
|---|
| 33 | # Minimum resource share |
|---|
| 34 | min_resource=None, |
|---|
| 35 | # Maximum resource share |
|---|
| 36 | max_resource=None, |
|---|
| 37 | # Ratio of total funds to be used to meet resource minimums |
|---|
| 38 | contingency_ratio=Decimal(0), |
|---|
| 39 | # Resource type-specific data |
|---|
| 40 | data=[], |
|---|
| 41 | # Minimum resource amount |
|---|
| 42 | min_resource_amount=None, |
|---|
| 43 | # Maximum resource amount |
|---|
| 44 | max_resource_amount=None, |
|---|
| 45 | # |
|---|
| 46 | ): |
|---|
| 47 | """Create a Preference. |
|---|
| 48 | """ |
|---|
| 49 | self.__dict__.update(locals()) |
|---|
| 50 | del self.self |
|---|
| 51 | if type(self.weight) == int or type(self.weight) == long: |
|---|
| 52 | self.weight = Decimal(self.weight) |
|---|
| 53 | self.__set_dict() |
|---|
| 54 | # Compute the non-contingency ratio using the fractional part |
|---|
| 55 | # of the CR. This is a hack to allow fractional non-contingency |
|---|
| 56 | # ratioes without disrupting the rest of the code. |
|---|
| 57 | if (self.contingency_ratio == int(self.contingency_ratio) and |
|---|
| 58 | self.contingency_ratio > 0): |
|---|
| 59 | self.non_contingency_ratio = Decimal(0) |
|---|
| 60 | else: |
|---|
| 61 | self.non_contingency_ratio = 1 - ( |
|---|
| 62 | self.contingency_ratio - int(self.contingency_ratio)) |
|---|
| 63 | |
|---|
| 64 | def parse(cls, pref_string, config_names): |
|---|
| 65 | """Parse a preference from a string. |
|---|
| 66 | """ |
|---|
| 67 | if not cls.__pref_re: |
|---|
| 68 | cls.__tcp_pref_re = re.compile( |
|---|
| 69 | "(?P<name>\w+)(,(?P<id>((\d)+)))?:" |
|---|
| 70 | "(?P<weight>\d+\.?\d*)(,(?P<cr>\d+\.?\d*))?:(?P<data>\d+)$") |
|---|
| 71 | cls.__ip_pref_re = re.compile( |
|---|
| 72 | "(?P<name>\w+)(,(?P<id>((\d)+)|(\w+.\w+.\w+.\w+)))?:" |
|---|
| 73 | "(?P<weight>\d+\.?\d*)(,(?P<cr>\d+\.?\d*))?$") |
|---|
| 74 | cls.__pref_re = re.compile( |
|---|
| 75 | "(?P<name>\w+)(,(?P<id>(\d)+))?:" |
|---|
| 76 | "(?P<weight>\d+\.?\d*)(,(?P<min>(\d+\.?\d*[a-zA-Z]*)))?" |
|---|
| 77 | "(,(?P<max>(\d+\.?\d*[a-zA-Z]*)))?(,(?P<cr>\d+\.?\d*))?" |
|---|
| 78 | "(:(?P<data>\d+))?$") |
|---|
| 79 | cls.__config_re = re.compile("(?P<name>\w+):(?P<value>.+)") |
|---|
| 80 | |
|---|
| 81 | if pref_string.startswith("TCPv4Port"): |
|---|
| 82 | m = cls.__tcp_pref_re.match(pref_string) |
|---|
| 83 | elif pref_string.startswith("IPv4Address"): |
|---|
| 84 | m = cls.__ip_pref_re.match(pref_string) |
|---|
| 85 | else: |
|---|
| 86 | m = (cls.__pref_re.match(pref_string) or |
|---|
| 87 | cls.__config_re.match(pref_string)) |
|---|
| 88 | if m: |
|---|
| 89 | g = m.groupdict() |
|---|
| 90 | else: |
|---|
| 91 | raise ArgumentError( |
|---|
| 92 | "Could not parse bid preference: %s" % (pref_string,)) |
|---|
| 93 | pd = { |
|---|
| 94 | 'weight': None, 'min_resource': None, |
|---|
| 95 | 'min_resource_amount': None, 'max_resource': None, |
|---|
| 96 | 'max_resource_amount': None, |
|---|
| 97 | } |
|---|
| 98 | names = (('min', 'min_resource', 'min_resource_amount'), |
|---|
| 99 | ('max', 'max_resource', 'max_resource_amount')) |
|---|
| 100 | if g.get('id'): |
|---|
| 101 | id = "%s,%s" % (g['name'], g['id']) |
|---|
| 102 | else: |
|---|
| 103 | id = g['name'] |
|---|
| 104 | if g.get('weight'): |
|---|
| 105 | pd['weight'] = Decimal(g['weight']) |
|---|
| 106 | elif not g['name'] in config_names: |
|---|
| 107 | raise ArgumentError( |
|---|
| 108 | "Could not parse bid preference: %s" % (pref_string,)) |
|---|
| 109 | |
|---|
| 110 | one = Decimal(1) |
|---|
| 111 | cr = Decimal("0.9") |
|---|
| 112 | if g['name'] == "TCPv4Port": |
|---|
| 113 | pd.update({'min_resource': one, 'max_resource': one, |
|---|
| 114 | 'contingency_ratio': cr, 'data':[long(g['data'])]}) |
|---|
| 115 | elif g['name'] == "IPv4Address": |
|---|
| 116 | pd.update({'min_resource': one, 'max_resource': one, |
|---|
| 117 | 'contingency_ratio': cr}) |
|---|
| 118 | elif g['name'] in Resources: |
|---|
| 119 | for name, s_name, a_name in names: |
|---|
| 120 | psi = g[name] |
|---|
| 121 | if not psi: |
|---|
| 122 | continue |
|---|
| 123 | parsed_unit = False |
|---|
| 124 | try: |
|---|
| 125 | quantity, base_unit = KL.Measurement.SI.parse(psi) |
|---|
| 126 | pd[a_name] = long(quantity) |
|---|
| 127 | parsed_unit = True |
|---|
| 128 | except ValueError: |
|---|
| 129 | pass |
|---|
| 130 | if parsed_unit: |
|---|
| 131 | if Resources[g['name']].units != base_unit: |
|---|
| 132 | raise ArgumentError( |
|---|
| 133 | "Units should be %s instead of %s" % ( |
|---|
| 134 | Resources[g['name']].units, base_unit)) |
|---|
| 135 | else: |
|---|
| 136 | pd[s_name] = psi |
|---|
| 137 | if g['data']: |
|---|
| 138 | pd['data'] = [int(g['data'])] |
|---|
| 139 | elif g['name'] in config_names: |
|---|
| 140 | return g['name'], g['value'] |
|---|
| 141 | else: |
|---|
| 142 | raise ArgumentError( |
|---|
| 143 | "Preference type '%s' is unknown" % (g['name'],)) |
|---|
| 144 | |
|---|
| 145 | # Temporarily set the CR until we figure out how to |
|---|
| 146 | # expose it. |
|---|
| 147 | if not g.get('cr') and not 'contingency_ratio' in pd: |
|---|
| 148 | if g['name'] == 'disk': |
|---|
| 149 | pd['contingency_ratio'] = Decimal(1) |
|---|
| 150 | elif g['name'] == 'memory': |
|---|
| 151 | pd['contingency_ratio'] = Decimal("0.9") |
|---|
| 152 | else: |
|---|
| 153 | pd['contingency_ratio'] = Decimal(0) |
|---|
| 154 | elif g['cr']: |
|---|
| 155 | pd['contingency_ratio'] = Decimal(g['cr']) |
|---|
| 156 | |
|---|
| 157 | pref = Preference(**pd) |
|---|
| 158 | try: |
|---|
| 159 | #pref.verify(True) |
|---|
| 160 | pass |
|---|
| 161 | except (TypeError, ValueError), e: |
|---|
| 162 | raise ArgumentError(e.args[0]) |
|---|
| 163 | return id, pref |
|---|
| 164 | |
|---|
| 165 | parse = classmethod(parse) |
|---|
| 166 | |
|---|
| 167 | def __check_var(self, name, var, minimum, maximum, var_type, accept_none): |
|---|
| 168 | t = type(var) |
|---|
| 169 | if var != None: |
|---|
| 170 | if not t in var_type: |
|---|
| 171 | raise TypeError("%s:%s must be a %s instead of a %s" % ( |
|---|
| 172 | name, var, var_type, t)) |
|---|
| 173 | if var < minimum or (maximum != None and var > maximum): |
|---|
| 174 | raise ValueError("%s must be in [%s,%s] instead of %s" % ( |
|---|
| 175 | name, minimum, maximum, var)) |
|---|
| 176 | elif not accept_none: |
|---|
| 177 | raise ValueError("%s cannot be None" % (name,)) |
|---|
| 178 | |
|---|
| 179 | def verify(self, accept_none): |
|---|
| 180 | """Verify that a preference is fully formed. |
|---|
| 181 | """ |
|---|
| 182 | if (not accept_none or self.weight != None): |
|---|
| 183 | if ((type(self.weight) != Decimal) or self.weight < 0): |
|---|
| 184 | raise TypeError( |
|---|
| 185 | "Weight must be a non-negative decimal instead of %s" % ( |
|---|
| 186 | self.weight,)) |
|---|
| 187 | if (not accept_none or self.weight != None): |
|---|
| 188 | if type(self.data) != list and type(self.data) != tuple: |
|---|
| 189 | raise TypeError( |
|---|
| 190 | "Data must be a list or tuple instead of %s" % ( |
|---|
| 191 | type(self.data))) |
|---|
| 192 | self.__check_var( |
|---|
| 193 | "contingency ratio", self.contingency_ratio, 0, None, |
|---|
| 194 | (Decimal,), accept_none) |
|---|
| 195 | self.__check_var( |
|---|
| 196 | "minimum resource", self.min_resource, 0, 1, (Decimal, ), True) |
|---|
| 197 | self.__check_var( |
|---|
| 198 | "maximum resource", self.max_resource, 0, 1, (Decimal,), True) |
|---|
| 199 | self.__check_var( |
|---|
| 200 | "minimum resource amount", self.min_resource_amount, 0, None, |
|---|
| 201 | (long, int), True) |
|---|
| 202 | self.__check_var( |
|---|
| 203 | "maximum resource amount", self.max_resource_amount, 0, None, |
|---|
| 204 | (long, int), True) |
|---|
| 205 | if ((self.min_resource != None and self.max_resource != None) and |
|---|
| 206 | (self.min_resource > self.max_resource)): |
|---|
| 207 | raise ValueError( |
|---|
| 208 | "Maximum resource share %s must exceed the minimum %s" % ( |
|---|
| 209 | self.max_resource, self.min_resource)) |
|---|
| 210 | |
|---|
| 211 | if not ((self.min_resource != None) ^ |
|---|
| 212 | (self.min_resource_amount != None)): |
|---|
| 213 | raise ValueError( |
|---|
| 214 | "Must specify a minimum share or amount instead of %s and %s" % ( |
|---|
| 215 | self.min_resource, self.min_resource_amount)) |
|---|
| 216 | if not ((self.max_resource != None) ^ |
|---|
| 217 | (self.max_resource_amount != None)): |
|---|
| 218 | raise ValueError( |
|---|
| 219 | "Must specify a maximum share or amount instead of %s and %s" % ( |
|---|
| 220 | self.max_resource, self.max_resource_amount)) |
|---|
| 221 | |
|---|
| 222 | if ((self.min_resource_amount != None and |
|---|
| 223 | self.max_resource_amount != None) and |
|---|
| 224 | (self.min_resource_amount > self.max_resource_amount)): |
|---|
| 225 | raise ValueError( |
|---|
| 226 | "Maximum resource amount %d must exceed the minimum %d" % ( |
|---|
| 227 | self.max_resource_amount, self.min_resource_amount)) |
|---|
| 228 | |
|---|
| 229 | def normalize(self, capacity): |
|---|
| 230 | """ |
|---|
| 231 | """ |
|---|
| 232 | min_resource = self.min_resource |
|---|
| 233 | if min_resource == None: |
|---|
| 234 | min_resource = min( |
|---|
| 235 | self.min_resource_amount / capacity, Decimal(1)) |
|---|
| 236 | max_resource = self.max_resource |
|---|
| 237 | if max_resource == None: |
|---|
| 238 | max_resource = min( |
|---|
| 239 | self.max_resource_amount / capacity, Decimal(1)) |
|---|
| 240 | return min_resource, max_resource |
|---|
| 241 | |
|---|
| 242 | def __getstate__(self): |
|---|
| 243 | return ( |
|---|
| 244 | self.weight, self.min_resource, self.max_resource, |
|---|
| 245 | self.contingency_ratio, self.data, self.min_resource_amount, |
|---|
| 246 | self.max_resource_amount) |
|---|
| 247 | |
|---|
| 248 | def __setstate__(self, t): |
|---|
| 249 | self.__init__(*t) |
|---|
| 250 | |
|---|
| 251 | def __set_dict(self): |
|---|
| 252 | """Get a non-object representation. |
|---|
| 253 | """ |
|---|
| 254 | def __repr__(self): |
|---|
| 255 | return "%s" % (self.get_dict(),) |
|---|
| 256 | |
|---|
| 257 | def get_dict(self): |
|---|
| 258 | d = { |
|---|
| 259 | 'weight': self.weight, 'min_resource': self.min_resource, |
|---|
| 260 | 'max_resource': self.max_resource, 'data': self.data, |
|---|
| 261 | 'contingency_ratio': self.contingency_ratio, |
|---|
| 262 | 'min_resource_amount': self.min_resource_amount, |
|---|
| 263 | 'max_resource_amount': self.max_resource_amount |
|---|
| 264 | } |
|---|
| 265 | return d |
|---|
| 266 | |
|---|
| 267 | def update(self, new_pref): |
|---|
| 268 | """ |
|---|
| 269 | Note that preferences are verified after they are updated, so |
|---|
| 270 | a preference here has not been verified. |
|---|
| 271 | """ |
|---|
| 272 | changed = False |
|---|
| 273 | if new_pref.weight != self.weight: |
|---|
| 274 | changed = True |
|---|
| 275 | self.weight = new_pref.weight |
|---|
| 276 | if new_pref.min_resource != None: |
|---|
| 277 | if new_pref.min_resource != self.min_resource: |
|---|
| 278 | changed = True |
|---|
| 279 | self.min_resource = new_pref.min_resource |
|---|
| 280 | self.min_resource_amount = None |
|---|
| 281 | elif new_pref.min_resource_amount != None: |
|---|
| 282 | if new_pref.min_resource_amount != self.min_resource_amount: |
|---|
| 283 | changed = True |
|---|
| 284 | self.min_resource_amount = new_pref.min_resource_amount |
|---|
| 285 | self.min_resource = None |
|---|
| 286 | if new_pref.max_resource != None: |
|---|
| 287 | if new_pref.max_resource != self.max_resource: |
|---|
| 288 | changed = True |
|---|
| 289 | self.max_resource = new_pref.max_resource |
|---|
| 290 | self.max_resource_amount = None |
|---|
| 291 | elif new_pref.max_resource_amount != None: |
|---|
| 292 | if new_pref.max_resource_amount != self.max_resource_amount: |
|---|
| 293 | changed = True |
|---|
| 294 | self.max_resource_amount = new_pref.max_resource_amount |
|---|
| 295 | self.max_resource = None |
|---|
| 296 | # Changed data does not trigger changes in the bid |
|---|
| 297 | if new_pref.data != None: |
|---|
| 298 | self.data = new_pref.data |
|---|
| 299 | if new_pref.contingency_ratio != None: |
|---|
| 300 | if new_pref.contingency_ratio != self.contingency_ratio: |
|---|
| 301 | changed = True |
|---|
| 302 | self.contingency_ratio = new_pref.contingency_ratio |
|---|
| 303 | self.__set_dict() |
|---|
| 304 | |
|---|
| 305 | return changed |
|---|
| 306 | |
|---|
| 307 | def __convert(value): |
|---|
| 308 | if value == None: |
|---|
| 309 | return value |
|---|
| 310 | elif type(value) == float: |
|---|
| 311 | return Decimal(str(value)) |
|---|
| 312 | else: |
|---|
| 313 | try: |
|---|
| 314 | d = Decimal(value) |
|---|
| 315 | except: |
|---|
| 316 | d = value |
|---|
| 317 | return d |
|---|
| 318 | |
|---|
| 319 | __convert = staticmethod(__convert) |
|---|
| 320 | |
|---|
| 321 | def create(pref_dict): |
|---|
| 322 | d = pref_dict |
|---|
| 323 | c = Preference.__convert |
|---|
| 324 | return Preference( |
|---|
| 325 | d['weight'], c(d['min_resource']), c(d['max_resource']), |
|---|
| 326 | c(d['contingency_ratio']), d['data'], d['min_resource_amount'], |
|---|
| 327 | d['max_resource_amount']) |
|---|
| 328 | create = staticmethod(create) |
|---|
| 329 | |
|---|
| 330 | class Bid(object): |
|---|
| 331 | |
|---|
| 332 | __duration_re = None |
|---|
| 333 | |
|---|
| 334 | def __init__( |
|---|
| 335 | self, duration=0L, preferences={}, expiration=0L): |
|---|
| 336 | """ |
|---|
| 337 | If duration == 0 and expiration == 0, then the existing duration and |
|---|
| 338 | expiration are used. |
|---|
| 339 | If duration != 0 and expiration == 0, then a new expiration is |
|---|
| 340 | calculated on the auctioneer. |
|---|
| 341 | If expiration != 0, then the expiration is used in the auctioneer's |
|---|
| 342 | time space. |
|---|
| 343 | """ |
|---|
| 344 | self.__dict__.update(locals()) |
|---|
| 345 | del self.self |
|---|
| 346 | # Check that the bid is well-formed: has correct types and ranges |
|---|
| 347 | if (type(self.duration) != long or self.duration < 0): |
|---|
| 348 | raise TypeError( |
|---|
| 349 | "Duration must be a long >= 0 instead of %s" % ( |
|---|
| 350 | self.duration,)) |
|---|
| 351 | if (type(self.expiration) != long or self.expiration < 0): |
|---|
| 352 | raise TypeError( |
|---|
| 353 | "Expiration must be a long >= 0 instead of %s" % ( |
|---|
| 354 | self.expiration,)) |
|---|
| 355 | self.sum_weights() |
|---|
| 356 | |
|---|
| 357 | def __add_month_year(date, month, year): |
|---|
| 358 | # Subtract one to shift from 1..12 to 0..11 space |
|---|
| 359 | months = year * 12.0 + month - 1 + date.month |
|---|
| 360 | |
|---|
| 361 | # Whole years |
|---|
| 362 | years, leftover_months = divmod(months, 12.0) |
|---|
| 363 | long_years = date.year + long(years) |
|---|
| 364 | |
|---|
| 365 | # Whole months, add one to shift from 0..11 to 1..12 space |
|---|
| 366 | long_months = long(leftover_months) + 1 |
|---|
| 367 | |
|---|
| 368 | fractional_months = leftover_months - long(leftover_months) |
|---|
| 369 | return (date.replace(year=long_years, month=long_months), |
|---|
| 370 | fractional_months) |
|---|
| 371 | __add_month_year = staticmethod(__add_month_year) |
|---|
| 372 | |
|---|
| 373 | def __parse_duration(s): |
|---|
| 374 | """Parse one or two durations from a string. |
|---|
| 375 | """ |
|---|
| 376 | if not Bid.__duration_re: |
|---|
| 377 | Bid.__duration_re = re.compile( |
|---|
| 378 | "^((?P<s>[0-9.]+)([sS]|$))?((?P<m>[0-9.]+)[mM])?" |
|---|
| 379 | "((?P<h>[0-9.]+)[hH])?((?P<d>[0-9.]+)[dD])?" |
|---|
| 380 | "((?P<w>[0-9.]+)[wW])?((?P<o>[0-9.]+)[oO])?" |
|---|
| 381 | "((?P<y>[0-9.]+)[yY])?$") |
|---|
| 382 | return [Bid.__parse_one_duration(sp) for sp in s.split(",")] |
|---|
| 383 | __parse_duration = staticmethod(__parse_duration) |
|---|
| 384 | |
|---|
| 385 | def __parse_one_duration(s): |
|---|
| 386 | """Parse one duration from a string. |
|---|
| 387 | """ |
|---|
| 388 | m = Bid.__duration_re.search(s) |
|---|
| 389 | if m: |
|---|
| 390 | g = m.groupdict(0) |
|---|
| 391 | else: |
|---|
| 392 | raise ArgumentError("Could not parse bid duration: %s" % (s,)) |
|---|
| 393 | # Some time units can be directly converted |
|---|
| 394 | duration = long( |
|---|
| 395 | float(g['s']) + 60 * float(g['m']) + 3600 * float(g['h']) + |
|---|
| 396 | 86400 * float(g['d']) + 604800 * float(g['w'])) |
|---|
| 397 | now = datetime.datetime.now() |
|---|
| 398 | # The library does not support year and month conversion, |
|---|
| 399 | # probably because there is no absolute definition of how |
|---|
| 400 | # long a year and month are. Leap years are longer than non-leap |
|---|
| 401 | # years, and months can have several different lengths. This |
|---|
| 402 | # code assumes that all month and year durations are relative |
|---|
| 403 | # to the current time. Unfortunately, this means that the |
|---|
| 404 | # same string definition will result in different in numerical |
|---|
| 405 | # amounts at different times, which can be confusing. |
|---|
| 406 | |
|---|
| 407 | # Account for all year amounts and month amounts greater than or |
|---|
| 408 | # equal to a year. |
|---|
| 409 | # Convert into months |
|---|
| 410 | then, fractional_months = Bid.__add_month_year( |
|---|
| 411 | now, float(g['o']), float(g['y'])) |
|---|
| 412 | |
|---|
| 413 | # Handle fractional months |
|---|
| 414 | offset = 0 |
|---|
| 415 | if fractional_months > 0.0: |
|---|
| 416 | next_month, _ = Bid.__add_month_year(then, 1, 0) |
|---|
| 417 | after = time.mktime(next_month.timetuple()) |
|---|
| 418 | secs = after - time.mktime(then.timetuple()) |
|---|
| 419 | offset = secs * fractional_months |
|---|
| 420 | |
|---|
| 421 | duration = duration + long( |
|---|
| 422 | time.mktime(then.timetuple()) - time.mktime(now.timetuple()) + |
|---|
| 423 | offset) |
|---|
| 424 | |
|---|
| 425 | return duration |
|---|
| 426 | __parse_one_duration = staticmethod(__parse_one_duration) |
|---|
| 427 | |
|---|
| 428 | def parse( |
|---|
| 429 | args, duration=(0L,), preferences=None, config=None, config_names=()): |
|---|
| 430 | """Parse a bid from strings. |
|---|
| 431 | """ |
|---|
| 432 | if len(args) >= 1: |
|---|
| 433 | d = Bid.__parse_duration(args[0]) |
|---|
| 434 | if d != 0: |
|---|
| 435 | duration = d |
|---|
| 436 | |
|---|
| 437 | if not preferences: |
|---|
| 438 | preferences = {} |
|---|
| 439 | |
|---|
| 440 | if not config: |
|---|
| 441 | config = {} |
|---|
| 442 | |
|---|
| 443 | if len(args) >= 2: |
|---|
| 444 | new_prefs = {} |
|---|
| 445 | for ps in args[1:]: |
|---|
| 446 | k, v = Preference.parse(ps, config_names) |
|---|
| 447 | if k in config_names: |
|---|
| 448 | config[k] = v |
|---|
| 449 | else: |
|---|
| 450 | new_prefs[k] = v |
|---|
| 451 | |
|---|
| 452 | preferences.update(new_prefs) |
|---|
| 453 | |
|---|
| 454 | bid = Bid(duration[0], preferences) |
|---|
| 455 | d2 = None |
|---|
| 456 | if len(duration) > 1: |
|---|
| 457 | d2 = duration[1] |
|---|
| 458 | return bid, config, d2 |
|---|
| 459 | |
|---|
| 460 | parse = staticmethod(parse) |
|---|
| 461 | |
|---|
| 462 | def create(bid_dict): |
|---|
| 463 | d = bid_dict |
|---|
| 464 | prefs = dict([(k, Preference.create(v)) |
|---|
| 465 | for k,v in d['preferences'].iteritems()]) |
|---|
| 466 | return Bid(d['duration'], prefs, d['expiration']) |
|---|
| 467 | create = staticmethod(create) |
|---|
| 468 | |
|---|
| 469 | def __getstate__(self): |
|---|
| 470 | return ( |
|---|
| 471 | self.duration, self.preferences, self.expiration,) |
|---|
| 472 | |
|---|
| 473 | def __setstate__(self, t): |
|---|
| 474 | self.__init__(*t) |
|---|
| 475 | |
|---|
| 476 | def sum_weights(self): |
|---|
| 477 | values = self.preferences.values() |
|---|
| 478 | self.total_weight = sum([p.weight for p in values]) |
|---|
| 479 | self.total_contingency_weight = sum( |
|---|
| 480 | [pref.weight * (1 - pref.non_contingency_ratio) for pref in values]) |
|---|
| 481 | |
|---|
| 482 | def add_preference(self, key, pref): |
|---|
| 483 | """ |
|---|
| 484 | """ |
|---|
| 485 | self.preferences[key] = pref |
|---|
| 486 | self.sum_weights() |
|---|
| 487 | |
|---|
| 488 | def update(self, new_bid): |
|---|
| 489 | """Update the old bid using the new bid. |
|---|
| 490 | """ |
|---|
| 491 | changed = False |
|---|
| 492 | old_expiration = self.expiration |
|---|
| 493 | if new_bid.duration != 0: |
|---|
| 494 | self.expiration = long(time.time() + new_bid.duration) |
|---|
| 495 | self.duration = new_bid.duration |
|---|
| 496 | elif new_bid.expiration != 0: |
|---|
| 497 | self.expiration = new_bid.expiration |
|---|
| 498 | |
|---|
| 499 | if self.expiration != old_expiration: |
|---|
| 500 | changed = True |
|---|
| 501 | |
|---|
| 502 | for r_name, r in new_bid.preferences.iteritems(): |
|---|
| 503 | # Copy new preferences |
|---|
| 504 | if not r_name in self.preferences: |
|---|
| 505 | self.preferences[r_name] = r |
|---|
| 506 | changed = True |
|---|
| 507 | else: |
|---|
| 508 | changed |= self.preferences[r_name].update(r) |
|---|
| 509 | |
|---|
| 510 | self.sum_weights() |
|---|
| 511 | return changed |
|---|
| 512 | |
|---|
| 513 | def normalize_time(self): |
|---|
| 514 | if self.duration != 0 and self.expiration == 0: |
|---|
| 515 | self.expiration = long(time.time() + self.duration) |
|---|
| 516 | |
|---|
| 517 | def get_dict(self): |
|---|
| 518 | """Get a non-object representation. |
|---|
| 519 | """ |
|---|
| 520 | pref = dict([(k, v.get_dict()) for k,v in self.preferences.iteritems()]) |
|---|
| 521 | return {'duration': self.duration, 'expiration': self.expiration, |
|---|
| 522 | 'preferences': pref} |
|---|
| 523 | |
|---|
| 524 | def main(argv=None): |
|---|
| 525 | """Test the code. |
|---|
| 526 | """ |
|---|
| 527 | cpu = Preference(1, 1000000, 2000000, 0.5) |
|---|
| 528 | memory = Preference(2, 500000000, 1000000000, 0.75) |
|---|
| 529 | port = Preference(3, data=2048) |
|---|
| 530 | b = Bid(100, {("cpu",): cpu, ("memory",): memory, ('TCPv4Port', 22): port}) |
|---|
| 531 | s = b.xml_rpc_serialize() |
|---|
| 532 | print s |
|---|
| 533 | new_bid = xml_rpc_parse(s) |
|---|
| 534 | print new_bid |
|---|
| 535 | port = Preference(0, data=2048) |
|---|
| 536 | disk = Preference(4, 500000000, 1000000000, 0.75) |
|---|
| 537 | b = Bid(0, {("disk",): disk, ('TCPv4Port', 22): port}) |
|---|
| 538 | b.update(new_bid) |
|---|
| 539 | print b |
|---|
| 540 | |
|---|
| 541 | if __name__ == "__main__": |
|---|
| 542 | import sys |
|---|
| 543 | sys.exit(main()) |
|---|