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 }