github.com/Cloud-Foundations/Dominator@v0.3.4/dom/lib/buildUpdateRequest.go (about)

     1  package lib
     2  
     3  import (
     4  	"path"
     5  	"sort"
     6  	"time"
     7  
     8  	"github.com/Cloud-Foundations/Dominator/lib/filesystem"
     9  	"github.com/Cloud-Foundations/Dominator/lib/hash"
    10  	"github.com/Cloud-Foundations/Dominator/lib/image"
    11  	"github.com/Cloud-Foundations/Dominator/lib/log"
    12  	"github.com/Cloud-Foundations/Dominator/lib/log/debuglogger"
    13  	"github.com/Cloud-Foundations/Dominator/lib/stringutil"
    14  	subproto "github.com/Cloud-Foundations/Dominator/proto/sub"
    15  )
    16  
    17  // Returns true if there is a failure due to missing computed files.
    18  func (sub *Sub) buildUpdateRequest(img *image.Image,
    19  	request *subproto.UpdateRequest, deleteMissingComputedFiles bool,
    20  	ignoreMissingComputedFiles bool, slogger log.Logger) bool {
    21  	logger := debuglogger.Upgrade(slogger)
    22  	sub.requiredFS = img.FileSystem
    23  	sub.filter = img.Filter
    24  	request.Triggers = img.Triggers
    25  	sub.requiredInodeToSubInode = make(map[uint64]uint64)
    26  	sub.inodesMapped = make(map[uint64]struct{})
    27  	sub.inodesChanged = make(map[uint64]struct{})
    28  	sub.inodesCreated = make(map[uint64]string)
    29  	sub.subObjectCacheUsage = make(map[hash.Hash]uint64, len(sub.ObjectCache))
    30  	// Populate subObjectCacheUsage.
    31  	for _, hash := range sub.ObjectCache {
    32  		sub.subObjectCacheUsage[hash] = 0
    33  	}
    34  	if !filesystem.CompareDirectoriesMetadata(&sub.FileSystem.DirectoryInode,
    35  		&sub.requiredFS.DirectoryInode, nil) {
    36  		makeDirectory(request, &sub.requiredFS.DirectoryInode, "/", false)
    37  	}
    38  	if sub.compareDirectories(request,
    39  		&sub.FileSystem.DirectoryInode, &sub.requiredFS.DirectoryInode,
    40  		"/", deleteMissingComputedFiles, ignoreMissingComputedFiles, logger) {
    41  		return true
    42  	}
    43  	// Look for multiply used objects and tell the sub.
    44  	for obj, useCount := range sub.subObjectCacheUsage {
    45  		if useCount > 1 {
    46  			if request.MultiplyUsedObjects == nil {
    47  				request.MultiplyUsedObjects = make(map[hash.Hash]uint64)
    48  			}
    49  			request.MultiplyUsedObjects[obj] = useCount
    50  		}
    51  	}
    52  	return false
    53  }
    54  
    55  // Returns true if there is a failure due to missing computed files.
    56  func (sub *Sub) compareDirectories(request *subproto.UpdateRequest,
    57  	subDirectory, requiredDirectory *filesystem.DirectoryInode,
    58  	myPathName string, deleteMissingComputedFiles bool,
    59  	ignoreMissingComputedFiles bool, logger log.DebugLogger) bool {
    60  	// First look for entries that should be deleted.
    61  	if sub.filter != nil && subDirectory != nil {
    62  		for name := range subDirectory.EntriesByName {
    63  			pathname := path.Join(myPathName, name)
    64  			if sub.filter.Match(pathname) {
    65  				continue
    66  			}
    67  			if _, ok := requiredDirectory.EntriesByName[name]; !ok {
    68  				request.PathsToDelete = append(request.PathsToDelete, pathname)
    69  			}
    70  		}
    71  	}
    72  	// For the love of repeatable unit tests, sort before looping.
    73  	names := make([]string, 0, len(requiredDirectory.EntriesByName))
    74  	for name := range requiredDirectory.EntriesByName {
    75  		names = append(names, name)
    76  	}
    77  	sort.Strings(names)
    78  	for _, name := range names {
    79  		requiredEntry := requiredDirectory.EntriesByName[name]
    80  		pathname := path.Join(myPathName, name)
    81  		if sub.filter != nil && sub.filter.Match(pathname) {
    82  			continue
    83  		}
    84  		var subEntry *filesystem.DirectoryEntry
    85  		if subDirectory != nil {
    86  			if se, ok := subDirectory.EntriesByName[name]; ok {
    87  				subEntry = se
    88  			}
    89  		}
    90  		requiredInode := requiredEntry.Inode()
    91  		if _, ok := requiredInode.(*filesystem.ComputedRegularInode); ok {
    92  			// Replace with computed file.
    93  			inode, ok := sub.ComputedInodes[pathname]
    94  			if !ok {
    95  				if deleteMissingComputedFiles {
    96  					if subEntry != nil {
    97  						request.PathsToDelete = append(request.PathsToDelete,
    98  							pathname)
    99  					}
   100  					continue
   101  				}
   102  				if ignoreMissingComputedFiles {
   103  					continue
   104  				}
   105  				logger.Printf(
   106  					"compareDirectories(%s): missing computed file: %s\n",
   107  					sub, pathname)
   108  				return true
   109  			}
   110  			setComputedFileMtime(inode, subEntry)
   111  			newEntry := new(filesystem.DirectoryEntry)
   112  			newEntry.Name = name
   113  			newEntry.InodeNumber = requiredEntry.InodeNumber
   114  			newEntry.SetInode(inode)
   115  			requiredEntry = newEntry
   116  		}
   117  		if subEntry == nil {
   118  			sub.addEntry(request, requiredEntry, pathname, logger)
   119  		} else {
   120  			sub.compareEntries(request, subEntry, requiredEntry, pathname,
   121  				logger)
   122  		}
   123  		// If a directory: descend (possibly with the directory for the sub).
   124  		if requiredInode, ok := requiredInode.(*filesystem.DirectoryInode); ok {
   125  			var subInode *filesystem.DirectoryInode
   126  			if subEntry != nil {
   127  				if si, ok := subEntry.Inode().(*filesystem.DirectoryInode); ok {
   128  					subInode = si
   129  				}
   130  			}
   131  			sub.compareDirectories(request, subInode, requiredInode, pathname,
   132  				deleteMissingComputedFiles, ignoreMissingComputedFiles, logger)
   133  		}
   134  	}
   135  	return false
   136  }
   137  
   138  func setComputedFileMtime(requiredInode *filesystem.RegularInode,
   139  	subEntry *filesystem.DirectoryEntry) {
   140  	if requiredInode.MtimeSeconds >= 0 {
   141  		return
   142  	}
   143  	if subEntry != nil {
   144  		subInode := subEntry.Inode()
   145  		if subInode, ok := subInode.(*filesystem.RegularInode); ok {
   146  			if requiredInode.Hash == subInode.Hash {
   147  				requiredInode.MtimeNanoSeconds = subInode.MtimeNanoSeconds
   148  				requiredInode.MtimeSeconds = subInode.MtimeSeconds
   149  				return
   150  			}
   151  		}
   152  	}
   153  	requiredInode.MtimeSeconds = time.Now().Unix()
   154  }
   155  
   156  func (sub *Sub) addEntry(request *subproto.UpdateRequest,
   157  	requiredEntry *filesystem.DirectoryEntry, myPathName string,
   158  	logger log.DebugLogger) {
   159  	requiredInode := requiredEntry.Inode()
   160  	if requiredInode, ok := requiredInode.(*filesystem.DirectoryInode); ok {
   161  		makeDirectory(request, requiredInode, myPathName, true)
   162  	} else {
   163  		sub.addInode(request, requiredEntry, myPathName, logger)
   164  	}
   165  }
   166  
   167  func (sub *Sub) compareEntries(request *subproto.UpdateRequest,
   168  	subEntry, requiredEntry *filesystem.DirectoryEntry, myPathName string,
   169  	logger log.DebugLogger) {
   170  	subInode := subEntry.Inode()
   171  	requiredInode := requiredEntry.Inode()
   172  	sameType, sameMetadata, sameData := filesystem.CompareInodes(
   173  		subInode, requiredInode, nil)
   174  	if requiredInode, ok := requiredInode.(*filesystem.DirectoryInode); ok {
   175  		if sameMetadata {
   176  			return
   177  		}
   178  		if sameType {
   179  			makeDirectory(request, requiredInode, myPathName, false)
   180  		} else {
   181  			makeDirectory(request, requiredInode, myPathName, true)
   182  		}
   183  		return
   184  	}
   185  	if sameType && sameData && sameMetadata {
   186  		if sub.relink(request, subEntry, requiredEntry, myPathName, logger) {
   187  			logger.Debugf(0, "identical relink OK for %s\n", myPathName)
   188  			return
   189  		}
   190  	} else if sameType && sameData &&
   191  		sub.compareInodeLinks(requiredEntry.InodeNumber, subEntry.InodeNumber) {
   192  		if sub.relink(request, subEntry, requiredEntry, myPathName,
   193  			logger) {
   194  			logger.Debugf(0, "mapped and changed for %s, inum: %d\n",
   195  				myPathName, subEntry.InodeNumber)
   196  			sub.updateMetadata(request, requiredEntry, myPathName)
   197  			return
   198  		}
   199  	}
   200  	sub.addInode(request, requiredEntry, myPathName, logger)
   201  }
   202  
   203  func (sub *Sub) relink(request *subproto.UpdateRequest,
   204  	subEntry, requiredEntry *filesystem.DirectoryEntry,
   205  	myPathName string, logger log.DebugLogger) bool {
   206  	subInum, ok := sub.requiredInodeToSubInode[requiredEntry.InodeNumber]
   207  	if !ok {
   208  		if _, mapped := sub.inodesMapped[subEntry.InodeNumber]; mapped {
   209  			return false
   210  		}
   211  		sub.requiredInodeToSubInode[requiredEntry.InodeNumber] =
   212  			subEntry.InodeNumber
   213  		logger.Debugf(0, "mapping required inum: %d to sub inum: %d for: %s\n",
   214  			requiredEntry.InodeNumber, subEntry.InodeNumber, myPathName)
   215  		sub.inodesMapped[subEntry.InodeNumber] = struct{}{}
   216  		return true
   217  	}
   218  	if subInum == subEntry.InodeNumber {
   219  		return true
   220  	}
   221  	makeHardlink(request,
   222  		myPathName, sub.FileSystem.InodeToFilenamesTable()[subInum][0])
   223  	return true
   224  }
   225  
   226  func makeHardlink(request *subproto.UpdateRequest, newLink, target string) {
   227  	var hardlink subproto.Hardlink
   228  	hardlink.NewLink = newLink
   229  	hardlink.Target = target
   230  	request.HardlinksToMake = append(request.HardlinksToMake, hardlink)
   231  }
   232  
   233  func (sub *Sub) compareInodeLinks(requiredInum, subInum uint64) bool {
   234  	requiredNames := sub.requiredFS.InodeToFilenamesTable()[requiredInum]
   235  	subNames := sub.FileSystem.InodeToFilenamesTable()[subInum]
   236  	if len(subNames) > len(requiredNames) {
   237  		return false
   238  	}
   239  	requiredLinks := stringutil.ConvertListToMap(requiredNames, false)
   240  	for _, name := range subNames {
   241  		if _, ok := requiredLinks[name]; !ok {
   242  			return false
   243  		}
   244  	}
   245  	return true
   246  }
   247  
   248  func (sub *Sub) updateMetadata(request *subproto.UpdateRequest,
   249  	requiredEntry *filesystem.DirectoryEntry, myPathName string) {
   250  	if _, ok := sub.inodesChanged[requiredEntry.InodeNumber]; ok {
   251  		return
   252  	}
   253  	var inode subproto.Inode
   254  	inode.Name = myPathName
   255  	inode.GenericInode = requiredEntry.Inode()
   256  	request.InodesToChange = append(request.InodesToChange, inode)
   257  	sub.inodesChanged[requiredEntry.InodeNumber] = struct{}{}
   258  }
   259  
   260  func makeDirectory(request *subproto.UpdateRequest,
   261  	requiredInode *filesystem.DirectoryInode, pathName string, create bool) {
   262  	var newInode subproto.Inode
   263  	newInode.Name = pathName
   264  	var newDirectoryInode filesystem.DirectoryInode
   265  	newDirectoryInode.Mode = requiredInode.Mode
   266  	newDirectoryInode.Uid = requiredInode.Uid
   267  	newDirectoryInode.Gid = requiredInode.Gid
   268  	newInode.GenericInode = &newDirectoryInode
   269  	if create {
   270  		request.DirectoriesToMake = append(request.DirectoriesToMake, newInode)
   271  	} else {
   272  		request.InodesToChange = append(request.InodesToChange, newInode)
   273  	}
   274  }
   275  
   276  func (sub *Sub) addInode(request *subproto.UpdateRequest,
   277  	requiredEntry *filesystem.DirectoryEntry, myPathName string,
   278  	logger log.DebugLogger) {
   279  	requiredInode := requiredEntry.Inode()
   280  	logger.Debugf(0, "addInode(%s, %d) Uid=%d\n",
   281  		myPathName, requiredEntry.InodeNumber, requiredInode.GetUid())
   282  	if name, ok := sub.inodesCreated[requiredEntry.InodeNumber]; ok {
   283  		logger.Debugf(0, "make link: %s to %s\n", myPathName, name)
   284  		makeHardlink(request, myPathName, name)
   285  		return
   286  	}
   287  	// Try to find a sibling inode.
   288  	names := sub.requiredFS.InodeToFilenamesTable()[requiredEntry.InodeNumber]
   289  	subFS := sub.FileSystem
   290  	if len(names) > 1 {
   291  		for _, name := range names {
   292  			if name == myPathName {
   293  				logger.Debugf(0, "skipping self comparison: %s\n", name)
   294  				continue
   295  			}
   296  			if inum, found := subFS.FilenameToInodeTable()[name]; found {
   297  				subInode := sub.FileSystem.InodeTable[inum]
   298  				_, sameMetadata, sameData := filesystem.CompareInodes(
   299  					subInode, requiredInode, nil)
   300  				if sameMetadata && sameData {
   301  					logger.Debugf(0, "make sibling link: %s to %s (uid=%d)\n",
   302  						myPathName, name, subInode.GetUid())
   303  					makeHardlink(request, myPathName, name)
   304  					return
   305  				}
   306  			}
   307  		}
   308  	}
   309  	if inode, ok := requiredEntry.Inode().(*filesystem.RegularInode); ok {
   310  		if inode.Size > 0 {
   311  			if old, ok := sub.subObjectCacheUsage[inode.Hash]; ok {
   312  				sub.subObjectCacheUsage[inode.Hash]++
   313  				if old > 0 {
   314  					logger.Debugf(0, "duplicate in cache for: %s\n", myPathName)
   315  				}
   316  			} else {
   317  				// Not in object cache: grab it from file-system.
   318  				logger.Debugf(0, "copy to cache for: %s\n", myPathName)
   319  				request.FilesToCopyToCache = append(
   320  					request.FilesToCopyToCache,
   321  					sub.getFileToCopy(myPathName, inode.Hash))
   322  				sub.subObjectCacheUsage[inode.Hash] = 1
   323  			}
   324  		}
   325  	}
   326  	var inode subproto.Inode
   327  	inode.Name = myPathName
   328  	inode.GenericInode = requiredEntry.Inode()
   329  	request.InodesToMake = append(request.InodesToMake, inode)
   330  	sub.inodesCreated[requiredEntry.InodeNumber] = myPathName
   331  }
   332  
   333  func (sub *Sub) getFileToCopy(myPathName string,
   334  	hashVal hash.Hash) subproto.FileToCopyToCache {
   335  	subFS := sub.FileSystem
   336  	requiredFS := sub.requiredFS
   337  	inos, ok := subFS.HashToInodesTable()[hashVal]
   338  	if !ok {
   339  		panic("No object in cache for: " + myPathName)
   340  	}
   341  	file := subproto.FileToCopyToCache{
   342  		Name: subFS.InodeToFilenamesTable()[inos[0]][0],
   343  		Hash: hashVal,
   344  	}
   345  	// Try to find an inode where all its links will be deleted and mark one of
   346  	// the links (filenames) to be hardlinked instead of copied into the cache.
   347  	for _, iNum := range inos {
   348  		filenames := subFS.InodeToFilenamesTable()[iNum]
   349  		for _, filename := range filenames {
   350  			if _, ok := requiredFS.FilenameToInodeTable()[filename]; ok {
   351  				filenames = nil
   352  				break
   353  			}
   354  			if sub.filter == nil || sub.filter.Match(filename) {
   355  				filenames = nil
   356  				break
   357  			}
   358  		}
   359  		if filenames != nil {
   360  			file.DoHardlink = true
   361  			break
   362  		}
   363  	}
   364  	return file
   365  }