github.com/Cloud-Foundations/Dominator@v0.3.4/imagebuilder/builder/addImage.go (about)

     1  package builder
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"path"
    10  	"path/filepath"
    11  	"syscall"
    12  	"time"
    13  
    14  	imageclient "github.com/Cloud-Foundations/Dominator/imageserver/client"
    15  	"github.com/Cloud-Foundations/Dominator/lib/filesystem"
    16  	"github.com/Cloud-Foundations/Dominator/lib/filesystem/scanner"
    17  	"github.com/Cloud-Foundations/Dominator/lib/filesystem/util"
    18  	"github.com/Cloud-Foundations/Dominator/lib/filter"
    19  	"github.com/Cloud-Foundations/Dominator/lib/format"
    20  	"github.com/Cloud-Foundations/Dominator/lib/goroutine"
    21  	"github.com/Cloud-Foundations/Dominator/lib/hash"
    22  	"github.com/Cloud-Foundations/Dominator/lib/image"
    23  	"github.com/Cloud-Foundations/Dominator/lib/image/packageutil"
    24  	objectclient "github.com/Cloud-Foundations/Dominator/lib/objectserver/client"
    25  	"github.com/Cloud-Foundations/Dominator/lib/srpc"
    26  	"github.com/Cloud-Foundations/Dominator/lib/tags"
    27  	"github.com/Cloud-Foundations/Dominator/lib/triggers"
    28  	proto "github.com/Cloud-Foundations/Dominator/proto/imaginator"
    29  )
    30  
    31  const timeFormat = "2006-01-02:15:04:05"
    32  
    33  var (
    34  	errorTestTimedOut = errors.New("test timed out")
    35  	tmpFilter         *filter.Filter
    36  )
    37  
    38  type hasher struct {
    39  	cache *treeCache
    40  	objQ  *objectclient.ObjectAdderQueue
    41  }
    42  
    43  func init() {
    44  	if filt, err := filter.New([]string{"/tmp/.*"}); err != nil {
    45  		panic(err)
    46  	} else {
    47  		tmpFilter = filt
    48  	}
    49  }
    50  
    51  func (h *hasher) Hash(reader io.Reader, length uint64) (
    52  	hash.Hash, error) {
    53  	hash, err := h.objQ.Add(reader, length)
    54  	if err != nil {
    55  		return hash, errors.New("error sending image data: " + err.Error())
    56  	}
    57  	return hash, nil
    58  }
    59  
    60  func (h *hasher) OpenAndHash(inode *filesystem.RegularInode,
    61  	pathName string) (bool, error) {
    62  	if len(h.cache.inodeTable) < 1 {
    63  		return false, nil
    64  	}
    65  	inum, ok := h.cache.pathToInode[pathName]
    66  	if !ok {
    67  		return false, nil
    68  	}
    69  	inodeData, ok := h.cache.inodeTable[inum]
    70  	if !ok {
    71  		return false, nil
    72  	}
    73  	if inode.Size != inodeData.size {
    74  		return false, nil
    75  	}
    76  	var stat syscall.Stat_t
    77  	if err := syscall.Stat(pathName, &stat); err != nil {
    78  		return false, err
    79  	}
    80  	if stat.Ino != inum {
    81  		return false, nil
    82  	}
    83  	if stat.Size != int64(inodeData.size) {
    84  		return false, nil
    85  	}
    86  	if stat.Ctim != inodeData.ctime {
    87  		return false, nil
    88  	}
    89  	inode.Hash = inodeData.hash
    90  	h.cache.numHits++
    91  	h.cache.hitBytes += inodeData.size
    92  	return true, nil
    93  }
    94  
    95  func addImage(client srpc.ClientI, request proto.BuildImageRequest,
    96  	img *image.Image) (string, error) {
    97  	if request.ExpiresIn > 0 {
    98  		img.ExpiresAt = time.Now().Add(request.ExpiresIn)
    99  	}
   100  	name := makeImageName(request.StreamName)
   101  	if err := imageclient.AddImage(client, name, img); err != nil {
   102  		return "", errors.New("remote error: " + err.Error())
   103  	}
   104  	return name, nil
   105  }
   106  
   107  func buildFileSystem(client srpc.ClientI, dirname string,
   108  	scanFilter *filter.Filter, cache *treeCache) (
   109  	*filesystem.FileSystem, error) {
   110  	h := hasher{cache: cache}
   111  	var err error
   112  	h.objQ, err = objectclient.NewObjectAdderQueue(client)
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	fs, err := buildFileSystemWithHasher(dirname, &h, scanFilter)
   117  	if err != nil {
   118  		h.objQ.Close()
   119  		return nil, err
   120  	}
   121  	err = h.objQ.Close()
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  	return fs, nil
   126  }
   127  
   128  func buildFileSystemWithHasher(dirname string, h *hasher,
   129  	scanFilter *filter.Filter) (
   130  	*filesystem.FileSystem, error) {
   131  	fs, err := scanner.ScanFileSystem(dirname, nil, scanFilter, nil, h, nil)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  	return &fs.FileSystem, nil
   136  }
   137  
   138  func listPackages(g *goroutine.Goroutine, rootDir string) (
   139  	[]image.Package, error) {
   140  	return packageutil.GetPackageList(func(cmd string, w io.Writer) error {
   141  		return runInTarget(g, nil, w, rootDir, nil, packagerPathname, cmd)
   142  	})
   143  }
   144  
   145  func makeImageName(streamName string) string {
   146  	return path.Join(streamName, time.Now().Format(timeFormat))
   147  }
   148  
   149  func packImage(g *goroutine.Goroutine, client srpc.ClientI,
   150  	request proto.BuildImageRequest, dirname string, scanFilter *filter.Filter,
   151  	cache *treeCache, computedFilesList []util.ComputedFile,
   152  	imageFilter *filter.Filter, rawTags tags.Tags, trig *triggers.Triggers,
   153  	copyMtimesFilter *filter.Filter, buildLog buildLogger) (
   154  	*image.Image, error) {
   155  	if cache == nil {
   156  		cache = &treeCache{}
   157  	}
   158  	if g == nil {
   159  		var err error
   160  		g, err = newNamespaceTarget()
   161  		if err != nil {
   162  			return nil, err
   163  		}
   164  		defer g.Quit()
   165  	}
   166  	packages, err := listPackages(g, dirname)
   167  	if err != nil {
   168  		return nil, fmt.Errorf("error listing packages: %s", err)
   169  	}
   170  	if err := util.DeletedFilteredFiles(dirname, tmpFilter); err != nil {
   171  		return nil, err
   172  	}
   173  	fmt.Fprintln(buildLog, "Scanning file-system and uploading objects")
   174  	buildStartTime := time.Now()
   175  	fs, err := buildFileSystem(client, dirname, scanFilter, cache)
   176  	if err != nil {
   177  		return nil, fmt.Errorf("error building file-system: %s", err)
   178  	}
   179  	if err := util.SpliceComputedFiles(fs, computedFilesList); err != nil {
   180  		return nil, fmt.Errorf("error splicing computed files: %s", err)
   181  	}
   182  	fs.ComputeTotalDataBytes()
   183  	duration := time.Since(buildStartTime)
   184  	speed := uint64(float64(fs.TotalDataBytes-cache.hitBytes) /
   185  		duration.Seconds())
   186  	fmt.Fprintf(buildLog, "Skipped %d unchanged objects (%s)\n",
   187  		cache.numHits, format.FormatBytes(cache.hitBytes))
   188  	fmt.Fprintf(buildLog,
   189  		"Scanned file-system and uploaded %d objects (%s) in %s (%s/s)\n",
   190  		fs.NumRegularInodes-cache.numHits,
   191  		format.FormatBytes(fs.TotalDataBytes-cache.hitBytes),
   192  		format.Duration(duration), format.FormatBytes(speed))
   193  	_, oldImage, err := getLatestImage(client, request.StreamName, "", nil,
   194  		buildLog)
   195  	if err != nil {
   196  		return nil, fmt.Errorf("error getting latest image: %s", err)
   197  	} else if oldImage != nil {
   198  		patchStartTime := time.Now()
   199  		util.CopyMtimesWithFilter(oldImage.FileSystem, fs, copyMtimesFilter)
   200  		fmt.Fprintf(buildLog, "Copied mtimes in %s\n",
   201  			format.Duration(time.Since(patchStartTime)))
   202  	}
   203  	if err := runTests(g, dirname, buildLog); err != nil {
   204  		return nil, err
   205  	}
   206  	objClient := objectclient.AttachObjectClient(client)
   207  	// Make a copy of the build log because AddObject() drains the buffer.
   208  	logReader := bytes.NewBuffer(buildLog.Bytes())
   209  	hashVal, _, err := objClient.AddObject(logReader, uint64(logReader.Len()),
   210  		nil)
   211  	if err != nil {
   212  		return nil, err
   213  	}
   214  	if err := objClient.Close(); err != nil {
   215  		return nil, err
   216  	}
   217  	tgs := rawTags.Copy()
   218  	for key, value := range tgs {
   219  		newValue := expandExpression(value, func(name string) string {
   220  			return request.Variables[name]
   221  		})
   222  		tgs[key] = newValue
   223  	}
   224  	img := &image.Image{
   225  		BuildLog:   &image.Annotation{Object: &hashVal},
   226  		FileSystem: fs,
   227  		Filter:     imageFilter,
   228  		Triggers:   trig,
   229  		Packages:   packages,
   230  		Tags:       tgs,
   231  	}
   232  	if err := img.Verify(); err != nil {
   233  		return nil, err
   234  	}
   235  	return img, nil
   236  }
   237  
   238  func runTests(g *goroutine.Goroutine, rootDir string,
   239  	buildLog buildLogger) error {
   240  	var testProgrammes []string
   241  	err := filepath.Walk(filepath.Join(rootDir, "tests"),
   242  		func(path string, fi os.FileInfo, err error) error {
   243  			if fi == nil || !fi.Mode().IsRegular() || fi.Mode()&0100 == 0 {
   244  				return nil
   245  			}
   246  			testProgrammes = append(testProgrammes, path[len(rootDir):])
   247  			return nil
   248  		})
   249  	if err != nil {
   250  		return err
   251  	}
   252  	if len(testProgrammes) < 1 {
   253  		return nil
   254  	}
   255  	fmt.Fprintf(buildLog, "Running %d tests\n", len(testProgrammes))
   256  	results := make(chan testResultType, 1)
   257  	for _, prog := range testProgrammes {
   258  		go func(prog string) {
   259  			results <- runTest(g, rootDir, prog)
   260  		}(prog)
   261  	}
   262  	numFailures := 0
   263  	for range testProgrammes {
   264  		result := <-results
   265  		io.Copy(buildLog, &result)
   266  		if result.err != nil {
   267  			fmt.Fprintf(buildLog, "error running: %s: %s\n",
   268  				result.prog, result.err)
   269  			numFailures++
   270  		} else {
   271  			fmt.Fprintf(buildLog, "%s passed in %s\n",
   272  				result.prog, format.Duration(result.duration))
   273  		}
   274  		fmt.Fprintln(buildLog)
   275  	}
   276  	if numFailures > 0 {
   277  		return fmt.Errorf("%d tests failed", numFailures)
   278  	}
   279  	return nil
   280  }
   281  
   282  func runTest(g *goroutine.Goroutine, rootDir, prog string) testResultType {
   283  	startTime := time.Now()
   284  	result := testResultType{
   285  		buffer: make(chan byte, 4096),
   286  		prog:   prog,
   287  	}
   288  	errChannel := make(chan error, 1)
   289  	timer := time.NewTimer(time.Second * 10)
   290  	go func() {
   291  		errChannel <- runInTarget(g, nil, &result, rootDir, nil,
   292  			packagerPathname, "run", prog)
   293  	}()
   294  	select {
   295  	case result.err = <-errChannel:
   296  		result.duration = time.Since(startTime)
   297  	case <-timer.C:
   298  		result.err = errorTestTimedOut
   299  	}
   300  	return result
   301  }
   302  
   303  func (w *testResultType) Read(p []byte) (int, error) {
   304  	for count := 0; count < len(p); count++ {
   305  		select {
   306  		case p[count] = <-w.buffer:
   307  		default:
   308  			return count, io.EOF
   309  		}
   310  	}
   311  	return len(p), nil
   312  }
   313  
   314  func (w *testResultType) Write(p []byte) (int, error) {
   315  	for index, ch := range p {
   316  		select {
   317  		case w.buffer <- ch:
   318  		default:
   319  			return index, io.ErrShortWrite
   320  		}
   321  	}
   322  	return len(p), nil
   323  }