• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Lint as: python2, python3
2# Copyright (c) 2013 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import logging
7import random
8
9from time import sleep
10
11from . import common
12from autotest_lib.client.common_lib import error
13from autotest_lib.client.common_lib import utils
14from autotest_lib.server.cros.ap_configurators import \
15    ap_configurator_factory
16from autotest_lib.client.common_lib.cros.network import ap_constants
17from autotest_lib.server.cros.ap_configurators import ap_cartridge
18
19# Max number of retry attempts to lock an ap.
20MAX_RETRIES = 3
21
22
23class ApLocker(object):
24    """Object to keep track of AP lock state.
25
26    @attribute configurator: an APConfigurator object.
27    @attribute to_be_locked: a boolean, True iff ap has not been locked.
28    @attribute retries: an integer, max number of retry attempts to lock ap.
29    """
30
31
32    def __init__(self, configurator, retries):
33        """Initialize.
34
35        @param configurator: an APConfigurator object.
36        @param retries: an integer, max number of retry attempts to lock ap.
37        """
38        self.configurator = configurator
39        self.to_be_locked = True
40        self.retries = retries
41
42
43    def __repr__(self):
44        """@return class name, ap host name, lock status and retries."""
45        return 'class: %s, host name: %s, to_be_locked = %s, retries = %d' % (
46                self.__class__.__name__,
47                self.configurator.host_name,
48                self.to_be_locked,
49                self.retries)
50
51
52def construct_ap_lockers(ap_spec, retries, hostname_matching_only=False,
53                         ap_test_type=ap_constants.AP_TEST_TYPE_CHAOS):
54    """Convert APConfigurator objects to ApLocker objects for locking.
55
56    @param ap_spec: an APSpec object
57    @param retries: an integer, max number of retry attempts to lock ap.
58    @param hostname_matching_only: a boolean, if True matching against
59                                   all other APSpec parameters is not
60                                   performed.
61    @param ap_test_type: Used to determine which type of test we're
62                         currently running (Chaos vs Clique).
63
64    @return a list of ApLocker objects.
65    """
66    ap_lockers_list = []
67    factory = ap_configurator_factory.APConfiguratorFactory(ap_test_type,
68                                                            ap_spec)
69    if hostname_matching_only:
70        for ap in factory.get_aps_by_hostnames(ap_spec.hostnames):
71            ap_lockers_list.append(ApLocker(ap, retries))
72    else:
73        for ap in factory.get_ap_configurators_by_spec(ap_spec):
74            ap_lockers_list.append(ApLocker(ap, retries))
75
76    if not len(ap_lockers_list):
77        logging.error('Found no matching APs to test against for %s', ap_spec)
78
79    logging.debug('Found %d APs', len(ap_lockers_list))
80    return ap_lockers_list
81
82
83class ApBatchLocker(object):
84    """Object to lock/unlock an APConfigurator.
85
86    @attribute SECONDS_TO_SLEEP: an integer, number of seconds to sleep between
87                                 retries.
88    @attribute ap_spec: an APSpec object
89    @attribute retries: an integer, max number of retry attempts to lock ap.
90                        Defaults to MAX_RETRIES.
91    @attribute aps_to_lock: a list of ApLocker objects.
92    @attribute manager: a HostLockManager object, used to lock/unlock APs.
93    """
94
95
96    MIN_SECONDS_TO_SLEEP = 10
97    MAX_SECONDS_TO_SLEEP = 20
98
99
100    def __init__(self, lock_manager, ap_spec, retries=MAX_RETRIES,
101                 hostname_matching_only=False,
102                 ap_test_type=ap_constants.AP_TEST_TYPE_CHAOS):
103        """Initialize.
104
105        @param ap_spec: an APSpec object
106        @param retries: an integer, max number of retry attempts to lock ap.
107                        Defaults to MAX_RETRIES.
108        @param hostname_matching_only : a boolean, if True matching against
109                                        all other APSpec parameters is not
110                                        performed.
111        @param ap_test_type: Used to determine which type of test we're
112                             currently running (Chaos vs Clique).
113        """
114        self.aps_to_lock = construct_ap_lockers(ap_spec, retries,
115                           hostname_matching_only=hostname_matching_only,
116                           ap_test_type=ap_test_type)
117        self.manager = lock_manager
118        self._locked_aps = []
119
120
121    def has_more_aps(self):
122        """@return True iff there is at least one AP to be locked."""
123        return len(self.aps_to_lock) > 0
124
125
126    def lock_ap(self, ap_locker):
127        """Locks an AP host in DataStore.
128
129        @param ap_locker: an ApLocker object, AP to be locked.
130        @return a boolean, True if ap_locker is locked.
131        """
132
133        if self.manager.lock([ap_locker.configurator.host_name]):
134            self._locked_aps.append(ap_locker)
135            logging.info('locked %s', ap_locker.configurator.host_name)
136            ap_locker.to_be_locked = False
137            return True
138        else:
139            ap_locker.retries -= 1
140            logging.info('%d retries left for %s',
141                         ap_locker.retries,
142                         ap_locker.configurator.host_name)
143            if ap_locker.retries == 0:
144                logging.info('No more retries left. Remove %s from list',
145                             ap_locker.configurator.host_name)
146                ap_locker.to_be_locked = False
147
148        return False
149
150
151
152    def get_ap_batch(self, batch_size=ap_cartridge.THREAD_MAX):
153        """Allocates a batch of locked APs.
154
155        @param batch_size: an integer, max. number of aps to lock in one batch.
156                           Defaults to THREAD_MAX in ap_cartridge.py
157        @return a list of APConfigurator objects, locked in datastore.
158        """
159        # We need this while loop to continuously loop over the for loop.
160        # To exit the while loop, we either:
161        #  - locked batch_size number of aps and return them
162        #  - exhausted all retries on all aps in aps_to_lock
163        while len(self.aps_to_lock):
164            ap_batch = []
165
166            for ap_locker in self.aps_to_lock:
167                logging.info('checking %s', ap_locker.configurator.host_name)
168                # Lock AP in DataStore
169                if self.lock_ap(ap_locker):
170                    ap_batch.append(ap_locker.configurator)
171                    if len(ap_batch) == batch_size:
172                        break
173
174            # Remove locked APs from list of APs to process.
175            aps_to_rm = [ap for ap in self.aps_to_lock if not ap.to_be_locked]
176            self.aps_to_lock = list(set(self.aps_to_lock) - set(aps_to_rm))
177            for ap in aps_to_rm:
178                logging.info('Removed %s from self.aps_to_lock',
179                             ap.configurator.host_name)
180            logging.info('Remaining aps to lock = %s',
181                         [ap.configurator.host_name for ap in self.aps_to_lock])
182
183            # Return available APs and retry remaining ones later.
184            if ap_batch:
185                return ap_batch
186
187            # Sleep before next retry.
188            if self.aps_to_lock:
189                seconds_to_sleep = random.randint(self.MIN_SECONDS_TO_SLEEP,
190                                                  self.MAX_SECONDS_TO_SLEEP)
191                logging.info('Sleep %d sec before retry', seconds_to_sleep)
192                sleep(seconds_to_sleep)
193
194        return []
195
196
197    def unlock_one_ap(self, host_name):
198        """
199        Unlock one AP from datastore after we're done.
200
201        @param host_name: a string, AP host name.
202
203        @raise TestError: when unable to unlock AP in datastore.
204
205        """
206        for ap_locker in self._locked_aps:
207            if host_name == ap_locker.configurator.host_name:
208                self.manager.unlock(hosts=[host_name])
209                self._locked_aps.remove(ap_locker)
210                return
211
212        logging.error('Tried to unlock a host we have not locked (%s)?',
213                      host_name)
214
215
216    def unlock_aps(self):
217        """Unlock APs after we're done."""
218        # Make a copy of all of the hostnames to process
219
220        host_names = list()
221        for ap_locker in self._locked_aps:
222            host_names.append(ap_locker.configurator.host_name)
223        for host_name in host_names:
224            self.unlock_one_ap(host_name)
225
226
227    def unlock_and_reclaim_ap(self, host_name):
228        """Unlock an AP but return it to the remaining batch of APs.
229
230        @param host_name: a string, host name.
231        """
232        for ap_locker in self._locked_aps:
233            if host_name == ap_locker.configurator.host_name:
234                self.aps_to_lock.append(ap_locker)
235                self.unlock_one_ap(host_name)
236                return
237
238
239    def unlock_and_reclaim_aps(self):
240        """Unlock APs but return them to the batch of remining APs.
241
242        unlock_aps() will remove the remaining APs from the list of all APs
243        to process.  This method will add the remaining APs back to the pool
244        of unprocessed APs.
245
246        """
247        # Add the APs back into the pool
248        self.aps_to_lock.extend(self._locked_aps)
249        self.unlock_aps()
250