github.com/Cloud-Foundations/Dominator@v0.3.4/imageunpacker/unpacker/unpackImage.go (about)

     1  package unpacker
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"time"
    10  
    11  	domlib "github.com/Cloud-Foundations/Dominator/dom/lib"
    12  	imageclient "github.com/Cloud-Foundations/Dominator/imageserver/client"
    13  	"github.com/Cloud-Foundations/Dominator/lib/filesystem"
    14  	"github.com/Cloud-Foundations/Dominator/lib/filesystem/util"
    15  	"github.com/Cloud-Foundations/Dominator/lib/filter"
    16  	"github.com/Cloud-Foundations/Dominator/lib/format"
    17  	"github.com/Cloud-Foundations/Dominator/lib/fsutil"
    18  	"github.com/Cloud-Foundations/Dominator/lib/hash"
    19  	"github.com/Cloud-Foundations/Dominator/lib/image"
    20  	"github.com/Cloud-Foundations/Dominator/lib/log"
    21  	"github.com/Cloud-Foundations/Dominator/lib/objectcache"
    22  	"github.com/Cloud-Foundations/Dominator/lib/objectserver"
    23  	objectclient "github.com/Cloud-Foundations/Dominator/lib/objectserver/client"
    24  	"github.com/Cloud-Foundations/Dominator/lib/srpc"
    25  	unpackproto "github.com/Cloud-Foundations/Dominator/proto/imageunpacker"
    26  	subproto "github.com/Cloud-Foundations/Dominator/proto/sub"
    27  	sublib "github.com/Cloud-Foundations/Dominator/sub/lib"
    28  )
    29  
    30  func (u *Unpacker) unpackImage(streamName string, imageLeafName string) error {
    31  	u.updateUsageTime()
    32  	defer u.updateUsageTime()
    33  	streamInfo := u.getStream(streamName)
    34  	if streamInfo == nil {
    35  		return errors.New("unknown stream")
    36  	}
    37  	imageName := filepath.Join(streamName, imageLeafName)
    38  	fs := u.getImage(imageName, streamInfo.dualLogger).FileSystem
    39  	if err := fs.RebuildInodePointers(); err != nil {
    40  		return err
    41  	}
    42  	fs.InodeToFilenamesTable()
    43  	fs.FilenameToInodeTable()
    44  	fs.HashToInodesTable()
    45  	fs.ComputeTotalDataBytes()
    46  	fs.BuildEntryMap()
    47  	errorChannel := make(chan error)
    48  	request := requestType{
    49  		request:      requestUnpack,
    50  		desiredFS:    fs,
    51  		imageName:    imageName,
    52  		errorChannel: errorChannel,
    53  	}
    54  	streamInfo.requestChannel <- request
    55  	return <-errorChannel
    56  }
    57  
    58  func (u *Unpacker) getImage(imageName string,
    59  	logger log.DebugLogger) *image.Image {
    60  	logger.Printf("Getting image: %s\n", imageName)
    61  	interval := time.Second
    62  	for ; true; time.Sleep(interval) {
    63  		srpcClient, err := srpc.DialHTTP("tcp", u.imageServerAddress,
    64  			time.Second*15)
    65  		if err != nil {
    66  			logger.Printf("Error connecting to image server: %s\n", err)
    67  			continue
    68  		}
    69  		image, err := imageclient.GetImageWithTimeout(srpcClient, imageName,
    70  			time.Minute)
    71  		srpcClient.Close()
    72  		if err != nil {
    73  			logger.Printf("Error getting image: %s\n", err)
    74  			continue
    75  		}
    76  		if image != nil {
    77  			return image
    78  		}
    79  		logger.Printf("Image: %s not ready yet\n", imageName)
    80  		if interval < time.Second*10 {
    81  			interval += time.Second
    82  		}
    83  	}
    84  	return nil
    85  }
    86  
    87  func (stream *streamManagerState) unpack(imageName string,
    88  	desiredFS *filesystem.FileSystem) error {
    89  	srpcClient, err := srpc.DialHTTP("tcp", stream.unpacker.imageServerAddress,
    90  		time.Second*15)
    91  	if err != nil {
    92  		return err
    93  	}
    94  	defer srpcClient.Close()
    95  	objectServer := objectclient.AttachObjectClient(srpcClient)
    96  	defer objectServer.Close()
    97  	mountPoint := filepath.Join(stream.unpacker.baseDir, "mnt")
    98  	streamInfo := stream.streamInfo
    99  	switch streamInfo.status {
   100  	case unpackproto.StatusStreamScanned:
   101  		// Everything is set up. Ready to unpack.
   102  	case unpackproto.StatusStreamNoFileSystem:
   103  		err := stream.mkfs(desiredFS, objectServer, streamInfo.dualLogger)
   104  		if err != nil {
   105  			return err
   106  		}
   107  		if err := stream.scan(false); err != nil {
   108  			return err
   109  		}
   110  	default:
   111  		return errors.New("not yet scanned")
   112  	}
   113  	err = stream.deleteUnneededFiles(imageName, stream.fileSystem, desiredFS,
   114  		mountPoint)
   115  	if err != nil {
   116  		return err
   117  	}
   118  	subObj := domlib.Sub{
   119  		FileSystem:  stream.fileSystem,
   120  		ObjectCache: stream.objectCache,
   121  	}
   122  	stream.fileSystem = nil
   123  	emptyFilter, _ := filter.New(nil)
   124  	desiredImage := &image.Image{FileSystem: desiredFS, Filter: emptyFilter}
   125  	fetchMap, _ := domlib.BuildMissingLists(subObj, desiredImage, false,
   126  		true, streamInfo.dualLogger)
   127  	objectsToFetch := objectcache.ObjectMapToCache(fetchMap)
   128  	objectsDir := filepath.Join(mountPoint, ".subd", "objects")
   129  	err = stream.fetch(imageName, objectsToFetch, objectsDir, objectServer)
   130  	if err != nil {
   131  		streamInfo.status = unpackproto.StatusStreamMounted
   132  		return err
   133  	}
   134  	subObj.ObjectCache = append(subObj.ObjectCache, objectsToFetch...)
   135  	streamInfo.status = unpackproto.StatusStreamUpdating
   136  	streamInfo.dualLogger.Printf("Update(%s) starting\n", imageName)
   137  	startTime := time.Now()
   138  	var request subproto.UpdateRequest
   139  	domlib.BuildUpdateRequest(subObj, desiredImage, &request, true, false,
   140  		streamInfo.dualLogger)
   141  	_, _, err = sublib.Update(request, mountPoint, objectsDir, nil, nil, nil,
   142  		streamInfo.streamLogger)
   143  	if err == nil {
   144  		err = util.WriteImageName(mountPoint, imageName)
   145  	}
   146  	streamInfo.status = unpackproto.StatusStreamMounted
   147  	streamInfo.dualLogger.Printf("Update(%s) completed in %s\n",
   148  		imageName, format.Duration(time.Since(startTime)))
   149  	return err
   150  }
   151  
   152  func (stream *streamManagerState) deleteUnneededFiles(imageName string,
   153  	subFS, imgFS *filesystem.FileSystem, mountPoint string) error {
   154  	pathsToDelete := make([]string, 0)
   155  	imgHashToInodesTable := imgFS.HashToInodesTable()
   156  	imgFilenameToInodeTable := imgFS.FilenameToInodeTable()
   157  	for pathname, inum := range subFS.FilenameToInodeTable() {
   158  		if inode, ok := subFS.InodeTable[inum].(*filesystem.RegularInode); ok {
   159  			if inode.Size > 0 {
   160  				if _, ok := imgHashToInodesTable[inode.Hash]; !ok {
   161  					pathsToDelete = append(pathsToDelete, pathname)
   162  				}
   163  			} else {
   164  				if _, ok := imgFilenameToInodeTable[pathname]; !ok {
   165  					pathsToDelete = append(pathsToDelete, pathname)
   166  				}
   167  			}
   168  		}
   169  	}
   170  	if len(pathsToDelete) < 1 {
   171  		return nil
   172  	}
   173  	streamInfo := stream.streamInfo
   174  	streamInfo.dualLogger.Printf("Deleting(%s): %d unneeded files\n",
   175  		imageName, len(pathsToDelete))
   176  	for _, pathname := range pathsToDelete {
   177  		streamInfo.streamLogger.Printf("Delete(%s): %s\n", imageName, pathname)
   178  		os.Remove(filepath.Join(mountPoint, pathname))
   179  	}
   180  	return nil
   181  }
   182  
   183  func (stream *streamManagerState) fetch(imageName string,
   184  	objectsToFetch []hash.Hash, destDirname string,
   185  	objectsGetter objectserver.ObjectsGetter) error {
   186  	startTime := time.Now()
   187  	stream.streamInfo.status = unpackproto.StatusStreamFetching
   188  	objectsReader, err := objectsGetter.GetObjects(objectsToFetch)
   189  	if err != nil {
   190  		stream.streamInfo.status = unpackproto.StatusStreamMounted
   191  		return err
   192  	}
   193  	defer objectsReader.Close()
   194  	streamInfo := stream.streamInfo
   195  	streamInfo.dualLogger.Printf("Fetching(%s) %d objects\n",
   196  		imageName, len(objectsToFetch))
   197  	var totalBytes uint64
   198  	for _, hashVal := range objectsToFetch {
   199  		length, reader, err := objectsReader.NextObject()
   200  		if err != nil {
   201  			streamInfo.dualLogger.Println(err)
   202  			stream.streamInfo.status = unpackproto.StatusStreamMounted
   203  			return err
   204  		}
   205  		err = readOne(destDirname, hashVal, length, reader)
   206  		reader.Close()
   207  		if err != nil {
   208  			streamInfo.dualLogger.Println(err)
   209  			stream.streamInfo.status = unpackproto.StatusStreamMounted
   210  			return err
   211  		}
   212  		totalBytes += length
   213  	}
   214  	timeTaken := time.Since(startTime)
   215  	streamInfo.dualLogger.Printf("Fetched(%s) %d objects, %s in %s (%s/s)\n",
   216  		imageName, len(objectsToFetch), format.FormatBytes(totalBytes),
   217  		format.Duration(timeTaken),
   218  		format.FormatBytes(uint64(float64(totalBytes)/timeTaken.Seconds())))
   219  	return nil
   220  }
   221  
   222  func (stream *streamManagerState) mkfs(fs *filesystem.FileSystem,
   223  	objectsGetter objectserver.ObjectsGetter, logger log.Logger) error {
   224  	unsupportedOptions, err := util.GetUnsupportedExt4fsOptions(fs,
   225  		objectsGetter)
   226  	if err != nil {
   227  		return err
   228  	}
   229  	stream.unpacker.rwMutex.RLock()
   230  	device := stream.unpacker.pState.Devices[stream.streamInfo.DeviceId]
   231  	stream.unpacker.rwMutex.RUnlock()
   232  	// udev has a bug where the partition device node is created and sometimes
   233  	// is removed and then created again. Based on experiments the device node
   234  	// is gone for ~15 milliseconds. Wait long enough since the partition was
   235  	// created to hopefully never encounter this race again.
   236  	if !device.partitionTimestamp.IsZero() {
   237  		timeSincePartition := time.Since(device.partitionTimestamp)
   238  		if timeSincePartition < time.Second {
   239  			sleepTime := time.Second - timeSincePartition
   240  			logger.Printf("sleeping %s to work around udev race\n",
   241  				format.Duration(sleepTime))
   242  			time.Sleep(sleepTime)
   243  		}
   244  	}
   245  	partitionPath, err := getPartition(filepath.Join("/dev", device.DeviceName))
   246  	if err != nil {
   247  		return err
   248  	}
   249  	rootLabel := fmt.Sprintf("rootfs@%x", time.Now().Unix())
   250  	err = util.MakeExt4fs(partitionPath, rootLabel, unsupportedOptions, 8192,
   251  		logger)
   252  	if err != nil {
   253  		return err
   254  	}
   255  	// Make sure it's still a block device. If not it means udev still had not
   256  	// settled down after waiting, so remove the inode and return an error.
   257  	if err := checkIfBlockDevice(partitionPath); err != nil {
   258  		os.Remove(partitionPath)
   259  		return err
   260  	}
   261  	stream.streamInfo.status = unpackproto.StatusStreamNotMounted
   262  	stream.rootLabel = rootLabel
   263  	return nil
   264  }
   265  
   266  func checkIfBlockDevice(path string) error {
   267  	if fi, err := os.Lstat(path); err != nil {
   268  		return err
   269  	} else if fi.Mode()&os.ModeType != os.ModeDevice {
   270  		return fmt.Errorf("%s is not a device, mode: %s", path, fi.Mode())
   271  	}
   272  	return nil
   273  }
   274  
   275  func getPartition(devicePath string) (string, error) {
   276  	partitionPaths := []string{devicePath + "1", devicePath + "p1"}
   277  	for _, partitionPath := range partitionPaths {
   278  		if err := checkIfBlockDevice(partitionPath); err != nil {
   279  			if os.IsNotExist(err) {
   280  				continue
   281  			}
   282  			return "", err
   283  		}
   284  		if file, err := os.Open(partitionPath); err == nil {
   285  			file.Close()
   286  			return partitionPath, nil
   287  		}
   288  	}
   289  	return "", fmt.Errorf("no partitions found for: %s", devicePath)
   290  }
   291  
   292  func readOne(objectsDir string, hashVal hash.Hash, length uint64,
   293  	reader io.Reader) error {
   294  	filename := filepath.Join(objectsDir, objectcache.HashToFilename(hashVal))
   295  	dirname := filepath.Dir(filename)
   296  	if err := os.MkdirAll(dirname, dirPerms); err != nil {
   297  		return err
   298  	}
   299  	return fsutil.CopyToFile(filename, filePerms, reader, length)
   300  }