def testUpdateNoMaster(self): """An update skips updating the master map, and approprate sub maps.""" source_entry1 = automount.AutomountMapEntry() source_entry2 = automount.AutomountMapEntry() source_entry1.key = '/home' source_entry2.key = '/auto' source_entry1.location = 'ou=auto.home,ou=automounts' source_entry2.location = 'ou=auto.auto,ou=automounts' source_master = automount.AutomountMap([source_entry1, source_entry2]) local_entry1 = automount.AutomountMapEntry() local_entry2 = automount.AutomountMapEntry() local_entry1.key = '/home' local_entry2.key = '/auto' local_entry1.location = '/etc/auto.home' local_entry2.location = '/etc/auto.null' local_master = automount.AutomountMap([local_entry1, local_entry2]) source_mock = self.mox.CreateMock(source.Source) # return the source master map source_mock.GetAutomountMasterMap().AndReturn(source_master) # the auto.home cache cache_home = self.mox.CreateMock(caches.Cache) # GetMapLocation() is called, and set to the master map map_entry cache_home.GetMapLocation().AndReturn('/etc/auto.home') # the auto.auto cache cache_auto = self.mox.CreateMock(caches.Cache) # GetMapLocation() is called, and set to the master map map_entry cache_auto.GetMapLocation().AndReturn('/etc/auto.auto') # the auto.master cache, which should not be written to cache_master = self.mox.CreateMock(caches.Cache) cache_master.GetMap().AndReturn(local_master) self.mox.StubOutWithMock(cache_factory, 'Create') cache_factory.Create(mox.IgnoreArg(), mox.IgnoreArg(), automount_mountpoint=None).AndReturn(cache_master) cache_factory.Create(mox.IgnoreArg(), mox.IgnoreArg(), automount_mountpoint='/home').AndReturn(cache_home) cache_factory.Create(mox.IgnoreArg(), mox.IgnoreArg(), automount_mountpoint='/auto').AndReturn(cache_auto) skip = map_updater.AutomountUpdater.OPT_LOCAL_MASTER updater = map_updater.AutomountUpdater( config.MAP_AUTOMOUNT, self.workdir, {skip: 'yes'}) self.mox.StubOutClassWithMocks(map_updater, 'MapUpdater') updater_home = map_updater.MapUpdater(config.MAP_AUTOMOUNT, self.workdir, {'local_automount_master': 'yes'}, automount_mountpoint='/home') updater_home.UpdateCacheFromSource(cache_home, source_mock, True, False, 'ou=auto.home,ou=automounts').AndReturn(0) self.mox.ReplayAll() updater.UpdateFromSource(source_mock)
def testUpdate(self): """An update gets a master map and updates each entry.""" map_entry1 = automount.AutomountMapEntry() map_entry2 = automount.AutomountMapEntry() map_entry1.key = '/home' map_entry2.key = '/auto' map_entry1.location = 'ou=auto.home,ou=automounts' map_entry2.location = 'ou=auto.auto,ou=automounts' master_map = automount.AutomountMap([map_entry1, map_entry2]) source_mock = self.mox.CreateMock(source.Source) # return the master map source_mock.GetAutomountMasterMap().AndReturn(master_map) # the auto.home cache cache_home = self.mox.CreateMock(caches.Cache) # GetMapLocation() is called, and set to the master map map_entry cache_home.GetMapLocation().AndReturn('/etc/auto.home') # the auto.auto cache cache_auto = self.mox.CreateMock(caches.Cache) # GetMapLocation() is called, and set to the master map map_entry cache_auto.GetMapLocation().AndReturn('/etc/auto.auto') # the auto.master cache cache_master = self.mox.CreateMock(caches.Cache) self.mox.StubOutWithMock(cache_factory, 'Create') cache_factory.Create(mox.IgnoreArg(), 'automount', automount_mountpoint='/home').AndReturn(cache_home) cache_factory.Create(mox.IgnoreArg(), 'automount', automount_mountpoint='/auto').AndReturn(cache_auto) cache_factory.Create(mox.IgnoreArg(), 'automount', automount_mountpoint=None).AndReturn(cache_master) updater = map_updater.AutomountUpdater( config.MAP_AUTOMOUNT, self.workdir, {}) self.mox.StubOutClassWithMocks(map_updater, 'MapUpdater') updater_home = map_updater.MapUpdater(config.MAP_AUTOMOUNT, self.workdir, {}, automount_mountpoint='/home') updater_home.UpdateCacheFromSource(cache_home, source_mock, True, False, 'ou=auto.home,ou=automounts').AndReturn(0) updater_auto = map_updater.MapUpdater(config.MAP_AUTOMOUNT, self.workdir, {}, automount_mountpoint='/auto') updater_auto.UpdateCacheFromSource(cache_auto, source_mock, True, False, 'ou=auto.auto,ou=automounts').AndReturn(0) updater_master = map_updater.MapUpdater(config.MAP_AUTOMOUNT, self.workdir, {}) updater_master.FullUpdateFromMap(cache_master, master_map).AndReturn(0) self.mox.ReplayAll() updater.UpdateFromSource(source_mock) self.assertEqual(map_entry1.location, '/etc/auto.home') self.assertEqual(map_entry2.location, '/etc/auto.auto')
def UpdateFromSource(self, source, incremental=True, force_write=False): """Update this map's cache from the source provided. The FileMapUpdater expects to fetch as single map from the source and write/merge it to disk. We create a cache to write to, and then call UpdateCacheFromSource() with that cache. Note that AutomountUpdater also calls UpdateCacheFromSource() for each cache it is writing, hence the distinct seperation. Args: source: A nss_cache.sources.Source object. incremental: A boolean flag indicating that an incremental update should be performed, defaults to True. force_write: A boolean flag forcing empty map updates, defaults to False. Returns: An int indicating success of update (0 == good, fail otherwise). """ # Create the single cache we write to cache = cache_factory.Create(self.cache_options, self.map_name) return self.UpdateCacheFromSource(cache, source, incremental, force_write, location=None)
def testGetSingleMapMetadata(self): # test both automount and non-automount maps. # cache mock is returned by FakeCreate() for automount maps cache_mock = self.mox.CreateMock(caches.Cache) cache_mock.GetMapLocation().AndReturn('/etc/auto.master') self.mox.StubOutWithMock(cache_factory, 'Create') cache_factory.Create( self.conf.options[config.MAP_AUTOMOUNT].cache, config.MAP_AUTOMOUNT, automount_mountpoint='automount_mountpoint').AndReturn(cache_mock) self.mox.ReplayAll() c = command.Status() values = c.GetSingleMapMetadata(config.MAP_PASSWORD, self.conf) self.failUnless('map' in values[0]) self.failUnless('key' in values[0]) self.failUnless('value' in values[0]) values = c.GetSingleMapMetadata( config.MAP_AUTOMOUNT, self.conf, automount_mountpoint='automount_mountpoint') self.failUnless('map' in values[0]) self.failUnless('key' in values[0]) self.failUnless('value' in values[0]) self.failUnless('automount' in values[0])
def testUpdateCatchesMissingMaster(self): """Gracefully handle a missing local master maps.""" # use an empty master map from the source, to avoid mocking out already # tested code master_map = automount.AutomountMap() source_mock = self.mox.CreateMockAnything() source_mock.GetAutomountMasterMap().AndReturn(master_map) cache_mock = self.mox.CreateMock(caches.Cache) # raise error on GetMap() cache_mock.GetMap().AndRaise(error.CacheNotFound) skip = map_updater.AutomountUpdater.OPT_LOCAL_MASTER cache_options = {skip: 'yes'} self.mox.StubOutWithMock(cache_factory, 'Create') cache_factory.Create( cache_options, 'automount', automount_mountpoint=None).AndReturn(cache_mock) self.mox.ReplayAll() updater = map_updater.AutomountUpdater( config.MAP_AUTOMOUNT, self.workdir, cache_options) return_value = updater.UpdateFromSource(source_mock) self.assertEqual(return_value, 1)
def testUpdateSingleMaps(self): self.mox.StubOutClassWithMocks(lock, 'PidFile') lock_mock = lock.PidFile(filename=None) lock_mock.Lock(force=False).AndReturn(True) lock_mock.Locked().AndReturn(True) lock_mock.Unlock() self.conf.maps = [config.MAP_PASSWORD] self.conf.cache = 'dummy' modify_stamp = 1 map_entry = passwd.PasswdMapEntry({'name': 'foo', 'uid': 10, 'gid': 10}) passwd_map = passwd.PasswdMap([map_entry]) passwd_map.SetModifyTimestamp(modify_stamp) source_mock = self.mox.CreateMock(source.Source) source_mock.GetMap(config.MAP_PASSWORD, location=None).AndReturn(passwd_map) self.mox.StubOutWithMock(source_factory, 'Create') source_factory.Create(self.conf.options[ config.MAP_PASSWORD].source).AndReturn(source_mock) cache_mock = self.mox.CreateMock(caches.Cache) cache_mock.WriteMap(map_data=passwd_map).AndReturn(0) self.mox.StubOutWithMock(cache_factory, 'Create') cache_factory.Create(self.conf.options[config.MAP_PASSWORD].cache, config.MAP_PASSWORD).AndReturn(cache_mock) self.mox.ReplayAll() c = command.Update() self.assertEqual( 0, c.UpdateMaps(self.conf, incremental=True, force_write=False))
def UpdateCacheFromSource(self, cache, source, incremental=False, force_write=False, location=None): """Update a single cache file, from a given source. Args: cache: A nss_cache.caches.Cache object. source: A nss_cache.sources.Source object. incremental: We ignore this. force_write: A boolean flag forcing empty map updates when False, defaults to False. location: The optional location in the source of this map used by automount to specify which automount map to get, defaults to None. Returns: An int indicating the success of an update (0 == good, fail otherwise). """ return_val = 0 cache_filename = cache.GetCacheFilename() if cache_filename is not None: new_file_fd, new_file = tempfile.mkstemp( dir=os.path.dirname(cache_filename), prefix=os.path.basename(cache_filename), suffix='.nsscache.tmp') else: raise error.CacheInvalid('Cache has no filename.') self.log.debug('temp source filename: %s', new_file) try: # Writes the source to new_file. # Current file is passed in to allow the source to do partial diffs. # TODO(jaq): refactor this to pass in the whole cache, so that the source # can decide how to reduce downloads, c.f. last-modify-timestamp for ldap. source.GetFile(self.map_name, new_file, current_file=cache.GetCacheFilename(), location=location) os.lseek(new_file_fd, 0, os.SEEK_SET) # TODO(jaq): this sucks. source_cache = cache_factory.Create(self.cache_options, self.map_name) source_map = source_cache.GetMap(new_file) # Update the cache from the new file. return_val += self._FullUpdateFromFile(cache, source_map, force_write) finally: try: os.unlink(new_file) except OSError as e: # If we're using zsync source, it already renames the file for us. if e.errno != errno.ENOENT: raise return return_val
def testUpdateAutomounts(self): self.mox.StubOutClassWithMocks(lock, 'PidFile') lock_mock = lock.PidFile(filename=None) lock_mock.Lock(force=False).AndReturn(True) lock_mock.Locked().AndReturn(True) lock_mock.Unlock() self.conf.maps = [config.MAP_AUTOMOUNT] self.conf.cache = 'dummy' modify_stamp = 1 map_entry = automount.AutomountMapEntry() map_entry.key = '/home' map_entry.location = 'foo' automount_map = automount.AutomountMap([map_entry]) automount_map.SetModifyTimestamp(modify_stamp) source_mock = self.mox.CreateMock(source.Source) source_mock.GetAutomountMasterMap().AndReturn(automount_map) source_mock.GetMap(config.MAP_AUTOMOUNT, location='foo').AndReturn(automount_map) self.mox.StubOutWithMock(source_factory, 'Create') source_factory.Create(self.conf.options[ config.MAP_PASSWORD].source).AndReturn(source_mock) cache_mock = self.mox.CreateMock(caches.Cache) cache_mock.GetMapLocation().AndReturn('home') cache_mock.WriteMap(map_data=automount_map).AndReturn(0) cache_mock.WriteMap(map_data=automount_map).AndReturn(0) self.mox.StubOutWithMock(cache_factory, 'Create') cache_factory.Create( self.conf.options[config.MAP_AUTOMOUNT].cache, config.MAP_AUTOMOUNT, automount_mountpoint='/home').AndReturn(cache_mock) cache_factory.Create(self.conf.options[config.MAP_AUTOMOUNT].cache, config.MAP_AUTOMOUNT, automount_mountpoint=None).AndReturn(cache_mock) self.mox.ReplayAll() c = command.Update() self.assertEquals( 0, c.UpdateMaps(self.conf, incremental=True, force_write=False))
def GetSingleMapMetadata(self, map_name, conf, automount_mountpoint=None, epoch=False): """Return metadata from map specified. Args: map_name: name of map to extract data from conf: a config.Config object automount_mountpoint: information necessary for automount maps epoch: return times as an integer epoch (time_t) instead of a human readable name Returns: a list of dicts of metadata key/value pairs """ cache_options = conf.options[map_name].cache updater = map_updater.MapUpdater(map_name, conf.timestamp_dir, cache_options, automount_mountpoint) modify_dict = {'key': 'last-modify-timestamp', 'map': map_name} update_dict = {'key': 'last-update-timestamp', 'map': map_name} if map_name == config.MAP_AUTOMOUNT: # have to find out *which* automount map from a cache object! cache = cache_factory.Create( cache_options, config.MAP_AUTOMOUNT, automount_mountpoint=automount_mountpoint) automount = cache.GetMapLocation() modify_dict['automount'] = automount update_dict['automount'] = automount last_modify_timestamp = updater.GetModifyTimestamp() or 0 last_update_timestamp = updater.GetUpdateTimestamp() or 0 if not epoch: # If we are displaying the time as a string, do so in localtime. This is # the only place such a conversion is appropriate. if last_modify_timestamp: last_modify_timestamp = time.asctime( time.localtime(last_modify_timestamp)) else: last_modify_timestamp = 'Unknown' if last_update_timestamp: last_update_timestamp = time.asctime( time.localtime(last_update_timestamp)) else: last_update_timestamp = 'Unknown' modify_dict['value'] = last_modify_timestamp update_dict['value'] = last_update_timestamp return [modify_dict, update_dict]
def testGetAutomountMapMetadata(self): # need to stub out GetSingleMapMetadata (tested above) and then # stub out cache_factory.Create to return a cache mock that spits # out an iterable map for the function to use. # stub out GetSingleMapMetadata class DummyStatus(command.Status): def GetSingleMapMetadata(self, unused_map_name, unused_conf, automount_mountpoint=None, epoch=False): return { 'map': 'map_name', 'last-modify-timestamp': 'foo', 'last-update-timestamp': 'bar' } # the master map to loop over master_map = automount.AutomountMap() master_map.Add( automount.AutomountMapEntry({ 'key': '/home', 'location': '/etc/auto.home' })) master_map.Add( automount.AutomountMapEntry({ 'key': '/auto', 'location': '/etc/auto.auto' })) # mock out a cache to return the master map cache_mock = self.mox.CreateMock(caches.Cache) cache_mock.GetMap().AndReturn(master_map) self.mox.StubOutWithMock(cache_factory, 'Create') cache_factory.Create(self.conf.options[config.MAP_AUTOMOUNT].cache, config.MAP_AUTOMOUNT, automount_mountpoint=None).AndReturn(cache_mock) self.mox.ReplayAll() c = DummyStatus() value_list = c.GetAutomountMapMetadata(self.conf) self.assertEqual(9, len(value_list))
def testVerifyMapsException(self): cache_mock = self.mox.CreateMock(caches.Cache) cache_mock.GetMap().AndRaise(error.CacheNotFound) self.mox.StubOutWithMock(cache_factory, 'Create') cache_factory.Create(self.conf.options[config.MAP_PASSWORD].cache, config.MAP_PASSWORD).AndReturn(cache_mock) self.conf.maps = [config.MAP_PASSWORD] self.mox.StubOutWithMock(nss, 'GetMap') nss.GetMap(config.MAP_PASSWORD).AndReturn(self.small_map) self.mox.ReplayAll() c = command.Verify() self.assertEquals(1, c.VerifyMaps(self.conf))
def testVerifyMapsSucceedsOnGoodMaps(self): cache_mock = self.mox.CreateMock(caches.Cache) cache_mock.GetMap().AndReturn(self.small_map) self.mox.StubOutWithMock(cache_factory, 'Create') cache_factory.Create(self.conf.options[config.MAP_PASSWORD].cache, config.MAP_PASSWORD).AndReturn(cache_mock) self.conf.maps = [config.MAP_PASSWORD] self.mox.StubOutWithMock(nss, 'GetMap') nss.GetMap(config.MAP_PASSWORD).AndReturn(self.big_map) self.mox.ReplayAll() c = command.Verify() self.assertEqual(0, c.VerifyMaps(self.conf))
def GetAutomountMapMetadata(self, conf, epoch=False): """Return status of automount master map and all listed automount maps. We retrieve the automount master map, and build a list of dicts which are used by the caller to print the status output. Args: conf: a config.Config object epoch: return times as an integer epoch (time_t) instead of a human readable name Returns: a list of dicts of metadata key/value pairs """ map_name = config.MAP_AUTOMOUNT cache_options = conf.options[map_name].cache value_list = [] # get the value_dict for the master map, note that automount_mountpoint=None # defaults to the master map! values = self.GetSingleMapMetadata(map_name, conf, automount_mountpoint=None, epoch=epoch) value_list.extend(values) # now get the contents of the master map, and get the status for each map # we find cache = cache_factory.Create(cache_options, config.MAP_AUTOMOUNT, automount_mountpoint=None) master_map = cache.GetMap() for map_entry in master_map: values = self.GetSingleMapMetadata( map_name, conf, automount_mountpoint=map_entry.key, epoch=epoch) value_list.extend(values) return value_list
def VerifyMaps(self, conf): """Compare each configured map against data retrieved from NSS. For each configured map, build a Map object from NSS and compare it against a Map object retrieved directly from the cache. We expect the cache Map to be a subset of the nss Map due to possible inclusion of other NSS map types (e.g. files, nis, ldap, etc). This could be done via series of get*nam calls, however at this time it appears to be more efficient to grab them in bulk and use the Map.__contains__() membership test. Args: conf: nss_cache.config.Config object Returns: count of failures when verifying """ retval = 0 for map_name in conf.maps: self.log.info('Verifying map: %s.', map_name) # The netgroup map does not have an enumerator, # to test this we'd have to loop over the loaded cache map # and verify each entry is retrievable via getent directly. # TODO(blaed): apply fix from comment to allow for netgroup checking if map_name == config.MAP_NETGROUP: self.log.info(('The netgroup map does not support enumeration, ' 'skipping.')) continue # Automount maps do not support getent, we'll have to come up with # a good way to verify these. if map_name == config.MAP_AUTOMOUNT: self.log.info(('The automount map does not support enumeration, ' 'skipping.')) continue try: nss_map = nss.GetMap(map_name) except error.UnsupportedMap: self.log.warning('Verification of %s map is unsupported!', map_name) continue self.log.debug('built NSS map of %d entries', len(nss_map)) cache_options = conf.options[map_name].cache cache = cache_factory.Create(cache_options, map_name) try: cache_map = cache.GetMap() except error.CacheNotFound: self.log.error('Cache missing!') retval +=1 continue self.log.debug('built cache map of %d entries', len(cache_map)) # cache_map is a subset of nss_map due to possible other maps, # e.g. files, nis, ldap, etc. missing_entries = 0 for map_entry in cache_map: if map_entry not in nss_map: self.log.info('The following entry is present in the cache ' 'but not availible via NSS! %s', map_entry.name) self.log.debug('missing entry data: %s', map_entry) missing_entries += 1 if missing_entries > 0: self.log.warning('Missing %d entries in %s map', missing_entries, map_name) retval +=1 return retval
def UpdateFromSource(self, source, incremental=False, force_write=False): """Update the automount master map, and every map it points to. We fetch a full copy of the master map everytime, and then use the FileMapUpdater to write each map the master map points to, as well as the master map itself. During this process, the master map will be modified. It starts out pointing to other maps in the source, but when written it needs to point to other maps in the cache instead. For example, using ldap we store this data in ldap: map_entry.key = /auto map_entry.location = ou=auto.auto,ou=automounts,dc=example,dc=com We need to go back to ldap get the map in ou=auto.auto, but when it comes time to write the master map to (for example) a file, we need to write out the /etc/auto.master file with: map_entry.key = /auto map_entry.location = /etc/auto.auto This is annoying :) Since the keys are fixed, namely /auto is a mountpoint that isn't going to change format, we expect each Cache implementation that supports automount maps to support a GetMapLocation() method which returns the correct cache location from the key. Args: source: An nss_cache.sources.Source object. incremental: Not used by this class force_write: A boolean flag forcing empty map updates when False, defaults to False. Returns: An int indicating success of update (0 == good, fail otherwise). """ return_val = 0 try: if not self.local_master: self.log.info('Retrieving automount master map.') master_file = source.GetAutomountMasterFile( os.path.join(self.cache_options['dir'], 'auto.master')) master_cache = cache_factory.Create(self.cache_options, self.map_name, None) master_map = master_cache.GetMap() except error.CacheNotFound: return 1 if self.local_master: self.log.info('Using local master map to determine maps to update.') # we need the local map to determine which of the other maps to update cache = cache_factory.Create(self.cache_options, self.map_name, automount_mountpoint=None) try: local_master = cache.GetMap() except error.CacheNotFound: self.log.warning('Local master map specified but no map found! ' 'No maps will update.') return return_val + 1 # update specific maps, e.g. auto.home and auto.auto for map_entry in master_map: source_location = os.path.basename(map_entry.location) mountpoint = map_entry.key # e.g. /auto mountpoint self.log.debug('Looking at mountpoint %s', mountpoint) # create the cache to update cache = cache_factory.Create(self.cache_options, self.map_name, automount_mountpoint=mountpoint) # update the master map with the location of the map in the cache # e.g. /etc/auto.auto replaces ou=auto.auto map_entry.location = cache.GetMapLocation() self.log.debug('Map location: %s', map_entry.location) # if configured to use the local master map, skip any not defined there if self.local_master: if map_entry not in local_master: self.log.info('Skipping entry %s, not in map %s', map_entry, local_master) continue self.log.info('Updating mountpoint %s', map_entry.key) # update this map (e.g. /etc/auto.auto) update_obj = FileMapUpdater(self.map_name, self.timestamp_dir, self.cache_options, automount_mountpoint=mountpoint) return_val += update_obj.UpdateCacheFromSource(cache, source, False, force_write, source_location) # with sub-maps updated, write modified master map to disk if configured to if not self.local_master: # automount_mountpoint=None defaults to master cache = cache_factory.Create(self.cache_options, self.map_name, automount_mountpoint=None) update_obj = FileMapUpdater(self.map_name, self.timestamp_dir, self.cache_options) return_val += update_obj.FullUpdateFromMap(cache, master_file) return return_val