github.com/Cloud-Foundations/Dominator@v0.3.4/cmd/imagetool/lib.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "encoding/gob" 6 "errors" 7 "fmt" 8 "io" 9 "net" 10 "net/http" 11 "os" 12 "os/exec" 13 "path/filepath" 14 "time" 15 16 hyperclient "github.com/Cloud-Foundations/Dominator/hypervisor/client" 17 imgclient "github.com/Cloud-Foundations/Dominator/imageserver/client" 18 "github.com/Cloud-Foundations/Dominator/lib/constants" 19 "github.com/Cloud-Foundations/Dominator/lib/filesystem" 20 "github.com/Cloud-Foundations/Dominator/lib/filter" 21 "github.com/Cloud-Foundations/Dominator/lib/image" 22 "github.com/Cloud-Foundations/Dominator/lib/image/packageutil" 23 "github.com/Cloud-Foundations/Dominator/lib/srpc" 24 "github.com/Cloud-Foundations/Dominator/lib/triggers" 25 fm_proto "github.com/Cloud-Foundations/Dominator/proto/fleetmanager" 26 img_proto "github.com/Cloud-Foundations/Dominator/proto/imageserver" 27 "github.com/Cloud-Foundations/Dominator/proto/sub" 28 subclient "github.com/Cloud-Foundations/Dominator/sub/client" 29 ) 30 31 const ( 32 imageTypeDirectory = iota 33 imageTypeFileSystem 34 imageTypeImage 35 imageTypeLatestImage 36 imageTypeImageFile 37 imageTypeSub 38 imageTypeVM 39 ) 40 41 type typedImage struct { 42 buildLog *image.Annotation 43 fileSystem *filesystem.FileSystem 44 filter *filter.Filter 45 image *image.Image 46 imageName string 47 imageType uint 48 specifier string 49 triggers *triggers.Triggers 50 } 51 52 type readCloser struct { 53 closer io.Closer 54 reader io.Reader 55 } 56 57 func dialMdbd() (*srpc.Client, error) { 58 clientName := fmt.Sprintf("%s:%d", 59 *mdbServerHostname, *mdbServerPortNum) 60 mdbdClient, err := srpc.DialHTTP("tcp", clientName, 0) 61 if err != nil { 62 return nil, fmt.Errorf("error dialing: %s: %s\n", clientName, err) 63 } 64 return mdbdClient, err 65 } 66 67 // getTypedFileReader returns a file reader. The reader must be closed before 68 // the next call to getTypedFileReader. 69 func getTypedFileReader(typedName, filename string) (io.ReadCloser, error) { 70 ti, err := makeTypedImage(typedName) 71 if err != nil { 72 return nil, err 73 } 74 return ti.openFile(filename) 75 } 76 77 func getTypedFileSystem(typedName string) (*filesystem.FileSystem, error) { 78 ti, err := getTypedImageType(typedName) 79 if err != nil { 80 return nil, err 81 } 82 fs, err := ti.getFileSystem() 83 if err != nil { 84 return nil, err 85 } 86 return fs, nil 87 } 88 89 func getTypedFileSystemAndFilter(typedName string) ( 90 *filesystem.FileSystem, *filter.Filter, error) { 91 ti, err := getTypedImageType(typedName) 92 if err != nil { 93 return nil, nil, err 94 } 95 fs, err := ti.getFileSystem() 96 if err != nil { 97 return nil, nil, err 98 } 99 return fs, ti.filter, nil 100 } 101 102 func getTypedImage(typedName string) (*image.Image, error) { 103 ti, err := getTypedImageType(typedName) 104 if err != nil { 105 return nil, err 106 } 107 img, err := ti.getImage() 108 if err != nil { 109 return nil, err 110 } 111 return img, nil 112 } 113 114 func getTypedImageAndName(typedName string) (*image.Image, string, error) { 115 ti, err := getTypedImageType(typedName) 116 if err != nil { 117 return nil, "", err 118 } 119 img, err := ti.getImage() 120 if err != nil { 121 return nil, "", err 122 } 123 name, err := ti.getImageName() 124 if err != nil { 125 return nil, "", err 126 } 127 return img, name, nil 128 } 129 130 func getTypedImageBuildLog(typedName string) (*image.Annotation, error) { 131 ti, err := makeTypedImage(typedName) 132 if err != nil { 133 return nil, err 134 } 135 if err := ti.loadMetadata(); err != nil { 136 return nil, err 137 } 138 buildLog, err := ti.getBuildLog() 139 if err != nil { 140 return nil, err 141 } 142 return buildLog, nil 143 } 144 145 // getTypedImageBuildLogReader returns a build log reader. The reader must be 146 // closed before the next call to getTypedImageBuildLogReader. 147 func getTypedImageBuildLogReader(typedName string) (io.ReadCloser, error) { 148 buildLog, err := getTypedImageBuildLog(typedName) 149 if err != nil { 150 return nil, err 151 } 152 if hashPtr := buildLog.Object; hashPtr != nil { 153 _, objectClient := getClients() 154 _, r, err := objectClient.GetObject(*hashPtr) 155 if err != nil { 156 return nil, err 157 } 158 return r, nil 159 } else if buildLog.URL != "" { 160 resp, err := http.Get(buildLog.URL) 161 if err != nil { 162 return nil, err 163 } 164 if resp.StatusCode != http.StatusOK { 165 return nil, errors.New(resp.Status) 166 } 167 if resp.ContentLength > 0 { 168 return &readCloser{resp.Body, 169 &io.LimitedReader{resp.Body, resp.ContentLength}}, nil 170 } 171 return resp.Body, nil 172 } else { 173 return nil, errors.New("no build log data") 174 } 175 } 176 177 func getTypedImageFilter(typedName string) (*filter.Filter, error) { 178 ti, err := makeTypedImage(typedName) 179 if err != nil { 180 return nil, err 181 } 182 if err := ti.loadMetadata(); err != nil { 183 return nil, err 184 } 185 filt, err := ti.getFilter() 186 if err != nil { 187 return nil, err 188 } 189 return filt, nil 190 } 191 192 func getTypedImageMetadata(typedName string) (*image.Image, error) { 193 ti, err := makeTypedImage(typedName) 194 if err != nil { 195 return nil, err 196 } 197 if err := ti.loadMetadata(); err != nil { 198 return nil, err 199 } 200 img, err := ti.getImage() 201 if err != nil { 202 return nil, err 203 } 204 return img, nil 205 } 206 207 func getTypedImageTriggers(typedName string) (*triggers.Triggers, error) { 208 ti, err := makeTypedImage(typedName) 209 if err != nil { 210 return nil, err 211 } 212 if err := ti.loadMetadata(); err != nil { 213 return nil, err 214 } 215 trig, err := ti.getTriggers() 216 if err != nil { 217 return nil, err 218 } 219 return trig, nil 220 } 221 222 func getTypedImageType(typedName string) (*typedImage, error) { 223 ti, err := makeTypedImage(typedName) 224 if err != nil { 225 return nil, err 226 } 227 if err := ti.load(); err != nil { 228 return nil, err 229 } 230 return ti, nil 231 } 232 233 func getTypedPackageList(typedName string) ([]image.Package, error) { 234 ti, err := makeTypedImage(typedName) 235 if err != nil { 236 return nil, err 237 } 238 return ti.loadPackages() 239 } 240 241 func makeTypedImage(typedName string) (*typedImage, error) { 242 if len(typedName) < 3 || typedName[1] != ':' { 243 typedName = "i:" + typedName 244 } 245 var retval *typedImage 246 switch name := typedName[2:]; typedName[0] { 247 case 'd': 248 retval = &typedImage{imageType: imageTypeDirectory, specifier: name} 249 case 'f': 250 retval = &typedImage{imageType: imageTypeFileSystem, specifier: name} 251 case 'i': 252 retval = &typedImage{imageType: imageTypeImage, specifier: name} 253 case 'I': 254 retval = &typedImage{imageType: imageTypeLatestImage, specifier: name} 255 case 'l': 256 retval = &typedImage{imageType: imageTypeImageFile, specifier: name} 257 case 's': 258 retval = &typedImage{imageType: imageTypeSub, specifier: name} 259 case 'v': 260 retval = &typedImage{imageType: imageTypeVM, specifier: name} 261 default: 262 return nil, errors.New("unknown image type: " + typedName[:1]) 263 } 264 return retval, nil 265 } 266 267 func (rc *readCloser) Close() error { 268 return rc.closer.Close() 269 } 270 271 func (rc *readCloser) Read(p []byte) (int, error) { 272 return rc.reader.Read(p) 273 } 274 275 func (ti *typedImage) getBuildLog() (*image.Annotation, error) { 276 if buildLog := ti.buildLog; buildLog == nil { 277 return nil, errors.New("BuildLog data not available") 278 } else { 279 return buildLog, nil 280 } 281 } 282 283 func (ti *typedImage) getFileSystem() (*filesystem.FileSystem, error) { 284 if fs := ti.fileSystem; fs == nil { 285 return nil, errors.New("FileSystem data not available") 286 } else { 287 return fs, nil 288 } 289 } 290 291 func (ti *typedImage) getFilter() (*filter.Filter, error) { 292 if filt := ti.filter; filt == nil { 293 return nil, errors.New("Filter not available") 294 } else { 295 return filt, nil 296 } 297 } 298 299 func (ti *typedImage) getImage() (*image.Image, error) { 300 if img := ti.image; img == nil { 301 return nil, errors.New("Image data not available") 302 } else { 303 return img, nil 304 } 305 } 306 307 func (ti *typedImage) getImageName() (string, error) { 308 if name := ti.imageName; name == "" { 309 return "", errors.New("Image name not available") 310 } else { 311 return name, nil 312 } 313 } 314 315 func (ti *typedImage) load() error { 316 switch ti.imageType { 317 case imageTypeDirectory: 318 fs, err := scanDirectory(ti.specifier) 319 if err != nil { 320 return err 321 } 322 ti.fileSystem = fs 323 case imageTypeFileSystem: 324 fs, err := readFileSystem(ti.specifier) 325 if err != nil { 326 return err 327 } 328 ti.fileSystem = fs 329 case imageTypeImage: 330 imageSClient, _ := getClients() 331 img, err := getImage(imageSClient, ti.specifier) 332 if err != nil { 333 return err 334 } 335 ti.buildLog = img.BuildLog 336 ti.fileSystem = img.FileSystem 337 ti.filter = img.Filter 338 ti.image = img 339 ti.imageName = ti.specifier 340 ti.triggers = img.Triggers 341 case imageTypeLatestImage: 342 imageSClient, _ := getClients() 343 img, name, err := getLatestImage(imageSClient, ti.specifier, false) 344 if err != nil { 345 return err 346 } 347 ti.buildLog = img.BuildLog 348 ti.fileSystem = img.FileSystem 349 ti.filter = img.Filter 350 ti.image = img 351 ti.imageName = name 352 ti.triggers = img.Triggers 353 case imageTypeImageFile: 354 img, err := readImage(ti.specifier) 355 if err != nil { 356 return err 357 } 358 ti.buildLog = img.BuildLog 359 ti.fileSystem = img.FileSystem 360 ti.filter = img.Filter 361 ti.image = img 362 ti.triggers = img.Triggers 363 case imageTypeSub: 364 fs, err := pollImage(ti.specifier) 365 if err != nil { 366 return err 367 } 368 ti.fileSystem = fs 369 case imageTypeVM: 370 fs, err := scanVm(ti.specifier) 371 if err != nil { 372 return err 373 } 374 ti.fileSystem = fs 375 default: 376 panic("unsupported typedImage in load()") 377 } 378 return nil 379 } 380 381 func (ti *typedImage) loadMetadata() error { 382 switch ti.imageType { 383 case imageTypeImage: 384 img, err := getImageMetadata(ti.specifier) 385 if err != nil { 386 return err 387 } 388 ti.buildLog = img.BuildLog 389 ti.filter = img.Filter 390 ti.image = img 391 ti.triggers = img.Triggers 392 case imageTypeLatestImage: 393 imageSClient, _ := getClients() 394 img, name, err := getLatestImage(imageSClient, ti.specifier, true) 395 if err != nil { 396 return err 397 } 398 ti.buildLog = img.BuildLog 399 ti.filter = img.Filter 400 ti.image = img 401 ti.imageName = name 402 ti.triggers = img.Triggers 403 case imageTypeImageFile: 404 img, err := readImage(ti.specifier) 405 if err != nil { 406 return err 407 } 408 ti.buildLog = img.BuildLog 409 ti.filter = img.Filter 410 ti.image = img 411 ti.triggers = img.Triggers 412 default: 413 return errors.New("package data not available") 414 } 415 return nil 416 } 417 418 func (ti *typedImage) loadPackages() ([]image.Package, error) { 419 switch ti.imageType { 420 case imageTypeDirectory: 421 return packageutil.GetPackageList(func(cmd string, w io.Writer) error { 422 command := exec.Command("/bin/generic-packager", cmd) 423 command.Stdout = w 424 return command.Run() 425 }) 426 case imageTypeImage, imageTypeLatestImage, imageTypeImageFile: 427 if err := ti.loadMetadata(); err != nil { 428 return nil, err 429 } 430 return ti.image.Packages, nil 431 default: 432 return nil, errors.New("package data not available") 433 } 434 } 435 436 func (ti *typedImage) openFile(filename string) (io.ReadCloser, error) { 437 switch ti.imageType { 438 case imageTypeDirectory: 439 return os.Open(filepath.Join(ti.specifier, filename)) 440 case imageTypeFileSystem, imageTypeImage, imageTypeLatestImage, imageTypeImageFile: 441 if err := ti.load(); err != nil { 442 return nil, err 443 } 444 case imageTypeSub: 445 data, err := readFileFromSub(ti.specifier, filename) 446 if err != nil { 447 return nil, err 448 } 449 return io.NopCloser(bytes.NewReader(data)), nil 450 default: 451 return nil, errors.New("unsupported typedImage in openFile()") 452 } 453 fs, err := ti.getFileSystem() 454 if err != nil { 455 return nil, err 456 } 457 filenameToInodeTable := fs.FilenameToInodeTable() 458 if inum, ok := filenameToInodeTable[filename]; !ok { 459 return nil, fmt.Errorf("file: \"%s\" not present in image", filename) 460 } else if inode, ok := fs.InodeTable[inum]; !ok { 461 return nil, fmt.Errorf("inode: %d not present in image", inum) 462 } else if inode, ok := inode.(*filesystem.RegularInode); !ok { 463 return nil, fmt.Errorf("file: \"%s\" is not a regular file", filename) 464 } else { 465 _, objectClient := getClients() 466 _, reader, err := objectClient.GetObject(inode.Hash) 467 if err != nil { 468 return nil, err 469 } 470 return reader, nil 471 } 472 } 473 474 func (ti *typedImage) getTriggers() (*triggers.Triggers, error) { 475 if trig := ti.triggers; trig == nil { 476 return nil, errors.New("Triggers not available") 477 } else { 478 return trig, nil 479 } 480 } 481 482 func findHypervisor(vmIpAddr net.IP) (string, error) { 483 if *hypervisorHostname != "" { 484 return fmt.Sprintf("%s:%d", *hypervisorHostname, *hypervisorPortNum), 485 nil 486 } else if *fleetManagerHostname != "" { 487 fm := fmt.Sprintf("%s:%d", *fleetManagerHostname, *fleetManagerPortNum) 488 client, err := srpc.DialHTTP("tcp", fm, time.Second*10) 489 if err != nil { 490 return "", err 491 } 492 defer client.Close() 493 return findHypervisorClient(client, vmIpAddr) 494 } else { 495 return fmt.Sprintf("localhost:%d", *hypervisorPortNum), nil 496 } 497 } 498 499 func findHypervisorClient(client *srpc.Client, 500 vmIpAddr net.IP) (string, error) { 501 request := fm_proto.GetHypervisorForVMRequest{vmIpAddr} 502 var reply fm_proto.GetHypervisorForVMResponse 503 err := client.RequestReply("FleetManager.GetHypervisorForVM", request, 504 &reply) 505 if err != nil { 506 return "", err 507 } 508 if err := errors.New(reply.Error); err != nil { 509 return "", err 510 } 511 return reply.HypervisorAddress, nil 512 } 513 514 func getImage(client *srpc.Client, name string) (*image.Image, error) { 515 img, err := imgclient.GetImageWithTimeout(client, name, *timeout) 516 if err != nil { 517 return nil, err 518 } 519 if img == nil { 520 return nil, errors.New(name + ": not found") 521 } 522 if err := img.FileSystem.RebuildInodePointers(); err != nil { 523 return nil, err 524 } 525 return img, nil 526 } 527 528 func getImageMetadata(imageName string) (*image.Image, error) { 529 imageSClient, _ := getClients() 530 logger.Debugf(0, "getting image: %s\n", imageName) 531 request := img_proto.GetImageRequest{ 532 ImageName: imageName, 533 IgnoreFilesystem: true, 534 Timeout: *timeout, 535 } 536 var reply img_proto.GetImageResponse 537 err := imageSClient.RequestReply("ImageServer.GetImage", request, &reply) 538 if err != nil { 539 return nil, err 540 } 541 if reply.Image == nil { 542 return nil, fmt.Errorf("image: %s not found", imageName) 543 } 544 return reply.Image, nil 545 } 546 547 func getLatestImage(client *srpc.Client, name string, 548 ignoreFilesystem bool) (*image.Image, string, error) { 549 imageName, err := imgclient.FindLatestImageReq(client, 550 img_proto.FindLatestImageRequest{ 551 BuildCommitId: *buildCommitId, 552 DirectoryName: name, 553 IgnoreExpiringImages: *ignoreExpiring, 554 }) 555 if err != nil { 556 return nil, "", err 557 } 558 if ignoreFilesystem { 559 img, err := getImageMetadata(imageName) 560 if err != nil { 561 return nil, "", err 562 } 563 return img, imageName, nil 564 } 565 img, err := getImage(client, imageName) 566 if err != nil { 567 return nil, "", err 568 } else { 569 return img, imageName, nil 570 } 571 } 572 573 func getVmIpAndHypervisor(vmHostname string) (net.IP, *srpc.Client, error) { 574 vmIpAddr, err := lookupIP(vmHostname) 575 if err != nil { 576 return nil, nil, err 577 } 578 hypervisorAddress, err := findHypervisor(vmIpAddr) 579 if err != nil { 580 return nil, nil, err 581 } 582 client, err := srpc.DialHTTP("tcp", hypervisorAddress, time.Second*10) 583 if err != nil { 584 return nil, nil, err 585 } 586 return vmIpAddr, client, nil 587 } 588 589 func lookupIP(vmHostname string) (net.IP, error) { 590 if ips, err := net.LookupIP(vmHostname); err != nil { 591 return nil, err 592 } else if len(ips) != 1 { 593 return nil, fmt.Errorf("num IPs: %d != 1", len(ips)) 594 } else { 595 return ips[0], nil 596 } 597 } 598 599 func pollImage(name string) (*filesystem.FileSystem, error) { 600 clientName := fmt.Sprintf("%s:%d", name, constants.SubPortNumber) 601 srpcClient, err := srpc.DialHTTP("tcp", clientName, 0) 602 if err != nil { 603 return nil, fmt.Errorf("error dialing %s", err) 604 } 605 defer srpcClient.Close() 606 var request sub.PollRequest 607 var reply sub.PollResponse 608 if err = subclient.CallPoll(srpcClient, request, &reply); err != nil { 609 return nil, err 610 } 611 if reply.FileSystem == nil { 612 return nil, errors.New("no poll data") 613 } 614 reply.FileSystem.RebuildInodePointers() 615 return reply.FileSystem, nil 616 } 617 618 func readFileFromSub(subHostname, filename string) ([]byte, error) { 619 clientName := fmt.Sprintf("%s:%d", subHostname, constants.SubPortNumber) 620 srpcClient, err := srpc.DialHTTP("tcp", clientName, 0) 621 if err != nil { 622 return nil, fmt.Errorf("error dialing %s", err) 623 } 624 defer srpcClient.Close() 625 buffer := &bytes.Buffer{} 626 err = subclient.GetFiles(srpcClient, []string{filename}, 627 func(reader io.Reader, size uint64) error { 628 _, err := io.Copy(buffer, reader) 629 return err 630 }) 631 if err != nil { 632 return nil, err 633 } 634 return buffer.Bytes(), nil 635 } 636 637 func readFileSystem(name string) (*filesystem.FileSystem, error) { 638 file, err := os.Open(name) 639 if err != nil { 640 return nil, err 641 } 642 defer file.Close() 643 var fileSystem filesystem.FileSystem 644 if err := gob.NewDecoder(file).Decode(&fileSystem); err != nil { 645 return nil, err 646 } 647 fileSystem.RebuildInodePointers() 648 return &fileSystem, nil 649 } 650 651 func readImage(name string) (*image.Image, error) { 652 file, err := os.Open(name) 653 if err != nil { 654 return nil, err 655 } 656 defer file.Close() 657 var img image.Image 658 if err := gob.NewDecoder(file).Decode(&img); err != nil { 659 return nil, err 660 } 661 img.FileSystem.RebuildInodePointers() 662 return &img, nil 663 } 664 665 func scanDirectory(name string) (*filesystem.FileSystem, error) { 666 fs, err := buildImageWithHasher(nil, nil, name, nil) 667 if err != nil { 668 return nil, err 669 } 670 return fs, nil 671 } 672 673 func scanVm(name string) (*filesystem.FileSystem, error) { 674 vmIpAddr, srpcClient, err := getVmIpAndHypervisor(name) 675 if err != nil { 676 return nil, err 677 } 678 defer srpcClient.Close() 679 fs, err := hyperclient.ScanVmRoot(srpcClient, vmIpAddr, nil) 680 if err != nil { 681 return nil, err 682 } 683 fs.RebuildInodePointers() 684 return fs, nil 685 }