github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/cmd/subtool/pushImage.go (about)

     1  package main
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"time"
     8  
     9  	"github.com/Cloud-Foundations/Dominator/dom/lib"
    10  	imgclient "github.com/Cloud-Foundations/Dominator/imageserver/client"
    11  	"github.com/Cloud-Foundations/Dominator/lib/filesystem"
    12  	"github.com/Cloud-Foundations/Dominator/lib/filesystem/scanner"
    13  	"github.com/Cloud-Foundations/Dominator/lib/filter"
    14  	"github.com/Cloud-Foundations/Dominator/lib/format"
    15  	"github.com/Cloud-Foundations/Dominator/lib/hash"
    16  	"github.com/Cloud-Foundations/Dominator/lib/image"
    17  	"github.com/Cloud-Foundations/Dominator/lib/log"
    18  	"github.com/Cloud-Foundations/Dominator/lib/objectcache"
    19  	"github.com/Cloud-Foundations/Dominator/lib/srpc"
    20  	"github.com/Cloud-Foundations/Dominator/lib/triggers"
    21  	"github.com/Cloud-Foundations/Dominator/proto/sub"
    22  	"github.com/Cloud-Foundations/Dominator/sub/client"
    23  )
    24  
    25  type nullObjectGetterType struct{}
    26  
    27  type timedImageFetch struct {
    28  	image    *image.Image
    29  	duration time.Duration
    30  }
    31  
    32  func (getter nullObjectGetterType) GetObject(hashVal hash.Hash) (
    33  	uint64, io.ReadCloser, error) {
    34  	return 0, nil, errors.New("no computed files")
    35  }
    36  
    37  func pushImageSubcommand(args []string, logger log.DebugLogger) error {
    38  	startTime := showStart("getSubClient()")
    39  	srpcClient := getSubClientRetry(logger)
    40  	defer srpcClient.Close()
    41  	showTimeTaken(startTime)
    42  	if err := pushImage(srpcClient, args[0]); err != nil {
    43  		return fmt.Errorf("Error pushing image: %s: %s", args[0], err)
    44  	}
    45  	return nil
    46  }
    47  
    48  func pushImage(srpcClient *srpc.Client, imageName string) error {
    49  	computedInodes := make(map[string]*filesystem.RegularInode)
    50  	// Start querying the imageserver for the image.
    51  	imageServerAddress := fmt.Sprintf("%s:%d",
    52  		*imageServerHostname, *imageServerPortNum)
    53  	imgChannel := getImageChannel(imageServerAddress, imageName, timeoutTime)
    54  	subObj := lib.Sub{
    55  		Hostname:       *subHostname,
    56  		Client:         srpcClient,
    57  		ComputedInodes: computedInodes}
    58  	deleteMissingComputedFiles := true
    59  	ignoreMissingComputedFiles := false
    60  	if *computedFilesRoot == "" {
    61  		subObj.ObjectGetter = nullObjectGetterType{}
    62  		deleteMissingComputedFiles = false
    63  		ignoreMissingComputedFiles = true
    64  	} else {
    65  		fs, err := scanner.ScanFileSystem(*computedFilesRoot, nil, nil, nil,
    66  			nil, nil)
    67  		if err != nil {
    68  			return err
    69  		}
    70  		subObj.ObjectGetter = fs
    71  		for filename, inum := range fs.FilenameToInodeTable() {
    72  			if inode, ok := fs.InodeTable[inum].(*filesystem.RegularInode); ok {
    73  				computedInodes[filename] = inode
    74  			}
    75  		}
    76  	}
    77  	startTime := showStart("<-imgChannel")
    78  	imageResult := <-imgChannel
    79  	showTimeTaken(startTime)
    80  	logger.Printf("Background image fetch took %s\n",
    81  		format.Duration(imageResult.duration))
    82  	img := imageResult.image
    83  	var err error
    84  	if *filterFile != "" {
    85  		img.Filter, err = filter.Load(*filterFile)
    86  		if err != nil {
    87  			return err
    88  		}
    89  	}
    90  	if *triggersFile != "" {
    91  		img.Triggers, err = triggers.Load(*triggersFile)
    92  		if err != nil {
    93  			return err
    94  		}
    95  	} else if *triggersString != "" {
    96  		img.Triggers, err = triggers.Decode([]byte(*triggersString))
    97  		if err != nil {
    98  			return err
    99  		}
   100  	}
   101  	if err := pollFetchAndPush(&subObj, img, imageServerAddress, timeoutTime,
   102  		logger); err != nil {
   103  		return err
   104  	}
   105  	var updateRequest sub.UpdateRequest
   106  	var updateReply sub.UpdateResponse
   107  	startTime = showStart("lib.BuildUpdateRequest()")
   108  	if lib.BuildUpdateRequest(subObj, img, &updateRequest,
   109  		deleteMissingComputedFiles, ignoreMissingComputedFiles, logger) {
   110  		showBlankLine()
   111  		return errors.New("missing computed file(s)")
   112  	}
   113  	showTimeTaken(startTime)
   114  	updateRequest.ImageName = imageName
   115  	updateRequest.Wait = true
   116  	startTime = showStart("Subd.Update()")
   117  	err = client.CallUpdate(srpcClient, updateRequest, &updateReply)
   118  	if err != nil {
   119  		showBlankLine()
   120  		return err
   121  	}
   122  	showTimeTaken(startTime)
   123  	return nil
   124  }
   125  
   126  func getImageChannel(clientName, imageName string,
   127  	timeoutTime time.Time) <-chan timedImageFetch {
   128  	resultChannel := make(chan timedImageFetch, 1)
   129  	go func() {
   130  		startTime := time.Now()
   131  		img, err := getImageRetry(clientName, imageName, timeoutTime)
   132  		if err != nil {
   133  			logger.Fatalf("Error getting image: %s\n", err)
   134  		}
   135  		resultChannel <- timedImageFetch{img, time.Since(startTime)}
   136  	}()
   137  	return resultChannel
   138  }
   139  
   140  func getImageRetry(clientName, imageName string,
   141  	timeoutTime time.Time) (*image.Image, error) {
   142  	imageSrpcClient, err := srpc.DialHTTP("tcp", clientName, 0)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  	defer imageSrpcClient.Close()
   147  	for ; time.Now().Before(timeoutTime); time.Sleep(time.Second) {
   148  		img, err := imgclient.GetImage(imageSrpcClient, imageName)
   149  		if err != nil {
   150  			return nil, err
   151  		} else if img != nil {
   152  			if err := img.FileSystem.RebuildInodePointers(); err != nil {
   153  				return nil, err
   154  			}
   155  			img.FileSystem.InodeToFilenamesTable()
   156  			img.FileSystem.FilenameToInodeTable()
   157  			img.FileSystem.HashToInodesTable()
   158  			img.FileSystem.ComputeTotalDataBytes()
   159  			img.FileSystem.BuildEntryMap()
   160  			return img, nil
   161  		}
   162  	}
   163  	return nil, errors.New("timed out getting image")
   164  }
   165  
   166  func pollFetchAndPush(subObj *lib.Sub, img *image.Image,
   167  	imageServerAddress string, timeoutTime time.Time,
   168  	logger log.DebugLogger) error {
   169  	var generationCount uint64
   170  	deleteEarly := *deleteBeforeFetch
   171  	ignoreMissingComputedFiles := true
   172  	pushComputedFiles := true
   173  	if *computedFilesRoot == "" {
   174  		ignoreMissingComputedFiles = false
   175  		pushComputedFiles = false
   176  	}
   177  	for ; time.Now().Before(timeoutTime); time.Sleep(time.Second) {
   178  		var pollReply sub.PollResponse
   179  		if err := pollAndBuildPointers(subObj.Client, &generationCount,
   180  			&pollReply); err != nil {
   181  			return err
   182  		}
   183  		if pollReply.FileSystem == nil {
   184  			continue
   185  		}
   186  		if deleteEarly {
   187  			deleteEarly = false
   188  			if deleteUnneededFiles(subObj.Client, pollReply.FileSystem,
   189  				img.FileSystem, logger) {
   190  				continue
   191  			}
   192  		}
   193  		subObj.FileSystem = pollReply.FileSystem
   194  		subObj.ObjectCache = pollReply.ObjectCache
   195  		startTime := showStart("lib.BuildMissingLists()")
   196  		objectsToFetch, objectsToPush := lib.BuildMissingLists(*subObj, img,
   197  			pushComputedFiles, ignoreMissingComputedFiles, logger)
   198  		showTimeTaken(startTime)
   199  		if len(objectsToFetch) < 1 && len(objectsToPush) < 1 {
   200  			return nil
   201  		}
   202  		if len(objectsToFetch) > 0 {
   203  			startTime := showStart("Fetch()")
   204  			err := fetchUntil(subObj, sub.FetchRequest{
   205  				ServerAddress: imageServerAddress,
   206  				Wait:          true,
   207  				Hashes:        objectcache.ObjectMapToCache(objectsToFetch)},
   208  				timeoutTime, logger)
   209  			if err != nil {
   210  				logger.Printf("Error calling %s:Subd.Fetch(%s): %s\n",
   211  					subObj.Hostname, imageServerAddress, err)
   212  				return err
   213  			}
   214  			showTimeTaken(startTime)
   215  		}
   216  		if len(objectsToPush) > 0 {
   217  			startTime := showStart("lib.PushObjects()")
   218  			err := lib.PushObjects(*subObj, objectsToPush, logger)
   219  			if err != nil {
   220  				showBlankLine()
   221  				return err
   222  			}
   223  			showTimeTaken(startTime)
   224  		}
   225  	}
   226  	return errors.New("timed out fetching and pushing objects")
   227  }
   228  
   229  func fetchUntil(subObj *lib.Sub, request sub.FetchRequest,
   230  	timeoutTime time.Time, logger log.DebugLogger) error {
   231  	showBlank := true
   232  	for ; time.Now().Before(timeoutTime); time.Sleep(time.Second) {
   233  		err := subObj.Client.RequestReply("Subd.Fetch", request,
   234  			&sub.FetchResponse{})
   235  		if err == nil {
   236  			return nil
   237  		}
   238  		if showBlank {
   239  			showBlankLine()
   240  			showBlank = false
   241  		}
   242  		logger.Printf("Error calling %s:Subd.Fetch(): %s\n",
   243  			subObj.Hostname, err)
   244  	}
   245  	if showBlank {
   246  		showBlankLine()
   247  	}
   248  	return errors.New("timed out fetching objects")
   249  }
   250  
   251  func pollAndBuildPointers(srpcClient *srpc.Client, generationCount *uint64,
   252  	pollReply *sub.PollResponse) error {
   253  	pollRequest := sub.PollRequest{HaveGeneration: *generationCount}
   254  	startTime := showStart("Poll()")
   255  	err := client.CallPoll(srpcClient, pollRequest, pollReply)
   256  	if err != nil {
   257  		showBlankLine()
   258  		return err
   259  	}
   260  	showTimeTaken(startTime)
   261  	*generationCount = pollReply.GenerationCount
   262  	fs := pollReply.FileSystem
   263  	if fs == nil {
   264  		return nil
   265  	}
   266  	startTime = showStart("FileSystem.RebuildInodePointers()")
   267  	if err := fs.RebuildInodePointers(); err != nil {
   268  		showBlankLine()
   269  		return err
   270  	}
   271  	showTimeTaken(startTime)
   272  	fs.BuildEntryMap()
   273  	return nil
   274  }
   275  
   276  func showStart(operation string) time.Time {
   277  	if *showTimes {
   278  		logger.Print(operation, " ")
   279  	}
   280  	return time.Now()
   281  }
   282  
   283  func showTimeTaken(startTime time.Time) {
   284  	if *showTimes {
   285  		stopTime := time.Now()
   286  		logger.Printf("took %s\n", format.Duration(stopTime.Sub(startTime)))
   287  	}
   288  }
   289  
   290  func showBlankLine() {
   291  	if *showTimes {
   292  		logger.Println()
   293  	}
   294  }
   295  
   296  func deleteUnneededFiles(srpcClient *srpc.Client, subFS *filesystem.FileSystem,
   297  	imgFS *filesystem.FileSystem, logger log.DebugLogger) bool {
   298  	startTime := showStart("compute early files to delete")
   299  	pathsToDelete := make([]string, 0)
   300  	imgHashToInodesTable := imgFS.HashToInodesTable()
   301  	imgFilenameToInodeTable := imgFS.FilenameToInodeTable()
   302  	for pathname, inum := range subFS.FilenameToInodeTable() {
   303  		if inode, ok := subFS.InodeTable[inum].(*filesystem.RegularInode); ok {
   304  			if inode.Size > 0 {
   305  				if _, ok := imgHashToInodesTable[inode.Hash]; !ok {
   306  					pathsToDelete = append(pathsToDelete, pathname)
   307  				}
   308  			} else {
   309  				if _, ok := imgFilenameToInodeTable[pathname]; !ok {
   310  					pathsToDelete = append(pathsToDelete, pathname)
   311  				}
   312  			}
   313  		}
   314  	}
   315  	showTimeTaken(startTime)
   316  	if len(pathsToDelete) < 1 {
   317  		return false
   318  	}
   319  	updateRequest := sub.UpdateRequest{
   320  		Wait:          true,
   321  		PathsToDelete: pathsToDelete}
   322  	var updateReply sub.UpdateResponse
   323  	startTime = showStart("Subd.Update() for early files to delete")
   324  	err := client.CallUpdate(srpcClient, updateRequest, &updateReply)
   325  	showTimeTaken(startTime)
   326  	if err != nil {
   327  		logger.Println(err)
   328  	}
   329  	return true
   330  }