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 }