1*9c5db199SXin Li# Copyright 2017 The Chromium OS Authors. All rights reserved. 2*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 3*9c5db199SXin Li# found in the LICENSE file. 4*9c5db199SXin Li 5*9c5db199SXin Lifrom __future__ import absolute_import 6*9c5db199SXin Lifrom __future__ import print_function 7*9c5db199SXin Li 8*9c5db199SXin Liimport logging 9*9c5db199SXin Li 10*9c5db199SXin Liimport common 11*9c5db199SXin Lifrom autotest_lib.server.hosts import host_info 12*9c5db199SXin Lifrom autotest_lib.utils.frozen_chromite.lib import metrics 13*9c5db199SXin Li 14*9c5db199SXin Li 15*9c5db199SXin Li_METRICS_PREFIX = 'chromeos/autotest/autoserv/host_info/shadowing_store/' 16*9c5db199SXin Li_REFRESH_METRIC_NAME = _METRICS_PREFIX + 'refresh_count' 17*9c5db199SXin Li_COMMIT_METRIC_NAME = _METRICS_PREFIX + 'commit_count' 18*9c5db199SXin Li 19*9c5db199SXin Li 20*9c5db199SXin Lilogger = logging.getLogger(__file__) 21*9c5db199SXin Li 22*9c5db199SXin Liclass ShadowingStore(host_info.CachingHostInfoStore): 23*9c5db199SXin Li """A composite CachingHostInfoStore that maintains a main and shadow store. 24*9c5db199SXin Li 25*9c5db199SXin Li ShadowingStore accepts two CachingHostInfoStore objects - primary_store and 26*9c5db199SXin Li shadow_store. All refresh/commit operations are serviced through 27*9c5db199SXin Li primary_store. In addition, shadow_store is updated and compared with this 28*9c5db199SXin Li information, leaving breadcrumbs when the two differ. Any errors in 29*9c5db199SXin Li shadow_store operations are logged and ignored so as to not affect the user. 30*9c5db199SXin Li 31*9c5db199SXin Li This is a transitional CachingHostInfoStore that allows us to continue to 32*9c5db199SXin Li use an AfeStore in practice, but also create a backing FileStore so that we 33*9c5db199SXin Li can validate the use of FileStore in prod. 34*9c5db199SXin Li """ 35*9c5db199SXin Li 36*9c5db199SXin Li def __init__(self, primary_store, shadow_store, 37*9c5db199SXin Li mismatch_callback=None): 38*9c5db199SXin Li """ 39*9c5db199SXin Li @param primary_store: A CachingHostInfoStore to be used as the primary 40*9c5db199SXin Li store. 41*9c5db199SXin Li @param shadow_store: A CachingHostInfoStore to be used to shadow the 42*9c5db199SXin Li primary store. 43*9c5db199SXin Li @param mismatch_callback: A callback used to notify whenever we notice a 44*9c5db199SXin Li mismatch between primary_store and shadow_store. The signature 45*9c5db199SXin Li of the callback must match: 46*9c5db199SXin Li callback(primary_info, shadow_info) 47*9c5db199SXin Li where primary_info and shadow_info are HostInfo objects obtained 48*9c5db199SXin Li from the two stores respectively. 49*9c5db199SXin Li Mostly used by unittests. Actual users don't know / nor care 50*9c5db199SXin Li that they're using a ShadowingStore. 51*9c5db199SXin Li """ 52*9c5db199SXin Li super(ShadowingStore, self).__init__() 53*9c5db199SXin Li self._primary_store = primary_store 54*9c5db199SXin Li self._shadow_store = shadow_store 55*9c5db199SXin Li self._mismatch_callback = ( 56*9c5db199SXin Li mismatch_callback if mismatch_callback is not None 57*9c5db199SXin Li else _log_info_mismatch) 58*9c5db199SXin Li try: 59*9c5db199SXin Li self._shadow_store.commit(self._primary_store.get()) 60*9c5db199SXin Li except host_info.StoreError as e: 61*9c5db199SXin Li metrics.Counter( 62*9c5db199SXin Li _METRICS_PREFIX + 'initialization_fail_count').increment() 63*9c5db199SXin Li logger.exception( 64*9c5db199SXin Li 'Failed to initialize shadow store. ' 65*9c5db199SXin Li 'Expect primary / shadow desync in the future.') 66*9c5db199SXin Li 67*9c5db199SXin Li def commit_with_substitute(self, info, primary_store=None, 68*9c5db199SXin Li shadow_store=None): 69*9c5db199SXin Li """Commit host information using alternative stores. 70*9c5db199SXin Li 71*9c5db199SXin Li This is used to commit using an alternative store implementation 72*9c5db199SXin Li to work around some issues (crbug.com/903589). 73*9c5db199SXin Li 74*9c5db199SXin Li Don't set cached_info in this function. 75*9c5db199SXin Li 76*9c5db199SXin Li @param info: A HostInfo object to set. 77*9c5db199SXin Li @param primary_store: A CachingHostInfoStore object to commit instead of 78*9c5db199SXin Li the original primary_store. 79*9c5db199SXin Li @param shadow_store: A CachingHostInfoStore object to commit instead of 80*9c5db199SXin Li the original shadow store. 81*9c5db199SXin Li """ 82*9c5db199SXin Li if primary_store is not None: 83*9c5db199SXin Li primary_store.commit(info) 84*9c5db199SXin Li else: 85*9c5db199SXin Li self._commit_to_primary_store(info) 86*9c5db199SXin Li 87*9c5db199SXin Li if shadow_store is not None: 88*9c5db199SXin Li shadow_store.commit(info) 89*9c5db199SXin Li else: 90*9c5db199SXin Li self._commit_to_shadow_store(info) 91*9c5db199SXin Li 92*9c5db199SXin Li def __str__(self): 93*9c5db199SXin Li return '%s[%s, %s]' % (type(self).__name__, self._primary_store, 94*9c5db199SXin Li self._shadow_store) 95*9c5db199SXin Li 96*9c5db199SXin Li def _refresh_impl(self): 97*9c5db199SXin Li """Obtains HostInfo from the primary and compares against shadow""" 98*9c5db199SXin Li primary_info = self._refresh_from_primary_store() 99*9c5db199SXin Li try: 100*9c5db199SXin Li shadow_info = self._refresh_from_shadow_store() 101*9c5db199SXin Li except host_info.StoreError: 102*9c5db199SXin Li logger.exception('Shadow refresh failed. ' 103*9c5db199SXin Li 'Skipping comparison with primary.') 104*9c5db199SXin Li return primary_info 105*9c5db199SXin Li self._verify_store_infos(primary_info, shadow_info) 106*9c5db199SXin Li return primary_info 107*9c5db199SXin Li 108*9c5db199SXin Li def _commit_impl(self, info): 109*9c5db199SXin Li """Commits HostInfo to both the primary and shadow store""" 110*9c5db199SXin Li self._commit_to_primary_store(info) 111*9c5db199SXin Li self._commit_to_shadow_store(info) 112*9c5db199SXin Li 113*9c5db199SXin Li def _commit_to_primary_store(self, info): 114*9c5db199SXin Li try: 115*9c5db199SXin Li self._primary_store.commit(info) 116*9c5db199SXin Li except host_info.StoreError: 117*9c5db199SXin Li metrics.Counter(_COMMIT_METRIC_NAME).increment( 118*9c5db199SXin Li fields={'file_commit_result': 'skipped'}) 119*9c5db199SXin Li raise 120*9c5db199SXin Li 121*9c5db199SXin Li def _commit_to_shadow_store(self, info): 122*9c5db199SXin Li try: 123*9c5db199SXin Li self._shadow_store.commit(info) 124*9c5db199SXin Li except host_info.StoreError: 125*9c5db199SXin Li logger.exception( 126*9c5db199SXin Li 'shadow commit failed. ' 127*9c5db199SXin Li 'Expect primary / shadow desync in the future.') 128*9c5db199SXin Li metrics.Counter(_COMMIT_METRIC_NAME).increment( 129*9c5db199SXin Li fields={'file_commit_result': 'fail'}) 130*9c5db199SXin Li else: 131*9c5db199SXin Li metrics.Counter(_COMMIT_METRIC_NAME).increment( 132*9c5db199SXin Li fields={'file_commit_result': 'success'}) 133*9c5db199SXin Li 134*9c5db199SXin Li def _refresh_from_primary_store(self): 135*9c5db199SXin Li try: 136*9c5db199SXin Li return self._primary_store.get(force_refresh=True) 137*9c5db199SXin Li except host_info.StoreError: 138*9c5db199SXin Li metrics.Counter(_REFRESH_METRIC_NAME).increment( 139*9c5db199SXin Li fields={'validation_result': 'skipped'}) 140*9c5db199SXin Li raise 141*9c5db199SXin Li 142*9c5db199SXin Li def _refresh_from_shadow_store(self): 143*9c5db199SXin Li try: 144*9c5db199SXin Li return self._shadow_store.get(force_refresh=True) 145*9c5db199SXin Li except host_info.StoreError: 146*9c5db199SXin Li metrics.Counter(_REFRESH_METRIC_NAME).increment(fields={ 147*9c5db199SXin Li 'validation_result': 'fail_shadow_store_refresh'}) 148*9c5db199SXin Li raise 149*9c5db199SXin Li 150*9c5db199SXin Li def _verify_store_infos(self, primary_info, shadow_info): 151*9c5db199SXin Li if primary_info == shadow_info: 152*9c5db199SXin Li metrics.Counter(_REFRESH_METRIC_NAME).increment( 153*9c5db199SXin Li fields={'validation_result': 'success'}) 154*9c5db199SXin Li else: 155*9c5db199SXin Li self._mismatch_callback(primary_info, shadow_info) 156*9c5db199SXin Li metrics.Counter(_REFRESH_METRIC_NAME).increment( 157*9c5db199SXin Li fields={'validation_result': 'fail_mismatch'}) 158*9c5db199SXin Li self._shadow_store.commit(primary_info) 159*9c5db199SXin Li 160*9c5db199SXin Li 161*9c5db199SXin Lidef _log_info_mismatch(primary_info, shadow_info): 162*9c5db199SXin Li """Log the two HostInfo instances. 163*9c5db199SXin Li 164*9c5db199SXin Li Used as the default mismatch_callback. 165*9c5db199SXin Li """ 166*9c5db199SXin Li logger.warning('primary / shadow disagree on refresh.') 167*9c5db199SXin Li logger.warning('primary: %s', primary_info) 168*9c5db199SXin Li logger.warning('shadow: %s', shadow_info) 169