github.com/Cloud-Foundations/Dominator@v0.3.4/cmd/subtool/pushImage.go (about)

     1  package main
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"time"
    10  
    11  	"github.com/Cloud-Foundations/Dominator/dom/lib"
    12  	imgclient "github.com/Cloud-Foundations/Dominator/imageserver/client"
    13  	"github.com/Cloud-Foundations/Dominator/lib/filesystem"
    14  	"github.com/Cloud-Foundations/Dominator/lib/filesystem/scanner"
    15  	"github.com/Cloud-Foundations/Dominator/lib/filter"
    16  	"github.com/Cloud-Foundations/Dominator/lib/format"
    17  	"github.com/Cloud-Foundations/Dominator/lib/hash"
    18  	"github.com/Cloud-Foundations/Dominator/lib/image"
    19  	"github.com/Cloud-Foundations/Dominator/lib/log"
    20  	"github.com/Cloud-Foundations/Dominator/lib/objectcache"
    21  	"github.com/Cloud-Foundations/Dominator/lib/srpc"
    22  	"github.com/Cloud-Foundations/Dominator/lib/triggers"
    23  	"github.com/Cloud-Foundations/Dominator/proto/sub"
    24  	"github.com/Cloud-Foundations/Dominator/sub/client"
    25  )
    26  
    27  type nullObjectGetterType struct{}
    28  
    29  type timedImageFetch struct {
    30  	image    *image.Image
    31  	duration time.Duration
    32  }
    33  
    34  func (getter nullObjectGetterType) GetObject(hashVal hash.Hash) (
    35  	uint64, io.ReadCloser, error) {
    36  	return 0, nil, errors.New("no computed files")
    37  }
    38  
    39  func pushImageSubcommand(args []string, logger log.DebugLogger) error {
    40  	startTime := showStart("getSubClient()")
    41  	srpcClient := getSubClientRetry(logger)
    42  	defer srpcClient.Close()
    43  	showTimeTaken(startTime)
    44  	if err := pushImage(srpcClient, args[0]); err != nil {
    45  		return fmt.Errorf("error pushing image: %s: %s", args[0], err)
    46  	}
    47  	return nil
    48  }
    49  
    50  func pushImage(srpcClient *srpc.Client, imageName string) error {
    51  	computedInodes := make(map[string]*filesystem.RegularInode)
    52  	// Start querying the imageserver for the image.
    53  	imageServerAddress := fmt.Sprintf("%s:%d",
    54  		*imageServerHostname, *imageServerPortNum)
    55  	imgChannel := getImageChannel(imageServerAddress, imageName, timeoutTime)
    56  	if !*forceImageChange {
    57  		subImageName, err := getSubImage(srpcClient)
    58  		if err != nil {
    59  			return err
    60  		}
    61  		if subImageName != "" {
    62  			imageStream := filepath.Dir(imageName)
    63  			subImageStream := filepath.Dir(subImageName)
    64  			if imageStream != subImageStream {
    65  				return fmt.Errorf("changing image from %s to %s is unsafe",
    66  					subImageStream, imageStream)
    67  			}
    68  		}
    69  	}
    70  	subObj := lib.Sub{
    71  		Hostname:       *subHostname,
    72  		Client:         srpcClient,
    73  		ComputedInodes: computedInodes}
    74  	deleteMissingComputedFiles := true
    75  	ignoreMissingComputedFiles := false
    76  	if *computedFilesRoot == "" {
    77  		subObj.ObjectGetter = nullObjectGetterType{}
    78  		deleteMissingComputedFiles = false
    79  		ignoreMissingComputedFiles = true
    80  	} else {
    81  		fs, err := scanner.ScanFileSystem(*computedFilesRoot, nil, nil, nil,
    82  			nil, nil)
    83  		if err != nil {
    84  			return err
    85  		}
    86  		subObj.ObjectGetter = fs
    87  		for filename, inum := range fs.FilenameToInodeTable() {
    88  			if inode, ok := fs.InodeTable[inum].(*filesystem.RegularInode); ok {
    89  				computedInodes[filename] = inode
    90  			}
    91  		}
    92  	}
    93  	startTime := showStart("<-imgChannel")
    94  	imageResult := <-imgChannel
    95  	showTimeTaken(startTime)
    96  	logger.Printf("Background image fetch took %s\n",
    97  		format.Duration(imageResult.duration))
    98  	img := imageResult.image
    99  	var err error
   100  	if *filterFile != "" {
   101  		img.Filter, err = filter.Load(*filterFile)
   102  		if err != nil {
   103  			return err
   104  		}
   105  	}
   106  	if *triggersFile != "" {
   107  		img.Triggers, err = triggers.Load(*triggersFile)
   108  		if err != nil {
   109  			return err
   110  		}
   111  	} else if *triggersString != "" {
   112  		img.Triggers, err = triggers.Decode([]byte(*triggersString))
   113  		if err != nil {
   114  			return err
   115  		}
   116  	}
   117  	err = pollFetchAndPush(&subObj, img, imageServerAddress, timeoutTime, false,
   118  		logger)
   119  	if err != nil {
   120  		return err
   121  	}
   122  	if err := srpcClient.SetKeepAlivePeriod(time.Second); err != nil {
   123  		return fmt.Errorf("error setting keep-alive period: %s", err)
   124  	}
   125  	updateRequest := sub.UpdateRequest{
   126  		ForceDisruption: *forceDisruption,
   127  	}
   128  	var updateReply sub.UpdateResponse
   129  	startTime = showStart("lib.BuildUpdateRequest()")
   130  	if lib.BuildUpdateRequest(subObj, img, &updateRequest,
   131  		deleteMissingComputedFiles, ignoreMissingComputedFiles, logger) {
   132  		showBlankLine()
   133  		return errors.New("missing computed file(s)")
   134  	}
   135  	showTimeTaken(startTime)
   136  	updateRequest.ImageName = imageName
   137  	updateRequest.Wait = true
   138  	stopTicker := make(chan struct{}, 1)
   139  	if !*showTimes {
   140  		logger.Println("Starting Subd.Update()")
   141  		go tickerLoop(stopTicker)
   142  	}
   143  	startTime = showStart("Subd.Update()")
   144  	err = client.CallUpdate(srpcClient, updateRequest, &updateReply)
   145  	stopTicker <- struct{}{}
   146  	if err != nil {
   147  		showBlankLine()
   148  		return err
   149  	}
   150  	if !*showTimes {
   151  		logger.Println("Subd.Update() complete")
   152  	}
   153  	showTimeTaken(startTime)
   154  	pollRequest := sub.PollRequest{ShortPollOnly: true}
   155  	var pollReply sub.PollResponse
   156  	if err := client.CallPoll(srpcClient, pollRequest, &pollReply); err != nil {
   157  		return err
   158  	}
   159  	return cleanup(srpcClient, pollReply.GenerationCount, true)
   160  }
   161  
   162  func getImageChannel(clientName, imageName string,
   163  	timeoutTime time.Time) <-chan timedImageFetch {
   164  	resultChannel := make(chan timedImageFetch, 1)
   165  	go func() {
   166  		startTime := time.Now()
   167  		img, err := getImageRetry(clientName, imageName, timeoutTime)
   168  		if err != nil {
   169  			logger.Fatalf("Error getting image: %s\n", err)
   170  		}
   171  		resultChannel <- timedImageFetch{img, time.Since(startTime)}
   172  	}()
   173  	return resultChannel
   174  }
   175  
   176  func getImageRetry(clientName, imageName string,
   177  	timeoutTime time.Time) (*image.Image, error) {
   178  	imageSrpcClient, err := srpc.DialHTTP("tcp", clientName, 0)
   179  	if err != nil {
   180  		return nil, err
   181  	}
   182  	defer imageSrpcClient.Close()
   183  	firstTime := true
   184  	for ; time.Now().Before(timeoutTime); time.Sleep(time.Second) {
   185  		img, err := imgclient.GetImage(imageSrpcClient, imageName)
   186  		if err != nil {
   187  			return nil, err
   188  		} else if img != nil {
   189  			if err := img.FileSystem.RebuildInodePointers(); err != nil {
   190  				return nil, err
   191  			}
   192  			img.FileSystem.InodeToFilenamesTable()
   193  			img.FileSystem.FilenameToInodeTable()
   194  			img.FileSystem.HashToInodesTable()
   195  			img.FileSystem.ComputeTotalDataBytes()
   196  			img.FileSystem.BuildEntryMap()
   197  			return img, nil
   198  		} else if firstTime {
   199  			logger.Printf("Image: %s not found, will retry\n", imageName)
   200  			firstTime = false
   201  		}
   202  	}
   203  	return nil, errors.New("timed out getting image")
   204  }
   205  
   206  func pollFetchAndPush(subObj *lib.Sub, img *image.Image,
   207  	imageServerAddress string, timeoutTime time.Time, singleFetch bool,
   208  	logger log.DebugLogger) error {
   209  	var generationCount, lastGenerationCount, lastScanCount uint64
   210  	deleteEarly := *deleteBeforeFetch
   211  	ignoreMissingComputedFiles := true
   212  	pushComputedFiles := true
   213  	if *computedFilesRoot == "" {
   214  		ignoreMissingComputedFiles = false
   215  		pushComputedFiles = false
   216  	}
   217  	logger.Println("Starting polling loop, waiting for completed scan")
   218  	interval := time.Second
   219  	newlineNeeded := false
   220  	objectsNeeded := false
   221  	for ; time.Now().Before(timeoutTime); time.Sleep(interval) {
   222  		var pollReply sub.PollResponse
   223  		if err := client.BoostCpuLimit(subObj.Client); err != nil {
   224  			return err
   225  		}
   226  		if err := pollAndBuildPointers(subObj.Client, &generationCount,
   227  			&pollReply); err != nil {
   228  			return err
   229  		}
   230  		if pollReply.FileSystem == nil {
   231  			if interval < 5*time.Second {
   232  				interval += 200 * time.Millisecond
   233  			}
   234  		} else {
   235  			interval = time.Second
   236  		}
   237  		if !*showTimes {
   238  			if pollReply.FileSystem == nil {
   239  				fmt.Fprintf(os.Stderr, ".")
   240  				newlineNeeded = true
   241  			} else if newlineNeeded {
   242  				fmt.Fprintln(os.Stderr)
   243  				newlineNeeded = false
   244  			}
   245  		}
   246  		if pollReply.GenerationCount != lastGenerationCount ||
   247  			pollReply.ScanCount != lastScanCount {
   248  			if pollReply.FileSystem == nil {
   249  				logger.Debugf(0,
   250  					"Poll Scan: %d, Generation: %d, cached objects: %d\n",
   251  					pollReply.ScanCount, pollReply.GenerationCount,
   252  					len(pollReply.ObjectCache))
   253  			} else {
   254  				logger.Debugf(0,
   255  					"Poll Scan: %d, Generation: %d, FS objects: %d, cached objects: %d\n",
   256  					pollReply.ScanCount, pollReply.GenerationCount,
   257  					len(pollReply.FileSystem.InodeTable),
   258  					len(pollReply.ObjectCache))
   259  			}
   260  		}
   261  		lastGenerationCount = pollReply.GenerationCount
   262  		lastScanCount = pollReply.ScanCount
   263  		if pollReply.FileSystem == nil {
   264  			continue
   265  		}
   266  		if deleteEarly {
   267  			deleteEarly = false
   268  			if deleteUnneededFiles(subObj.Client, pollReply.FileSystem,
   269  				img.FileSystem, logger) {
   270  				continue
   271  			}
   272  		}
   273  		subObj.FileSystem = pollReply.FileSystem
   274  		subObj.ObjectCache = pollReply.ObjectCache
   275  		startTime := showStart("lib.BuildMissingLists()")
   276  		objectsToFetch, objectsToPush := lib.BuildMissingLists(*subObj, img,
   277  			pushComputedFiles, ignoreMissingComputedFiles, logger)
   278  		showTimeTaken(startTime)
   279  		if len(objectsToFetch) < 1 && len(objectsToPush) < 1 {
   280  			if !objectsNeeded {
   281  				logger.Println("No objects need to be fetched or pushed")
   282  			}
   283  			return nil
   284  		}
   285  		objectsNeeded = true
   286  		if len(objectsToFetch) > 0 {
   287  			logger.Debugf(0, "Fetch(%d)\n", len(objectsToFetch))
   288  			startTime := showStart("Fetch()")
   289  			err := fetchUntil(subObj, sub.FetchRequest{
   290  				LockFor:       *lockDuration,
   291  				ServerAddress: imageServerAddress,
   292  				Wait:          true,
   293  				Hashes:        objectcache.ObjectMapToCache(objectsToFetch)},
   294  				timeoutTime, logger)
   295  			if err != nil {
   296  				logger.Printf("Error calling %s:Subd.Fetch(%s): %s\n",
   297  					subObj.Hostname, imageServerAddress, err)
   298  				return err
   299  			}
   300  			showTimeTaken(startTime)
   301  			if singleFetch {
   302  				return nil
   303  			}
   304  		}
   305  		if len(objectsToPush) > 0 {
   306  			logger.Debugf(0, "PushObjects(%d)\n", len(objectsToPush))
   307  			startTime := showStart("lib.PushObjects()")
   308  			err := lib.PushObjects(*subObj, objectsToPush, logger)
   309  			if err != nil {
   310  				showBlankLine()
   311  				return err
   312  			}
   313  			showTimeTaken(startTime)
   314  		}
   315  	}
   316  	return errors.New("timed out fetching and pushing objects")
   317  }
   318  
   319  func fetchUntil(subObj *lib.Sub, request sub.FetchRequest,
   320  	timeoutTime time.Time, logger log.DebugLogger) error {
   321  	for ; time.Now().Before(timeoutTime); time.Sleep(time.Second) {
   322  		stopTicker := make(chan struct{}, 1)
   323  		if !*showTimes {
   324  			logger.Println("Starting Subd.Fetch()")
   325  			go tickerLoop(stopTicker)
   326  		}
   327  		err := client.CallFetch(subObj.Client, request, &sub.FetchResponse{})
   328  		stopTicker <- struct{}{}
   329  		if err == nil {
   330  			return nil
   331  		}
   332  		logger.Printf("Error calling %s:Subd.Fetch(): %s\n",
   333  			subObj.Hostname, err)
   334  	}
   335  	return errors.New("timed out fetching objects")
   336  }
   337  
   338  func pollAndBuildPointers(srpcClient *srpc.Client, generationCount *uint64,
   339  	pollReply *sub.PollResponse) error {
   340  	pollRequest := sub.PollRequest{
   341  		HaveGeneration: *generationCount,
   342  		LockFor:        *lockDuration,
   343  	}
   344  	startTime := showStart("Poll()")
   345  	err := client.CallPoll(srpcClient, pollRequest, pollReply)
   346  	if err != nil {
   347  		showBlankLine()
   348  		return err
   349  	}
   350  	showTimeTaken(startTime)
   351  	*generationCount = pollReply.GenerationCount
   352  	fs := pollReply.FileSystem
   353  	if fs == nil {
   354  		return nil
   355  	}
   356  	startTime = showStart("FileSystem.RebuildInodePointers()")
   357  	if err := fs.RebuildInodePointers(); err != nil {
   358  		showBlankLine()
   359  		return err
   360  	}
   361  	showTimeTaken(startTime)
   362  	fs.BuildEntryMap()
   363  	return nil
   364  }
   365  
   366  func showStart(operation string) time.Time {
   367  	if *showTimes {
   368  		logger.Print(operation, " ")
   369  	}
   370  	return time.Now()
   371  }
   372  
   373  func showTimeTaken(startTime time.Time) {
   374  	if *showTimes {
   375  		stopTime := time.Now()
   376  		logger.Printf("took %s\n", format.Duration(stopTime.Sub(startTime)))
   377  	}
   378  }
   379  
   380  func showBlankLine() {
   381  	if *showTimes {
   382  		logger.Println()
   383  	}
   384  }
   385  
   386  func tickerLoop(stopTicker <-chan struct{}) {
   387  	for {
   388  		timer := time.NewTimer(time.Second)
   389  		select {
   390  		case <-timer.C:
   391  			fmt.Fprintf(os.Stderr, ".")
   392  		case <-stopTicker:
   393  			if !timer.Stop() {
   394  				<-timer.C
   395  			}
   396  			fmt.Fprintln(os.Stderr)
   397  			return
   398  		}
   399  	}
   400  }
   401  
   402  func deleteUnneededFiles(srpcClient *srpc.Client, subFS *filesystem.FileSystem,
   403  	imgFS *filesystem.FileSystem, logger log.DebugLogger) bool {
   404  	startTime := showStart("compute early files to delete")
   405  	pathsToDelete := make([]string, 0)
   406  	imgHashToInodesTable := imgFS.HashToInodesTable()
   407  	imgFilenameToInodeTable := imgFS.FilenameToInodeTable()
   408  	for pathname, inum := range subFS.FilenameToInodeTable() {
   409  		if inode, ok := subFS.InodeTable[inum].(*filesystem.RegularInode); ok {
   410  			if inode.Size > 0 {
   411  				if _, ok := imgHashToInodesTable[inode.Hash]; !ok {
   412  					pathsToDelete = append(pathsToDelete, pathname)
   413  				}
   414  			} else {
   415  				if _, ok := imgFilenameToInodeTable[pathname]; !ok {
   416  					pathsToDelete = append(pathsToDelete, pathname)
   417  				}
   418  			}
   419  		}
   420  	}
   421  	showTimeTaken(startTime)
   422  	if len(pathsToDelete) < 1 {
   423  		return false
   424  	}
   425  	updateRequest := sub.UpdateRequest{
   426  		Wait:          true,
   427  		PathsToDelete: pathsToDelete}
   428  	var updateReply sub.UpdateResponse
   429  	startTime = showStart("Subd.Update() for early files to delete")
   430  	err := client.CallUpdate(srpcClient, updateRequest, &updateReply)
   431  	showTimeTaken(startTime)
   432  	if err != nil {
   433  		logger.Println(err)
   434  	}
   435  	return true
   436  }