| OLD | NEW |
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. | 1 # Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Library to generate, maintain, and read static slave pool maps.""" | 5 """Library to generate, maintain, and read static slave pool maps.""" |
| 6 | 6 |
| 7 import collections | 7 import collections |
| 8 import itertools | 8 import itertools |
| 9 import json | 9 import json |
| 10 import os | 10 import os |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 67 external JSON file. This can be used to enforce class-to-slave mapping | 67 external JSON file. This can be used to enforce class-to-slave mapping |
| 68 consistency (i.e., builder affinity). | 68 consistency (i.e., builder affinity). |
| 69 | 69 |
| 70 When a new allocation is performed, the SlaveAllocator's State is updated, and | 70 When a new allocation is performed, the SlaveAllocator's State is updated, and |
| 71 subsequent operations will prefer the previous layout. | 71 subsequent operations will prefer the previous layout. |
| 72 """ | 72 """ |
| 73 | 73 |
| 74 # The default path to load/save state to, if none is specified. | 74 # The default path to load/save state to, if none is specified. |
| 75 DEFAULT_STATE_PATH = 'slave_pool.json' | 75 DEFAULT_STATE_PATH = 'slave_pool.json' |
| 76 | 76 |
| 77 def __init__(self): | 77 def __init__(self, state_path=None, list_unallocated=False): |
| 78 """Initializes a new slave pool instance.""" | 78 """Initializes a new slave pool instance. |
| 79 |
| 80 Args: |
| 81 state_path (str): The path (relative or absolute) of the allocation |
| 82 save-state JSON file. If None, DEFAULT_STATE_PATH will be used. |
| 83 list_unallocated (bool): Include an entry listing unallocated slaves. |
| 84 This entry will be ignored for operations, but can be useful when |
| 85 generating expectations. |
| 86 """ |
| 87 self._state_path = state_path |
| 88 self._list_unallocated = list_unallocated |
| 89 |
| 79 self._state = None | 90 self._state = None |
| 80 self._pools = {} | 91 self._pools = {} |
| 81 self._classes = {} | 92 self._classes = {} |
| 82 self._membership = {} | 93 self._membership = {} |
| 83 self._all_slaves = {} | 94 self._all_slaves = {} |
| 84 | 95 |
| 96 @property |
| 97 def state_path(self): |
| 98 return self._state_path or self.DEFAULT_STATE_PATH |
| 99 |
| 85 def LoadStateDict(self, state_class_map=None): | 100 def LoadStateDict(self, state_class_map=None): |
| 86 """Loads previous allocation state from a state dictionary. | 101 """Loads previous allocation state from a state dictionary. |
| 87 | 102 |
| 88 The state dictionary is structured: | 103 The state dictionary is structured: |
| 89 <class-name>: { | 104 <class-name>: { |
| 90 <class-subtype>: [ | 105 <class-subtype>: [ |
| 91 <slave> | 106 <slave> |
| 92 ... | 107 ... |
| 93 ], | 108 ], |
| 94 ... | 109 ... |
| 95 } | 110 } |
| 96 | 111 |
| 97 Args: | 112 Args: |
| 98 state_class_map (dict): A state class map dictionary. If None or empty, | 113 state_class_map (dict): A state class map dictionary. If None or empty, |
| 99 the current state will be cleared. | 114 the current state will be cleared. |
| 100 """ | 115 """ |
| 101 if not state_class_map: | 116 if not state_class_map: |
| 102 self._state = None | 117 self._state = None |
| 103 return | 118 return |
| 104 | 119 |
| 105 class_map = {} | 120 class_map = {} |
| 106 for class_name, class_name_entry in state_class_map.iteritems(): | 121 for class_name, class_name_entry in state_class_map.iteritems(): |
| 107 for subtype, slave_list in class_name_entry.iteritems(): | 122 for subtype, slave_list in class_name_entry.iteritems(): |
| 108 cls = SlaveClass(name=class_name, subtype=subtype) | 123 cls = SlaveClass(name=class_name, subtype=subtype) |
| 109 class_map.setdefault(cls, []).extend(str(s) for s in slave_list) | 124 class_map.setdefault(cls, []).extend(str(s) for s in slave_list) |
| 110 self._state = SlaveState( | 125 self._state = SlaveState( |
| 111 class_map=class_map, | 126 class_map=class_map, |
| 112 unallocated=None) | 127 unallocated=None) |
| 113 | 128 |
| 114 def LoadState(self, path=None, enforce=True): | 129 def LoadState(self, enforce=True): |
| 115 """Loads slave pools from the store, replacing the current in-memory set. | 130 """Loads slave pools from the store, replacing the current in-memory set. |
| 116 | 131 |
| 117 Args: | 132 Args: |
| 118 path (str): If provided, the path to load from; otherwise, | |
| 119 DEFAULT_STATE_PATH will be used. | |
| 120 enforce (bool): If True, raise an IOError if the state file does not | 133 enforce (bool): If True, raise an IOError if the state file does not |
| 121 exist or a ValueError if it could not be loaded. | 134 exist or a ValueError if it could not be loaded. |
| 122 """ | 135 """ |
| 123 state = {} | 136 state = {} |
| 124 path = path or self.DEFAULT_STATE_PATH | 137 if not os.path.exists(self.state_path): |
| 125 if not os.path.exists(path): | |
| 126 if enforce: | 138 if enforce: |
| 127 raise IOError("State path does not exist: %s" % (path,)) | 139 raise IOError("State path does not exist: %s" % (self.state_path,)) |
| 128 try: | 140 try: |
| 129 with open(path or self.DEFAULT_STATE_PATH, 'r') as fd: | 141 with open(self.state_path, 'r') as fd: |
| 130 state = json.load(fd) | 142 state = json.load(fd) |
| 131 except (IOError, ValueError): | 143 except (IOError, ValueError): |
| 132 if enforce: | 144 if enforce: |
| 133 raise | 145 raise |
| 134 self.LoadStateDict(state.get('class_map')) | 146 self.LoadStateDict(state.get('class_map')) |
| 135 | 147 |
| 136 def SaveState(self, path=None, list_unallocated=False): | 148 def SaveState(self): |
| 137 """Saves the current slave pool set to the store path. | 149 """Saves the current slave pool set to the store path.""" |
| 138 | |
| 139 Args: | |
| 140 path (str): The path of the state file. If None, use DEFAULT_STATE_PATH. | |
| 141 list_unallocated (bool): Include an entry listing unallocated slaves. | |
| 142 This entry will be ignored for operations, but can be useful when | |
| 143 generating expectations. | |
| 144 """ | |
| 145 state_dict = {} | 150 state_dict = {} |
| 146 if self._state and self._state.class_map: | 151 if self._state and self._state.class_map: |
| 147 class_map = state_dict['class_map'] = {} | 152 class_map = state_dict['class_map'] = {} |
| 148 for sc, slave_list in self._state.class_map.iteritems(): | 153 for sc, slave_list in self._state.class_map.iteritems(): |
| 149 class_dict = class_map.setdefault(sc.name, {}) | 154 class_dict = class_map.setdefault(sc.name, {}) |
| 150 subtype_dict = class_dict.setdefault(sc.subtype, []) | 155 subtype_dict = class_dict.setdefault(sc.subtype, []) |
| 151 subtype_dict.extend(slave_list) | 156 subtype_dict.extend(slave_list) |
| 152 | 157 |
| 153 if list_unallocated: | 158 if self._list_unallocated: |
| 154 state_dict['unallocated'] = list(self._state.unallocated or ()) | 159 state_dict['unallocated'] = list(self._state.unallocated or ()) |
| 155 | 160 |
| 156 with open(path or self.DEFAULT_STATE_PATH, 'w') as fd: | 161 with open(self.state_path, 'w') as fd: |
| 157 json.dump(state_dict, fd, sort_keys=True, indent=2) | 162 json.dump(state_dict, fd, sort_keys=True, indent=2) |
| 158 | 163 |
| 159 def AddPool(self, name, *slaves): | 164 def AddPool(self, name, *slaves): |
| 160 """Returns (str): The slave pool that was allocated (for chaining). | 165 """Returns (str): The slave pool that was allocated (for chaining). |
| 161 | 166 |
| 162 Args: | 167 Args: |
| 163 name (str): The slave pool name. | 168 name (str): The slave pool name. |
| 164 slaves: Slave name strings that belong to this pool. | 169 slaves: Slave name strings that belong to this pool. |
| 165 """ | 170 """ |
| 166 pool = self._pools.get(name) | 171 pool = self._pools.get(name) |
| (...skipping 167 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 334 | 339 |
| 335 # Convert SlaveMapEntry fields to immutable form. | 340 # Convert SlaveMapEntry fields to immutable form. |
| 336 result = SlaveMap( | 341 result = SlaveMap( |
| 337 entries={}, | 342 entries={}, |
| 338 unallocated=frozenset(n_state.unallocated)) | 343 unallocated=frozenset(n_state.unallocated)) |
| 339 for k, v in slave_map_entries.iteritems(): | 344 for k, v in slave_map_entries.iteritems(): |
| 340 result.entries[k] = SlaveMapEntry( | 345 result.entries[k] = SlaveMapEntry( |
| 341 classes=frozenset(v.classes), | 346 classes=frozenset(v.classes), |
| 342 keys=tuple(sorted(v.keys))) | 347 keys=tuple(sorted(v.keys))) |
| 343 return result | 348 return result |
| OLD | NEW |