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 |