Home | History | Annotate | Download | only in dynamic_suite
      1 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 
      6 #pylint: disable-msg=C0111
      7 def order_by_complexity(host_spec_list):
      8     """
      9     Returns a new list of HostSpecs, ordered from most to least complex.
     10 
     11     Currently, 'complex' means that the spec contains more labels.
     12     We may want to get smarter about this.
     13 
     14     @param host_spec_list: a list of HostSpec objects.
     15     @return a new list of HostSpec, ordered from most to least complex.
     16     """
     17     def extract_label_list_len(host_spec):
     18         return len(host_spec.labels)
     19     return sorted(host_spec_list, key=extract_label_list_len, reverse=True)
     20 
     21 
     22 def is_simple_list(host_spec_list):
     23     """
     24     Returns true if this is a 'simple' list of HostSpec objects.
     25 
     26     A 'simple' list of HostSpec objects is defined as a list of one HostSpec.
     27 
     28     @param host_spec_list: a list of HostSpec objects.
     29     @return True if this is a list of size 1, False otherwise.
     30     """
     31     return len(host_spec_list) == 1
     32 
     33 
     34 def simple_get_spec_and_hosts(host_specs, hosts_per_spec):
     35     """Given a simple list of HostSpec, extract hosts from hosts_per_spec.
     36 
     37     Given a simple list of HostSpec objects, pull out the spec and use it to
     38     get the associated hosts out of hosts_per_spec.  Return the spec and the
     39     host list as a pair.
     40 
     41     @param host_specs: an iterable of HostSpec objects.
     42     @param hosts_per_spec: map of {HostSpec: [list, of, hosts]}
     43     @return (HostSpec, [list, of, hosts]}
     44     """
     45     spec = host_specs.pop()
     46     return spec, hosts_per_spec[spec]
     47 
     48 
     49 class HostGroup(object):
     50     """A high-level specification of a group of hosts.
     51 
     52     A HostGroup represents a group of hosts against which a job can be
     53     scheduled.  An instance is capable of returning arguments that can specify
     54     this group in a call to AFE.create_job().
     55     """
     56     def __init__(self):
     57         pass
     58 
     59 
     60     def as_args(self):
     61         """Return args suitable for passing to AFE.create_job()."""
     62         raise NotImplementedError()
     63 
     64 
     65     def size(self):
     66         """Returns the number of hosts specified by the group."""
     67         raise NotImplementedError()
     68 
     69 
     70     def mark_host_success(self, hostname):
     71         """Marks the provided host as successfully reimaged.
     72 
     73         @param hostname: the name of the host that was reimaged.
     74         """
     75         raise NotImplementedError()
     76 
     77 
     78     def enough_hosts_succeeded(self):
     79         """Returns True if enough hosts in the group were reimaged for use."""
     80         raise NotImplementedError()
     81 
     82 
     83     #pylint: disable-msg=C0111
     84     @property
     85     def unsatisfied_specs(self):
     86         return []
     87 
     88 
     89     #pylint: disable-msg=C0111
     90     @property
     91     def doomed_specs(self):
     92         return []
     93 
     94 
     95 class ExplicitHostGroup(HostGroup):
     96     """A group of hosts, specified by name, to be reimaged for use.
     97 
     98     @var _hostname_data_dict: {hostname: HostData()}.
     99     """
    100 
    101     class HostData(object):
    102         """A HostSpec of a given host, and whether it reimaged successfully."""
    103         def __init__(self, spec):
    104             self.spec = spec
    105             self.image_success = False
    106 
    107 
    108     def __init__(self, hosts_per_spec={}):
    109         """Constructor.
    110 
    111         @param hosts_per_spec: {HostSpec: [list, of, hosts]}.
    112                                Each host can appear only once.
    113         """
    114         self._hostname_data_dict = {}
    115         self._potentially_unsatisfied_specs = []
    116         for spec, host_list in hosts_per_spec.iteritems():
    117             for host in host_list:
    118                 self.add_host_for_spec(spec, host)
    119 
    120 
    121     def _get_host_datas(self):
    122         return self._hostname_data_dict.itervalues()
    123 
    124 
    125     def as_args(self):
    126         return {'hosts': self._hostname_data_dict.keys()}
    127 
    128 
    129     def size(self):
    130         return len(self._hostname_data_dict)
    131 
    132 
    133     def mark_host_success(self, hostname):
    134         self._hostname_data_dict[hostname].image_success = True
    135 
    136 
    137     def enough_hosts_succeeded(self):
    138         """If _any_ hosts were reimaged, that's enough."""
    139         return True in [d.image_success for d in self._get_host_datas()]
    140 
    141 
    142     def add_host_for_spec(self, spec, host):
    143         """Add a new host for the given HostSpec to the group.
    144 
    145         @param spec: HostSpec to associate host with.
    146         @param host: a Host object; each host can appear only once.
    147                      If None, this spec will be relegated to the list of
    148                      potentially unsatisfied specs.
    149         """
    150         if not host:
    151             if spec not in [d.spec for d in self._get_host_datas()]:
    152                 self._potentially_unsatisfied_specs.append(spec)
    153             return
    154 
    155         if self.contains_host(host):
    156             raise ValueError('A Host can appear in an '
    157                              'ExplicitHostGroup only once.')
    158         if spec in self._potentially_unsatisfied_specs:
    159             self._potentially_unsatisfied_specs.remove(spec)
    160         self._hostname_data_dict[host.hostname] = self.HostData(spec)
    161 
    162 
    163     def contains_host(self, host):
    164         """Whether host is already part of this HostGroup
    165 
    166         @param host: a Host object.
    167         @return True if the host is already tracked; False otherwise.
    168         """
    169         return host.hostname in self._hostname_data_dict
    170 
    171 
    172     @property
    173     def unsatisfied_specs(self):
    174         unsatisfied = []
    175         for spec in self._potentially_unsatisfied_specs:
    176             # If a spec in _potentially_unsatisfied_specs is a subset of some
    177             # satisfied spec, then it's not unsatisfied.
    178             if filter(lambda d: spec.is_subset(d.spec), self._get_host_datas()):
    179                 continue
    180             unsatisfied.append(spec)
    181         return unsatisfied
    182 
    183 
    184     @property
    185     def doomed_specs(self):
    186         ok = set()
    187         possibly_doomed = set()
    188         for data in self._get_host_datas():
    189             # If imaging succeeded for any host that satisfies a spec,
    190             # it's definitely not doomed.
    191             if data.image_success:
    192                 ok.add(data.spec)
    193             else:
    194                 possibly_doomed.add(data.spec)
    195         # If a spec is not a subset of any ok spec, it's doomed.
    196         return set([s for s in possibly_doomed if not filter(s.is_subset, ok)])
    197 
    198 
    199 class MetaHostGroup(HostGroup):
    200     """A group of hosts, specified by a meta_host and deps, to be reimaged.
    201 
    202     @var _meta_hosts: a meta_host, as expected by AFE.create_job()
    203     @var _dependencies: list of dependencies that all hosts to be used
    204                         must satisfy
    205     @var _successful_hosts: set of successful hosts.
    206     """
    207     def __init__(self, labels, num):
    208         """Constructor.
    209 
    210         Given a set of labels specifying what kind of hosts we need,
    211         and the num of hosts we need, build a meta_host and dependency list
    212         that represent this group of hosts.
    213 
    214         @param labels: list of labels indicating what kind of hosts need
    215                        to be reimaged.
    216         @param num: how many hosts we'd like to reimage.
    217         """
    218         self._spec = HostSpec(labels)
    219         self._meta_hosts = labels[:1]*num
    220         self._dependencies = labels[1:]
    221         self._successful_hosts = set()
    222 
    223 
    224     def as_args(self):
    225         return {'meta_hosts': self._meta_hosts,
    226                 'dependencies': self._dependencies}
    227 
    228 
    229     def size(self):
    230         return len(self._meta_hosts)
    231 
    232 
    233     def mark_host_success(self, hostname):
    234         self._successful_hosts.add(hostname)
    235 
    236 
    237     def enough_hosts_succeeded(self):
    238         return self._successful_hosts
    239 
    240 
    241     @property
    242     def doomed_specs(self):
    243         if self._successful_hosts:
    244             return []
    245         return [self._spec]
    246 
    247 
    248 def _safeunion(iter_a, iter_b):
    249     """Returns an immutable set that contains the union of two iterables.
    250 
    251     This function returns a frozen set containing the all the elements of
    252     two iterables, regardless of whether those iterables are lists, sets,
    253     or whatever.
    254 
    255     @param iter_a: The first iterable.
    256     @param iter_b: The second iterable.
    257     @returns: An immutable union of the contents of iter_a and iter_b.
    258     """
    259     return frozenset({a for a in iter_a} | {b for b in iter_b})
    260 
    261 
    262 
    263 class HostSpec(object):
    264     """Specifies a kind of host on which dependency-having tests can be run.
    265 
    266     Wraps a list of labels, for the purposes of specifying a set of hosts
    267     on which a test with matching dependencies can be run.
    268     """
    269 
    270     def __init__(self, base, extended=[]):
    271         self._labels = _safeunion(base, extended)
    272         # To amortize cost of __hash__()
    273         self._str = 'HostSpec %r' % sorted(self._labels)
    274         self._trivial = extended == []
    275 
    276 
    277     #pylint: disable-msg=C0111
    278     @property
    279     def labels(self):
    280         # Can I just do this as a set?  Inquiring minds want to know.
    281         return sorted(self._labels)
    282 
    283 
    284     #pylint: disable-msg=C0111
    285     @property
    286     def is_trivial(self):
    287         return self._trivial
    288 
    289 
    290     #pylint: disable-msg=C0111
    291     def is_subset(self, other):
    292         return self._labels <= other._labels
    293 
    294 
    295     def __str__(self):
    296         return self._str
    297 
    298 
    299     def __repr__(self):
    300         return self._str
    301 
    302 
    303     def __lt__(self, other):
    304         return str(self) < str(other)
    305 
    306 
    307     def __le__(self, other):
    308         return str(self) <= str(other)
    309 
    310 
    311     def __eq__(self, other):
    312         return str(self) == str(other)
    313 
    314 
    315     def __ne__(self, other):
    316         return str(self) != str(other)
    317 
    318 
    319     def __gt__(self, other):
    320         return str(self) > str(other)
    321 
    322 
    323     def __ge__(self, other):
    324         return str(self) >= str(other)
    325 
    326 
    327     def __hash__(self):
    328         """Allows instances to be correctly deduped when used in a set."""
    329         return hash(str(self))
    330