yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/esxi/storage.go (about) 1 // Copyright 2019 Yunion 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package esxi 16 17 import ( 18 "bytes" 19 "context" 20 "encoding/binary" 21 "fmt" 22 "io" 23 "io/ioutil" 24 "math/rand" 25 "net/http" 26 "net/url" 27 "os" 28 "path" 29 "path/filepath" 30 "regexp" 31 "strconv" 32 "strings" 33 "text/template" 34 "time" 35 "unicode" 36 37 "github.com/vmware/govmomi/object" 38 "github.com/vmware/govmomi/ovf" 39 "github.com/vmware/govmomi/property" 40 "github.com/vmware/govmomi/vim25/mo" 41 "github.com/vmware/govmomi/vim25/progress" 42 "github.com/vmware/govmomi/vim25/soap" 43 "github.com/vmware/govmomi/vim25/types" 44 45 "yunion.io/x/jsonutils" 46 "yunion.io/x/log" 47 "yunion.io/x/pkg/errors" 48 49 api "yunion.io/x/cloudmux/pkg/apis/compute" 50 "yunion.io/x/cloudmux/pkg/cloudprovider" 51 "yunion.io/x/cloudmux/pkg/multicloud" 52 "yunion.io/x/onecloud/pkg/util/vmdkutils" 53 ) 54 55 var DATASTORE_PROPS = []string{"name", "parent", "info", "summary", "host", "vm"} 56 57 type SDatastore struct { 58 multicloud.SStorageBase 59 SManagedObject 60 61 // vms []cloudprovider.ICloudVM 62 63 ihosts []cloudprovider.ICloudHost 64 idisks []cloudprovider.ICloudDisk 65 66 storageCache *SDatastoreImageCache 67 } 68 69 func NewDatastore(manager *SESXiClient, ds *mo.Datastore, dc *SDatacenter) *SDatastore { 70 return &SDatastore{SManagedObject: newManagedObject(manager, ds, dc)} 71 } 72 73 func (self *SDatastore) getDatastore() *mo.Datastore { 74 return self.object.(*mo.Datastore) 75 } 76 77 func (self *SDatastore) GetGlobalId() string { 78 volId, err := self.getVolumeId() 79 if err != nil { 80 log.Errorf("get datastore global ID error %s", err) 81 } 82 return volId 83 } 84 85 func (self *SDatastore) GetName() string { 86 volName, err := self.getVolumeName() 87 if err != nil { 88 log.Fatalf("datastore get name error %s", err) 89 } 90 return fmt.Sprintf("%s-%s", self.getVolumeType(), volName) 91 } 92 93 func (self *SDatastore) GetRelName() string { 94 return self.getDatastore().Info.GetDatastoreInfo().Name 95 } 96 97 func (self *SDatastore) GetCapacityMB() int64 { 98 moStore := self.getDatastore() 99 return moStore.Summary.Capacity / 1024 / 1024 100 } 101 102 func (self *SDatastore) GetCapacityUsedMB() int64 { 103 moStore := self.getDatastore() 104 return self.GetCapacityMB() - moStore.Summary.FreeSpace/1024/1024 105 } 106 107 func (self *SDatastore) GetEnabled() bool { 108 return true 109 } 110 111 func (self *SDatastore) GetStatus() string { 112 if self.getDatastore().Summary.Accessible { 113 return api.STORAGE_ONLINE 114 } else { 115 return api.STORAGE_OFFLINE 116 } 117 } 118 119 func (self *SDatastore) Refresh() error { 120 base := self.SManagedObject 121 var moObj mo.Datastore 122 err := self.manager.reference2Object(self.object.Reference(), DATASTORE_PROPS, &moObj) 123 if err != nil { 124 return err 125 } 126 base.object = &moObj 127 *self = SDatastore{} 128 self.SManagedObject = base 129 return nil 130 } 131 132 func (self *SDatastore) IsEmulated() bool { 133 return false 134 } 135 136 func (self *SDatastore) getVolumeId() (string, error) { 137 moStore := self.getDatastore() 138 switch fsInfo := moStore.Info.(type) { 139 case *types.VmfsDatastoreInfo: 140 if fsInfo.Vmfs.Local == nil || *fsInfo.Vmfs.Local { 141 host, err := self.getLocalHost() 142 if err == nil { 143 return fmt.Sprintf("%s:%s", host.GetAccessIp(), fsInfo.Vmfs.Uuid), nil 144 } 145 } 146 return fsInfo.Vmfs.Uuid, nil 147 case *types.NasDatastoreInfo: 148 return fmt.Sprintf("%s:%s", fsInfo.Nas.RemoteHost, fsInfo.Nas.RemotePath), nil 149 } 150 if moStore.Summary.Type == "vsan" { 151 vsanId := moStore.Summary.Url 152 vsanId = vsanId[strings.Index(vsanId, "vsan:"):] 153 endIdx := len(vsanId) 154 for ; endIdx >= 0 && vsanId[endIdx-1] == '/'; endIdx -= 1 { 155 } 156 return vsanId[:endIdx], nil 157 } 158 log.Fatalf("unsupported volume type %#v", moStore.Info) 159 return "", cloudprovider.ErrNotImplemented 160 } 161 162 func (self *SDatastore) getVolumeType() string { 163 return self.getDatastore().Summary.Type 164 } 165 166 func (self *SDatastore) getVolumeName() (string, error) { 167 moStore := self.getDatastore() 168 169 if self.isLocalVMFS() { 170 host, err := self.getLocalHost() 171 if err != nil { 172 return "", err 173 } 174 return fmt.Sprintf("%s-%s", host.GetAccessIp(), moStore.Info.GetDatastoreInfo().Name), nil 175 } 176 dc, err := self.GetDatacenter() 177 if err != nil { 178 return "", nil 179 } 180 return fmt.Sprintf("%s-%s", dc.GetName(), moStore.Info.GetDatastoreInfo().Name), nil 181 } 182 183 func (self *SDatastore) getAttachedHosts() ([]cloudprovider.ICloudHost, error) { 184 ihosts := make([]cloudprovider.ICloudHost, 0) 185 186 moStore := self.getDatastore() 187 for i := 0; i < len(moStore.Host); i += 1 { 188 idstr := moRefId(moStore.Host[i].Key) 189 host, err := self.datacenter.GetIHostByMoId(idstr) 190 if err != nil { 191 return nil, err 192 } 193 ihosts = append(ihosts, host) 194 } 195 196 return ihosts, nil 197 } 198 199 func (self *SDatastore) getCachedAttachedHosts() ([]cloudprovider.ICloudHost, error) { 200 if self.ihosts == nil { 201 var err error 202 self.ihosts, err = self.getAttachedHosts() 203 if err != nil { 204 return nil, err 205 } 206 } 207 return self.ihosts, nil 208 } 209 210 func (self *SDatastore) GetAttachedHosts() ([]cloudprovider.ICloudHost, error) { 211 return self.getCachedAttachedHosts() 212 } 213 214 func (self *SDatastore) getLocalHost() (cloudprovider.ICloudHost, error) { 215 hosts, err := self.GetAttachedHosts() 216 if err != nil { 217 return nil, errors.Wrap(err, "self.GetAttachedHosts") 218 } 219 if len(hosts) == 1 { 220 return hosts[0], nil 221 } 222 return nil, cloudprovider.ErrInvalidStatus 223 } 224 225 func (self *SDatastore) GetIStoragecache() cloudprovider.ICloudStoragecache { 226 if self.isLocalVMFS() { 227 ihost, err := self.getLocalHost() 228 if err != nil { 229 log.Errorf("GetIStoragecache getLocalHost fail %s", err) 230 return nil 231 } 232 host := ihost.(*SHost) 233 sc, err := host.getLocalStorageCache() 234 if err != nil { 235 log.Errorf("GetIStoragecache getLocalStorageCache fail %s", err) 236 return nil 237 } 238 return sc 239 } else { 240 return self.getStorageCache() 241 } 242 } 243 244 func (self *SDatastore) getStorageCache() *SDatastoreImageCache { 245 if self.storageCache == nil { 246 self.storageCache = &SDatastoreImageCache{ 247 datastore: self, 248 } 249 } 250 return self.storageCache 251 } 252 253 func (self *SDatastore) GetIZone() cloudprovider.ICloudZone { 254 return nil 255 } 256 257 func (self *SDatastore) FetchNoTemplateVMs() ([]*SVirtualMachine, error) { 258 mods := self.getDatastore() 259 filter := property.Filter{} 260 filter["datastore"] = mods.Reference() 261 return self.datacenter.fetchVMsWithFilter(filter) 262 } 263 264 func (self *SDatastore) FetchTemplateVMs() ([]*SVirtualMachine, error) { 265 mods := self.getDatastore() 266 filter := property.Filter{} 267 filter["config.template"] = true 268 filter["datastore"] = mods.Reference() 269 return self.datacenter.fetchVMsWithFilter(filter) 270 } 271 272 func (self *SDatastore) FetchTemplateVMById(id string) (*SVirtualMachine, error) { 273 mods := self.getDatastore() 274 filter := property.Filter{} 275 uuid := toTemplateUuid(id) 276 filter["summary.config.uuid"] = uuid 277 filter["config.template"] = true 278 filter["datastore"] = mods.Reference() 279 vms, err := self.datacenter.fetchVMsWithFilter(filter) 280 if err != nil { 281 return nil, err 282 } 283 if len(vms) == 0 { 284 return nil, errors.ErrNotFound 285 } 286 return vms[0], nil 287 } 288 289 func (self *SDatastore) FetchFakeTempateVMById(id string, regex string) (*SVirtualMachine, error) { 290 mods := self.getDatastore() 291 filter := property.Filter{} 292 uuid := toTemplateUuid(id) 293 filter["summary.config.uuid"] = uuid 294 filter["datastore"] = mods.Reference() 295 filter["summary.runtime.powerState"] = types.VirtualMachinePowerStatePoweredOff 296 filter["config.template"] = false 297 movms, err := self.datacenter.fetchMoVms(filter, []string{"name"}) 298 if err != nil { 299 return nil, errors.Wrap(err, "unable to fetch mo.VirtualMachines") 300 } 301 vms, err := self.datacenter.fetchFakeTemplateVMs(movms, regex) 302 if err != nil { 303 return nil, err 304 } 305 if len(vms) == 0 { 306 return nil, errors.ErrNotFound 307 } 308 return vms[0], nil 309 } 310 311 func (self *SDatastore) FetchFakeTempateVMs(regex string) ([]*SVirtualMachine, error) { 312 mods := self.getDatastore() 313 filter := property.Filter{} 314 filter["datastore"] = mods.Reference() 315 filter["summary.runtime.powerState"] = types.VirtualMachinePowerStatePoweredOff 316 filter["config.template"] = false 317 movms, err := self.datacenter.fetchMoVms(filter, []string{"name"}) 318 if err != nil { 319 return nil, errors.Wrap(err, "unable to fetch mo.VirtualMachines") 320 } 321 return self.datacenter.fetchFakeTemplateVMs(movms, regex) 322 } 323 324 func (self *SDatastore) getVMs() ([]cloudprovider.ICloudVM, error) { 325 dc, err := self.GetDatacenter() 326 if err != nil { 327 log.Errorf("SDatastore GetDatacenter fail %s", err) 328 return nil, err 329 } 330 vms := self.getDatastore().Vm 331 if len(vms) == 0 { 332 return nil, nil 333 } 334 svms, err := dc.fetchVmsFromCache(vms) 335 if err != nil { 336 return nil, err 337 } 338 ret := make([]cloudprovider.ICloudVM, len(svms)) 339 for i := range svms { 340 ret[i] = svms[i] 341 } 342 return ret, err 343 } 344 345 func (self *SDatastore) GetIDiskById(idStr string) (cloudprovider.ICloudDisk, error) { 346 vms, err := self.getVMs() 347 if err != nil { 348 log.Errorf("self.getVMs fail %s", err) 349 return nil, err 350 } 351 for i := 0; i < len(vms); i += 1 { 352 vm := vms[i].(*SVirtualMachine) 353 disk, err := vm.GetIDiskById(idStr) 354 if err == nil { 355 return disk, nil 356 } 357 } 358 return nil, cloudprovider.ErrNotFound 359 } 360 361 func (self *SDatastore) fetchDisks() error { 362 vms, err := self.getVMs() 363 if err != nil { 364 return err 365 } 366 allDisks := make([]cloudprovider.ICloudDisk, 0) 367 for i := 0; i < len(vms); i += 1 { 368 disks, err := vms[i].GetIDisks() 369 if err != nil { 370 return err 371 } 372 allDisks = append(allDisks, disks...) 373 } 374 self.idisks = allDisks 375 return nil 376 } 377 378 func (self *SDatastore) GetIDisks() ([]cloudprovider.ICloudDisk, error) { 379 if self.idisks != nil { 380 return self.idisks, nil 381 } 382 err := self.fetchDisks() 383 if err != nil { 384 return nil, err 385 } 386 return self.idisks, nil 387 } 388 389 func (self *SDatastore) isLocalVMFS() bool { 390 moStore := self.getDatastore() 391 switch vmfsInfo := moStore.Info.(type) { 392 case *types.VmfsDatastoreInfo: 393 if vmfsInfo.Vmfs.Local == nil || *vmfsInfo.Vmfs.Local == true { 394 _, err := self.getLocalHost() 395 if err == nil { 396 return true 397 } 398 } 399 } 400 return false 401 } 402 403 func (self *SDatastore) GetStorageType() string { 404 moStore := self.getDatastore() 405 t := strings.ToLower(moStore.Summary.Type) 406 switch t { 407 case "vmfs": 408 if self.isLocalVMFS() { 409 return api.STORAGE_LOCAL 410 } else { 411 return api.STORAGE_NAS 412 } 413 case "nfs", "nfs41": 414 return api.STORAGE_NFS 415 case "vsan": 416 return api.STORAGE_VSAN 417 case "cifs": 418 return api.STORAGE_CIFS 419 default: 420 log.Fatalf("unsupported datastore type %s", moStore.Summary.Type) 421 return "" 422 } 423 } 424 425 func (self *SDatastore) GetMediumType() string { 426 moStore := self.getDatastore() 427 vmfsInfo, ok := moStore.Info.(*types.VmfsDatastoreInfo) 428 if ok && vmfsInfo.Vmfs.Ssd != nil && *vmfsInfo.Vmfs.Ssd { 429 return api.DISK_TYPE_SSD 430 } 431 return api.DISK_TYPE_ROTATE 432 } 433 434 func (self *SDatastore) GetStorageConf() jsonutils.JSONObject { 435 conf := jsonutils.NewDict() 436 conf.Add(jsonutils.NewString(self.GetName()), "name") 437 conf.Add(jsonutils.NewString(self.GetGlobalId()), "id") 438 conf.Add(jsonutils.NewString(self.GetDatacenterPathString()), "dc_path") 439 volId, err := self.getVolumeId() 440 if err != nil { 441 log.Errorf("getVaolumeId fail %s", err) 442 } 443 conf.Add(jsonutils.NewString(volId), "volume_id") 444 445 volType := self.getVolumeType() 446 conf.Add(jsonutils.NewString(volType), "volume_type") 447 448 volName, err := self.getVolumeName() 449 if err != nil { 450 log.Errorf("getVaolumeName fail %s", err) 451 } 452 conf.Add(jsonutils.NewString(volName), "volume_name") 453 return conf 454 } 455 456 const dsPrefix = "ds://" 457 458 func (self *SDatastore) GetUrl() string { 459 url := self.getDatastore().Info.GetDatastoreInfo().Url 460 if strings.HasPrefix(url, dsPrefix) { 461 url = url[len(dsPrefix):] 462 } 463 return url 464 } 465 466 func (self *SDatastore) GetMountPoint() string { 467 return self.GetUrl() 468 } 469 470 func (self *SDatastore) HasFile(remotePath string) bool { 471 dsName := fmt.Sprintf("[%s]", self.SManagedObject.GetName()) 472 if strings.HasPrefix(remotePath, dsName) { 473 return true 474 } else { 475 return false 476 } 477 } 478 479 func (self *SDatastore) cleanPath(remotePath string) string { 480 dsName := fmt.Sprintf("[%s]", self.SManagedObject.GetName()) 481 dsUrl := self.GetUrl() 482 if strings.HasPrefix(remotePath, dsName) { 483 remotePath = remotePath[len(dsName):] 484 } else if strings.HasPrefix(remotePath, dsUrl) { 485 remotePath = remotePath[len(dsUrl):] 486 } 487 return strings.TrimSpace(remotePath) 488 } 489 490 func pathEscape(path string) string { 491 segs := strings.Split(path, "/") 492 for i := 0; i < len(segs); i += 1 { 493 segs[i] = url.PathEscape(segs[i]) 494 } 495 return strings.Join(segs, "/") 496 } 497 498 func (self *SDatastore) GetPathUrl(remotePath string) string { 499 remotePath = self.cleanPath(remotePath) 500 if len(remotePath) == 0 || remotePath[0] != '/' { 501 remotePath = fmt.Sprintf("/%s", remotePath) 502 } 503 httpUrl := fmt.Sprintf("%s/folder%s", self.getManagerUri(), pathEscape(remotePath)) 504 params := jsonutils.NewDict() 505 params.Add(jsonutils.NewString(self.SManagedObject.GetName()), "dsName") 506 params.Add(jsonutils.NewString(self.GetDatacenterPathString()), "dcPath") 507 508 return fmt.Sprintf("%s?%s", httpUrl, params.QueryString()) 509 } 510 511 func (self *SDatastore) getPathString(path string) string { 512 for len(path) > 0 && path[0] == '/' { 513 path = path[1:] 514 } 515 return fmt.Sprintf("[%s] %s", self.SManagedObject.GetName(), path) 516 } 517 518 func (self *SDatastore) GetFullPath(remotePath string) string { 519 remotePath = self.cleanPath(remotePath) 520 return path.Join(self.GetUrl(), remotePath) 521 } 522 523 func (self *SDatastore) CreateIDisk(conf *cloudprovider.DiskCreateConfig) (cloudprovider.ICloudDisk, error) { 524 return nil, cloudprovider.ErrNotImplemented 525 } 526 527 func (self *SDatastore) FileGetContent(ctx context.Context, remotePath string) ([]byte, error) { 528 url := self.GetPathUrl(remotePath) 529 530 req, err := http.NewRequest("GET", url, nil) 531 if err != nil { 532 return nil, err 533 } 534 535 var bytes []byte 536 537 err = self.manager.client.Do(ctx, req, func(resp *http.Response) error { 538 if resp.StatusCode == 404 { 539 return cloudprovider.ErrNotFound 540 } 541 if resp.StatusCode >= 400 { 542 return fmt.Errorf("%s", resp.Status) 543 } 544 cont, err := ioutil.ReadAll(resp.Body) 545 if err != nil { 546 return err 547 } 548 bytes = cont 549 return nil 550 }) 551 552 return bytes, err 553 } 554 555 type SDatastoreFileInfo struct { 556 Url string 557 Name string 558 Date time.Time 559 FileType string 560 Size uint64 561 } 562 563 const ( 564 fileListPattern = `<tr><td><a href="(?P<url>[\w\d:#@%/;$()~_?\+-=\\\.&]+)">(?P<name>[^<]+)<\/a></td><td align="right">(?P<date>[^<]+)</td><td align="right">(?P<size>[^<]+)</td></tr>` 565 fileDateFormat = "02-Jan-2006 15:04" 566 fileDateFormat2 = "Mon, 2 Jan 2006 15:04:05 GMT" 567 ) 568 569 var ( 570 fileListRegexp = regexp.MustCompile(fileListPattern) 571 ) 572 573 func (self *SDatastore) ListDir(ctx context.Context, remotePath string) ([]SDatastoreFileInfo, error) { 574 listContent, err := self.FileGetContent(ctx, remotePath) 575 if err != nil { 576 return nil, err 577 } 578 ret := make([]SDatastoreFileInfo, 0) 579 matches := fileListRegexp.FindAllStringSubmatch(string(listContent), -1) 580 for r := 0; r < len(matches); r += 1 { 581 url := strings.TrimSpace(matches[r][1]) 582 name := strings.TrimSpace(matches[r][2]) 583 dateStr := strings.TrimSpace(matches[r][3]) 584 sizeStr := strings.TrimSpace(matches[r][4]) 585 var ftype string 586 var size uint64 587 if sizeStr == "-" { 588 ftype = "dir" 589 size = 0 590 } else { 591 ftype = "file" 592 size, _ = strconv.ParseUint(sizeStr, 10, 64) 593 } 594 date, err := time.Parse(fileDateFormat, dateStr) 595 if err != nil { 596 return nil, err 597 } 598 info := SDatastoreFileInfo{ 599 Url: url, 600 Name: name, 601 FileType: ftype, 602 Size: size, 603 Date: date, 604 } 605 ret = append(ret, info) 606 } 607 608 return ret, nil 609 } 610 611 func (self *SDatastore) listPath(b *object.HostDatastoreBrowser, path string, spec types.HostDatastoreBrowserSearchSpec) ([]types.HostDatastoreBrowserSearchResults, error) { 612 ctx := context.TODO() 613 614 path = self.getDatastoreObj().Path(path) 615 616 search := b.SearchDatastore 617 618 task, err := search(ctx, path, &spec) 619 if err != nil { 620 return nil, err 621 } 622 623 info, err := task.WaitForResult(ctx, nil) 624 if err != nil { 625 return nil, err 626 } 627 628 switch r := info.Result.(type) { 629 case types.HostDatastoreBrowserSearchResults: 630 return []types.HostDatastoreBrowserSearchResults{r}, nil 631 case types.ArrayOfHostDatastoreBrowserSearchResults: 632 return r.HostDatastoreBrowserSearchResults, nil 633 default: 634 return nil, errors.Error(fmt.Sprintf("unknown result type: %T", r)) 635 } 636 637 } 638 639 func (self *SDatastore) ListPath(ctx context.Context, remotePath string) ([]types.HostDatastoreBrowserSearchResults, error) { 640 //types.HostDatastoreBrowserSearchResults 641 ds := self.getDatastoreObj() 642 643 b, err := ds.Browser(ctx) 644 if err != nil { 645 return nil, err 646 } 647 648 ret := make([]types.HostDatastoreBrowserSearchResults, 0) 649 650 spec := types.HostDatastoreBrowserSearchSpec{ 651 MatchPattern: []string{"*"}, 652 Details: &types.FileQueryFlags{ 653 FileType: true, 654 FileSize: true, 655 FileOwner: types.NewBool(true), // TODO: omitempty is generated, but seems to be required 656 Modification: true, 657 }, 658 } 659 660 for i := 0; ; i++ { 661 r, err := self.listPath(b, remotePath, spec) 662 if err != nil { 663 // Treat the argument as a match pattern if not found as directory 664 if i == 0 && types.IsFileNotFound(err) || isInvalid(err) { 665 spec.MatchPattern[0] = path.Base(remotePath) 666 remotePath = path.Dir(remotePath) 667 continue 668 } 669 if types.IsFileNotFound(err) { 670 return nil, errors.ErrNotFound 671 } 672 return nil, err 673 } 674 if i == 1 && len(r) == 1 && len(r[0].File) == 0 { 675 return nil, errors.ErrNotFound 676 } 677 for n := range r { 678 ret = append(ret, r[n]) 679 } 680 break 681 } 682 return ret, nil 683 } 684 685 func isInvalid(err error) bool { 686 if f, ok := err.(types.HasFault); ok { 687 switch f.Fault().(type) { 688 case *types.InvalidArgument: 689 return true 690 } 691 } 692 693 return false 694 } 695 696 func (self *SDatastore) CheckFile(ctx context.Context, remotePath string) (*SDatastoreFileInfo, error) { 697 url := self.GetPathUrl(remotePath) 698 699 req, err := http.NewRequest("HEAD", url, nil) 700 if err != nil { 701 return nil, err 702 } 703 704 var size uint64 705 var date time.Time 706 707 err = self.manager.client.Do(ctx, req, func(resp *http.Response) error { 708 if resp.StatusCode >= 400 { 709 if resp.StatusCode == 404 { 710 return cloudprovider.ErrNotFound 711 } 712 return fmt.Errorf("%s", resp.Status) 713 } 714 sizeStr := resp.Header.Get("Content-Length") 715 size, _ = strconv.ParseUint(sizeStr, 10, 64) 716 717 dateStr := resp.Header.Get("Date") 718 date, _ = time.Parse(fileDateFormat2, dateStr) 719 return nil 720 }) 721 722 if err != nil { 723 return nil, err 724 } 725 return &SDatastoreFileInfo{Date: date, Size: size}, nil 726 } 727 728 func (self *SDatastore) Download(ctx context.Context, remotePath string, writer io.Writer) error { 729 url := self.GetPathUrl(remotePath) 730 731 req, err := http.NewRequest("GET", url, nil) 732 if err != nil { 733 return err 734 } 735 736 err = self.manager.client.Do(ctx, req, func(resp *http.Response) error { 737 if resp.StatusCode >= 400 { 738 return fmt.Errorf("%s", resp.Status) 739 } 740 buffer := make([]byte, 4096) 741 for { 742 rn, re := resp.Body.Read(buffer) 743 if rn > 0 { 744 wo := 0 745 for wo < rn { 746 wn, we := writer.Write(buffer[wo:rn]) 747 if we != nil { 748 return we 749 } 750 wo += wn 751 } 752 } 753 if re != nil { 754 if re != io.EOF { 755 return re 756 } else { 757 break 758 } 759 } 760 } 761 return nil 762 }) 763 764 return err 765 } 766 767 func (self *SDatastore) Upload(ctx context.Context, remotePath string, body io.Reader) error { 768 url := self.GetPathUrl(remotePath) 769 770 req, err := http.NewRequest("PUT", url, body) 771 if err != nil { 772 return err 773 } 774 775 err = self.manager.client.Do(ctx, req, func(resp *http.Response) error { 776 if resp.StatusCode >= 400 { 777 return fmt.Errorf("%s", resp.Status) 778 } 779 _, err := ioutil.ReadAll(resp.Body) 780 if err != nil { 781 return err 782 } 783 // log.Debugf("upload respose %s", buffer) 784 return nil 785 }) 786 787 return err 788 } 789 790 func (self *SDatastore) FilePutContent(ctx context.Context, remotePath string, content string) error { 791 return self.Upload(ctx, remotePath, strings.NewReader(content)) 792 } 793 794 // Delete2 can delete file from this Datastore. 795 // isNamespace: remotePath is uuid of namespace on vsan datastore 796 // force: ignore nonexistent files and arguments 797 func (self *SDatastore) Delete2(ctx context.Context, remotePath string, isNamespace, force bool) error { 798 var err error 799 ds := self.getDatastoreObj() 800 dc := self.datacenter.getObjectDatacenter() 801 if isNamespace { 802 nm := object.NewDatastoreNamespaceManager(self.manager.client.Client) 803 err = nm.DeleteDirectory(ctx, dc, remotePath) 804 } else { 805 fm := ds.NewFileManager(dc, force) 806 err = fm.Delete(ctx, remotePath) 807 } 808 809 if err != nil && types.IsFileNotFound(err) && force { 810 // Ignore error 811 return nil 812 } 813 return err 814 } 815 816 func (self *SDatastore) Delete(ctx context.Context, remotePath string) error { 817 url := self.GetPathUrl(remotePath) 818 819 req, err := http.NewRequest("DELETE", url, nil) 820 if err != nil { 821 return err 822 } 823 824 err = self.manager.client.Do(ctx, req, func(resp *http.Response) error { 825 if resp.StatusCode >= 400 { 826 return fmt.Errorf("%s", resp.Status) 827 } 828 _, err := ioutil.ReadAll(resp.Body) 829 if err != nil { 830 return err 831 } 832 // log.Debugf("delete respose %s", buffer) 833 return nil 834 }) 835 836 return err 837 } 838 839 func (self *SDatastore) DeleteVmdk(ctx context.Context, remotePath string) error { 840 info, err := self.CheckFile(ctx, remotePath) 841 if err != nil { 842 return err 843 } 844 if info.Size > 4096 { 845 return fmt.Errorf("not a valid vmdk file") 846 } 847 vmdkContent, err := self.FileGetContent(ctx, remotePath) 848 if err != nil { 849 return err 850 } 851 vmdkInfo, err := vmdkutils.Parse(string(vmdkContent)) 852 if err != nil { 853 return err 854 } 855 err = self.Delete(ctx, remotePath) 856 if err != nil { 857 return err 858 } 859 if len(vmdkInfo.ExtentFile) > 0 { 860 err = self.Delete(ctx, path.Join(path.Dir(remotePath), vmdkInfo.ExtentFile)) 861 if err != nil { 862 return err 863 } 864 } 865 return nil 866 } 867 868 func (self *SDatastore) GetVmdkInfo(ctx context.Context, remotePath string) (*vmdkutils.SVMDKInfo, error) { 869 vmdkContent, err := self.FileGetContent(ctx, remotePath) 870 if err != nil { 871 return nil, err 872 } 873 return vmdkutils.Parse(string(vmdkContent)) 874 } 875 876 func (self *SDatastore) CheckVmdk(ctx context.Context, remotePath string) error { 877 dm := object.NewVirtualDiskManager(self.manager.client.Client) 878 879 dc, err := self.GetDatacenter() 880 if err != nil { 881 return err 882 } 883 884 dcObj := dc.getObjectDatacenter() 885 886 infoList, err := dm.QueryVirtualDiskInfo(ctx, self.getPathString(remotePath), dcObj, true) 887 if err != nil { 888 return err 889 } 890 891 log.Debugf("%#v", infoList) 892 return nil 893 } 894 895 func (self *SDatastore) getDatastoreObj() *object.Datastore { 896 od := object.NewDatastore(self.manager.client.Client, self.getDatastore().Self) 897 od.DatacenterPath = self.GetDatacenterPathString() 898 od.InventoryPath = fmt.Sprintf("%s/%s", od.DatacenterPath, self.SManagedObject.GetName()) 899 return od 900 } 901 902 func (self *SDatastore) MakeDir(remotePath string) error { 903 remotePath = self.cleanPath(remotePath) 904 905 m := object.NewFileManager(self.manager.client.Client) 906 path := fmt.Sprintf("[%s] %s", self.GetRelName(), remotePath) 907 return m.MakeDirectory(self.manager.context, path, self.datacenter.getObjectDatacenter(), true) 908 } 909 910 func (self *SDatastore) RemoveDir(ctx context.Context, remotePath string) error { 911 dnm := object.NewDatastoreNamespaceManager(self.manager.client.Client) 912 913 remotePath = self.GetFullPath(remotePath) 914 915 dc, err := self.GetDatacenter() 916 if err != nil { 917 return err 918 } 919 920 dcObj := dc.getObjectDatacenter() 921 922 return dnm.DeleteDirectory(ctx, dcObj, remotePath) 923 } 924 925 // CheckDirC will check that Dir 'remotePath' is exist, if not, create one. 926 func (self *SDatastore) CheckDirC(remotePath string) error { 927 _, err := self.CheckFile(self.manager.context, remotePath) 928 if err == nil { 929 return nil 930 } 931 if errors.Cause(err) != cloudprovider.ErrNotFound { 932 return err 933 } 934 return self.MakeDir(remotePath) 935 936 } 937 938 func (self *SDatastore) IsSysDiskStore() bool { 939 return true 940 } 941 942 func (self *SDatastore) MoveVmdk(ctx context.Context, srcPath string, dstPath string) error { 943 dm := object.NewVirtualDiskManager(self.manager.client.Client) 944 defer dm.Destroy(ctx) 945 946 srcUrl := self.GetPathUrl(srcPath) 947 dstUrl := self.GetPathUrl(dstPath) 948 task, err := dm.MoveVirtualDisk(ctx, srcUrl, nil, dstUrl, nil, true) 949 if err != nil { 950 return err 951 } 952 return task.Wait(ctx) 953 } 954 955 // domainName will abandon the rune which should't disapear 956 func (self *SDatastore) domainName(name string) string { 957 var b bytes.Buffer 958 for _, r := range name { 959 if b.Len() == 0 { 960 if unicode.IsLetter(r) { 961 b.WriteRune(r) 962 } 963 } else { 964 if unicode.IsDigit(r) || unicode.IsLetter(r) || r == '-' { 965 b.WriteRune(r) 966 } 967 } 968 } 969 return strings.TrimRight(b.String(), "-") 970 } 971 972 // ImportVMDK will upload local vmdk 'diskFile' to the 'remotePath' of remote datastore 973 func (self *SDatastore) ImportVMDK(ctx context.Context, diskFile, remotePath string, host *SHost) error { 974 name := fmt.Sprintf("yunioncloud.%s%d", self.domainName(remotePath)[:20], rand.Int()) 975 vm, err := self.ImportVM(ctx, diskFile, name, host) 976 if err != nil { 977 return errors.Wrap(err, "SDatastore.ImportVM") 978 } 979 980 defer func() { 981 task, err := vm.Destroy(ctx) 982 if err != nil { 983 log.Errorf("vm.Destory: %s", err) 984 return 985 } 986 987 if err = task.Wait(ctx); err != nil { 988 log.Errorf("task.Wait: %s", err) 989 } 990 }() 991 992 // check if 'image_cache' is esixt 993 err = self.CheckDirC("image_cache") 994 if err != nil { 995 return errors.Wrap(err, "SDatastore.CheckDirC") 996 } 997 998 fm := self.getDatastoreObj().NewFileManager(self.datacenter.getObjectDatacenter(), true) 999 1000 // if image_cache not exist 1001 return fm.Move(ctx, fmt.Sprintf("[%s] %s/%s.vmdk", self.GetRelName(), name, name), fmt.Sprintf("[%s] %s", 1002 self.GetRelName(), remotePath)) 1003 } 1004 1005 func (self *SDatastore) ImportISO(ctx context.Context, isoFile, remotePath string, host *SHost) error { 1006 p := soap.DefaultUpload 1007 ds := self.getDatastoreObj() 1008 return ds.UploadFile(ctx, isoFile, remotePath, &p) 1009 } 1010 1011 var ( 1012 ErrInvalidFormat = errors.Error("vmdk: invalid format (must be streamOptimized)") 1013 ) 1014 1015 // info is used to inspect a vmdk and generate an ovf template 1016 type info struct { 1017 Header struct { 1018 MagicNumber uint32 1019 Version uint32 1020 Flags uint32 1021 Capacity uint64 1022 } 1023 1024 Capacity uint64 1025 Size int64 1026 Name string 1027 ImportName string 1028 } 1029 1030 // stat looks at the vmdk header to make sure the format is streamOptimized and 1031 // extracts the disk capacity required to properly generate the ovf descriptor. 1032 func stat(name string) (*info, error) { 1033 f, err := os.Open(name) 1034 if err != nil { 1035 return nil, err 1036 } 1037 1038 var ( 1039 di info 1040 buf bytes.Buffer 1041 ) 1042 1043 _, err = io.CopyN(&buf, f, int64(binary.Size(di.Header))) 1044 fi, _ := f.Stat() 1045 _ = f.Close() 1046 if err != nil { 1047 return nil, err 1048 } 1049 1050 err = binary.Read(&buf, binary.LittleEndian, &di.Header) 1051 if err != nil { 1052 return nil, err 1053 } 1054 1055 if di.Header.MagicNumber != 0x564d444b { // SPARSE_MAGICNUMBER 1056 return nil, ErrInvalidFormat 1057 } 1058 1059 if di.Header.Flags&(1<<16) == 0 { // SPARSEFLAG_COMPRESSED 1060 // Needs to be converted, for example: 1061 // vmware-vdiskmanager -r src.vmdk -t 5 dst.vmdk 1062 // qemu-img convert -O vmdk -o subformat=streamOptimized src.vmdk dst.vmdk 1063 return nil, ErrInvalidFormat 1064 } 1065 1066 di.Capacity = di.Header.Capacity * 512 // VMDK_SECTOR_SIZE 1067 di.Size = fi.Size() 1068 di.Name = filepath.Base(name) 1069 di.ImportName = strings.TrimSuffix(di.Name, ".vmdk") 1070 1071 return &di, nil 1072 } 1073 1074 // ovf returns an expanded descriptor template 1075 func (di *info) ovf() (string, error) { 1076 var buf bytes.Buffer 1077 1078 tmpl, err := template.ParseFiles("/opt/yunion/share/vmware/ovf.xml") 1079 if err != nil { 1080 return "", err 1081 } 1082 1083 err = tmpl.Execute(&buf, di) 1084 if err != nil { 1085 return "", err 1086 } 1087 1088 return buf.String(), nil 1089 } 1090 1091 // ImportParams contains the set of optional params to the Import function. 1092 // Note that "optional" may depend on environment, such as ESX or vCenter. 1093 type ImportParams struct { 1094 Path string 1095 Logger progress.Sinker 1096 Type types.VirtualDiskType 1097 Force bool 1098 Datacenter *object.Datacenter 1099 Pool *object.ResourcePool 1100 Folder *object.Folder 1101 Host *object.HostSystem 1102 } 1103 1104 // ImportVM will import a vm by uploading a local vmdk 1105 func (self *SDatastore) ImportVM(ctx context.Context, diskFile, name string, host *SHost) (*object.VirtualMachine, error) { 1106 1107 var ( 1108 c = self.manager.client.Client 1109 datastore = self.getDatastoreObj() 1110 ) 1111 1112 m := ovf.NewManager(c) 1113 1114 disk, err := stat(diskFile) 1115 if err != nil { 1116 return nil, errors.Wrap(err, "stat") 1117 } 1118 1119 disk.ImportName = name 1120 1121 // Expand the ovf template 1122 descriptor, err := disk.ovf() 1123 if err != nil { 1124 return nil, errors.Wrap(err, "disk.ovf") 1125 } 1126 1127 folders, err := self.datacenter.getObjectDatacenter().Folders(ctx) 1128 if err != nil { 1129 return nil, errors.Wrap(err, "Folders") 1130 } 1131 pool, err := host.GetResourcePool() 1132 if err != nil { 1133 return nil, errors.Wrap(err, "getResourcePool") 1134 } 1135 1136 kind := types.VirtualDiskTypeThin 1137 1138 params := types.OvfCreateImportSpecParams{ 1139 DiskProvisioning: string(kind), 1140 EntityName: disk.ImportName, 1141 } 1142 1143 spec, err := m.CreateImportSpec(ctx, descriptor, pool, datastore, params) 1144 if err != nil { 1145 return nil, err 1146 } 1147 if spec.Error != nil { 1148 return nil, errors.Error(spec.Error[0].LocalizedMessage) 1149 } 1150 1151 lease, err := pool.ImportVApp(ctx, spec.ImportSpec, folders.VmFolder, host.GetoHostSystem()) 1152 if err != nil { 1153 return nil, err 1154 } 1155 1156 info, err := lease.Wait(ctx, spec.FileItem) 1157 if err != nil { 1158 return nil, err 1159 } 1160 1161 f, err := os.Open(diskFile) 1162 if err != nil { 1163 return nil, err 1164 } 1165 1166 lr := newLeaseLogger("upload vmdk", 5) 1167 1168 lr.Log() 1169 defer lr.End() 1170 1171 opts := soap.Upload{ 1172 ContentLength: disk.Size, 1173 Progress: lr, 1174 } 1175 1176 u := lease.StartUpdater(ctx, info) 1177 defer u.Done() 1178 1179 item := info.Items[0] // we only have 1 disk to upload 1180 1181 err = lease.Upload(ctx, item, f, opts) 1182 1183 _ = f.Close() 1184 1185 if err != nil { 1186 return nil, errors.Wrap(err, "lease.Upload") 1187 } 1188 1189 if err = lease.Complete(ctx); err != nil { 1190 log.Debugf("lease complete error: %s, sleep 1s and try again", err) 1191 time.Sleep(time.Second) 1192 if err = lease.Complete(ctx); err != nil { 1193 return nil, errors.Wrap(err, "lease.Complete") 1194 } 1195 } 1196 1197 return object.NewVirtualMachine(c, info.Entity), nil 1198 }