From 4d43fcd39cbe0f97e9c9e18cf862c26718989f55 Mon Sep 17 00:00:00 2001 From: Qiu Jian Date: Wed, 7 Nov 2018 15:47:46 +0800 Subject: [PATCH 01/29] =?UTF-8?q?=E5=A2=9E=E5=8A=A0server=20sku=E8=B5=84?= =?UTF-8?q?=E6=BA=90=EF=BC=8C=E8=BF=98=E9=9C=80=E8=A6=81=E5=92=8C=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E8=B5=84=E6=BA=90=E6=B5=81=E7=A8=8B=EF=BC=8C=E4=BB=A5?= =?UTF-8?q?=E5=8F=8A=E5=85=AC=E6=9C=89=E4=BA=91=E6=8E=A5=E5=8F=A3=E6=94=B9?= =?UTF-8?q?=E9=80=A0=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/climc/shell/skus.go | 131 ++++++++++++++++++++++++++ pkg/compute/handlers.go | 2 + pkg/compute/models/cloudregions.go | 14 +-- pkg/compute/models/guests.go | 2 + pkg/compute/models/skus.go | 132 ++++++++++++++++++++++++++ pkg/compute/models/vpcs.go | 14 +-- pkg/mcclient/modules/mod_skus.go | 17 ++++ pkg/util/aliyun/instancetype.go | 2 + pkg/util/azure/region.go | 11 ++- pkg/util/azure/resourcesku.go | 145 +++++++++++++++++++++++++++++ pkg/util/azure/shell/instance.go | 13 ++- 11 files changed, 464 insertions(+), 19 deletions(-) create mode 100644 cmd/climc/shell/skus.go create mode 100644 pkg/compute/models/skus.go create mode 100644 pkg/mcclient/modules/mod_skus.go create mode 100644 pkg/util/azure/resourcesku.go diff --git a/cmd/climc/shell/skus.go b/cmd/climc/shell/skus.go new file mode 100644 index 0000000000..57b135e78f --- /dev/null +++ b/cmd/climc/shell/skus.go @@ -0,0 +1,131 @@ +package shell + +import ( + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + type ServerSkusListOptions struct { + options.BaseListOptions + } + R(&ServerSkusListOptions{}, "server-sku-list", "List all avaiable Server SKU", func(s *mcclient.ClientSession, args *ServerSkusListOptions) error { + params, err := options.ListStructToParams(args) + if err != nil { + return err + } + results, err := modules.ServerSkus.List(s, params) + if err != nil { + return err + } + printList(results, modules.ServerSkus.GetColumns(s)) + return nil + }) + + type ServerSkusShowOptions struct { + ID string `help:"ID or Name of SKU to show"` + } + R(&ServerSkusShowOptions{}, "server-sku-show", "show details of a avaiable Server SKU", func(s *mcclient.ClientSession, args *ServerSkusShowOptions) error { + result, err := modules.ServerSkus.Get(s, args.ID, nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type ServerSkusCreateOptions struct { + Name string `help:"Name ID of SKU" required:"true" positional:"true"` + CpuCoreCount int `help:"Cpu Count" required:"true" positional:"true"` + MemorySizeMB int `help:"Memory MB" required:"true" positional:"true"` + + SkuFamily *string `help:"sku family"` + SkuCategory *string `help:"sku category" choices:"general_purpose|compute_optimized|memory_optimized|storage_optimized|hardware_accelerated|high_memory|high_storage"` + + SysDiskResizable *bool `help:"system disk is resizable"` + SysDiskMaxSizeGB *int `help:"system disk maximal size in gb"` + + AttachedDiskType *string `help:"attached data disk type"` + AttachedDiskSizeGB *int `help:"attached data disk size in GB"` + AttachedDiskCount *int `help:"attached data disk count"` + + MaxDataDiskCount *int `help:"maximal allowed data disk count"` + + NicType *string `help:"nic type"` + MaxNicCount *int `help:"maximal nic count"` + + GPUSpec *string `help:"GPU spec"` + GPUCount *int `help:"GPU count"` + GPUAttachable *bool `help:"Allow attach GPU"` + + Zone *string `help:"Zone ID or name"` + Region *string `help:"Region ID or name"` + } + R(&ServerSkusCreateOptions{}, "server-sku-create", "Create a server sku record", func(s *mcclient.ClientSession, args *ServerSkusCreateOptions) error { + params, err := options.StructToParams(args) + if err != nil { + return err + } + result, err := modules.ServerSkus.Create(s, params) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type ServerSkusUpdateOptions struct { + ID string `help:"Name or ID of SKU" json:"-"` + + Name *string `help:"new name of SKU"` + CpuCoreCount *int `help:"Cpu Count"` + MemorySizeMB *int `help:"Memory MB"` + + SkuFamily *string `help:"sku family"` + SkuCategory *string `help:"sku category" choices:"general_purpose|compute_optimized|memory_optimized|storage_optimized|hardware_accelerated|high_memory|high_storage"` + + SysDiskResizable *bool `help:"system disk is resizable"` + SysDiskMaxSizeGB *int `help:"system disk maximal size in gb"` + + AttachedDiskType *string `help:"attached data disk type"` + AttachedDiskSizeGB *int `help:"attached data disk size in GB"` + AttachedDiskCount *int `help:"attached data disk count"` + + MaxDataDiskCount *int `help:"maximal allowed data disk count"` + + NicType *string `help:"nic type"` + MaxNicCount *int `help:"maximal nic count"` + + GPUSpec *string `help:"GPU spec"` + GPUCount *int `help:"GPU count"` + GPUAttachable *bool `help:"Allow attach GPU"` + + Zone *string `help:"Zone ID or name"` + Region *string `help:"Region ID or name"` + } + R(&ServerSkusUpdateOptions{}, "server-sku-update", "Update server sku attributes", func(s *mcclient.ClientSession, args *ServerSkusUpdateOptions) error { + params, err := options.StructToParams(args) + if err != nil { + return err + } + result, err := modules.ServerSkus.Update(s, args.ID, params) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type ServerSkusDeleteOptions struct { + ID string `help:"Id or name of server sku"` + } + R(&ServerSkusDeleteOptions{}, "server-sku-delete", "Delete a server sku", func(s *mcclient.ClientSession, args *ServerSkusDeleteOptions) error { + result, err := modules.ServerSkus.Delete(s, args.ID, nil) + if err != nil { + return err + } + printObject(result) + return nil + }) +} diff --git a/pkg/compute/handlers.go b/pkg/compute/handlers.go index 22550bf00e..6d7ed755f0 100644 --- a/pkg/compute/handlers.go +++ b/pkg/compute/handlers.go @@ -74,6 +74,8 @@ func InitHandlers(app *appsrv.Application) { models.SchedpolicyManager, models.DynamicschedtagManager, + + models.ServerSkuManager, } { db.RegisterModelManager(manager) handler := db.NewModelHandler(manager) diff --git a/pkg/compute/models/cloudregions.go b/pkg/compute/models/cloudregions.go index 3e4c931cc8..bf2fcd1eb3 100644 --- a/pkg/compute/models/cloudregions.go +++ b/pkg/compute/models/cloudregions.go @@ -19,6 +19,8 @@ import ( const ( CLOUD_REGION_STATUS_INSERVER = "inservice" CLOUD_REGION_STATUS_OUTOFSERVICE = "outofservice" + + DEFAULT_REGION_ID = "default" ) type SCloudregionManager struct { @@ -64,7 +66,7 @@ func (self *SCloudregion) ValidateDeleteCondition(ctx context.Context) error { if self.GetZoneCount() > 0 || self.GetVpcCount() > 0 { return httperrors.NewNotEmptyError("not empty cloud region") } - if self.Id == "default" { + if self.Id == DEFAULT_REGION_ID { return httperrors.NewProtectedResourceError("not allow to delete default cloud region") } return self.SEnabledStatusStandaloneResourceBase.ValidateDeleteCondition(ctx) @@ -72,7 +74,7 @@ func (self *SCloudregion) ValidateDeleteCondition(ctx context.Context) error { func (self *SCloudregion) GetZoneCount() int { zones := ZoneManager.Query() - if self.Id == "default" { + if self.Id == DEFAULT_REGION_ID { return zones.Filter(sqlchemy.OR(sqlchemy.IsNull(zones.Field("cloudregion_id")), sqlchemy.IsEmpty(zones.Field("cloudregion_id")), sqlchemy.Equals(zones.Field("cloudregion_id"), self.Id))).Count() @@ -83,7 +85,7 @@ func (self *SCloudregion) GetZoneCount() int { func (self *SCloudregion) GetGuestCount(increment bool) int { zoneTable := ZoneManager.Query("id") - if self.Id == "default" { + if self.Id == DEFAULT_REGION_ID { zoneTable = zoneTable.Filter(sqlchemy.OR(sqlchemy.IsNull(zoneTable.Field("cloudregion_id")), sqlchemy.IsEmpty(zoneTable.Field("cloudregion_id")), sqlchemy.Equals(zoneTable.Field("cloudregion_id"), self.Id))) @@ -102,7 +104,7 @@ func (self *SCloudregion) GetGuestCount(increment bool) int { func (self *SCloudregion) GetVpcCount() int { vpcs := VpcManager.Query() - if self.Id == "default" { + if self.Id == DEFAULT_REGION_ID { return vpcs.Filter(sqlchemy.OR(sqlchemy.IsNull(vpcs.Field("cloudregion_id")), sqlchemy.IsEmpty(vpcs.Field("cloudregion_id")), sqlchemy.Equals(vpcs.Field("cloudregion_id"), self.Id))).Count() @@ -294,11 +296,11 @@ func (manager *SCloudregionManager) FetchRegionById(id string) *SCloudregion { func (manager *SCloudregionManager) InitializeData() error { // check if default region exists - _, err := manager.FetchById("default") + _, err := manager.FetchById(DEFAULT_REGION_ID) if err != nil { if err == sql.ErrNoRows { defRegion := SCloudregion{} - defRegion.Id = "default" + defRegion.Id = DEFAULT_REGION_ID defRegion.Name = "Default" defRegion.Enabled = true defRegion.Description = "Default Region" diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index df16410468..21115e52d6 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -201,6 +201,8 @@ type SGuest struct { AdminSecgrpId string `width:"36" charset:"ascii" nullable:"true" get:"admin"` // Column(VARCHAR(36, charset='ascii'), nullable=True) Hypervisor string `width:"16" charset:"ascii" nullable:"false" default:"kvm" list:"user" create:"required"` // Column(VARCHAR(16, charset='ascii'), nullable=False, default=HYPERVISOR_DEFAULT) + + SkuId string `width:"64" charset:"ascii" nullable:"true" list:"user" create:"optional"` } func (manager *SGuestManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { diff --git a/pkg/compute/models/skus.go b/pkg/compute/models/skus.go new file mode 100644 index 0000000000..56f30fbd18 --- /dev/null +++ b/pkg/compute/models/skus.go @@ -0,0 +1,132 @@ +package models + +import ( + "context" + + "database/sql" + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" +) + +const ( + SkuCategoryGeneralPurpose = "general_purpose" // 通用型 + SkuCategoryBurstable = "burstable" // 突发性能型 + SkuCategoryComputeOptimized = "compute_optimized" // 计算优化型 + SkuCategoryMemoryOptimized = "memory_optimized" // 内存优化型 + SkuCategoryStorageIOOptimized = "storage_optimized" // 存储IO优化型 + SkuCategoryHardwareAccelerated = "hardware_accelerated" // 硬件加速型 + SkuCategoryHighStorage = "high_storage" // 高存储型 + SkuCategoryHighMemory = "high_memory" // 高内存型 +) + +type SServerSkuManager struct { + db.SStandaloneResourceBaseManager + SInfrastructureManager +} + +var ServerSkuManager *SServerSkuManager + +func init() { + ServerSkuManager = &SServerSkuManager{ + SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager( + SServerSku{}, + "serverskus_tbl", + "serversku", + "serverskus", + ), + } + ServerSkuManager.NameRequireAscii = false +} + +type SServerSku struct { + db.SStandaloneResourceBase + SInfrastructure + + // SkuId string `width:"64" charset:"ascii" nullable:"false" list:"user" create:"admin_required"` + SkuFamily string `width:"32" charset:"ascii" nullable:"false" list:"user" create:"admin_optional" update:"admin"` + SkuCategory string `width:"32" charset:"ascii" nullable:"false" list:"user" create:"admin_optional" update:"admin"` + + CpuCoreCount int `nullable:"false" list:"user" create:"admin_required" update:"admin"` + MemorySizeMB int `nullable:"false" list:"user" create:"admin_required" update:"admin"` + + SysDiskResizable bool `default:"true" nullable:"false" list:"user" create:"admin_optional" update:"admin"` + SysDiskMaxSizeGB int `nullable:"false" list:"user" create:"admin_optional" update:"admin"` + + AttachedDiskType string `nullable:"false" list:"user" create:"admin_optional" update:"admin"` + AttachedDiskSizeGB int `nullable:"false" list:"user" create:"admin_optional" update:"admin"` + AttachedDiskCount int `nullable:"false" list:"user" create:"admin_optional" update:"admin"` + + MaxDataDiskCount int `nullable:"false" list:"user" create:"admin_optional" update:"admin"` + + NicType string `nullable:"false" list:"user" create:"admin_optional" update:"admin"` + MaxNicCount int `default:"1" nullable:"false" list:"user" create:"admin_optional" update:"admin"` + + GpuAttachable bool `default:"true" nullable:"false" list:"user" create:"admin_optional" update:"admin"` + GpuSpec string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"admin_optional" update:"admin"` + GpuCount int `nullable:"false" list:"user" create:"admin_optional" update:"admin"` + + CloudregionId string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"admin_required" update:"admin"` + ZoneId string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"admin_optional" update:"admin"` +} + +func (self *SServerSkuManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return true +} + +func (self *SServerSku) AllowGetDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return true +} + +func (self *SServerSkuManager) ValidateCreateData(ctx context.Context, + userCred mcclient.TokenCredential, + ownerProjId string, + query jsonutils.JSONObject, + data *jsonutils.JSONDict, +) (*jsonutils.JSONDict, error) { + regionStr := jsonutils.GetAnyString(data, []string{"region", "region_id", "cloudregion", "cloudregion_id"}) + if len(regionStr) > 0 { + regionObj, err := CloudregionManager.FetchByIdOrName(userCred, regionStr) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError("region %s not found", regionStr) + } + return nil, httperrors.NewGeneralError(err) + } + data.Add(jsonutils.NewString(regionObj.GetId()), "cloudregion_id") + } else { + data.Add(jsonutils.NewString(DEFAULT_REGION_ID), "cloudregion_id") + } + zoneStr := jsonutils.GetAnyString(data, []string{"zone", "zone_id"}) + if len(zoneStr) > 0 { + zoneObj, err := ZoneManager.FetchByIdOrName(userCred, zoneStr) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError("zone %s not found", zoneStr) + } + return nil, httperrors.NewGeneralError(err) + } + data.Add(jsonutils.NewString(zoneObj.GetId()), "zone_id") + } + return self.SStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data) +} + +func (self *SServerSku) ValidateUpdateData( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + zoneStr := jsonutils.GetAnyString(data, []string{"zone", "zone_id"}) + if len(zoneStr) > 0 { + zoneObj, err := ZoneManager.FetchByIdOrName(userCred, zoneStr) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError("zone %s not found", zoneStr) + } + return nil, httperrors.NewGeneralError(err) + } + data.Add(jsonutils.NewString(zoneObj.GetId()), "zone_id") + } + return self.SStandaloneResourceBase.ValidateUpdateData(ctx, userCred, query, data) +} diff --git a/pkg/compute/models/vpcs.go b/pkg/compute/models/vpcs.go index 6bc2ffca33..27b38f3545 100644 --- a/pkg/compute/models/vpcs.go +++ b/pkg/compute/models/vpcs.go @@ -29,6 +29,8 @@ const ( VPC_STATUS_UNKNOWN = "unknown" MAX_VPC_PER_REGION = 3 + + DEFAULT_VPC_ID = "default" ) type SVpcManager struct { @@ -67,7 +69,7 @@ func (manager *SVpcManager) GetContextManager() []db.IModelManager { func (self *SVpc) GetCloudRegionId() string { if len(self.CloudregionId) == 0 { - return "default" + return DEFAULT_REGION_ID } else { return self.CloudregionId } @@ -85,7 +87,7 @@ func (self *SVpc) ValidateDeleteCondition(ctx context.Context) error { if self.GetNetworkCount() > 0 { return httperrors.NewNotEmptyError("VPC not empty") } - if self.Id == "default" { + if self.Id == DEFAULT_VPC_ID { return httperrors.NewProtectedResourceError("not allow to delete default vpc") } return self.SEnabledStatusStandaloneResourceBase.ValidateDeleteCondition(ctx) @@ -93,7 +95,7 @@ func (self *SVpc) ValidateDeleteCondition(ctx context.Context) error { func (self *SVpc) getWireQuery() *sqlchemy.SQuery { wires := WireManager.Query() - if self.Id == "default" { + if self.Id == DEFAULT_VPC_ID { return wires.Filter(sqlchemy.OR(sqlchemy.IsNull(wires.Field("vpc_id")), sqlchemy.IsEmpty(wires.Field("vpc_id")), sqlchemy.Equals(wires.Field("vpc_id"), self.Id))) @@ -308,15 +310,15 @@ func (self *SVpc) markAllNetworksUnknown(userCred mcclient.TokenCredential) erro } func (manager *SVpcManager) InitializeData() error { - vpcObj, err := manager.FetchById("default") + vpcObj, err := manager.FetchById(DEFAULT_VPC_ID) if err != nil { if err == sql.ErrNoRows { defVpc := SVpc{} defVpc.SetModelManager(VpcManager) - defVpc.Id = "default" + defVpc.Id = DEFAULT_VPC_ID defVpc.Name = "Default" - defVpc.CloudregionId = "default" + defVpc.CloudregionId = DEFAULT_REGION_ID defVpc.Description = "Default VPC" defVpc.Status = VPC_STATUS_AVAILABLE defVpc.IsDefault = true diff --git a/pkg/mcclient/modules/mod_skus.go b/pkg/mcclient/modules/mod_skus.go new file mode 100644 index 0000000000..bb381cab1a --- /dev/null +++ b/pkg/mcclient/modules/mod_skus.go @@ -0,0 +1,17 @@ +package modules + +var ( + ServerSkus ResourceManager +) + +func init() { + ServerSkus = NewComputeManager("serversku", + "serverskus", + []string{"id", "name", "sku_id", + "sku_family", "cpu_core_count", "memory_size_mb", + "cloudregion_id", "zone_id", + }, + []string{}) + + registerComputeV2(&ServerSkus) +} diff --git a/pkg/util/aliyun/instancetype.go b/pkg/util/aliyun/instancetype.go index 49b4a1c3ad..75b730725d 100644 --- a/pkg/util/aliyun/instancetype.go +++ b/pkg/util/aliyun/instancetype.go @@ -9,12 +9,14 @@ import ( // InstanceBandwidthRx":26214400,"InstanceBandwidthTx":26214400,"InstancePpsRx":4500000,"InstancePpsTx":4500000 type SInstanceType struct { + BaselineCredit int CpuCoreCount int MemorySize float32 EniQuantity int // 实例规格支持网卡数量 GPUAmount int GPUSpec string InstanceTypeFamily string + InstanceFamilyLevel string InstanceTypeId string LocalStorageCategory string LocalStorageAmount int diff --git a/pkg/util/azure/region.go b/pkg/util/azure/region.go index 20b018f4a4..ff634bdb8c 100644 --- a/pkg/util/azure/region.go +++ b/pkg/util/azure/region.go @@ -46,8 +46,11 @@ func (self *SRegion) GetClient() *SAzureClient { return self.client } -func (self *SRegion) GetVMSize() (map[string]SVMSize, error) { - body, err := self.client.ListVmSizes(self.Name) +func (self *SRegion) GetVMSize(location string) (map[string]SVMSize, error) { + if len(location) == 0 { + location = self.Name + } + body, err := self.client.ListVmSizes(location) if err != nil { return nil, err } @@ -64,7 +67,7 @@ func (self *SRegion) GetVMSize() (map[string]SVMSize, error) { } func (self *SRegion) getHardwareProfile(cpu, memMB int) []string { - if vmSizes, err := self.GetVMSize(); err != nil { + if vmSizes, err := self.GetVMSize(""); err != nil { return []string{} } else { profiles := make([]string, 0) @@ -78,7 +81,7 @@ func (self *SRegion) getHardwareProfile(cpu, memMB int) []string { } func (self *SRegion) getVMSize(size string) (*SVMSize, error) { - vmSizes, err := self.GetVMSize() + vmSizes, err := self.GetVMSize("") if err != nil { return nil, err } diff --git a/pkg/util/azure/resourcesku.go b/pkg/util/azure/resourcesku.go new file mode 100644 index 0000000000..b26207fe74 --- /dev/null +++ b/pkg/util/azure/resourcesku.go @@ -0,0 +1,145 @@ +package azure + +import ( + "fmt" + "yunion.io/x/pkg/utils" +) + +/* +{ + "capabilities":[ + {"name":"MaxResourceVolumeMB","value":"286720"}, + {"name":"OSVhdSizeMB","value":"1047552"}, + {"name":"vCPUs","value":"20"}, + {"name":"MemoryGB","value":"140"}, + {"name":"MaxDataDiskCount","value":"64"}, + {"name":"LowPriorityCapable","value":"True"}, + {"name":"PremiumIO","value":"True"}, + {"name":"EphemeralOSDiskSupported","value":"True"} + ], + "family":"standardDSv2Family", + "locations":["CentralUSEUAP"], + "name":"Standard_DS15_v2", + "resourceType":"virtualMachines", + "restrictions":[], + "size":"DS15_v2", + "tier":"Standard" +} +*/ + +type SResourceSkuCapability struct { + Name string + Value string +} + +type TResourceSkuCapacityScaleType string + +const ( + ResourceSkuCapacityScaleTypeAutomatic = TResourceSkuCapacityScaleType("Automatic") + ResourceSkuCapacityScaleTypeManual = TResourceSkuCapacityScaleType("Manual") + ResourceSkuCapacityScaleTypeNone = TResourceSkuCapacityScaleType("None") +) + +type SResourceSkuCapacity struct { + Default int + Maximum int + Minimum int + ScaleType TResourceSkuCapacityScaleType +} + +type SResourceSkuLocationInfo struct { + Location string + Zones []string +} + +type TResourceSkuRestrictionsType string + +const ( + ResourceSkuRestrictionsTypeLocation = TResourceSkuRestrictionsType("Location") + ResourceSkuRestrictionsTypeZone = TResourceSkuRestrictionsType("Zone") +) + +type TResourceSkuRestrictionsReasonCode string + +const ( + ResourceSkuRestrictionsReasonCodeNotAvailable = TResourceSkuRestrictionsReasonCode("NotAvailableForSubscription") + ResourceSkuRestrictionsReasonCodeQuotaId = TResourceSkuRestrictionsReasonCode("QuotaId") +) + +type SResourceSkuRestrictionInfo struct { + Locations []string + Zones []string +} + +type SResourceSkuRestrictions struct { + ReasonCode TResourceSkuRestrictionsReasonCode + RestrictionInfo SResourceSkuRestrictionInfo + Type TResourceSkuRestrictionsType + Values []string +} + +type SResourceSku struct { + Capabilities []SResourceSkuCapability + Capacity *SResourceSkuCapacity + Family string + Kind string + LocationInfo []SResourceSkuLocationInfo + Locations []string + Name string + ResourceType string + Restrictions []SResourceSkuRestrictions + Size string + Tier string +} + +type SResourceSkusResult struct { + NextLink string + Value []SResourceSku +} + +func (self *SAzureClient) ListResourceSkus() ([]SResourceSku, error) { + cli, err := self.getDefaultClient() + if err != nil { + return nil, err + } + if len(self.subscriptionId) == 0 { + return nil, fmt.Errorf("need subscription id") + } + url := fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Compute/skus?api-version=2017-09-01", self.subscriptionId) + skus := make([]SResourceSku, 0) + for { + body, err := jsonRequest(cli, "GET", self.domain, url, "") + if err != nil { + return nil, err + } + result := SResourceSkusResult{} + err = body.Unmarshal(&result) + if err != nil { + return nil, err + } + skus = append(skus, result.Value...) + if len(result.NextLink) > 0 { + url = result.NextLink + } else { + break + } + } + return skus, nil +} + +func (self *SRegion) GetResourceSkus(location string) ([]SResourceSku, error) { + skus, err := self.client.ListResourceSkus() + if err != nil { + return nil, err + } + if len(location) == 0 { + return skus, nil + } + ret := make([]SResourceSku, 0) + for i := 0; i < len(skus); i += 1 { + if utils.IsInStringArray(location, skus[i].Locations) { + ret = append(ret, skus[i]) + } + } + return ret, nil +} diff --git a/pkg/util/azure/shell/instance.go b/pkg/util/azure/shell/instance.go index a5e5f6987a..9013ae394a 100644 --- a/pkg/util/azure/shell/instance.go +++ b/pkg/util/azure/shell/instance.go @@ -39,18 +39,25 @@ func init() { }) type InstanceSizeListOptions struct { - Limit int `help:"page size"` - Offset int `help:"page offset"` + Location string } shellutils.R(&InstanceSizeListOptions{}, "instance-size-list", "List intances", func(cli *azure.SRegion, args *InstanceSizeListOptions) error { - if vmSize, err := cli.GetVMSize(); err != nil { + if vmSize, err := cli.GetVMSize(args.Location); err != nil { return err } else { printObject(vmSize) return nil } }) + shellutils.R(&InstanceSizeListOptions{}, "resource-sku-list", "List resource sku", func(cli *azure.SRegion, args *InstanceSizeListOptions) error { + skus, err := cli.GetResourceSkus(args.Location) + if err != nil { + return err + } + printList(skus, len(skus), 0, 0, []string{}) + return nil + }) type InstanceCrateOptions struct { NAME string `help:"name of instance"` From 5631f5ed68cb4bf08f6d67b6f49e20b214f2affb Mon Sep 17 00:00:00 2001 From: TangBin Date: Mon, 12 Nov 2018 16:05:31 +0800 Subject: [PATCH 02/29] disk create fix --- pkg/util/aws/disk.go | 6 ++++++ pkg/util/aws/instance.go | 10 ++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/pkg/util/aws/disk.go b/pkg/util/aws/disk.go index edef37c559..2b3535f6ad 100644 --- a/pkg/util/aws/disk.go +++ b/pkg/util/aws/disk.go @@ -389,5 +389,11 @@ func (self *SRegion) CreateDisk(zoneId string, category string, name string, siz return "", err } + paramsWait := &ec2.DescribeVolumesInput{} + paramsWait.SetVolumeIds([]*string{ret.VolumeId}) + err = self.ec2Client.WaitUntilVolumeAvailable(paramsWait) + if err != nil { + return "", err + } return StrVal(ret.VolumeId), nil } diff --git a/pkg/util/aws/instance.go b/pkg/util/aws/instance.go index 4deb29516e..13748b02fd 100644 --- a/pkg/util/aws/instance.go +++ b/pkg/util/aws/instance.go @@ -374,12 +374,18 @@ func (self *SInstance) GetVNCInfo() (jsonutils.JSONObject, error) { } func (self *SInstance) AttachDisk(diskId string) error { - // todo:bugfix . self.DeviceNames => self.GetDeviceNames() name, err := NextDeviceName(self.DeviceNames) if err != nil { return err } - return self.host.zone.region.AttachDisk(self.InstanceId, diskId, name) + + err = self.host.zone.region.AttachDisk(self.InstanceId, diskId, name) + if err != nil { + return err + } + + self.DeviceNames = append(self.DeviceNames, name) + return nil } func (self *SInstance) DetachDisk(diskId string) error { From 6ec78519cb95235d87ebb766912763af3186cd5b Mon Sep 17 00:00:00 2001 From: TangBin Date: Mon, 12 Nov 2018 17:09:44 +0800 Subject: [PATCH 03/29] detach disk add more log --- pkg/util/aws/disk.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/util/aws/disk.go b/pkg/util/aws/disk.go index 2b3535f6ad..a1a304162c 100644 --- a/pkg/util/aws/disk.go +++ b/pkg/util/aws/disk.go @@ -338,6 +338,7 @@ func (self *SRegion) DeleteDisk(diskId string) error { } params.SetVolumeId(diskId) + log.Debugf("DeleteDisk with params: %s", params.String()) _, err = self.ec2Client.DeleteVolume(params) return err } From c8f2510fcba5622a1f1d45b87cc31c32359203f3 Mon Sep 17 00:00:00 2001 From: TangBin Date: Mon, 12 Nov 2018 18:11:24 +0800 Subject: [PATCH 04/29] snapshot fix --- pkg/util/aws/snapshot.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pkg/util/aws/snapshot.go b/pkg/util/aws/snapshot.go index b7a2a9241b..c1a13fc38d 100644 --- a/pkg/util/aws/snapshot.go +++ b/pkg/util/aws/snapshot.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/aws/aws-sdk-go/service/ec2" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" ) @@ -11,9 +12,9 @@ import ( type SnapshotStatusType string const ( - SnapshotStatusAccomplished SnapshotStatusType = "accomplished" - SnapshotStatusProgress SnapshotStatusType = "progressing" - SnapshotStatusFailed SnapshotStatusType = "failed" + SnapshotStatusAccomplished SnapshotStatusType = "completed" + SnapshotStatusProgress SnapshotStatusType = "pending" + SnapshotStatusFailed SnapshotStatusType = "error" ) type SSnapshot struct { @@ -176,8 +177,9 @@ func (self *SRegion) CreateSnapshot(diskId, name, desc string) (string, error) { } params.SetDescription(desc) - _, err := self.ec2Client.CreateSnapshot(params) - return "", err + log.Debugf("CreateSnapshots with params %s", params) + ret, err := self.ec2Client.CreateSnapshot(params) + return StrVal(ret.SnapshotId), err } func (self *SRegion) DeleteSnapshot(snapshotId string) error { From e11a46058594e8c3a1abba6c7f2410792844417b Mon Sep 17 00:00:00 2001 From: Yousong Zhou Date: Tue, 13 Nov 2018 06:37:58 +0000 Subject: [PATCH 05/29] climc: add commands yunionagent-version-list, yunionagent-version-show --- cmd/climc/shell/version.go | 33 ++++++++++++++++++++++++++++++++- pkg/mcclient/options/version.go | 9 +++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 pkg/mcclient/options/version.go diff --git a/cmd/climc/shell/version.go b/cmd/climc/shell/version.go index 5a28eaf0a5..e85f22c3d3 100644 --- a/cmd/climc/shell/version.go +++ b/cmd/climc/shell/version.go @@ -2,15 +2,17 @@ package shell import ( "fmt" + "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" ) func init() { type VersionOptions struct { SERVICE string `help:"Service type"` } - R(&VersionOptions{}, "version-show", "Show version of a backend service", func(s *mcclient.ClientSession, args *VersionOptions) error { + R(&VersionOptions{}, "version-show", "query backend service for its version", func(s *mcclient.ClientSession, args *VersionOptions) error { body, err := modules.GetVersion(s, args.SERVICE) if err != nil { return err @@ -18,4 +20,33 @@ func init() { fmt.Println(body) return nil }) + + R(&options.VersionListOptions{}, "yunionagent-version-list", "show versions of backend services", func(s *mcclient.ClientSession, opts *options.VersionListOptions) error { + if len(opts.Region) == 0 { + opts.Region = s.GetRegion() + } + params, err := options.StructToParams(opts) + if err != nil { + return err + } + result, err := modules.Version.List(s, params) + if err != nil { + return err + } + printList(result, []string{}) + return nil + }) + + R(&options.VersionGetOptions{}, "yunionagent-version-show", "Show service version", func(s *mcclient.ClientSession, opts *options.VersionGetOptions) error { + result, err := modules.Version.Get(s, opts.Service, nil) + if err != nil { + return err + } + ver, err := result.GetString() + if err != nil { + return err + } + fmt.Printf("%s\n", ver) + return nil + }) } diff --git a/pkg/mcclient/options/version.go b/pkg/mcclient/options/version.go new file mode 100644 index 0000000000..99bf335773 --- /dev/null +++ b/pkg/mcclient/options/version.go @@ -0,0 +1,9 @@ +package options + +type VersionListOptions struct { + Region string +} + +type VersionGetOptions struct { + Service string `choices:"cloud" default:"cloud"` +} From 826717129b0108667085e5879b121802392296a6 Mon Sep 17 00:00:00 2001 From: TangBin Date: Tue, 13 Nov 2018 15:57:05 +0800 Subject: [PATCH 06/29] add more log --- pkg/compute/models/cloudaccounts.go | 1 + pkg/compute/models/cloudproviders.go | 2 +- pkg/util/aws/aws.go | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/compute/models/cloudaccounts.go b/pkg/compute/models/cloudaccounts.go index b6c7672bf4..0c40545812 100644 --- a/pkg/compute/models/cloudaccounts.go +++ b/pkg/compute/models/cloudaccounts.go @@ -113,6 +113,7 @@ func (manager *SCloudaccountManager) ValidateCreateData(ctx context.Context, use if err == cloudprovider.ErrNoSuchProvder { return nil, httperrors.NewResourceNotFoundError("no such provider %s", provider) } + log.Debugf("ValidateCreateData %s", err.Error()) return nil, httperrors.NewInvalidCredentialError("invalid cloud account info") } diff --git a/pkg/compute/models/cloudproviders.go b/pkg/compute/models/cloudproviders.go index 5b25fb1cbb..fc8607ce7e 100644 --- a/pkg/compute/models/cloudproviders.go +++ b/pkg/compute/models/cloudproviders.go @@ -429,7 +429,7 @@ func (self *SCloudprovider) SaveSysInfo(info jsonutils.JSONObject) { func (manager *SCloudproviderManager) FetchCloudproviderById(providerId string) *SCloudprovider { providerObj, err := manager.FetchById(providerId) if err != nil { - log.Errorf("%s", err) + log.Errorf("fetch cloud provider %s: %s", providerId, err) return nil } return providerObj.(*SCloudprovider) diff --git a/pkg/util/aws/aws.go b/pkg/util/aws/aws.go index 684e44281c..cbdfc5bf06 100644 --- a/pkg/util/aws/aws.go +++ b/pkg/util/aws/aws.go @@ -1,7 +1,7 @@ package aws import ( - "github.com/coredns/coredns/plugin/pkg/log" + "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" @@ -33,6 +33,7 @@ func NewAwsClient(providerId string, providerName string, accessUrl string, acce client := SAwsClient{providerId: providerId, providerName: providerName, accessUrl: accessUrl, accessKey: accessKey, secret: secret} err := client.fetchRegions() if err != nil { + log.Debugf("NewAwsClient %s", err.Error()) return nil, err } return &client, nil From 538ee71202fe5723b5f878d20ed329690a95aa95 Mon Sep 17 00:00:00 2001 From: TangBin Date: Tue, 13 Nov 2018 17:45:53 +0800 Subject: [PATCH 07/29] snapshot delete & disk reset --- pkg/compute/hostdrivers/aws.go | 16 ++++++++++++ pkg/util/aws/disk.go | 47 +++++++++++++++++++++++++++++++--- pkg/util/aws/snapshot.go | 7 ++++- 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/pkg/compute/hostdrivers/aws.go b/pkg/compute/hostdrivers/aws.go index b4022ab211..255282bcc7 100644 --- a/pkg/compute/hostdrivers/aws.go +++ b/pkg/compute/hostdrivers/aws.go @@ -188,6 +188,22 @@ func (self *SAwsHostDriver) RequestResizeDiskOnHost(host *models.SHost, storage return nil } +func (self *SAwsHostDriver) RequestResetDisk(ctx context.Context, host *models.SHost, disk *models.SDisk, params *jsonutils.JSONDict, task taskman.ITask) error { + iDisk, err := disk.GetIDisk() + if err != nil { + return err + } + snapshotId, err := params.GetString("snapshot_id") + if err != nil { + return err + } + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + err := iDisk.Reset(snapshotId) + return nil, err + }) + return nil +} + func init() { driver := SAwsHostDriver{} models.RegisterHostDriver(&driver) diff --git a/pkg/util/aws/disk.go b/pkg/util/aws/disk.go index a1a304162c..02e5c23ae5 100644 --- a/pkg/util/aws/disk.go +++ b/pkg/util/aws/disk.go @@ -27,7 +27,7 @@ type SDisk struct { DiskId string // VolumeId DiskName string // Tag Name - Size int // Size + Size int // Size GB Category string // VolumeType Type string // system | data Status string // State @@ -366,8 +366,49 @@ func (self *SRegion) resizeDisk(diskId string, size int64) error { } func (self *SRegion) resetDisk(diskId, snapshotId string) error { - // aws貌似不支持直接重置 - return cloudprovider.ErrNotImplemented + // 这里实际是回滚快照 + disk, err := self.GetDisk(diskId) + if err != nil { + log.Debugf("resetDisk %s:%s",diskId, err.Error()) + return err + } + + params := &ec2.CreateVolumeInput{} + params.SetSnapshotId(snapshotId) + params.SetSize(int64(disk.Size)) + params.SetVolumeType(disk.Category) + params.SetAvailabilityZone(disk.ZoneId) + tags,_ := disk.Tags.GetTagSpecifications() + params.SetTagSpecifications([]*ec2.TagSpecification{tags}) + ret, err := self.ec2Client.CreateVolume(params) + if err != nil { + log.Debugf("resetDisk %s: %s",params.String(), err.Error()) + return err + } + + // detach disk + if disk.Status == ec2.VolumeStateInUse { + err := self.DetachDisk(disk.InstanceId, diskId) + if err != nil { + log.Debugf("resetDisk %s %s: %s", disk.InstanceId, diskId, err.Error()) + return err + } + + err = self.ec2Client.WaitUntilVolumeAvailable(&ec2.DescribeVolumesInput{VolumeIds:[]*string{&diskId}}) + if err != nil { + log.Debugf("resetDisk :%s", err.Error()) + return err + } + } + + err = self.AttachDisk(disk.InstanceId, *ret.VolumeId, disk.Device) + if err != nil { + log.Debugf("resetDisk %s %s %s: %s",disk.InstanceId, *ret.VolumeId, disk.Device, err.Error()) + return err + } + + // 绑定成功后删除原磁盘 + return self.DeleteDisk(diskId) } func (self *SRegion) CreateDisk(zoneId string, category string, name string, sizeGb int, snapshotId string, desc string) (string, error) { diff --git a/pkg/util/aws/snapshot.go b/pkg/util/aws/snapshot.go index c1a13fc38d..506044282b 100644 --- a/pkg/util/aws/snapshot.go +++ b/pkg/util/aws/snapshot.go @@ -3,6 +3,7 @@ package aws import ( "fmt" "github.com/aws/aws-sdk-go/service/ec2" + "strings" "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudprovider" @@ -90,7 +91,7 @@ func (self *SSnapshot) GetDiskId() string { } func (self *SSnapshot) Delete() error { - panic("implement me") + return self.region.DeleteSnapshot(self.SnapshotId) } func (self *SSnapshot) GetRegionId() string { @@ -125,6 +126,10 @@ func (self *SRegion) GetSnapshots(instanceId string, diskId string, snapshotName ret, err := self.ec2Client.DescribeSnapshots(params) if err != nil { + if strings.Contains(err.Error(), "InvalidSnapshot.NotFound") { + return nil, 0, cloudprovider.ErrNotFound + } + return nil, 0, err } From f62c5fa22ed2dd1395b445af2b76029aa99381e1 Mon Sep 17 00:00:00 2001 From: TangBin Date: Tue, 13 Nov 2018 19:19:04 +0800 Subject: [PATCH 08/29] snapshot region id bugfix --- pkg/compute/guestdrivers/aws.go | 8 +++++++- pkg/compute/models/cloudregions.go | 2 +- pkg/util/aws/snapshot.go | 3 ++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pkg/compute/guestdrivers/aws.go b/pkg/compute/guestdrivers/aws.go index 36fa893b8f..ca3d31a513 100644 --- a/pkg/compute/guestdrivers/aws.go +++ b/pkg/compute/guestdrivers/aws.go @@ -308,6 +308,12 @@ func (self *SAwsGuestDriver) RequestDiskSnapshot(ctx context.Context, guest *mod if err != nil { return err } + + zone := disk.GetZone() + if zone == nil { + return fmt.Errorf("can't not fetch zone by disk %s", disk.Id) + } + iSnapshot, _ := models.SnapshotManager.FetchById(snapshotId) snapshot := iSnapshot.(*models.SSnapshot) taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { @@ -318,7 +324,7 @@ func (self *SAwsGuestDriver) RequestDiskSnapshot(ctx context.Context, guest *mod res := jsonutils.NewDict() res.Set("snapshot_id", jsonutils.NewString(cloudSnapshot.GetId())) res.Set("manager_id", jsonutils.NewString(cloudSnapshot.GetManagerId())) - res.Set("cloudregion_id", jsonutils.NewString(cloudSnapshot.GetRegionId())) + res.Set("cloudregion_id", jsonutils.NewString(zone.CloudregionId)) return res, nil }) return nil diff --git a/pkg/compute/models/cloudregions.go b/pkg/compute/models/cloudregions.go index bf2fcd1eb3..204678c689 100644 --- a/pkg/compute/models/cloudregions.go +++ b/pkg/compute/models/cloudregions.go @@ -288,7 +288,7 @@ func (self *SCloudregion) PerformDefaultVpc(ctx context.Context, userCred mcclie func (manager *SCloudregionManager) FetchRegionById(id string) *SCloudregion { obj, err := manager.FetchById(id) if err != nil { - log.Errorf("%s", err) + log.Errorf("region %s %s", id, err) return nil } return obj.(*SCloudregion) diff --git a/pkg/util/aws/snapshot.go b/pkg/util/aws/snapshot.go index 506044282b..259fcdca39 100644 --- a/pkg/util/aws/snapshot.go +++ b/pkg/util/aws/snapshot.go @@ -95,7 +95,8 @@ func (self *SSnapshot) Delete() error { } func (self *SSnapshot) GetRegionId() string { - return self.region.GetId() + // 这里特别注意:aws没有有uuid形式的region id + return self.region.Get } func (self *SRegion) GetSnapshots(instanceId string, diskId string, snapshotName string, snapshotIds []string, offset int, limit int) ([]SSnapshot, int, error) { From 5f5a0e2fb7ba8b3484d15f8f142c9349923bd8b4 Mon Sep 17 00:00:00 2001 From: TangBin Date: Tue, 13 Nov 2018 20:14:06 +0800 Subject: [PATCH 09/29] add more log & fix YunionSecRuleToAws\ --- pkg/util/aws/securitygroup.go | 10 +++++----- pkg/util/aws/utils.go | 10 +++++++++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/pkg/util/aws/securitygroup.go b/pkg/util/aws/securitygroup.go index bade424345..32d69d9355 100644 --- a/pkg/util/aws/securitygroup.go +++ b/pkg/util/aws/securitygroup.go @@ -324,7 +324,7 @@ func (self *SRegion) syncSecgroupRules(secgroupId string, rules []secrules.Secur if cmp == 0 { if secgroup.Permissions[j].Description != rules[i].Description { if err := self.updateSecurityGroupRuleDescription(secgroupId, &rules[i]); err != nil { - log.Errorf("updateSecurityGroupRuleDescription error %v", rules[i]) + log.Errorf("updateSecurityGroupRuleDescription %v error: %s", rules[i], err.Error()) return err } } @@ -332,26 +332,26 @@ func (self *SRegion) syncSecgroupRules(secgroupId string, rules []secrules.Secur j += 1 } else if cmp > 0 { if err := self.delSecurityGroupRule(secgroupId, &secgroup.Permissions[j]); err != nil { - log.Errorf("delSecurityGroupRule error %v", secgroup.Permissions[j]) + log.Errorf("delSecurityGroupRule %v error: %s", secgroup.Permissions[j], err.Error()) return err } j += 1 } else { if err := self.addSecurityGroupRules(secgroupId, &rules[i]); err != nil { - log.Errorf("addSecurityGroupRule error %v", rules[i]) + log.Errorf("addSecurityGroupRule %v error: %s", rules[i], err.Error()) return err } i += 1 } } else if i >= len(rules) { if err := self.delSecurityGroupRule(secgroupId, &secgroup.Permissions[j]); err != nil { - log.Errorf("delSecurityGroupRule error %v", secgroup.Permissions[j]) + log.Errorf("delSecurityGroupRule %v error: %s", secgroup.Permissions[j], err.Error()) return err } j += 1 } else if j >= len(secgroup.Permissions) { if err := self.addSecurityGroupRules(secgroupId, &rules[i]); err != nil { - log.Errorf("addSecurityGroupRule error %v", rules[i]) + log.Errorf("addSecurityGroupRule %v error: %s", rules[i], err.Error()) return err } i += 1 diff --git a/pkg/util/aws/utils.go b/pkg/util/aws/utils.go index 6db4151d9d..6c9d648769 100644 --- a/pkg/util/aws/utils.go +++ b/pkg/util/aws/utils.go @@ -4,6 +4,7 @@ import ( "fmt" "net" "reflect" + "regexp" "strings" "yunion.io/x/jsonutils" @@ -295,6 +296,8 @@ func AwsIpPermissionToYunion(direction secrules.TSecurityRuleDirection, p ec2.Ip return rules, nil } +// YunionSecRuleToAws 不能保证无损转换 +// 规则描述如果包含中文等字符,将被丢弃掉 func YunionSecRuleToAws(rule secrules.SecurityRule) ([]*ec2.IpPermission, error) { if rule.Action == secrules.SecurityRuleDeny { return nil, fmt.Errorf("YunionSecRuleToAws ignored aws not supported deny rule") @@ -304,8 +307,13 @@ func YunionSecRuleToAws(rule secrules.SecurityRule) ([]*ec2.IpPermission, error) if iprange == "" { return nil, fmt.Errorf("YunionSecRuleToAws ignored ipnet should not be empty") } + + description := "" + if match, err := regexp.MatchString("^[\\sa-zA-Z0-9. _:/()#,@\\]\\[+=&;{}!$*-]+$", rule.Description);err == nil && match { + description = rule.Description + } ipranges := []*ec2.IpRange{} - ipranges = append(ipranges, &ec2.IpRange{CidrIp: &iprange, Description: &rule.Description}) + ipranges = append(ipranges, &ec2.IpRange{CidrIp: &iprange, Description: &description}) portranges := yunionPortRangeToAws(rule) protocol := yunionProtocolToAws(rule) From 74c024c52e8cbfaae5824e069a481c7ca8d355f8 Mon Sep 17 00:00:00 2001 From: TangBin Date: Wed, 14 Nov 2018 15:45:14 +0800 Subject: [PATCH 10/29] add latitude & longitude info --- pkg/util/aws/disk.go | 10 +++--- pkg/util/aws/latitude_and_longitude.go | 23 +++++++++++++ pkg/util/aws/region.go | 47 ++++++++++++++++++++++++-- pkg/util/aws/snapshot.go | 2 +- pkg/util/aws/utils.go | 2 +- 5 files changed, 75 insertions(+), 9 deletions(-) create mode 100644 pkg/util/aws/latitude_and_longitude.go diff --git a/pkg/util/aws/disk.go b/pkg/util/aws/disk.go index 02e5c23ae5..e4c34ef1cc 100644 --- a/pkg/util/aws/disk.go +++ b/pkg/util/aws/disk.go @@ -369,7 +369,7 @@ func (self *SRegion) resetDisk(diskId, snapshotId string) error { // 这里实际是回滚快照 disk, err := self.GetDisk(diskId) if err != nil { - log.Debugf("resetDisk %s:%s",diskId, err.Error()) + log.Debugf("resetDisk %s:%s", diskId, err.Error()) return err } @@ -378,11 +378,11 @@ func (self *SRegion) resetDisk(diskId, snapshotId string) error { params.SetSize(int64(disk.Size)) params.SetVolumeType(disk.Category) params.SetAvailabilityZone(disk.ZoneId) - tags,_ := disk.Tags.GetTagSpecifications() + tags, _ := disk.Tags.GetTagSpecifications() params.SetTagSpecifications([]*ec2.TagSpecification{tags}) ret, err := self.ec2Client.CreateVolume(params) if err != nil { - log.Debugf("resetDisk %s: %s",params.String(), err.Error()) + log.Debugf("resetDisk %s: %s", params.String(), err.Error()) return err } @@ -394,7 +394,7 @@ func (self *SRegion) resetDisk(diskId, snapshotId string) error { return err } - err = self.ec2Client.WaitUntilVolumeAvailable(&ec2.DescribeVolumesInput{VolumeIds:[]*string{&diskId}}) + err = self.ec2Client.WaitUntilVolumeAvailable(&ec2.DescribeVolumesInput{VolumeIds: []*string{&diskId}}) if err != nil { log.Debugf("resetDisk :%s", err.Error()) return err @@ -403,7 +403,7 @@ func (self *SRegion) resetDisk(diskId, snapshotId string) error { err = self.AttachDisk(disk.InstanceId, *ret.VolumeId, disk.Device) if err != nil { - log.Debugf("resetDisk %s %s %s: %s",disk.InstanceId, *ret.VolumeId, disk.Device, err.Error()) + log.Debugf("resetDisk %s %s %s: %s", disk.InstanceId, *ret.VolumeId, disk.Device, err.Error()) return err } diff --git a/pkg/util/aws/latitude_and_longitude.go b/pkg/util/aws/latitude_and_longitude.go new file mode 100644 index 0000000000..f3082a9e6c --- /dev/null +++ b/pkg/util/aws/latitude_and_longitude.go @@ -0,0 +1,23 @@ +package aws + +var LatitudeAndLongitude = map[string]map[string]float32{ + "ap-south-1": {"latitude": 19.0759837, "longitude": 72.8776559}, + "ap-northeast-3": {"latitude": 34.6937378, "longitude": 135.5021651}, + "us-east-1": {"latitude": 37.4315734, "longitude": -78.6568942}, + "us-east-2": {"latitude": 40.4172871, "longitude": -82.90712300000001}, + "ap-southeast-2": {"latitude": -33.8688197, "longitude": 151.2092955}, + "cn-northwest-1": {"latitude": 37.198731, "longitude": 106.1580937}, + "eu-west-1": {"latitude": 53.41291, "longitude": -8.24389}, + "eu-central-1": {"latitude": 50.1109221, "longitude": 8.6821267}, + "sa-east-1": {"latitude": -23.5505199, "longitude": -46.63330939999999}, + "ap-southeast-1": {"latitude": 1.352083, "longitude": 103.819836}, + "ca-central-1": {"latitude": 56.130366, "longitude": -106.346771}, + "ap-northeast-2": {"latitude": 37.566535, "longitude": 126.9779692}, + "us-west-2": {"latitude": 43.8041334, "longitude": -120.5542012}, + "us-gov-west-1": {"latitude": 37.09024, "longitude": -95.712891}, + "us-west-1": {"latitude": 38.8375215, "longitude": -120.8958242}, + "cn-north-1": {"latitude": 39.90419989999999, "longitude": 116.4073963}, + "ap-northeast-1": {"latitude": 35.7090259, "longitude": 139.7319925}, + "eu-west-2": {"latitude": 51.5073509, "longitude": -0.1277583}, + "eu-west-3": {"latitude": 48.856614, "longitude": 2.3522219}, +} diff --git a/pkg/util/aws/region.go b/pkg/util/aws/region.go index a03f4303ff..a3c8fcb875 100644 --- a/pkg/util/aws/region.go +++ b/pkg/util/aws/region.go @@ -2,6 +2,7 @@ package aws import ( "fmt" + sdk "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" @@ -14,6 +15,28 @@ import ( "yunion.io/x/onecloud/pkg/compute/models" ) +var RegionLocations map[string]string = map[string]string{ + "us-east-2": "美国东部(俄亥俄州)", + "us-east-1": "美国东部(弗吉尼亚北部)", + "us-west-1": "美国西部(加利福尼亚北部)", + "us-west-2": "美国西部(俄勒冈)", + "ap-south-1": "亚太地区(孟买)", + "ap-northeast-2": "亚太区域(首尔)", + "ap-northeast-3": "亚太区域(大阪)", + "ap-southeast-1": "亚太区域(新加坡)", + "ap-southeast-2": "亚太区域(悉尼)", + "ap-northeast-1": "亚太区域(东京)", + "ca-central-1": "加拿大(中部)", + "cn-north-1": "中国(北京)", + "cn-northwest-1": "中国(宁夏)", + "eu-central-1": "欧洲(法兰克福)", + "eu-west-1": "欧洲(爱尔兰)", + "eu-west-2": "欧洲(伦敦)", + "eu-west-3": "欧洲(巴黎)", + "sa-east-1": "南美洲(圣保罗)", + "us-gov-west-1": "AWS GovCloud(美国)", +} + type SRegion struct { client *SAwsClient ec2Client *ec2.EC2 @@ -164,6 +187,10 @@ func (self *SRegion) GetId() string { } func (self *SRegion) GetName() string { + if localName, ok := RegionLocations[self.RegionId]; ok { + return fmt.Sprintf("%s %s", CLOUD_PROVIDER_AWS_CN, localName) + } + return fmt.Sprintf("%s %s", CLOUD_PROVIDER_AWS_CN, self.RegionId) } @@ -188,11 +215,27 @@ func (self *SRegion) GetMetadata() *jsonutils.JSONDict { } func (self *SRegion) GetLatitude() float32 { - return 0.0 + if data, ok := LatitudeAndLongitude[self.RegionId]; !ok { + log.Debugf("Region %s not found in LatitudeAndLongitude", self.RegionId) + return 0.0 + } else if lat, ok := data["latitude"]; !ok { + log.Debugf("Region %s's latitude not found in LatitudeAndLongitude", self.RegionId) + return 0.0 + } else { + return lat + } } func (self *SRegion) GetLongitude() float32 { - return 0.0 + if data, ok := LatitudeAndLongitude[self.RegionId]; !ok { + log.Debugf("Region %s not found in LatitudeAndLongitude", self.RegionId) + return 0.0 + } else if lat, ok := data["longitude"]; !ok { + log.Debugf("Region %s's latitude not found in LatitudeAndLongitude", self.RegionId) + return 0.0 + } else { + return lat + } } func (self *SRegion) GetIZones() ([]cloudprovider.ICloudZone, error) { diff --git a/pkg/util/aws/snapshot.go b/pkg/util/aws/snapshot.go index 259fcdca39..c69816ecb4 100644 --- a/pkg/util/aws/snapshot.go +++ b/pkg/util/aws/snapshot.go @@ -96,7 +96,7 @@ func (self *SSnapshot) Delete() error { func (self *SSnapshot) GetRegionId() string { // 这里特别注意:aws没有有uuid形式的region id - return self.region.Get + return self.region.GetId() } func (self *SRegion) GetSnapshots(instanceId string, diskId string, snapshotName string, snapshotIds []string, offset int, limit int) ([]SSnapshot, int, error) { diff --git a/pkg/util/aws/utils.go b/pkg/util/aws/utils.go index 6c9d648769..3ac2d34f9e 100644 --- a/pkg/util/aws/utils.go +++ b/pkg/util/aws/utils.go @@ -309,7 +309,7 @@ func YunionSecRuleToAws(rule secrules.SecurityRule) ([]*ec2.IpPermission, error) } description := "" - if match, err := regexp.MatchString("^[\\sa-zA-Z0-9. _:/()#,@\\]\\[+=&;{}!$*-]+$", rule.Description);err == nil && match { + if match, err := regexp.MatchString("^[\\sa-zA-Z0-9. _:/()#,@\\]\\[+=&;{}!$*-]+$", rule.Description); err == nil && match { description = rule.Description } ipranges := []*ec2.IpRange{} From ebbedb9458213019016f3be0152a793a617360a5 Mon Sep 17 00:00:00 2001 From: TangBin Date: Wed, 14 Nov 2018 20:32:16 +0800 Subject: [PATCH 11/29] vpc & securtiy group sync --- pkg/util/aws/instance.go | 27 ++++++----- pkg/util/aws/region.go | 21 ++++++++ pkg/util/aws/securitygroup.go | 90 +++++++++++++++++++---------------- pkg/util/aws/vpc.go | 29 ++++++++++- 4 files changed, 112 insertions(+), 55 deletions(-) diff --git a/pkg/util/aws/instance.go b/pkg/util/aws/instance.go index 13748b02fd..4d693c6ac3 100644 --- a/pkg/util/aws/instance.go +++ b/pkg/util/aws/instance.go @@ -273,23 +273,26 @@ func (self *SInstance) SyncSecurityGroup(secgroupId string, name string, rules [ if vpc, err := self.getVpc(); err != nil { return err } else if len(secgroupId) == 0 { - for index, secgrpId := range self.SecurityGroupIds.SecurityGroupId { - if err := vpc.revokeSecurityGroup(secgrpId, self.InstanceId, index == 0); err != nil { - return err - } - } + // todo : 这里应该有问题。aws不能直接删除安全组。且至少选择一个安全组 + // for index, secgrpId := range self.SecurityGroupIds.SecurityGroupId { + // if err := vpc.revokeSecurityGroup(secgrpId, self.InstanceId, index == 0); err != nil { + // return err + // } + // } + return nil } else if secgrpId, err := vpc.SyncSecurityGroup(secgroupId, name, rules); err != nil { return err } else if err := vpc.assignSecurityGroup(secgrpId, self.InstanceId); err != nil { return err } else { - for _, secgroupId := range self.SecurityGroupIds.SecurityGroupId { - if secgroupId != secgrpId { - if err := vpc.revokeSecurityGroup(secgroupId, self.InstanceId, false); err != nil { - return err - } - } - } + // todo : 这里应该有问题。aws不能直接删除安全组。且至少选择一个安全组 + // for _, secgroupId := range self.SecurityGroupIds.SecurityGroupId { + // if secgroupId != secgrpId { + // if err := vpc.revokeSecurityGroup(secgroupId, self.InstanceId, false); err != nil { + // return err + // } + // } + // } self.SecurityGroupIds.SecurityGroupId = []string{secgrpId} } return nil diff --git a/pkg/util/aws/region.go b/pkg/util/aws/region.go index a3c8fcb875..558a307604 100644 --- a/pkg/util/aws/region.go +++ b/pkg/util/aws/region.go @@ -363,11 +363,32 @@ func (self *SRegion) GetIStoragecacheById(id string) (cloudprovider.ICloudStorag } func (self *SRegion) CreateIVpc(name string, desc string, cidr string) (cloudprovider.ICloudVpc, error) { + tagspec := TagSpec{ResourceType: "vpc"} + if len(name) > 0 { + tagspec.SetNameTag(name) + } + + if len(desc) > 0 { + tagspec.SetDescTag(desc) + } + + spec, err := tagspec.GetTagSpecifications() + if err != nil { + return nil, err + } + + // start create vpc vpc, err := self.ec2Client.CreateVpc(&ec2.CreateVpcInput{CidrBlock: &cidr}) if err != nil { return nil, err } + tagsParams := &ec2.CreateTagsInput{Resources: []*string{vpc.Vpc.VpcId}, Tags: spec.Tags} + _, err = self.ec2Client.CreateTags(tagsParams) + if err != nil { + log.Debugf("CreateIVpc add tag failed %s", err.Error()) + } + err = self.fetchInfrastructure() if err != nil { return nil, err diff --git a/pkg/util/aws/securitygroup.go b/pkg/util/aws/securitygroup.go index 32d69d9355..29399d61b0 100644 --- a/pkg/util/aws/securitygroup.go +++ b/pkg/util/aws/securitygroup.go @@ -27,7 +27,7 @@ type SSecurityGroup struct { VpcId string SecurityGroupId string Description string - SecurityGroupName string + SecurityGroupName string //对应tag中的name标签 Permissions []secrules.SecurityRule Tags Tags @@ -117,22 +117,22 @@ func (self *SRegion) addSecurityGroupRule(secGrpId string, rule *secrules.Securi params := &ec2.AuthorizeSecurityGroupIngressInput{} params.SetGroupId(secGrpId) params.SetIpPermissions(ipPermissions) - _, err := self.ec2Client.AuthorizeSecurityGroupIngress(params) - if err != nil { - return err - } + _, err = self.ec2Client.AuthorizeSecurityGroupIngress(params) } if rule.Direction == secrules.SecurityRuleEgress { params := &ec2.AuthorizeSecurityGroupEgressInput{} params.SetGroupId(secGrpId) params.SetIpPermissions(ipPermissions) - _, err := self.ec2Client.AuthorizeSecurityGroupEgress(params) - if err != nil { - return err - } + _, err = self.ec2Client.AuthorizeSecurityGroupEgress(params) } - return nil + + if err != nil && strings.Contains(err.Error(),"InvalidPermission.Duplicate") { + log.Debugf("addSecurityGroupRule %s %s", rule.Direction, err.Error()) + return nil + } + + return err } func (self *SRegion) delSecurityGroupRule(secGrpId string, rule *secrules.SecurityRule) error { @@ -145,20 +145,19 @@ func (self *SRegion) delSecurityGroupRule(secGrpId string, rule *secrules.Securi params := &ec2.RevokeSecurityGroupIngressInput{} params.SetGroupId(secGrpId) params.SetIpPermissions(ipPermissions) - _, err := self.ec2Client.RevokeSecurityGroupIngress(params) - if err != nil { - return err - } + _, err = self.ec2Client.RevokeSecurityGroupIngress(params) } if rule.Direction == secrules.SecurityRuleEgress { params := &ec2.RevokeSecurityGroupEgressInput{} params.SetGroupId(secGrpId) params.SetIpPermissions(ipPermissions) - _, err := self.ec2Client.RevokeSecurityGroupEgress(params) - if err != nil { - return err - } + _, err = self.ec2Client.RevokeSecurityGroupEgress(params) + } + + if err != nil { + log.Debugf("delSecurityGroupRule %s %s", rule.Direction, err.Error()) + return err } return nil } @@ -198,8 +197,10 @@ func (self *SRegion) updateSecurityGroupRuleDescription(secGrpId string, rule *s func (self *SRegion) createSecurityGroup(vpcId string, name string, secgroupIdTag string, desc string) (string, error) { params := &ec2.CreateSecurityGroupInput{} params.SetVpcId(vpcId) + // 这里的描述aws 上层代码拼接的描述。并非用户提交的描述,用户描述放置在Yunion本地数据库中。) params.SetDescription(desc) - params.SetGroupName(name) + // 这里使用id作为组名。原因name容易重名、另外有可能包含中文,aws不支持中文 + params.SetGroupName(secgroupIdTag) group, err := self.ec2Client.CreateSecurityGroup(params) if err != nil { @@ -208,6 +209,8 @@ func (self *SRegion) createSecurityGroup(vpcId string, name string, secgroupIdTa tagspec := TagSpec{ResourceType: "security-group"} tagspec.SetTag("id", secgroupIdTag) + tagspec.SetNameTag(name) + tagspec.SetDescTag(desc) tags, _ := tagspec.GetTagSpecifications() tagParams := &ec2.CreateTagsInput{} tagParams.SetResources([]*string{group.GroupId}) @@ -308,6 +311,9 @@ func (self *SRegion) modifySecurityGroup(secGrpId string, name string, desc stri } func (self *SRegion) syncSecgroupRules(secgroupId string, rules []secrules.SecurityRule) error { + var DeleteRules []secrules.SecurityRule + var AddRules []secrules.SecurityRule + if secgroup, err := self.GetSecurityGroupDetails(secgroupId); err != nil { return err } else { @@ -315,6 +321,9 @@ func (self *SRegion) syncSecgroupRules(secgroupId string, rules []secrules.Secur sort.Sort(secrules.SecurityRuleSet(rules)) sort.Sort(secrules.SecurityRuleSet(secgroup.Permissions)) + log.Debugf("local security rules %s",rules) + log.Debugf("remote security rules %s",secgroup.Permissions) + i, j := 0, 0 for i < len(rules) || j < len(secgroup.Permissions) { if i < len(rules) && j < len(secgroup.Permissions) { @@ -322,42 +331,41 @@ func (self *SRegion) syncSecgroupRules(secgroupId string, rules []secrules.Secur ruleStr := rules[i].String() cmp := strings.Compare(permissionStr, ruleStr) if cmp == 0 { - if secgroup.Permissions[j].Description != rules[i].Description { - if err := self.updateSecurityGroupRuleDescription(secgroupId, &rules[i]); err != nil { - log.Errorf("updateSecurityGroupRuleDescription %v error: %s", rules[i], err.Error()) - return err - } - } + DeleteRules = append(DeleteRules, secgroup.Permissions[j]) + AddRules = append(AddRules, rules[i]) i += 1 j += 1 } else if cmp > 0 { - if err := self.delSecurityGroupRule(secgroupId, &secgroup.Permissions[j]); err != nil { - log.Errorf("delSecurityGroupRule %v error: %s", secgroup.Permissions[j], err.Error()) - return err - } + DeleteRules = append(DeleteRules, secgroup.Permissions[j]) j += 1 } else { - if err := self.addSecurityGroupRules(secgroupId, &rules[i]); err != nil { - log.Errorf("addSecurityGroupRule %v error: %s", rules[i], err.Error()) - return err - } + AddRules = append(AddRules, rules[i]) i += 1 } } else if i >= len(rules) { - if err := self.delSecurityGroupRule(secgroupId, &secgroup.Permissions[j]); err != nil { - log.Errorf("delSecurityGroupRule %v error: %s", secgroup.Permissions[j], err.Error()) - return err - } + DeleteRules = append(DeleteRules, secgroup.Permissions[j]) j += 1 } else if j >= len(secgroup.Permissions) { - if err := self.addSecurityGroupRules(secgroupId, &rules[i]); err != nil { - log.Errorf("addSecurityGroupRule %v error: %s", rules[i], err.Error()) - return err - } + AddRules = append(AddRules, rules[i]) i += 1 } } } + + for _, r := range DeleteRules { + if err := self.delSecurityGroupRule(secgroupId, &r); err != nil { + log.Errorf("delSecurityGroupRule %v error: %s", r, err.Error()) + return err + } + } + + for _, r := range AddRules { + if err := self.addSecurityGroupRules(secgroupId, &r); err != nil { + log.Errorf("addSecurityGroupRule %v error: %s", r, err.Error()) + return err + } + } + return nil } diff --git a/pkg/util/aws/vpc.go b/pkg/util/aws/vpc.go index 9d93352d27..1288c063d4 100644 --- a/pkg/util/aws/vpc.go +++ b/pkg/util/aws/vpc.go @@ -240,19 +240,44 @@ func (self *SRegion) getVpc(vpcId string) (*SVpc, error) { } func (self *SRegion) revokeSecurityGroup(secgroupId, instanceId string, keep bool) error { + // todo : keep ? 直接使用assignSecurityGroup 即可? return nil } func (self *SRegion) assignSecurityGroup(secgroupId, instanceId string) error { + instance, err := self.GetInstance(instanceId) + if err != nil { + return err + } + + for _, eth := range instance.NetworkInterfaces.NetworkInterface { + params := &ec2.ModifyNetworkInterfaceAttributeInput{} + params.SetNetworkInterfaceId(eth.NetworkInterfaceId) + params.SetGroups([]*string{&secgroupId}) + + _, err := self.ec2Client.ModifyNetworkInterfaceAttribute(params) + if err != nil { + return err + } + } + return nil } func (self *SRegion) deleteSecurityGroup(secGrpId string) error { - return nil + params := &ec2.DeleteSecurityGroupInput{} + params.SetGroupId(secGrpId) + + _, err := self.ec2Client.DeleteSecurityGroup(params) + return err } func (self *SRegion) DeleteVpc(vpcId string) error { - return nil + params := &ec2.DeleteVpcInput{} + params.SetVpcId(vpcId) + + _, err := self.ec2Client.DeleteVpc(params) + return err } func (self *SRegion) GetVpcs(vpcId []string, offset int, limit int) ([]SVpc, int, error) { From 010f5c1f979ec933d606e21faa826e9acb76f810 Mon Sep 17 00:00:00 2001 From: TangBin Date: Thu, 15 Nov 2018 18:00:40 +0800 Subject: [PATCH 12/29] vpc fix --- pkg/util/aws/vpc.go | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/pkg/util/aws/vpc.go b/pkg/util/aws/vpc.go index 1288c063d4..72b35b3a80 100644 --- a/pkg/util/aws/vpc.go +++ b/pkg/util/aws/vpc.go @@ -108,19 +108,7 @@ func (self *SVpc) GetManagerId() string { } func (self *SVpc) Delete() error { - err := self.fetchSecurityGroups() - if err != nil { - log.Errorf("fetchSecurityGroup for VPC delete fail %s", err) - return err - } - for i := 0; i < len(self.secgroups); i += 1 { - secgroup := self.secgroups[i].(*SSecurityGroup) - err := self.region.deleteSecurityGroup(secgroup.SecurityGroupId) - if err != nil { - log.Errorf("deleteSecurityGroup for VPC delete fail %s", err) - return err - } - } + // 删除vpc会同步删除关联的安全组 return self.region.DeleteVpc(self.VpcId) } @@ -287,6 +275,9 @@ func (self *SRegion) GetVpcs(vpcId []string, offset int, limit int) ([]SVpc, int } ret, err := self.ec2Client.DescribeVpcs(params) if err != nil { + if strings.Contains(err.Error(), "InvalidVpcID.NotFound") { + return nil, 0, cloudprovider.ErrNotFound + } return nil, 0, err } From 9bf73e99d915cb89bea190ece65709f520ca87d1 Mon Sep 17 00:00:00 2001 From: TangBin Date: Thu, 15 Nov 2018 19:26:35 +0800 Subject: [PATCH 13/29] return err --- pkg/compute/models/cloudaccounts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/compute/models/cloudaccounts.go b/pkg/compute/models/cloudaccounts.go index 0c40545812..14c2e97f39 100644 --- a/pkg/compute/models/cloudaccounts.go +++ b/pkg/compute/models/cloudaccounts.go @@ -114,7 +114,7 @@ func (manager *SCloudaccountManager) ValidateCreateData(ctx context.Context, use return nil, httperrors.NewResourceNotFoundError("no such provider %s", provider) } log.Debugf("ValidateCreateData %s", err.Error()) - return nil, httperrors.NewInvalidCredentialError("invalid cloud account info") + return nil, httperrors.NewInputParameterError("invalid cloud account info") } return manager.SEnabledStatusStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data) From bc1fae8ebb672b39b46e4907f2cf3e2b1fdcfd2a Mon Sep 17 00:00:00 2001 From: TangBin Date: Sat, 17 Nov 2018 10:25:25 +0800 Subject: [PATCH 14/29] rebuild root disk --- Gopkg.lock | 21 +- pkg/util/aws/image.go | 11 + pkg/util/aws/region.go | 22 +- pkg/util/aws/storagecache.go | 29 +- .../service/s3/s3iface/interface.go | 403 +++++++++ .../aws-sdk-go/service/s3/s3manager/batch.go | 529 ++++++++++++ .../service/s3/s3manager/bucket_region.go | 88 ++ .../aws-sdk-go/service/s3/s3manager/doc.go | 3 + .../service/s3/s3manager/download.go | 555 ++++++++++++ .../aws-sdk-go/service/s3/s3manager/upload.go | 802 ++++++++++++++++++ 10 files changed, 2429 insertions(+), 34 deletions(-) create mode 100644 vendor/github.com/aws/aws-sdk-go/service/s3/s3iface/interface.go create mode 100644 vendor/github.com/aws/aws-sdk-go/service/s3/s3manager/batch.go create mode 100644 vendor/github.com/aws/aws-sdk-go/service/s3/s3manager/bucket_region.go create mode 100644 vendor/github.com/aws/aws-sdk-go/service/s3/s3manager/doc.go create mode 100644 vendor/github.com/aws/aws-sdk-go/service/s3/s3manager/download.go create mode 100644 vendor/github.com/aws/aws-sdk-go/service/s3/s3manager/upload.go diff --git a/Gopkg.lock b/Gopkg.lock index 414c3a0e55..7ec8fb6624 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -121,7 +121,7 @@ version = "0.11.0" [[projects]] - digest = "1:55bf2a4da68caa693d660683e53bab3e651a940cd46354b997ab68bec7920e23" + digest = "1:ae41c49d4812dd31a848045447637fd0f10cac09af75e1d20825a241c626f3fa" name = "github.com/aws/aws-sdk-go" packages = [ "aws", @@ -159,6 +159,8 @@ "service/ec2", "service/iam", "service/s3", + "service/s3/s3iface", + "service/s3/s3manager", "service/sts", ] pruneopts = "UT" @@ -1292,14 +1294,6 @@ revision = "1638f8970cefaa404ff3a62950f88b08292b2696" version = "v9.0.0" -[[projects]] - digest = "1:7719608fe0b52a4ece56c2dde37bedd95b938677d1ab0f84b8a7852e4c59f849" - name = "sigs.k8s.io/yaml" - packages = ["."] - pruneopts = "UT" - revision = "fd68e9863619f6ec2fdd8625fe1f02e7c877e480" - version = "v1.1.0" - [[projects]] digest = "1:356a33d2d627cbb232a1d71cc42051fbac0e5e6a1717679f110054baa02472e5" name = "k8s.io/kubernetes" @@ -1311,6 +1305,14 @@ revision = "17c77c7898218073f14c8d573582e8d2313dc740" version = "v1.12.2" +[[projects]] + digest = "1:7719608fe0b52a4ece56c2dde37bedd95b938677d1ab0f84b8a7852e4c59f849" + name = "sigs.k8s.io/yaml" + packages = ["."] + pruneopts = "UT" + revision = "fd68e9863619f6ec2fdd8625fe1f02e7c877e480" + version = "v1.1.0" + [[projects]] branch = "master" digest = "1:54554b3c72f4fcbd3c27c5137f9acd9d153d7e7150d77cd509af50c75fcb41b9" @@ -1409,6 +1411,7 @@ "github.com/aws/aws-sdk-go/service/ec2", "github.com/aws/aws-sdk-go/service/iam", "github.com/aws/aws-sdk-go/service/s3", + "github.com/aws/aws-sdk-go/service/s3/s3manager", "github.com/bitly/go-simplejson", "github.com/c-bata/go-prompt", "github.com/coredns/coredns/core/dnsserver", diff --git a/pkg/util/aws/image.go b/pkg/util/aws/image.go index 8389e38b9a..700e43692b 100644 --- a/pkg/util/aws/image.go +++ b/pkg/util/aws/image.go @@ -2,6 +2,7 @@ package aws import ( "fmt" + "strings" "github.com/aws/aws-sdk-go/service/ec2" "yunion.io/x/jsonutils" @@ -189,6 +190,8 @@ func (self *SRegion) GetImageByName(name string) (*SImage, error) { if len(images) == 0 { return nil, cloudprovider.ErrNotFound } + + log.Debugf("%d image found match name %", len(images), name) return &images[0], nil } @@ -230,8 +233,16 @@ func (self *SRegion) GetImages(status ImageStatusType, owner ImageOwnerType, ima if len(imageId) > 0 { params.SetImageIds(ConvertedList(imageId)) } + + if len(filters) > 0 { + params.SetFilters(filters) + } + ret, err := self.ec2Client.DescribeImages(params) if err != nil { + if strings.Contains(err.Error(), ".NotFound") { + return nil, 0, cloudprovider.ErrNotFound + } return nil, 0, err } diff --git a/pkg/util/aws/region.go b/pkg/util/aws/region.go index 558a307604..910bae7767 100644 --- a/pkg/util/aws/region.go +++ b/pkg/util/aws/region.go @@ -59,12 +59,16 @@ func (self *SRegion) GetClient() *SAwsClient { return self.client } +func (self *SRegion) getAwsSession() (*session.Session, error) { + return session.NewSession(&sdk.Config{ + Region: sdk.String(self.RegionId), + Credentials: credentials.NewStaticCredentials(self.client.accessKey, self.client.secret, ""), + }) +} + func (self *SRegion) getEc2Client() (*ec2.EC2, error) { if self.ec2Client == nil { - s, err := session.NewSession(&sdk.Config{ - Region: sdk.String(self.RegionId), - Credentials: credentials.NewStaticCredentials(self.client.accessKey, self.client.secret, ""), - }) + s, err := self.getAwsSession() if err != nil { return nil, err @@ -79,10 +83,7 @@ func (self *SRegion) getEc2Client() (*ec2.EC2, error) { func (self *SRegion) getIamClient() (*iam.IAM, error) { if self.iamClient == nil { - s, err := session.NewSession(&sdk.Config{ - Region: sdk.String(self.RegionId), - Credentials: credentials.NewStaticCredentials(self.client.accessKey, self.client.secret, ""), - }) + s, err := self.getAwsSession() if err != nil { return nil, err @@ -96,10 +97,7 @@ func (self *SRegion) getIamClient() (*iam.IAM, error) { func (self *SRegion) getS3Client() (*s3.S3, error) { if self.s3Client == nil { - s, err := session.NewSession(&sdk.Config{ - Region: sdk.String(self.RegionId), - Credentials: credentials.NewStaticCredentials(self.client.accessKey, self.client.secret, ""), - }) + s, err := self.getAwsSession() if err != nil { return nil, err diff --git a/pkg/util/aws/storagecache.go b/pkg/util/aws/storagecache.go index 20760bc86c..eaed1175b2 100644 --- a/pkg/util/aws/storagecache.go +++ b/pkg/util/aws/storagecache.go @@ -1,12 +1,11 @@ package aws import ( - "bytes" "fmt" "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/iam" "github.com/aws/aws-sdk-go/service/s3" - "io/ioutil" + "github.com/aws/aws-sdk-go/service/s3/s3manager" "strings" "time" "yunion.io/x/jsonutils" @@ -148,17 +147,20 @@ func (self *SStoragecache) uploadImage(userCred mcclient.TokenCredential, imageI return "", err } - s3Client, err := self.region.getS3Client() - if err != nil { - return "", nil + // uploader to aws s3 + input := &s3manager.UploadInput{ + Bucket: &bucketName, + Key: &imageId, + Body: reader, } - // 内存? - f, err := ioutil.ReadAll(reader) - params := &s3.PutObjectInput{} - params.SetBucket(bucketName) - params.SetKey(imageId) - params.SetBody(bytes.NewReader(f)) - _, err = s3Client.PutObject(params) + + awsSession, err := self.region.getAwsSession() + if err != nil { + log.Debugf("uploadImage %s", err.Error()) + return "", fmt.Errorf("get aws session failed") + } + uploader := s3manager.NewUploader(awsSession) + _, err = uploader.Upload(input) if err != nil { return "", nil } @@ -183,6 +185,7 @@ func (self *SStoragecache) uploadImage(userCred mcclient.TokenCredential, imageI imageName = fmt.Sprintf("%s-%d", imageBaseName, nameIdx) nameIdx += 1 + log.Debugf("uploadImage Match remote name %s", imageName) } task, err := self.region.ImportImage(imageName, osArch, osType, osDist, diskFormat, bucketName, imageId) @@ -194,6 +197,7 @@ func (self *SStoragecache) uploadImage(userCred mcclient.TokenCredential, imageI // todo:// 等待镜像导入完成 for i := 1; i < 120; i++ { + time.Sleep(2 * time.Minute) ret, err := self.region.ec2Client.DescribeImportImageTasks(&ec2.DescribeImportImageTasksInput{ImportTaskIds: []*string{&task.TaskId}}) if err != nil { return "", err @@ -210,7 +214,6 @@ func (self *SStoragecache) uploadImage(userCred mcclient.TokenCredential, imageI return *item.ImageId, nil } } - time.Sleep(1 * time.Minute) } return task.ImageId, fmt.Errorf("uploadImage uncompleted: %s", task) diff --git a/vendor/github.com/aws/aws-sdk-go/service/s3/s3iface/interface.go b/vendor/github.com/aws/aws-sdk-go/service/s3/s3iface/interface.go new file mode 100644 index 0000000000..cc427882b8 --- /dev/null +++ b/vendor/github.com/aws/aws-sdk-go/service/s3/s3iface/interface.go @@ -0,0 +1,403 @@ +// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT. + +// Package s3iface provides an interface to enable mocking the Amazon Simple Storage Service service client +// for testing your code. +// +// It is important to note that this interface will have breaking changes +// when the service model is updated and adds new API operations, paginators, +// and waiters. +package s3iface + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/service/s3" +) + +// S3API provides an interface to enable mocking the +// s3.S3 service client's API operation, +// paginators, and waiters. This make unit testing your code that calls out +// to the SDK's service client's calls easier. +// +// The best way to use this interface is so the SDK's service client's calls +// can be stubbed out for unit testing your code with the SDK without needing +// to inject custom request handlers into the SDK's request pipeline. +// +// // myFunc uses an SDK service client to make a request to +// // Amazon Simple Storage Service. +// func myFunc(svc s3iface.S3API) bool { +// // Make svc.AbortMultipartUpload request +// } +// +// func main() { +// sess := session.New() +// svc := s3.New(sess) +// +// myFunc(svc) +// } +// +// In your _test.go file: +// +// // Define a mock struct to be used in your unit tests of myFunc. +// type mockS3Client struct { +// s3iface.S3API +// } +// func (m *mockS3Client) AbortMultipartUpload(input *s3.AbortMultipartUploadInput) (*s3.AbortMultipartUploadOutput, error) { +// // mock response/functionality +// } +// +// func TestMyFunc(t *testing.T) { +// // Setup Test +// mockSvc := &mockS3Client{} +// +// myfunc(mockSvc) +// +// // Verify myFunc's functionality +// } +// +// It is important to note that this interface will have breaking changes +// when the service model is updated and adds new API operations, paginators, +// and waiters. Its suggested to use the pattern above for testing, or using +// tooling to generate mocks to satisfy the interfaces. +type S3API interface { + AbortMultipartUpload(*s3.AbortMultipartUploadInput) (*s3.AbortMultipartUploadOutput, error) + AbortMultipartUploadWithContext(aws.Context, *s3.AbortMultipartUploadInput, ...request.Option) (*s3.AbortMultipartUploadOutput, error) + AbortMultipartUploadRequest(*s3.AbortMultipartUploadInput) (*request.Request, *s3.AbortMultipartUploadOutput) + + CompleteMultipartUpload(*s3.CompleteMultipartUploadInput) (*s3.CompleteMultipartUploadOutput, error) + CompleteMultipartUploadWithContext(aws.Context, *s3.CompleteMultipartUploadInput, ...request.Option) (*s3.CompleteMultipartUploadOutput, error) + CompleteMultipartUploadRequest(*s3.CompleteMultipartUploadInput) (*request.Request, *s3.CompleteMultipartUploadOutput) + + CopyObject(*s3.CopyObjectInput) (*s3.CopyObjectOutput, error) + CopyObjectWithContext(aws.Context, *s3.CopyObjectInput, ...request.Option) (*s3.CopyObjectOutput, error) + CopyObjectRequest(*s3.CopyObjectInput) (*request.Request, *s3.CopyObjectOutput) + + CreateBucket(*s3.CreateBucketInput) (*s3.CreateBucketOutput, error) + CreateBucketWithContext(aws.Context, *s3.CreateBucketInput, ...request.Option) (*s3.CreateBucketOutput, error) + CreateBucketRequest(*s3.CreateBucketInput) (*request.Request, *s3.CreateBucketOutput) + + CreateMultipartUpload(*s3.CreateMultipartUploadInput) (*s3.CreateMultipartUploadOutput, error) + CreateMultipartUploadWithContext(aws.Context, *s3.CreateMultipartUploadInput, ...request.Option) (*s3.CreateMultipartUploadOutput, error) + CreateMultipartUploadRequest(*s3.CreateMultipartUploadInput) (*request.Request, *s3.CreateMultipartUploadOutput) + + DeleteBucket(*s3.DeleteBucketInput) (*s3.DeleteBucketOutput, error) + DeleteBucketWithContext(aws.Context, *s3.DeleteBucketInput, ...request.Option) (*s3.DeleteBucketOutput, error) + DeleteBucketRequest(*s3.DeleteBucketInput) (*request.Request, *s3.DeleteBucketOutput) + + DeleteBucketAnalyticsConfiguration(*s3.DeleteBucketAnalyticsConfigurationInput) (*s3.DeleteBucketAnalyticsConfigurationOutput, error) + DeleteBucketAnalyticsConfigurationWithContext(aws.Context, *s3.DeleteBucketAnalyticsConfigurationInput, ...request.Option) (*s3.DeleteBucketAnalyticsConfigurationOutput, error) + DeleteBucketAnalyticsConfigurationRequest(*s3.DeleteBucketAnalyticsConfigurationInput) (*request.Request, *s3.DeleteBucketAnalyticsConfigurationOutput) + + DeleteBucketCors(*s3.DeleteBucketCorsInput) (*s3.DeleteBucketCorsOutput, error) + DeleteBucketCorsWithContext(aws.Context, *s3.DeleteBucketCorsInput, ...request.Option) (*s3.DeleteBucketCorsOutput, error) + DeleteBucketCorsRequest(*s3.DeleteBucketCorsInput) (*request.Request, *s3.DeleteBucketCorsOutput) + + DeleteBucketEncryption(*s3.DeleteBucketEncryptionInput) (*s3.DeleteBucketEncryptionOutput, error) + DeleteBucketEncryptionWithContext(aws.Context, *s3.DeleteBucketEncryptionInput, ...request.Option) (*s3.DeleteBucketEncryptionOutput, error) + DeleteBucketEncryptionRequest(*s3.DeleteBucketEncryptionInput) (*request.Request, *s3.DeleteBucketEncryptionOutput) + + DeleteBucketInventoryConfiguration(*s3.DeleteBucketInventoryConfigurationInput) (*s3.DeleteBucketInventoryConfigurationOutput, error) + DeleteBucketInventoryConfigurationWithContext(aws.Context, *s3.DeleteBucketInventoryConfigurationInput, ...request.Option) (*s3.DeleteBucketInventoryConfigurationOutput, error) + DeleteBucketInventoryConfigurationRequest(*s3.DeleteBucketInventoryConfigurationInput) (*request.Request, *s3.DeleteBucketInventoryConfigurationOutput) + + DeleteBucketLifecycle(*s3.DeleteBucketLifecycleInput) (*s3.DeleteBucketLifecycleOutput, error) + DeleteBucketLifecycleWithContext(aws.Context, *s3.DeleteBucketLifecycleInput, ...request.Option) (*s3.DeleteBucketLifecycleOutput, error) + DeleteBucketLifecycleRequest(*s3.DeleteBucketLifecycleInput) (*request.Request, *s3.DeleteBucketLifecycleOutput) + + DeleteBucketMetricsConfiguration(*s3.DeleteBucketMetricsConfigurationInput) (*s3.DeleteBucketMetricsConfigurationOutput, error) + DeleteBucketMetricsConfigurationWithContext(aws.Context, *s3.DeleteBucketMetricsConfigurationInput, ...request.Option) (*s3.DeleteBucketMetricsConfigurationOutput, error) + DeleteBucketMetricsConfigurationRequest(*s3.DeleteBucketMetricsConfigurationInput) (*request.Request, *s3.DeleteBucketMetricsConfigurationOutput) + + DeleteBucketPolicy(*s3.DeleteBucketPolicyInput) (*s3.DeleteBucketPolicyOutput, error) + DeleteBucketPolicyWithContext(aws.Context, *s3.DeleteBucketPolicyInput, ...request.Option) (*s3.DeleteBucketPolicyOutput, error) + DeleteBucketPolicyRequest(*s3.DeleteBucketPolicyInput) (*request.Request, *s3.DeleteBucketPolicyOutput) + + DeleteBucketReplication(*s3.DeleteBucketReplicationInput) (*s3.DeleteBucketReplicationOutput, error) + DeleteBucketReplicationWithContext(aws.Context, *s3.DeleteBucketReplicationInput, ...request.Option) (*s3.DeleteBucketReplicationOutput, error) + DeleteBucketReplicationRequest(*s3.DeleteBucketReplicationInput) (*request.Request, *s3.DeleteBucketReplicationOutput) + + DeleteBucketTagging(*s3.DeleteBucketTaggingInput) (*s3.DeleteBucketTaggingOutput, error) + DeleteBucketTaggingWithContext(aws.Context, *s3.DeleteBucketTaggingInput, ...request.Option) (*s3.DeleteBucketTaggingOutput, error) + DeleteBucketTaggingRequest(*s3.DeleteBucketTaggingInput) (*request.Request, *s3.DeleteBucketTaggingOutput) + + DeleteBucketWebsite(*s3.DeleteBucketWebsiteInput) (*s3.DeleteBucketWebsiteOutput, error) + DeleteBucketWebsiteWithContext(aws.Context, *s3.DeleteBucketWebsiteInput, ...request.Option) (*s3.DeleteBucketWebsiteOutput, error) + DeleteBucketWebsiteRequest(*s3.DeleteBucketWebsiteInput) (*request.Request, *s3.DeleteBucketWebsiteOutput) + + DeleteObject(*s3.DeleteObjectInput) (*s3.DeleteObjectOutput, error) + DeleteObjectWithContext(aws.Context, *s3.DeleteObjectInput, ...request.Option) (*s3.DeleteObjectOutput, error) + DeleteObjectRequest(*s3.DeleteObjectInput) (*request.Request, *s3.DeleteObjectOutput) + + DeleteObjectTagging(*s3.DeleteObjectTaggingInput) (*s3.DeleteObjectTaggingOutput, error) + DeleteObjectTaggingWithContext(aws.Context, *s3.DeleteObjectTaggingInput, ...request.Option) (*s3.DeleteObjectTaggingOutput, error) + DeleteObjectTaggingRequest(*s3.DeleteObjectTaggingInput) (*request.Request, *s3.DeleteObjectTaggingOutput) + + DeleteObjects(*s3.DeleteObjectsInput) (*s3.DeleteObjectsOutput, error) + DeleteObjectsWithContext(aws.Context, *s3.DeleteObjectsInput, ...request.Option) (*s3.DeleteObjectsOutput, error) + DeleteObjectsRequest(*s3.DeleteObjectsInput) (*request.Request, *s3.DeleteObjectsOutput) + + GetBucketAccelerateConfiguration(*s3.GetBucketAccelerateConfigurationInput) (*s3.GetBucketAccelerateConfigurationOutput, error) + GetBucketAccelerateConfigurationWithContext(aws.Context, *s3.GetBucketAccelerateConfigurationInput, ...request.Option) (*s3.GetBucketAccelerateConfigurationOutput, error) + GetBucketAccelerateConfigurationRequest(*s3.GetBucketAccelerateConfigurationInput) (*request.Request, *s3.GetBucketAccelerateConfigurationOutput) + + GetBucketAcl(*s3.GetBucketAclInput) (*s3.GetBucketAclOutput, error) + GetBucketAclWithContext(aws.Context, *s3.GetBucketAclInput, ...request.Option) (*s3.GetBucketAclOutput, error) + GetBucketAclRequest(*s3.GetBucketAclInput) (*request.Request, *s3.GetBucketAclOutput) + + GetBucketAnalyticsConfiguration(*s3.GetBucketAnalyticsConfigurationInput) (*s3.GetBucketAnalyticsConfigurationOutput, error) + GetBucketAnalyticsConfigurationWithContext(aws.Context, *s3.GetBucketAnalyticsConfigurationInput, ...request.Option) (*s3.GetBucketAnalyticsConfigurationOutput, error) + GetBucketAnalyticsConfigurationRequest(*s3.GetBucketAnalyticsConfigurationInput) (*request.Request, *s3.GetBucketAnalyticsConfigurationOutput) + + GetBucketCors(*s3.GetBucketCorsInput) (*s3.GetBucketCorsOutput, error) + GetBucketCorsWithContext(aws.Context, *s3.GetBucketCorsInput, ...request.Option) (*s3.GetBucketCorsOutput, error) + GetBucketCorsRequest(*s3.GetBucketCorsInput) (*request.Request, *s3.GetBucketCorsOutput) + + GetBucketEncryption(*s3.GetBucketEncryptionInput) (*s3.GetBucketEncryptionOutput, error) + GetBucketEncryptionWithContext(aws.Context, *s3.GetBucketEncryptionInput, ...request.Option) (*s3.GetBucketEncryptionOutput, error) + GetBucketEncryptionRequest(*s3.GetBucketEncryptionInput) (*request.Request, *s3.GetBucketEncryptionOutput) + + GetBucketInventoryConfiguration(*s3.GetBucketInventoryConfigurationInput) (*s3.GetBucketInventoryConfigurationOutput, error) + GetBucketInventoryConfigurationWithContext(aws.Context, *s3.GetBucketInventoryConfigurationInput, ...request.Option) (*s3.GetBucketInventoryConfigurationOutput, error) + GetBucketInventoryConfigurationRequest(*s3.GetBucketInventoryConfigurationInput) (*request.Request, *s3.GetBucketInventoryConfigurationOutput) + + GetBucketLifecycle(*s3.GetBucketLifecycleInput) (*s3.GetBucketLifecycleOutput, error) + GetBucketLifecycleWithContext(aws.Context, *s3.GetBucketLifecycleInput, ...request.Option) (*s3.GetBucketLifecycleOutput, error) + GetBucketLifecycleRequest(*s3.GetBucketLifecycleInput) (*request.Request, *s3.GetBucketLifecycleOutput) + + GetBucketLifecycleConfiguration(*s3.GetBucketLifecycleConfigurationInput) (*s3.GetBucketLifecycleConfigurationOutput, error) + GetBucketLifecycleConfigurationWithContext(aws.Context, *s3.GetBucketLifecycleConfigurationInput, ...request.Option) (*s3.GetBucketLifecycleConfigurationOutput, error) + GetBucketLifecycleConfigurationRequest(*s3.GetBucketLifecycleConfigurationInput) (*request.Request, *s3.GetBucketLifecycleConfigurationOutput) + + GetBucketLocation(*s3.GetBucketLocationInput) (*s3.GetBucketLocationOutput, error) + GetBucketLocationWithContext(aws.Context, *s3.GetBucketLocationInput, ...request.Option) (*s3.GetBucketLocationOutput, error) + GetBucketLocationRequest(*s3.GetBucketLocationInput) (*request.Request, *s3.GetBucketLocationOutput) + + GetBucketLogging(*s3.GetBucketLoggingInput) (*s3.GetBucketLoggingOutput, error) + GetBucketLoggingWithContext(aws.Context, *s3.GetBucketLoggingInput, ...request.Option) (*s3.GetBucketLoggingOutput, error) + GetBucketLoggingRequest(*s3.GetBucketLoggingInput) (*request.Request, *s3.GetBucketLoggingOutput) + + GetBucketMetricsConfiguration(*s3.GetBucketMetricsConfigurationInput) (*s3.GetBucketMetricsConfigurationOutput, error) + GetBucketMetricsConfigurationWithContext(aws.Context, *s3.GetBucketMetricsConfigurationInput, ...request.Option) (*s3.GetBucketMetricsConfigurationOutput, error) + GetBucketMetricsConfigurationRequest(*s3.GetBucketMetricsConfigurationInput) (*request.Request, *s3.GetBucketMetricsConfigurationOutput) + + GetBucketNotification(*s3.GetBucketNotificationConfigurationRequest) (*s3.NotificationConfigurationDeprecated, error) + GetBucketNotificationWithContext(aws.Context, *s3.GetBucketNotificationConfigurationRequest, ...request.Option) (*s3.NotificationConfigurationDeprecated, error) + GetBucketNotificationRequest(*s3.GetBucketNotificationConfigurationRequest) (*request.Request, *s3.NotificationConfigurationDeprecated) + + GetBucketNotificationConfiguration(*s3.GetBucketNotificationConfigurationRequest) (*s3.NotificationConfiguration, error) + GetBucketNotificationConfigurationWithContext(aws.Context, *s3.GetBucketNotificationConfigurationRequest, ...request.Option) (*s3.NotificationConfiguration, error) + GetBucketNotificationConfigurationRequest(*s3.GetBucketNotificationConfigurationRequest) (*request.Request, *s3.NotificationConfiguration) + + GetBucketPolicy(*s3.GetBucketPolicyInput) (*s3.GetBucketPolicyOutput, error) + GetBucketPolicyWithContext(aws.Context, *s3.GetBucketPolicyInput, ...request.Option) (*s3.GetBucketPolicyOutput, error) + GetBucketPolicyRequest(*s3.GetBucketPolicyInput) (*request.Request, *s3.GetBucketPolicyOutput) + + GetBucketReplication(*s3.GetBucketReplicationInput) (*s3.GetBucketReplicationOutput, error) + GetBucketReplicationWithContext(aws.Context, *s3.GetBucketReplicationInput, ...request.Option) (*s3.GetBucketReplicationOutput, error) + GetBucketReplicationRequest(*s3.GetBucketReplicationInput) (*request.Request, *s3.GetBucketReplicationOutput) + + GetBucketRequestPayment(*s3.GetBucketRequestPaymentInput) (*s3.GetBucketRequestPaymentOutput, error) + GetBucketRequestPaymentWithContext(aws.Context, *s3.GetBucketRequestPaymentInput, ...request.Option) (*s3.GetBucketRequestPaymentOutput, error) + GetBucketRequestPaymentRequest(*s3.GetBucketRequestPaymentInput) (*request.Request, *s3.GetBucketRequestPaymentOutput) + + GetBucketTagging(*s3.GetBucketTaggingInput) (*s3.GetBucketTaggingOutput, error) + GetBucketTaggingWithContext(aws.Context, *s3.GetBucketTaggingInput, ...request.Option) (*s3.GetBucketTaggingOutput, error) + GetBucketTaggingRequest(*s3.GetBucketTaggingInput) (*request.Request, *s3.GetBucketTaggingOutput) + + GetBucketVersioning(*s3.GetBucketVersioningInput) (*s3.GetBucketVersioningOutput, error) + GetBucketVersioningWithContext(aws.Context, *s3.GetBucketVersioningInput, ...request.Option) (*s3.GetBucketVersioningOutput, error) + GetBucketVersioningRequest(*s3.GetBucketVersioningInput) (*request.Request, *s3.GetBucketVersioningOutput) + + GetBucketWebsite(*s3.GetBucketWebsiteInput) (*s3.GetBucketWebsiteOutput, error) + GetBucketWebsiteWithContext(aws.Context, *s3.GetBucketWebsiteInput, ...request.Option) (*s3.GetBucketWebsiteOutput, error) + GetBucketWebsiteRequest(*s3.GetBucketWebsiteInput) (*request.Request, *s3.GetBucketWebsiteOutput) + + GetObject(*s3.GetObjectInput) (*s3.GetObjectOutput, error) + GetObjectWithContext(aws.Context, *s3.GetObjectInput, ...request.Option) (*s3.GetObjectOutput, error) + GetObjectRequest(*s3.GetObjectInput) (*request.Request, *s3.GetObjectOutput) + + GetObjectAcl(*s3.GetObjectAclInput) (*s3.GetObjectAclOutput, error) + GetObjectAclWithContext(aws.Context, *s3.GetObjectAclInput, ...request.Option) (*s3.GetObjectAclOutput, error) + GetObjectAclRequest(*s3.GetObjectAclInput) (*request.Request, *s3.GetObjectAclOutput) + + GetObjectTagging(*s3.GetObjectTaggingInput) (*s3.GetObjectTaggingOutput, error) + GetObjectTaggingWithContext(aws.Context, *s3.GetObjectTaggingInput, ...request.Option) (*s3.GetObjectTaggingOutput, error) + GetObjectTaggingRequest(*s3.GetObjectTaggingInput) (*request.Request, *s3.GetObjectTaggingOutput) + + GetObjectTorrent(*s3.GetObjectTorrentInput) (*s3.GetObjectTorrentOutput, error) + GetObjectTorrentWithContext(aws.Context, *s3.GetObjectTorrentInput, ...request.Option) (*s3.GetObjectTorrentOutput, error) + GetObjectTorrentRequest(*s3.GetObjectTorrentInput) (*request.Request, *s3.GetObjectTorrentOutput) + + HeadBucket(*s3.HeadBucketInput) (*s3.HeadBucketOutput, error) + HeadBucketWithContext(aws.Context, *s3.HeadBucketInput, ...request.Option) (*s3.HeadBucketOutput, error) + HeadBucketRequest(*s3.HeadBucketInput) (*request.Request, *s3.HeadBucketOutput) + + HeadObject(*s3.HeadObjectInput) (*s3.HeadObjectOutput, error) + HeadObjectWithContext(aws.Context, *s3.HeadObjectInput, ...request.Option) (*s3.HeadObjectOutput, error) + HeadObjectRequest(*s3.HeadObjectInput) (*request.Request, *s3.HeadObjectOutput) + + ListBucketAnalyticsConfigurations(*s3.ListBucketAnalyticsConfigurationsInput) (*s3.ListBucketAnalyticsConfigurationsOutput, error) + ListBucketAnalyticsConfigurationsWithContext(aws.Context, *s3.ListBucketAnalyticsConfigurationsInput, ...request.Option) (*s3.ListBucketAnalyticsConfigurationsOutput, error) + ListBucketAnalyticsConfigurationsRequest(*s3.ListBucketAnalyticsConfigurationsInput) (*request.Request, *s3.ListBucketAnalyticsConfigurationsOutput) + + ListBucketInventoryConfigurations(*s3.ListBucketInventoryConfigurationsInput) (*s3.ListBucketInventoryConfigurationsOutput, error) + ListBucketInventoryConfigurationsWithContext(aws.Context, *s3.ListBucketInventoryConfigurationsInput, ...request.Option) (*s3.ListBucketInventoryConfigurationsOutput, error) + ListBucketInventoryConfigurationsRequest(*s3.ListBucketInventoryConfigurationsInput) (*request.Request, *s3.ListBucketInventoryConfigurationsOutput) + + ListBucketMetricsConfigurations(*s3.ListBucketMetricsConfigurationsInput) (*s3.ListBucketMetricsConfigurationsOutput, error) + ListBucketMetricsConfigurationsWithContext(aws.Context, *s3.ListBucketMetricsConfigurationsInput, ...request.Option) (*s3.ListBucketMetricsConfigurationsOutput, error) + ListBucketMetricsConfigurationsRequest(*s3.ListBucketMetricsConfigurationsInput) (*request.Request, *s3.ListBucketMetricsConfigurationsOutput) + + ListBuckets(*s3.ListBucketsInput) (*s3.ListBucketsOutput, error) + ListBucketsWithContext(aws.Context, *s3.ListBucketsInput, ...request.Option) (*s3.ListBucketsOutput, error) + ListBucketsRequest(*s3.ListBucketsInput) (*request.Request, *s3.ListBucketsOutput) + + ListMultipartUploads(*s3.ListMultipartUploadsInput) (*s3.ListMultipartUploadsOutput, error) + ListMultipartUploadsWithContext(aws.Context, *s3.ListMultipartUploadsInput, ...request.Option) (*s3.ListMultipartUploadsOutput, error) + ListMultipartUploadsRequest(*s3.ListMultipartUploadsInput) (*request.Request, *s3.ListMultipartUploadsOutput) + + ListMultipartUploadsPages(*s3.ListMultipartUploadsInput, func(*s3.ListMultipartUploadsOutput, bool) bool) error + ListMultipartUploadsPagesWithContext(aws.Context, *s3.ListMultipartUploadsInput, func(*s3.ListMultipartUploadsOutput, bool) bool, ...request.Option) error + + ListObjectVersions(*s3.ListObjectVersionsInput) (*s3.ListObjectVersionsOutput, error) + ListObjectVersionsWithContext(aws.Context, *s3.ListObjectVersionsInput, ...request.Option) (*s3.ListObjectVersionsOutput, error) + ListObjectVersionsRequest(*s3.ListObjectVersionsInput) (*request.Request, *s3.ListObjectVersionsOutput) + + ListObjectVersionsPages(*s3.ListObjectVersionsInput, func(*s3.ListObjectVersionsOutput, bool) bool) error + ListObjectVersionsPagesWithContext(aws.Context, *s3.ListObjectVersionsInput, func(*s3.ListObjectVersionsOutput, bool) bool, ...request.Option) error + + ListObjects(*s3.ListObjectsInput) (*s3.ListObjectsOutput, error) + ListObjectsWithContext(aws.Context, *s3.ListObjectsInput, ...request.Option) (*s3.ListObjectsOutput, error) + ListObjectsRequest(*s3.ListObjectsInput) (*request.Request, *s3.ListObjectsOutput) + + ListObjectsPages(*s3.ListObjectsInput, func(*s3.ListObjectsOutput, bool) bool) error + ListObjectsPagesWithContext(aws.Context, *s3.ListObjectsInput, func(*s3.ListObjectsOutput, bool) bool, ...request.Option) error + + ListObjectsV2(*s3.ListObjectsV2Input) (*s3.ListObjectsV2Output, error) + ListObjectsV2WithContext(aws.Context, *s3.ListObjectsV2Input, ...request.Option) (*s3.ListObjectsV2Output, error) + ListObjectsV2Request(*s3.ListObjectsV2Input) (*request.Request, *s3.ListObjectsV2Output) + + ListObjectsV2Pages(*s3.ListObjectsV2Input, func(*s3.ListObjectsV2Output, bool) bool) error + ListObjectsV2PagesWithContext(aws.Context, *s3.ListObjectsV2Input, func(*s3.ListObjectsV2Output, bool) bool, ...request.Option) error + + ListParts(*s3.ListPartsInput) (*s3.ListPartsOutput, error) + ListPartsWithContext(aws.Context, *s3.ListPartsInput, ...request.Option) (*s3.ListPartsOutput, error) + ListPartsRequest(*s3.ListPartsInput) (*request.Request, *s3.ListPartsOutput) + + ListPartsPages(*s3.ListPartsInput, func(*s3.ListPartsOutput, bool) bool) error + ListPartsPagesWithContext(aws.Context, *s3.ListPartsInput, func(*s3.ListPartsOutput, bool) bool, ...request.Option) error + + PutBucketAccelerateConfiguration(*s3.PutBucketAccelerateConfigurationInput) (*s3.PutBucketAccelerateConfigurationOutput, error) + PutBucketAccelerateConfigurationWithContext(aws.Context, *s3.PutBucketAccelerateConfigurationInput, ...request.Option) (*s3.PutBucketAccelerateConfigurationOutput, error) + PutBucketAccelerateConfigurationRequest(*s3.PutBucketAccelerateConfigurationInput) (*request.Request, *s3.PutBucketAccelerateConfigurationOutput) + + PutBucketAcl(*s3.PutBucketAclInput) (*s3.PutBucketAclOutput, error) + PutBucketAclWithContext(aws.Context, *s3.PutBucketAclInput, ...request.Option) (*s3.PutBucketAclOutput, error) + PutBucketAclRequest(*s3.PutBucketAclInput) (*request.Request, *s3.PutBucketAclOutput) + + PutBucketAnalyticsConfiguration(*s3.PutBucketAnalyticsConfigurationInput) (*s3.PutBucketAnalyticsConfigurationOutput, error) + PutBucketAnalyticsConfigurationWithContext(aws.Context, *s3.PutBucketAnalyticsConfigurationInput, ...request.Option) (*s3.PutBucketAnalyticsConfigurationOutput, error) + PutBucketAnalyticsConfigurationRequest(*s3.PutBucketAnalyticsConfigurationInput) (*request.Request, *s3.PutBucketAnalyticsConfigurationOutput) + + PutBucketCors(*s3.PutBucketCorsInput) (*s3.PutBucketCorsOutput, error) + PutBucketCorsWithContext(aws.Context, *s3.PutBucketCorsInput, ...request.Option) (*s3.PutBucketCorsOutput, error) + PutBucketCorsRequest(*s3.PutBucketCorsInput) (*request.Request, *s3.PutBucketCorsOutput) + + PutBucketEncryption(*s3.PutBucketEncryptionInput) (*s3.PutBucketEncryptionOutput, error) + PutBucketEncryptionWithContext(aws.Context, *s3.PutBucketEncryptionInput, ...request.Option) (*s3.PutBucketEncryptionOutput, error) + PutBucketEncryptionRequest(*s3.PutBucketEncryptionInput) (*request.Request, *s3.PutBucketEncryptionOutput) + + PutBucketInventoryConfiguration(*s3.PutBucketInventoryConfigurationInput) (*s3.PutBucketInventoryConfigurationOutput, error) + PutBucketInventoryConfigurationWithContext(aws.Context, *s3.PutBucketInventoryConfigurationInput, ...request.Option) (*s3.PutBucketInventoryConfigurationOutput, error) + PutBucketInventoryConfigurationRequest(*s3.PutBucketInventoryConfigurationInput) (*request.Request, *s3.PutBucketInventoryConfigurationOutput) + + PutBucketLifecycle(*s3.PutBucketLifecycleInput) (*s3.PutBucketLifecycleOutput, error) + PutBucketLifecycleWithContext(aws.Context, *s3.PutBucketLifecycleInput, ...request.Option) (*s3.PutBucketLifecycleOutput, error) + PutBucketLifecycleRequest(*s3.PutBucketLifecycleInput) (*request.Request, *s3.PutBucketLifecycleOutput) + + PutBucketLifecycleConfiguration(*s3.PutBucketLifecycleConfigurationInput) (*s3.PutBucketLifecycleConfigurationOutput, error) + PutBucketLifecycleConfigurationWithContext(aws.Context, *s3.PutBucketLifecycleConfigurationInput, ...request.Option) (*s3.PutBucketLifecycleConfigurationOutput, error) + PutBucketLifecycleConfigurationRequest(*s3.PutBucketLifecycleConfigurationInput) (*request.Request, *s3.PutBucketLifecycleConfigurationOutput) + + PutBucketLogging(*s3.PutBucketLoggingInput) (*s3.PutBucketLoggingOutput, error) + PutBucketLoggingWithContext(aws.Context, *s3.PutBucketLoggingInput, ...request.Option) (*s3.PutBucketLoggingOutput, error) + PutBucketLoggingRequest(*s3.PutBucketLoggingInput) (*request.Request, *s3.PutBucketLoggingOutput) + + PutBucketMetricsConfiguration(*s3.PutBucketMetricsConfigurationInput) (*s3.PutBucketMetricsConfigurationOutput, error) + PutBucketMetricsConfigurationWithContext(aws.Context, *s3.PutBucketMetricsConfigurationInput, ...request.Option) (*s3.PutBucketMetricsConfigurationOutput, error) + PutBucketMetricsConfigurationRequest(*s3.PutBucketMetricsConfigurationInput) (*request.Request, *s3.PutBucketMetricsConfigurationOutput) + + PutBucketNotification(*s3.PutBucketNotificationInput) (*s3.PutBucketNotificationOutput, error) + PutBucketNotificationWithContext(aws.Context, *s3.PutBucketNotificationInput, ...request.Option) (*s3.PutBucketNotificationOutput, error) + PutBucketNotificationRequest(*s3.PutBucketNotificationInput) (*request.Request, *s3.PutBucketNotificationOutput) + + PutBucketNotificationConfiguration(*s3.PutBucketNotificationConfigurationInput) (*s3.PutBucketNotificationConfigurationOutput, error) + PutBucketNotificationConfigurationWithContext(aws.Context, *s3.PutBucketNotificationConfigurationInput, ...request.Option) (*s3.PutBucketNotificationConfigurationOutput, error) + PutBucketNotificationConfigurationRequest(*s3.PutBucketNotificationConfigurationInput) (*request.Request, *s3.PutBucketNotificationConfigurationOutput) + + PutBucketPolicy(*s3.PutBucketPolicyInput) (*s3.PutBucketPolicyOutput, error) + PutBucketPolicyWithContext(aws.Context, *s3.PutBucketPolicyInput, ...request.Option) (*s3.PutBucketPolicyOutput, error) + PutBucketPolicyRequest(*s3.PutBucketPolicyInput) (*request.Request, *s3.PutBucketPolicyOutput) + + PutBucketReplication(*s3.PutBucketReplicationInput) (*s3.PutBucketReplicationOutput, error) + PutBucketReplicationWithContext(aws.Context, *s3.PutBucketReplicationInput, ...request.Option) (*s3.PutBucketReplicationOutput, error) + PutBucketReplicationRequest(*s3.PutBucketReplicationInput) (*request.Request, *s3.PutBucketReplicationOutput) + + PutBucketRequestPayment(*s3.PutBucketRequestPaymentInput) (*s3.PutBucketRequestPaymentOutput, error) + PutBucketRequestPaymentWithContext(aws.Context, *s3.PutBucketRequestPaymentInput, ...request.Option) (*s3.PutBucketRequestPaymentOutput, error) + PutBucketRequestPaymentRequest(*s3.PutBucketRequestPaymentInput) (*request.Request, *s3.PutBucketRequestPaymentOutput) + + PutBucketTagging(*s3.PutBucketTaggingInput) (*s3.PutBucketTaggingOutput, error) + PutBucketTaggingWithContext(aws.Context, *s3.PutBucketTaggingInput, ...request.Option) (*s3.PutBucketTaggingOutput, error) + PutBucketTaggingRequest(*s3.PutBucketTaggingInput) (*request.Request, *s3.PutBucketTaggingOutput) + + PutBucketVersioning(*s3.PutBucketVersioningInput) (*s3.PutBucketVersioningOutput, error) + PutBucketVersioningWithContext(aws.Context, *s3.PutBucketVersioningInput, ...request.Option) (*s3.PutBucketVersioningOutput, error) + PutBucketVersioningRequest(*s3.PutBucketVersioningInput) (*request.Request, *s3.PutBucketVersioningOutput) + + PutBucketWebsite(*s3.PutBucketWebsiteInput) (*s3.PutBucketWebsiteOutput, error) + PutBucketWebsiteWithContext(aws.Context, *s3.PutBucketWebsiteInput, ...request.Option) (*s3.PutBucketWebsiteOutput, error) + PutBucketWebsiteRequest(*s3.PutBucketWebsiteInput) (*request.Request, *s3.PutBucketWebsiteOutput) + + PutObject(*s3.PutObjectInput) (*s3.PutObjectOutput, error) + PutObjectWithContext(aws.Context, *s3.PutObjectInput, ...request.Option) (*s3.PutObjectOutput, error) + PutObjectRequest(*s3.PutObjectInput) (*request.Request, *s3.PutObjectOutput) + + PutObjectAcl(*s3.PutObjectAclInput) (*s3.PutObjectAclOutput, error) + PutObjectAclWithContext(aws.Context, *s3.PutObjectAclInput, ...request.Option) (*s3.PutObjectAclOutput, error) + PutObjectAclRequest(*s3.PutObjectAclInput) (*request.Request, *s3.PutObjectAclOutput) + + PutObjectTagging(*s3.PutObjectTaggingInput) (*s3.PutObjectTaggingOutput, error) + PutObjectTaggingWithContext(aws.Context, *s3.PutObjectTaggingInput, ...request.Option) (*s3.PutObjectTaggingOutput, error) + PutObjectTaggingRequest(*s3.PutObjectTaggingInput) (*request.Request, *s3.PutObjectTaggingOutput) + + RestoreObject(*s3.RestoreObjectInput) (*s3.RestoreObjectOutput, error) + RestoreObjectWithContext(aws.Context, *s3.RestoreObjectInput, ...request.Option) (*s3.RestoreObjectOutput, error) + RestoreObjectRequest(*s3.RestoreObjectInput) (*request.Request, *s3.RestoreObjectOutput) + + SelectObjectContent(*s3.SelectObjectContentInput) (*s3.SelectObjectContentOutput, error) + SelectObjectContentWithContext(aws.Context, *s3.SelectObjectContentInput, ...request.Option) (*s3.SelectObjectContentOutput, error) + SelectObjectContentRequest(*s3.SelectObjectContentInput) (*request.Request, *s3.SelectObjectContentOutput) + + UploadPart(*s3.UploadPartInput) (*s3.UploadPartOutput, error) + UploadPartWithContext(aws.Context, *s3.UploadPartInput, ...request.Option) (*s3.UploadPartOutput, error) + UploadPartRequest(*s3.UploadPartInput) (*request.Request, *s3.UploadPartOutput) + + UploadPartCopy(*s3.UploadPartCopyInput) (*s3.UploadPartCopyOutput, error) + UploadPartCopyWithContext(aws.Context, *s3.UploadPartCopyInput, ...request.Option) (*s3.UploadPartCopyOutput, error) + UploadPartCopyRequest(*s3.UploadPartCopyInput) (*request.Request, *s3.UploadPartCopyOutput) + + WaitUntilBucketExists(*s3.HeadBucketInput) error + WaitUntilBucketExistsWithContext(aws.Context, *s3.HeadBucketInput, ...request.WaiterOption) error + + WaitUntilBucketNotExists(*s3.HeadBucketInput) error + WaitUntilBucketNotExistsWithContext(aws.Context, *s3.HeadBucketInput, ...request.WaiterOption) error + + WaitUntilObjectExists(*s3.HeadObjectInput) error + WaitUntilObjectExistsWithContext(aws.Context, *s3.HeadObjectInput, ...request.WaiterOption) error + + WaitUntilObjectNotExists(*s3.HeadObjectInput) error + WaitUntilObjectNotExistsWithContext(aws.Context, *s3.HeadObjectInput, ...request.WaiterOption) error +} + +var _ S3API = (*s3.S3)(nil) diff --git a/vendor/github.com/aws/aws-sdk-go/service/s3/s3manager/batch.go b/vendor/github.com/aws/aws-sdk-go/service/s3/s3manager/batch.go new file mode 100644 index 0000000000..18215574bf --- /dev/null +++ b/vendor/github.com/aws/aws-sdk-go/service/s3/s3manager/batch.go @@ -0,0 +1,529 @@ +package s3manager + +import ( + "bytes" + "fmt" + "io" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/client" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go/service/s3/s3iface" +) + +const ( + // DefaultBatchSize is the batch size we initialize when constructing a batch delete client. + // This value is used when calling DeleteObjects. This represents how many objects to delete + // per DeleteObjects call. + DefaultBatchSize = 100 +) + +// BatchError will contain the key and bucket of the object that failed to +// either upload or download. +type BatchError struct { + Errors Errors + code string + message string +} + +// Errors is a typed alias for a slice of errors to satisfy the error +// interface. +type Errors []Error + +func (errs Errors) Error() string { + buf := bytes.NewBuffer(nil) + for i, err := range errs { + buf.WriteString(err.Error()) + if i+1 < len(errs) { + buf.WriteString("\n") + } + } + return buf.String() +} + +// Error will contain the original error, bucket, and key of the operation that failed +// during batch operations. +type Error struct { + OrigErr error + Bucket *string + Key *string +} + +func newError(err error, bucket, key *string) Error { + return Error{ + err, + bucket, + key, + } +} + +func (err *Error) Error() string { + origErr := "" + if err.OrigErr != nil { + origErr = ":\n" + err.OrigErr.Error() + } + return fmt.Sprintf("failed to perform batch operation on %q to %q%s", + aws.StringValue(err.Key), + aws.StringValue(err.Bucket), + origErr, + ) +} + +// NewBatchError will return a BatchError that satisfies the awserr.Error interface. +func NewBatchError(code, message string, err []Error) awserr.Error { + return &BatchError{ + Errors: err, + code: code, + message: message, + } +} + +// Code will return the code associated with the batch error. +func (err *BatchError) Code() string { + return err.code +} + +// Message will return the message associated with the batch error. +func (err *BatchError) Message() string { + return err.message +} + +func (err *BatchError) Error() string { + return awserr.SprintError(err.Code(), err.Message(), "", err.Errors) +} + +// OrigErr will return the original error. Which, in this case, will always be nil +// for batched operations. +func (err *BatchError) OrigErr() error { + return err.Errors +} + +// BatchDeleteIterator is an interface that uses the scanner pattern to +// iterate through what needs to be deleted. +type BatchDeleteIterator interface { + Next() bool + Err() error + DeleteObject() BatchDeleteObject +} + +// DeleteListIterator is an alternative iterator for the BatchDelete client. This will +// iterate through a list of objects and delete the objects. +// +// Example: +// iter := &s3manager.DeleteListIterator{ +// Client: svc, +// Input: &s3.ListObjectsInput{ +// Bucket: aws.String("bucket"), +// MaxKeys: aws.Int64(5), +// }, +// Paginator: request.Pagination{ +// NewRequest: func() (*request.Request, error) { +// var inCpy *ListObjectsInput +// if input != nil { +// tmp := *input +// inCpy = &tmp +// } +// req, _ := c.ListObjectsRequest(inCpy) +// return req, nil +// }, +// }, +// } +// +// batcher := s3manager.NewBatchDeleteWithClient(svc) +// if err := batcher.Delete(aws.BackgroundContext(), iter); err != nil { +// return err +// } +type DeleteListIterator struct { + Bucket *string + Paginator request.Pagination + objects []*s3.Object +} + +// NewDeleteListIterator will return a new DeleteListIterator. +func NewDeleteListIterator(svc s3iface.S3API, input *s3.ListObjectsInput, opts ...func(*DeleteListIterator)) BatchDeleteIterator { + iter := &DeleteListIterator{ + Bucket: input.Bucket, + Paginator: request.Pagination{ + NewRequest: func() (*request.Request, error) { + var inCpy *s3.ListObjectsInput + if input != nil { + tmp := *input + inCpy = &tmp + } + req, _ := svc.ListObjectsRequest(inCpy) + return req, nil + }, + }, + } + + for _, opt := range opts { + opt(iter) + } + return iter +} + +// Next will use the S3API client to iterate through a list of objects. +func (iter *DeleteListIterator) Next() bool { + if len(iter.objects) > 0 { + iter.objects = iter.objects[1:] + } + + if len(iter.objects) == 0 && iter.Paginator.Next() { + iter.objects = iter.Paginator.Page().(*s3.ListObjectsOutput).Contents + } + + return len(iter.objects) > 0 +} + +// Err will return the last known error from Next. +func (iter *DeleteListIterator) Err() error { + return iter.Paginator.Err() +} + +// DeleteObject will return the current object to be deleted. +func (iter *DeleteListIterator) DeleteObject() BatchDeleteObject { + return BatchDeleteObject{ + Object: &s3.DeleteObjectInput{ + Bucket: iter.Bucket, + Key: iter.objects[0].Key, + }, + } +} + +// BatchDelete will use the s3 package's service client to perform a batch +// delete. +type BatchDelete struct { + Client s3iface.S3API + BatchSize int +} + +// NewBatchDeleteWithClient will return a new delete client that can delete a batched amount of +// objects. +// +// Example: +// batcher := s3manager.NewBatchDeleteWithClient(client, size) +// +// objects := []BatchDeleteObject{ +// { +// Object: &s3.DeleteObjectInput { +// Key: aws.String("key"), +// Bucket: aws.String("bucket"), +// }, +// }, +// } +// +// if err := batcher.Delete(aws.BackgroundContext(), &s3manager.DeleteObjectsIterator{ +// Objects: objects, +// }); err != nil { +// return err +// } +func NewBatchDeleteWithClient(client s3iface.S3API, options ...func(*BatchDelete)) *BatchDelete { + svc := &BatchDelete{ + Client: client, + BatchSize: DefaultBatchSize, + } + + for _, opt := range options { + opt(svc) + } + + return svc +} + +// NewBatchDelete will return a new delete client that can delete a batched amount of +// objects. +// +// Example: +// batcher := s3manager.NewBatchDelete(sess, size) +// +// objects := []BatchDeleteObject{ +// { +// Object: &s3.DeleteObjectInput { +// Key: aws.String("key"), +// Bucket: aws.String("bucket"), +// }, +// }, +// } +// +// if err := batcher.Delete(aws.BackgroundContext(), &s3manager.DeleteObjectsIterator{ +// Objects: objects, +// }); err != nil { +// return err +// } +func NewBatchDelete(c client.ConfigProvider, options ...func(*BatchDelete)) *BatchDelete { + client := s3.New(c) + return NewBatchDeleteWithClient(client, options...) +} + +// BatchDeleteObject is a wrapper object for calling the batch delete operation. +type BatchDeleteObject struct { + Object *s3.DeleteObjectInput + // After will run after each iteration during the batch process. This function will + // be executed whether or not the request was successful. + After func() error +} + +// DeleteObjectsIterator is an interface that uses the scanner pattern to iterate +// through a series of objects to be deleted. +type DeleteObjectsIterator struct { + Objects []BatchDeleteObject + index int + inc bool +} + +// Next will increment the default iterator's index and and ensure that there +// is another object to iterator to. +func (iter *DeleteObjectsIterator) Next() bool { + if iter.inc { + iter.index++ + } else { + iter.inc = true + } + return iter.index < len(iter.Objects) +} + +// Err will return an error. Since this is just used to satisfy the BatchDeleteIterator interface +// this will only return nil. +func (iter *DeleteObjectsIterator) Err() error { + return nil +} + +// DeleteObject will return the BatchDeleteObject at the current batched index. +func (iter *DeleteObjectsIterator) DeleteObject() BatchDeleteObject { + object := iter.Objects[iter.index] + return object +} + +// Delete will use the iterator to queue up objects that need to be deleted. +// Once the batch size is met, this will call the deleteBatch function. +func (d *BatchDelete) Delete(ctx aws.Context, iter BatchDeleteIterator) error { + var errs []Error + objects := []BatchDeleteObject{} + var input *s3.DeleteObjectsInput + + for iter.Next() { + o := iter.DeleteObject() + + if input == nil { + input = initDeleteObjectsInput(o.Object) + } + + parity := hasParity(input, o) + if parity { + input.Delete.Objects = append(input.Delete.Objects, &s3.ObjectIdentifier{ + Key: o.Object.Key, + VersionId: o.Object.VersionId, + }) + objects = append(objects, o) + } + + if len(input.Delete.Objects) == d.BatchSize || !parity { + if err := deleteBatch(ctx, d, input, objects); err != nil { + errs = append(errs, err...) + } + + objects = objects[:0] + input = nil + + if !parity { + objects = append(objects, o) + input = initDeleteObjectsInput(o.Object) + input.Delete.Objects = append(input.Delete.Objects, &s3.ObjectIdentifier{ + Key: o.Object.Key, + VersionId: o.Object.VersionId, + }) + } + } + } + + // iter.Next() could return false (above) plus populate iter.Err() + if iter.Err() != nil { + errs = append(errs, newError(iter.Err(), nil, nil)) + } + + if input != nil && len(input.Delete.Objects) > 0 { + if err := deleteBatch(ctx, d, input, objects); err != nil { + errs = append(errs, err...) + } + } + + if len(errs) > 0 { + return NewBatchError("BatchedDeleteIncomplete", "some objects have failed to be deleted.", errs) + } + return nil +} + +func initDeleteObjectsInput(o *s3.DeleteObjectInput) *s3.DeleteObjectsInput { + return &s3.DeleteObjectsInput{ + Bucket: o.Bucket, + MFA: o.MFA, + RequestPayer: o.RequestPayer, + Delete: &s3.Delete{}, + } +} + +const ( + // ErrDeleteBatchFailCode represents an error code which will be returned + // only when DeleteObjects.Errors has an error that does not contain a code. + ErrDeleteBatchFailCode = "DeleteBatchError" + errDefaultDeleteBatchMessage = "failed to delete" +) + +// deleteBatch will delete a batch of items in the objects parameters. +func deleteBatch(ctx aws.Context, d *BatchDelete, input *s3.DeleteObjectsInput, objects []BatchDeleteObject) []Error { + errs := []Error{} + + if result, err := d.Client.DeleteObjectsWithContext(ctx, input); err != nil { + for i := 0; i < len(input.Delete.Objects); i++ { + errs = append(errs, newError(err, input.Bucket, input.Delete.Objects[i].Key)) + } + } else if len(result.Errors) > 0 { + for i := 0; i < len(result.Errors); i++ { + code := ErrDeleteBatchFailCode + msg := errDefaultDeleteBatchMessage + if result.Errors[i].Message != nil { + msg = *result.Errors[i].Message + } + if result.Errors[i].Code != nil { + code = *result.Errors[i].Code + } + + errs = append(errs, newError(awserr.New(code, msg, err), input.Bucket, result.Errors[i].Key)) + } + } + for _, object := range objects { + if object.After == nil { + continue + } + if err := object.After(); err != nil { + errs = append(errs, newError(err, object.Object.Bucket, object.Object.Key)) + } + } + + return errs +} + +func hasParity(o1 *s3.DeleteObjectsInput, o2 BatchDeleteObject) bool { + if o1.Bucket != nil && o2.Object.Bucket != nil { + if *o1.Bucket != *o2.Object.Bucket { + return false + } + } else if o1.Bucket != o2.Object.Bucket { + return false + } + + if o1.MFA != nil && o2.Object.MFA != nil { + if *o1.MFA != *o2.Object.MFA { + return false + } + } else if o1.MFA != o2.Object.MFA { + return false + } + + if o1.RequestPayer != nil && o2.Object.RequestPayer != nil { + if *o1.RequestPayer != *o2.Object.RequestPayer { + return false + } + } else if o1.RequestPayer != o2.Object.RequestPayer { + return false + } + + return true +} + +// BatchDownloadIterator is an interface that uses the scanner pattern to iterate +// through a series of objects to be downloaded. +type BatchDownloadIterator interface { + Next() bool + Err() error + DownloadObject() BatchDownloadObject +} + +// BatchDownloadObject contains all necessary information to run a batch operation once. +type BatchDownloadObject struct { + Object *s3.GetObjectInput + Writer io.WriterAt + // After will run after each iteration during the batch process. This function will + // be executed whether or not the request was successful. + After func() error +} + +// DownloadObjectsIterator implements the BatchDownloadIterator interface and allows for batched +// download of objects. +type DownloadObjectsIterator struct { + Objects []BatchDownloadObject + index int + inc bool +} + +// Next will increment the default iterator's index and and ensure that there +// is another object to iterator to. +func (batcher *DownloadObjectsIterator) Next() bool { + if batcher.inc { + batcher.index++ + } else { + batcher.inc = true + } + return batcher.index < len(batcher.Objects) +} + +// DownloadObject will return the BatchDownloadObject at the current batched index. +func (batcher *DownloadObjectsIterator) DownloadObject() BatchDownloadObject { + object := batcher.Objects[batcher.index] + return object +} + +// Err will return an error. Since this is just used to satisfy the BatchDeleteIterator interface +// this will only return nil. +func (batcher *DownloadObjectsIterator) Err() error { + return nil +} + +// BatchUploadIterator is an interface that uses the scanner pattern to +// iterate through what needs to be uploaded. +type BatchUploadIterator interface { + Next() bool + Err() error + UploadObject() BatchUploadObject +} + +// UploadObjectsIterator implements the BatchUploadIterator interface and allows for batched +// upload of objects. +type UploadObjectsIterator struct { + Objects []BatchUploadObject + index int + inc bool +} + +// Next will increment the default iterator's index and and ensure that there +// is another object to iterator to. +func (batcher *UploadObjectsIterator) Next() bool { + if batcher.inc { + batcher.index++ + } else { + batcher.inc = true + } + return batcher.index < len(batcher.Objects) +} + +// Err will return an error. Since this is just used to satisfy the BatchUploadIterator interface +// this will only return nil. +func (batcher *UploadObjectsIterator) Err() error { + return nil +} + +// UploadObject will return the BatchUploadObject at the current batched index. +func (batcher *UploadObjectsIterator) UploadObject() BatchUploadObject { + object := batcher.Objects[batcher.index] + return object +} + +// BatchUploadObject contains all necessary information to run a batch operation once. +type BatchUploadObject struct { + Object *UploadInput + // After will run after each iteration during the batch process. This function will + // be executed whether or not the request was successful. + After func() error +} diff --git a/vendor/github.com/aws/aws-sdk-go/service/s3/s3manager/bucket_region.go b/vendor/github.com/aws/aws-sdk-go/service/s3/s3manager/bucket_region.go new file mode 100644 index 0000000000..f61665a58a --- /dev/null +++ b/vendor/github.com/aws/aws-sdk-go/service/s3/s3manager/bucket_region.go @@ -0,0 +1,88 @@ +package s3manager + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/client" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go/service/s3/s3iface" +) + +// GetBucketRegion will attempt to get the region for a bucket using the +// regionHint to determine which AWS partition to perform the query on. +// +// The request will not be signed, and will not use your AWS credentials. +// +// A "NotFound" error code will be returned if the bucket does not exist in the +// AWS partition the regionHint belongs to. If the regionHint parameter is an +// empty string GetBucketRegion will fallback to the ConfigProvider's region +// config. If the regionHint is empty, and the ConfigProvider does not have a +// region value, an error will be returned.. +// +// For example to get the region of a bucket which exists in "eu-central-1" +// you could provide a region hint of "us-west-2". +// +// sess := session.Must(session.NewSession()) +// +// bucket := "my-bucket" +// region, err := s3manager.GetBucketRegion(ctx, sess, bucket, "us-west-2") +// if err != nil { +// if aerr, ok := err.(awserr.Error); ok && aerr.Code() == "NotFound" { +// fmt.Fprintf(os.Stderr, "unable to find bucket %s's region not found\n", bucket) +// } +// return err +// } +// fmt.Printf("Bucket %s is in %s region\n", bucket, region) +// +func GetBucketRegion(ctx aws.Context, c client.ConfigProvider, bucket, regionHint string, opts ...request.Option) (string, error) { + var cfg aws.Config + if len(regionHint) != 0 { + cfg.Region = aws.String(regionHint) + } + svc := s3.New(c, &cfg) + return GetBucketRegionWithClient(ctx, svc, bucket, opts...) +} + +const bucketRegionHeader = "X-Amz-Bucket-Region" + +// GetBucketRegionWithClient is the same as GetBucketRegion with the exception +// that it takes a S3 service client instead of a Session. The regionHint is +// derived from the region the S3 service client was created in. +// +// See GetBucketRegion for more information. +func GetBucketRegionWithClient(ctx aws.Context, svc s3iface.S3API, bucket string, opts ...request.Option) (string, error) { + req, _ := svc.HeadBucketRequest(&s3.HeadBucketInput{ + Bucket: aws.String(bucket), + }) + req.Config.S3ForcePathStyle = aws.Bool(true) + req.Config.Credentials = credentials.AnonymousCredentials + req.SetContext(ctx) + + // Disable HTTP redirects to prevent an invalid 301 from eating the response + // because Go's HTTP client will fail, and drop the response if an 301 is + // received without a location header. S3 will return a 301 without the + // location header for HeadObject API calls. + req.DisableFollowRedirects = true + + var bucketRegion string + req.Handlers.Send.PushBack(func(r *request.Request) { + bucketRegion = r.HTTPResponse.Header.Get(bucketRegionHeader) + if len(bucketRegion) == 0 { + return + } + r.HTTPResponse.StatusCode = 200 + r.HTTPResponse.Status = "OK" + r.Error = nil + }) + + req.ApplyOptions(opts...) + + if err := req.Send(); err != nil { + return "", err + } + + bucketRegion = s3.NormalizeBucketLocation(bucketRegion) + + return bucketRegion, nil +} diff --git a/vendor/github.com/aws/aws-sdk-go/service/s3/s3manager/doc.go b/vendor/github.com/aws/aws-sdk-go/service/s3/s3manager/doc.go new file mode 100644 index 0000000000..229c0d63bd --- /dev/null +++ b/vendor/github.com/aws/aws-sdk-go/service/s3/s3manager/doc.go @@ -0,0 +1,3 @@ +// Package s3manager provides utilities to upload and download objects from +// S3 concurrently. Helpful for when working with large objects. +package s3manager diff --git a/vendor/github.com/aws/aws-sdk-go/service/s3/s3manager/download.go b/vendor/github.com/aws/aws-sdk-go/service/s3/s3manager/download.go new file mode 100644 index 0000000000..4a9ad65e49 --- /dev/null +++ b/vendor/github.com/aws/aws-sdk-go/service/s3/s3manager/download.go @@ -0,0 +1,555 @@ +package s3manager + +import ( + "fmt" + "io" + "net/http" + "strconv" + "strings" + "sync" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/awsutil" + "github.com/aws/aws-sdk-go/aws/client" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go/service/s3/s3iface" +) + +// DefaultDownloadPartSize is the default range of bytes to get at a time when +// using Download(). +const DefaultDownloadPartSize = 1024 * 1024 * 5 + +// DefaultDownloadConcurrency is the default number of goroutines to spin up +// when using Download(). +const DefaultDownloadConcurrency = 5 + +// The Downloader structure that calls Download(). It is safe to call Download() +// on this structure for multiple objects and across concurrent goroutines. +// Mutating the Downloader's properties is not safe to be done concurrently. +type Downloader struct { + // The buffer size (in bytes) to use when buffering data into chunks and + // sending them as parts to S3. The minimum allowed part size is 5MB, and + // if this value is set to zero, the DefaultDownloadPartSize value will be used. + // + // PartSize is ignored if the Range input parameter is provided. + PartSize int64 + + // The number of goroutines to spin up in parallel when sending parts. + // If this is set to zero, the DefaultDownloadConcurrency value will be used. + // + // Concurrency of 1 will download the parts sequentially. + // + // Concurrency is ignored if the Range input parameter is provided. + Concurrency int + + // An S3 client to use when performing downloads. + S3 s3iface.S3API + + // List of request options that will be passed down to individual API + // operation requests made by the downloader. + RequestOptions []request.Option +} + +// WithDownloaderRequestOptions appends to the Downloader's API request options. +func WithDownloaderRequestOptions(opts ...request.Option) func(*Downloader) { + return func(d *Downloader) { + d.RequestOptions = append(d.RequestOptions, opts...) + } +} + +// NewDownloader creates a new Downloader instance to downloads objects from +// S3 in concurrent chunks. Pass in additional functional options to customize +// the downloader behavior. Requires a client.ConfigProvider in order to create +// a S3 service client. The session.Session satisfies the client.ConfigProvider +// interface. +// +// Example: +// // The session the S3 Downloader will use +// sess := session.Must(session.NewSession()) +// +// // Create a downloader with the session and default options +// downloader := s3manager.NewDownloader(sess) +// +// // Create a downloader with the session and custom options +// downloader := s3manager.NewDownloader(sess, func(d *s3manager.Downloader) { +// d.PartSize = 64 * 1024 * 1024 // 64MB per part +// }) +func NewDownloader(c client.ConfigProvider, options ...func(*Downloader)) *Downloader { + d := &Downloader{ + S3: s3.New(c), + PartSize: DefaultDownloadPartSize, + Concurrency: DefaultDownloadConcurrency, + } + for _, option := range options { + option(d) + } + + return d +} + +// NewDownloaderWithClient creates a new Downloader instance to downloads +// objects from S3 in concurrent chunks. Pass in additional functional +// options to customize the downloader behavior. Requires a S3 service client +// to make S3 API calls. +// +// Example: +// // The session the S3 Downloader will use +// sess := session.Must(session.NewSession()) +// +// // The S3 client the S3 Downloader will use +// s3Svc := s3.new(sess) +// +// // Create a downloader with the s3 client and default options +// downloader := s3manager.NewDownloaderWithClient(s3Svc) +// +// // Create a downloader with the s3 client and custom options +// downloader := s3manager.NewDownloaderWithClient(s3Svc, func(d *s3manager.Downloader) { +// d.PartSize = 64 * 1024 * 1024 // 64MB per part +// }) +func NewDownloaderWithClient(svc s3iface.S3API, options ...func(*Downloader)) *Downloader { + d := &Downloader{ + S3: svc, + PartSize: DefaultDownloadPartSize, + Concurrency: DefaultDownloadConcurrency, + } + for _, option := range options { + option(d) + } + + return d +} + +type maxRetrier interface { + MaxRetries() int +} + +// Download downloads an object in S3 and writes the payload into w using +// concurrent GET requests. +// +// Additional functional options can be provided to configure the individual +// download. These options are copies of the Downloader instance Download is called from. +// Modifying the options will not impact the original Downloader instance. +// +// It is safe to call this method concurrently across goroutines. +// +// The w io.WriterAt can be satisfied by an os.File to do multipart concurrent +// downloads, or in memory []byte wrapper using aws.WriteAtBuffer. +// +// Specifying a Downloader.Concurrency of 1 will cause the Downloader to +// download the parts from S3 sequentially. +// +// If the GetObjectInput's Range value is provided that will cause the downloader +// to perform a single GetObjectInput request for that object's range. This will +// caused the part size, and concurrency configurations to be ignored. +func (d Downloader) Download(w io.WriterAt, input *s3.GetObjectInput, options ...func(*Downloader)) (n int64, err error) { + return d.DownloadWithContext(aws.BackgroundContext(), w, input, options...) +} + +// DownloadWithContext downloads an object in S3 and writes the payload into w +// using concurrent GET requests. +// +// DownloadWithContext is the same as Download with the additional support for +// Context input parameters. The Context must not be nil. A nil Context will +// cause a panic. Use the Context to add deadlining, timeouts, etc. The +// DownloadWithContext may create sub-contexts for individual underlying +// requests. +// +// Additional functional options can be provided to configure the individual +// download. These options are copies of the Downloader instance Download is +// called from. Modifying the options will not impact the original Downloader +// instance. Use the WithDownloaderRequestOptions helper function to pass in request +// options that will be applied to all API operations made with this downloader. +// +// The w io.WriterAt can be satisfied by an os.File to do multipart concurrent +// downloads, or in memory []byte wrapper using aws.WriteAtBuffer. +// +// Specifying a Downloader.Concurrency of 1 will cause the Downloader to +// download the parts from S3 sequentially. +// +// It is safe to call this method concurrently across goroutines. +// +// If the GetObjectInput's Range value is provided that will cause the downloader +// to perform a single GetObjectInput request for that object's range. This will +// caused the part size, and concurrency configurations to be ignored. +func (d Downloader) DownloadWithContext(ctx aws.Context, w io.WriterAt, input *s3.GetObjectInput, options ...func(*Downloader)) (n int64, err error) { + impl := downloader{w: w, in: input, cfg: d, ctx: ctx} + + for _, option := range options { + option(&impl.cfg) + } + impl.cfg.RequestOptions = append(impl.cfg.RequestOptions, request.WithAppendUserAgent("S3Manager")) + + if s, ok := d.S3.(maxRetrier); ok { + impl.partBodyMaxRetries = s.MaxRetries() + } + + impl.totalBytes = -1 + if impl.cfg.Concurrency == 0 { + impl.cfg.Concurrency = DefaultDownloadConcurrency + } + + if impl.cfg.PartSize == 0 { + impl.cfg.PartSize = DefaultDownloadPartSize + } + + return impl.download() +} + +// DownloadWithIterator will download a batched amount of objects in S3 and writes them +// to the io.WriterAt specificed in the iterator. +// +// Example: +// svc := s3manager.NewDownloader(session) +// +// fooFile, err := os.Open("/tmp/foo.file") +// if err != nil { +// return err +// } +// +// barFile, err := os.Open("/tmp/bar.file") +// if err != nil { +// return err +// } +// +// objects := []s3manager.BatchDownloadObject { +// { +// Object: &s3.GetObjectInput { +// Bucket: aws.String("bucket"), +// Key: aws.String("foo"), +// }, +// Writer: fooFile, +// }, +// { +// Object: &s3.GetObjectInput { +// Bucket: aws.String("bucket"), +// Key: aws.String("bar"), +// }, +// Writer: barFile, +// }, +// } +// +// iter := &s3manager.DownloadObjectsIterator{Objects: objects} +// if err := svc.DownloadWithIterator(aws.BackgroundContext(), iter); err != nil { +// return err +// } +func (d Downloader) DownloadWithIterator(ctx aws.Context, iter BatchDownloadIterator, opts ...func(*Downloader)) error { + var errs []Error + for iter.Next() { + object := iter.DownloadObject() + if _, err := d.DownloadWithContext(ctx, object.Writer, object.Object, opts...); err != nil { + errs = append(errs, newError(err, object.Object.Bucket, object.Object.Key)) + } + + if object.After == nil { + continue + } + + if err := object.After(); err != nil { + errs = append(errs, newError(err, object.Object.Bucket, object.Object.Key)) + } + } + + if len(errs) > 0 { + return NewBatchError("BatchedDownloadIncomplete", "some objects have failed to download.", errs) + } + return nil +} + +// downloader is the implementation structure used internally by Downloader. +type downloader struct { + ctx aws.Context + cfg Downloader + + in *s3.GetObjectInput + w io.WriterAt + + wg sync.WaitGroup + m sync.Mutex + + pos int64 + totalBytes int64 + written int64 + err error + + partBodyMaxRetries int +} + +// download performs the implementation of the object download across ranged +// GETs. +func (d *downloader) download() (n int64, err error) { + // If range is specified fall back to single download of that range + // this enables the functionality of ranged gets with the downloader but + // at the cost of no multipart downloads. + if rng := aws.StringValue(d.in.Range); len(rng) > 0 { + d.downloadRange(rng) + return d.written, d.err + } + + // Spin off first worker to check additional header information + d.getChunk() + + if total := d.getTotalBytes(); total >= 0 { + // Spin up workers + ch := make(chan dlchunk, d.cfg.Concurrency) + + for i := 0; i < d.cfg.Concurrency; i++ { + d.wg.Add(1) + go d.downloadPart(ch) + } + + // Assign work + for d.getErr() == nil { + if d.pos >= total { + break // We're finished queuing chunks + } + + // Queue the next range of bytes to read. + ch <- dlchunk{w: d.w, start: d.pos, size: d.cfg.PartSize} + d.pos += d.cfg.PartSize + } + + // Wait for completion + close(ch) + d.wg.Wait() + } else { + // Checking if we read anything new + for d.err == nil { + d.getChunk() + } + + // We expect a 416 error letting us know we are done downloading the + // total bytes. Since we do not know the content's length, this will + // keep grabbing chunks of data until the range of bytes specified in + // the request is out of range of the content. Once, this happens, a + // 416 should occur. + e, ok := d.err.(awserr.RequestFailure) + if ok && e.StatusCode() == http.StatusRequestedRangeNotSatisfiable { + d.err = nil + } + } + + // Return error + return d.written, d.err +} + +// downloadPart is an individual goroutine worker reading from the ch channel +// and performing a GetObject request on the data with a given byte range. +// +// If this is the first worker, this operation also resolves the total number +// of bytes to be read so that the worker manager knows when it is finished. +func (d *downloader) downloadPart(ch chan dlchunk) { + defer d.wg.Done() + for { + chunk, ok := <-ch + if !ok { + break + } + if d.getErr() != nil { + // Drain the channel if there is an error, to prevent deadlocking + // of download producer. + continue + } + + if err := d.downloadChunk(chunk); err != nil { + d.setErr(err) + } + } +} + +// getChunk grabs a chunk of data from the body. +// Not thread safe. Should only used when grabbing data on a single thread. +func (d *downloader) getChunk() { + if d.getErr() != nil { + return + } + + chunk := dlchunk{w: d.w, start: d.pos, size: d.cfg.PartSize} + d.pos += d.cfg.PartSize + + if err := d.downloadChunk(chunk); err != nil { + d.setErr(err) + } +} + +// downloadRange downloads an Object given the passed in Byte-Range value. +// The chunk used down download the range will be configured for that range. +func (d *downloader) downloadRange(rng string) { + if d.getErr() != nil { + return + } + + chunk := dlchunk{w: d.w, start: d.pos} + // Ranges specified will short circuit the multipart download + chunk.withRange = rng + + if err := d.downloadChunk(chunk); err != nil { + d.setErr(err) + } + + // Update the position based on the amount of data received. + d.pos = d.written +} + +// downloadChunk downloads the chunk from s3 +func (d *downloader) downloadChunk(chunk dlchunk) error { + in := &s3.GetObjectInput{} + awsutil.Copy(in, d.in) + + // Get the next byte range of data + in.Range = aws.String(chunk.ByteRange()) + + var n int64 + var err error + for retry := 0; retry <= d.partBodyMaxRetries; retry++ { + var resp *s3.GetObjectOutput + resp, err = d.cfg.S3.GetObjectWithContext(d.ctx, in, d.cfg.RequestOptions...) + if err != nil { + return err + } + d.setTotalBytes(resp) // Set total if not yet set. + + n, err = io.Copy(&chunk, resp.Body) + resp.Body.Close() + if err == nil { + break + } + + chunk.cur = 0 + logMessage(d.cfg.S3, aws.LogDebugWithRequestRetries, + fmt.Sprintf("DEBUG: object part body download interrupted %s, err, %v, retrying attempt %d", + aws.StringValue(in.Key), err, retry)) + } + + d.incrWritten(n) + + return err +} + +func logMessage(svc s3iface.S3API, level aws.LogLevelType, msg string) { + s, ok := svc.(*s3.S3) + if !ok { + return + } + + if s.Config.Logger == nil { + return + } + + if s.Config.LogLevel.Matches(level) { + s.Config.Logger.Log(msg) + } +} + +// getTotalBytes is a thread-safe getter for retrieving the total byte status. +func (d *downloader) getTotalBytes() int64 { + d.m.Lock() + defer d.m.Unlock() + + return d.totalBytes +} + +// setTotalBytes is a thread-safe setter for setting the total byte status. +// Will extract the object's total bytes from the Content-Range if the file +// will be chunked, or Content-Length. Content-Length is used when the response +// does not include a Content-Range. Meaning the object was not chunked. This +// occurs when the full file fits within the PartSize directive. +func (d *downloader) setTotalBytes(resp *s3.GetObjectOutput) { + d.m.Lock() + defer d.m.Unlock() + + if d.totalBytes >= 0 { + return + } + + if resp.ContentRange == nil { + // ContentRange is nil when the full file contents is provided, and + // is not chunked. Use ContentLength instead. + if resp.ContentLength != nil { + d.totalBytes = *resp.ContentLength + return + } + } else { + parts := strings.Split(*resp.ContentRange, "/") + + total := int64(-1) + var err error + // Checking for whether or not a numbered total exists + // If one does not exist, we will assume the total to be -1, undefined, + // and sequentially download each chunk until hitting a 416 error + totalStr := parts[len(parts)-1] + if totalStr != "*" { + total, err = strconv.ParseInt(totalStr, 10, 64) + if err != nil { + d.err = err + return + } + } + + d.totalBytes = total + } +} + +func (d *downloader) incrWritten(n int64) { + d.m.Lock() + defer d.m.Unlock() + + d.written += n +} + +// getErr is a thread-safe getter for the error object +func (d *downloader) getErr() error { + d.m.Lock() + defer d.m.Unlock() + + return d.err +} + +// setErr is a thread-safe setter for the error object +func (d *downloader) setErr(e error) { + d.m.Lock() + defer d.m.Unlock() + + d.err = e +} + +// dlchunk represents a single chunk of data to write by the worker routine. +// This structure also implements an io.SectionReader style interface for +// io.WriterAt, effectively making it an io.SectionWriter (which does not +// exist). +type dlchunk struct { + w io.WriterAt + start int64 + size int64 + cur int64 + + // specifies the byte range the chunk should be downloaded with. + withRange string +} + +// Write wraps io.WriterAt for the dlchunk, writing from the dlchunk's start +// position to its end (or EOF). +// +// If a range is specified on the dlchunk the size will be ignored when writing. +// as the total size may not of be known ahead of time. +func (c *dlchunk) Write(p []byte) (n int, err error) { + if c.cur >= c.size && len(c.withRange) == 0 { + return 0, io.EOF + } + + n, err = c.w.WriteAt(p, c.start+c.cur) + c.cur += int64(n) + + return +} + +// ByteRange returns a HTTP Byte-Range header value that should be used by the +// client to request the chunk's range. +func (c *dlchunk) ByteRange() string { + if len(c.withRange) != 0 { + return c.withRange + } + + return fmt.Sprintf("bytes=%d-%d", c.start, c.start+c.size-1) +} diff --git a/vendor/github.com/aws/aws-sdk-go/service/s3/s3manager/upload.go b/vendor/github.com/aws/aws-sdk-go/service/s3/s3manager/upload.go new file mode 100644 index 0000000000..0ccf634c5b --- /dev/null +++ b/vendor/github.com/aws/aws-sdk-go/service/s3/s3manager/upload.go @@ -0,0 +1,802 @@ +package s3manager + +import ( + "bytes" + "fmt" + "io" + "sort" + "sync" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/awsutil" + "github.com/aws/aws-sdk-go/aws/client" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go/service/s3/s3iface" +) + +// MaxUploadParts is the maximum allowed number of parts in a multi-part upload +// on Amazon S3. +const MaxUploadParts = 10000 + +// MinUploadPartSize is the minimum allowed part size when uploading a part to +// Amazon S3. +const MinUploadPartSize int64 = 1024 * 1024 * 5 + +// DefaultUploadPartSize is the default part size to buffer chunks of a +// payload into. +const DefaultUploadPartSize = MinUploadPartSize + +// DefaultUploadConcurrency is the default number of goroutines to spin up when +// using Upload(). +const DefaultUploadConcurrency = 5 + +// A MultiUploadFailure wraps a failed S3 multipart upload. An error returned +// will satisfy this interface when a multi part upload failed to upload all +// chucks to S3. In the case of a failure the UploadID is needed to operate on +// the chunks, if any, which were uploaded. +// +// Example: +// +// u := s3manager.NewUploader(opts) +// output, err := u.upload(input) +// if err != nil { +// if multierr, ok := err.(s3manager.MultiUploadFailure); ok { +// // Process error and its associated uploadID +// fmt.Println("Error:", multierr.Code(), multierr.Message(), multierr.UploadID()) +// } else { +// // Process error generically +// fmt.Println("Error:", err.Error()) +// } +// } +// +type MultiUploadFailure interface { + awserr.Error + + // Returns the upload id for the S3 multipart upload that failed. + UploadID() string +} + +// So that the Error interface type can be included as an anonymous field +// in the multiUploadError struct and not conflict with the error.Error() method. +type awsError awserr.Error + +// A multiUploadError wraps the upload ID of a failed s3 multipart upload. +// Composed of BaseError for code, message, and original error +// +// Should be used for an error that occurred failing a S3 multipart upload, +// and a upload ID is available. If an uploadID is not available a more relevant +type multiUploadError struct { + awsError + + // ID for multipart upload which failed. + uploadID string +} + +// Error returns the string representation of the error. +// +// See apierr.BaseError ErrorWithExtra for output format +// +// Satisfies the error interface. +func (m multiUploadError) Error() string { + extra := fmt.Sprintf("upload id: %s", m.uploadID) + return awserr.SprintError(m.Code(), m.Message(), extra, m.OrigErr()) +} + +// String returns the string representation of the error. +// Alias for Error to satisfy the stringer interface. +func (m multiUploadError) String() string { + return m.Error() +} + +// UploadID returns the id of the S3 upload which failed. +func (m multiUploadError) UploadID() string { + return m.uploadID +} + +// UploadInput contains all input for upload requests to Amazon S3. +type UploadInput struct { + // The canned ACL to apply to the object. + ACL *string `location:"header" locationName:"x-amz-acl" type:"string"` + + Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"` + + // Specifies caching behavior along the request/reply chain. + CacheControl *string `location:"header" locationName:"Cache-Control" type:"string"` + + // Specifies presentational information for the object. + ContentDisposition *string `location:"header" locationName:"Content-Disposition" type:"string"` + + // Specifies what content encodings have been applied to the object and thus + // what decoding mechanisms must be applied to obtain the media-type referenced + // by the Content-Type header field. + ContentEncoding *string `location:"header" locationName:"Content-Encoding" type:"string"` + + // The language the content is in. + ContentLanguage *string `location:"header" locationName:"Content-Language" type:"string"` + + // The base64-encoded 128-bit MD5 digest of the part data. + ContentMD5 *string `location:"header" locationName:"Content-MD5" type:"string"` + + // A standard MIME type describing the format of the object data. + ContentType *string `location:"header" locationName:"Content-Type" type:"string"` + + // The date and time at which the object is no longer cacheable. + Expires *time.Time `location:"header" locationName:"Expires" type:"timestamp" timestampFormat:"rfc822"` + + // Gives the grantee READ, READ_ACP, and WRITE_ACP permissions on the object. + GrantFullControl *string `location:"header" locationName:"x-amz-grant-full-control" type:"string"` + + // Allows grantee to read the object data and its metadata. + GrantRead *string `location:"header" locationName:"x-amz-grant-read" type:"string"` + + // Allows grantee to read the object ACL. + GrantReadACP *string `location:"header" locationName:"x-amz-grant-read-acp" type:"string"` + + // Allows grantee to write the ACL for the applicable object. + GrantWriteACP *string `location:"header" locationName:"x-amz-grant-write-acp" type:"string"` + + Key *string `location:"uri" locationName:"Key" type:"string" required:"true"` + + // A map of metadata to store with the object in S3. + Metadata map[string]*string `location:"headers" locationName:"x-amz-meta-" type:"map"` + + // Confirms that the requester knows that she or he will be charged for the + // request. Bucket owners need not specify this parameter in their requests. + // Documentation on downloading objects from requester pays buckets can be found + // at http://docs.aws.amazon.com/AmazonS3/latest/dev/ObjectsinRequesterPaysBuckets.html + RequestPayer *string `location:"header" locationName:"x-amz-request-payer" type:"string"` + + // Specifies the algorithm to use to when encrypting the object (e.g., AES256, + // aws:kms). + SSECustomerAlgorithm *string `location:"header" locationName:"x-amz-server-side-encryption-customer-algorithm" type:"string"` + + // Specifies the customer-provided encryption key for Amazon S3 to use in encrypting + // data. This value is used to store the object and then it is discarded; Amazon + // does not store the encryption key. The key must be appropriate for use with + // the algorithm specified in the x-amz-server-side​-encryption​-customer-algorithm + // header. + SSECustomerKey *string `location:"header" locationName:"x-amz-server-side-encryption-customer-key" type:"string"` + + // Specifies the 128-bit MD5 digest of the encryption key according to RFC 1321. + // Amazon S3 uses this header for a message integrity check to ensure the encryption + // key was transmitted without error. + SSECustomerKeyMD5 *string `location:"header" locationName:"x-amz-server-side-encryption-customer-key-MD5" type:"string"` + + // Specifies the AWS KMS key ID to use for object encryption. All GET and PUT + // requests for an object protected by AWS KMS will fail if not made via SSL + // or using SigV4. Documentation on configuring any of the officially supported + // AWS SDKs and CLI can be found at http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingAWSSDK.html#specify-signature-version + SSEKMSKeyId *string `location:"header" locationName:"x-amz-server-side-encryption-aws-kms-key-id" type:"string"` + + // The Server-side encryption algorithm used when storing this object in S3 + // (e.g., AES256, aws:kms). + ServerSideEncryption *string `location:"header" locationName:"x-amz-server-side-encryption" type:"string"` + + // The type of storage to use for the object. Defaults to 'STANDARD'. + StorageClass *string `location:"header" locationName:"x-amz-storage-class" type:"string"` + + // The tag-set for the object. The tag-set must be encoded as URL Query parameters + Tagging *string `location:"header" locationName:"x-amz-tagging" type:"string"` + + // If the bucket is configured as a website, redirects requests for this object + // to another object in the same bucket or to an external URL. Amazon S3 stores + // the value of this header in the object metadata. + WebsiteRedirectLocation *string `location:"header" locationName:"x-amz-website-redirect-location" type:"string"` + + // The readable body payload to send to S3. + Body io.Reader +} + +// UploadOutput represents a response from the Upload() call. +type UploadOutput struct { + // The URL where the object was uploaded to. + Location string + + // The version of the object that was uploaded. Will only be populated if + // the S3 Bucket is versioned. If the bucket is not versioned this field + // will not be set. + VersionID *string + + // The ID for a multipart upload to S3. In the case of an error the error + // can be cast to the MultiUploadFailure interface to extract the upload ID. + UploadID string +} + +// WithUploaderRequestOptions appends to the Uploader's API request options. +func WithUploaderRequestOptions(opts ...request.Option) func(*Uploader) { + return func(u *Uploader) { + u.RequestOptions = append(u.RequestOptions, opts...) + } +} + +// The Uploader structure that calls Upload(). It is safe to call Upload() +// on this structure for multiple objects and across concurrent goroutines. +// Mutating the Uploader's properties is not safe to be done concurrently. +type Uploader struct { + // The buffer size (in bytes) to use when buffering data into chunks and + // sending them as parts to S3. The minimum allowed part size is 5MB, and + // if this value is set to zero, the DefaultUploadPartSize value will be used. + PartSize int64 + + // The number of goroutines to spin up in parallel per call to Upload when + // sending parts. If this is set to zero, the DefaultUploadConcurrency value + // will be used. + // + // The concurrency pool is not shared between calls to Upload. + Concurrency int + + // Setting this value to true will cause the SDK to avoid calling + // AbortMultipartUpload on a failure, leaving all successfully uploaded + // parts on S3 for manual recovery. + // + // Note that storing parts of an incomplete multipart upload counts towards + // space usage on S3 and will add additional costs if not cleaned up. + LeavePartsOnError bool + + // MaxUploadParts is the max number of parts which will be uploaded to S3. + // Will be used to calculate the partsize of the object to be uploaded. + // E.g: 5GB file, with MaxUploadParts set to 100, will upload the file + // as 100, 50MB parts. + // With a limited of s3.MaxUploadParts (10,000 parts). + // + // Defaults to package const's MaxUploadParts value. + MaxUploadParts int + + // The client to use when uploading to S3. + S3 s3iface.S3API + + // List of request options that will be passed down to individual API + // operation requests made by the uploader. + RequestOptions []request.Option +} + +// NewUploader creates a new Uploader instance to upload objects to S3. Pass In +// additional functional options to customize the uploader's behavior. Requires a +// client.ConfigProvider in order to create a S3 service client. The session.Session +// satisfies the client.ConfigProvider interface. +// +// Example: +// // The session the S3 Uploader will use +// sess := session.Must(session.NewSession()) +// +// // Create an uploader with the session and default options +// uploader := s3manager.NewUploader(sess) +// +// // Create an uploader with the session and custom options +// uploader := s3manager.NewUploader(session, func(u *s3manager.Uploader) { +// u.PartSize = 64 * 1024 * 1024 // 64MB per part +// }) +func NewUploader(c client.ConfigProvider, options ...func(*Uploader)) *Uploader { + u := &Uploader{ + S3: s3.New(c), + PartSize: DefaultUploadPartSize, + Concurrency: DefaultUploadConcurrency, + LeavePartsOnError: false, + MaxUploadParts: MaxUploadParts, + } + + for _, option := range options { + option(u) + } + + return u +} + +// NewUploaderWithClient creates a new Uploader instance to upload objects to S3. Pass in +// additional functional options to customize the uploader's behavior. Requires +// a S3 service client to make S3 API calls. +// +// Example: +// // The session the S3 Uploader will use +// sess := session.Must(session.NewSession()) +// +// // S3 service client the Upload manager will use. +// s3Svc := s3.New(sess) +// +// // Create an uploader with S3 client and default options +// uploader := s3manager.NewUploaderWithClient(s3Svc) +// +// // Create an uploader with S3 client and custom options +// uploader := s3manager.NewUploaderWithClient(s3Svc, func(u *s3manager.Uploader) { +// u.PartSize = 64 * 1024 * 1024 // 64MB per part +// }) +func NewUploaderWithClient(svc s3iface.S3API, options ...func(*Uploader)) *Uploader { + u := &Uploader{ + S3: svc, + PartSize: DefaultUploadPartSize, + Concurrency: DefaultUploadConcurrency, + LeavePartsOnError: false, + MaxUploadParts: MaxUploadParts, + } + + for _, option := range options { + option(u) + } + + return u +} + +// Upload uploads an object to S3, intelligently buffering large files into +// smaller chunks and sending them in parallel across multiple goroutines. You +// can configure the buffer size and concurrency through the Uploader's parameters. +// +// Additional functional options can be provided to configure the individual +// upload. These options are copies of the Uploader instance Upload is called from. +// Modifying the options will not impact the original Uploader instance. +// +// Use the WithUploaderRequestOptions helper function to pass in request +// options that will be applied to all API operations made with this uploader. +// +// It is safe to call this method concurrently across goroutines. +// +// Example: +// // Upload input parameters +// upParams := &s3manager.UploadInput{ +// Bucket: &bucketName, +// Key: &keyName, +// Body: file, +// } +// +// // Perform an upload. +// result, err := uploader.Upload(upParams) +// +// // Perform upload with options different than the those in the Uploader. +// result, err := uploader.Upload(upParams, func(u *s3manager.Uploader) { +// u.PartSize = 10 * 1024 * 1024 // 10MB part size +// u.LeavePartsOnError = true // Don't delete the parts if the upload fails. +// }) +func (u Uploader) Upload(input *UploadInput, options ...func(*Uploader)) (*UploadOutput, error) { + return u.UploadWithContext(aws.BackgroundContext(), input, options...) +} + +// UploadWithContext uploads an object to S3, intelligently buffering large +// files into smaller chunks and sending them in parallel across multiple +// goroutines. You can configure the buffer size and concurrency through the +// Uploader's parameters. +// +// UploadWithContext is the same as Upload with the additional support for +// Context input parameters. The Context must not be nil. A nil Context will +// cause a panic. Use the context to add deadlining, timeouts, etc. The +// UploadWithContext may create sub-contexts for individual underlying requests. +// +// Additional functional options can be provided to configure the individual +// upload. These options are copies of the Uploader instance Upload is called from. +// Modifying the options will not impact the original Uploader instance. +// +// Use the WithUploaderRequestOptions helper function to pass in request +// options that will be applied to all API operations made with this uploader. +// +// It is safe to call this method concurrently across goroutines. +func (u Uploader) UploadWithContext(ctx aws.Context, input *UploadInput, opts ...func(*Uploader)) (*UploadOutput, error) { + i := uploader{in: input, cfg: u, ctx: ctx} + + for _, opt := range opts { + opt(&i.cfg) + } + i.cfg.RequestOptions = append(i.cfg.RequestOptions, request.WithAppendUserAgent("S3Manager")) + + return i.upload() +} + +// UploadWithIterator will upload a batched amount of objects to S3. This operation uses +// the iterator pattern to know which object to upload next. Since this is an interface this +// allows for custom defined functionality. +// +// Example: +// svc:= s3manager.NewUploader(sess) +// +// objects := []BatchUploadObject{ +// { +// Object: &s3manager.UploadInput { +// Key: aws.String("key"), +// Bucket: aws.String("bucket"), +// }, +// }, +// } +// +// iter := &s3manager.UploadObjectsIterator{Objects: objects} +// if err := svc.UploadWithIterator(aws.BackgroundContext(), iter); err != nil { +// return err +// } +func (u Uploader) UploadWithIterator(ctx aws.Context, iter BatchUploadIterator, opts ...func(*Uploader)) error { + var errs []Error + for iter.Next() { + object := iter.UploadObject() + if _, err := u.UploadWithContext(ctx, object.Object, opts...); err != nil { + s3Err := Error{ + OrigErr: err, + Bucket: object.Object.Bucket, + Key: object.Object.Key, + } + + errs = append(errs, s3Err) + } + + if object.After == nil { + continue + } + + if err := object.After(); err != nil { + s3Err := Error{ + OrigErr: err, + Bucket: object.Object.Bucket, + Key: object.Object.Key, + } + + errs = append(errs, s3Err) + } + } + + if len(errs) > 0 { + return NewBatchError("BatchedUploadIncomplete", "some objects have failed to upload.", errs) + } + return nil +} + +// internal structure to manage an upload to S3. +type uploader struct { + ctx aws.Context + cfg Uploader + + in *UploadInput + + readerPos int64 // current reader position + totalSize int64 // set to -1 if the size is not known + + bufferPool sync.Pool +} + +// internal logic for deciding whether to upload a single part or use a +// multipart upload. +func (u *uploader) upload() (*UploadOutput, error) { + u.init() + + if u.cfg.PartSize < MinUploadPartSize { + msg := fmt.Sprintf("part size must be at least %d bytes", MinUploadPartSize) + return nil, awserr.New("ConfigError", msg, nil) + } + + // Do one read to determine if we have more than one part + reader, _, part, err := u.nextReader() + if err == io.EOF { // single part + return u.singlePart(reader) + } else if err != nil { + return nil, awserr.New("ReadRequestBody", "read upload data failed", err) + } + + mu := multiuploader{uploader: u} + return mu.upload(reader, part) +} + +// init will initialize all default options. +func (u *uploader) init() { + if u.cfg.Concurrency == 0 { + u.cfg.Concurrency = DefaultUploadConcurrency + } + if u.cfg.PartSize == 0 { + u.cfg.PartSize = DefaultUploadPartSize + } + if u.cfg.MaxUploadParts == 0 { + u.cfg.MaxUploadParts = MaxUploadParts + } + + u.bufferPool = sync.Pool{ + New: func() interface{} { return make([]byte, u.cfg.PartSize) }, + } + + // Try to get the total size for some optimizations + u.initSize() +} + +// initSize tries to detect the total stream size, setting u.totalSize. If +// the size is not known, totalSize is set to -1. +func (u *uploader) initSize() { + u.totalSize = -1 + + switch r := u.in.Body.(type) { + case io.Seeker: + n, err := aws.SeekerLen(r) + if err != nil { + return + } + u.totalSize = n + + // Try to adjust partSize if it is too small and account for + // integer division truncation. + if u.totalSize/u.cfg.PartSize >= int64(u.cfg.MaxUploadParts) { + // Add one to the part size to account for remainders + // during the size calculation. e.g odd number of bytes. + u.cfg.PartSize = (u.totalSize / int64(u.cfg.MaxUploadParts)) + 1 + } + } +} + +// nextReader returns a seekable reader representing the next packet of data. +// This operation increases the shared u.readerPos counter, but note that it +// does not need to be wrapped in a mutex because nextReader is only called +// from the main thread. +func (u *uploader) nextReader() (io.ReadSeeker, int, []byte, error) { + type readerAtSeeker interface { + io.ReaderAt + io.ReadSeeker + } + switch r := u.in.Body.(type) { + case readerAtSeeker: + var err error + + n := u.cfg.PartSize + if u.totalSize >= 0 { + bytesLeft := u.totalSize - u.readerPos + + if bytesLeft <= u.cfg.PartSize { + err = io.EOF + n = bytesLeft + } + } + + reader := io.NewSectionReader(r, u.readerPos, n) + u.readerPos += n + + return reader, int(n), nil, err + + default: + part := u.bufferPool.Get().([]byte) + n, err := readFillBuf(r, part) + u.readerPos += int64(n) + + return bytes.NewReader(part[0:n]), n, part, err + } +} + +func readFillBuf(r io.Reader, b []byte) (offset int, err error) { + for offset < len(b) && err == nil { + var n int + n, err = r.Read(b[offset:]) + offset += n + } + + return offset, err +} + +// singlePart contains upload logic for uploading a single chunk via +// a regular PutObject request. Multipart requests require at least two +// parts, or at least 5MB of data. +func (u *uploader) singlePart(buf io.ReadSeeker) (*UploadOutput, error) { + params := &s3.PutObjectInput{} + awsutil.Copy(params, u.in) + params.Body = buf + + // Need to use request form because URL generated in request is + // used in return. + req, out := u.cfg.S3.PutObjectRequest(params) + req.SetContext(u.ctx) + req.ApplyOptions(u.cfg.RequestOptions...) + if err := req.Send(); err != nil { + return nil, err + } + + url := req.HTTPRequest.URL.String() + return &UploadOutput{ + Location: url, + VersionID: out.VersionId, + }, nil +} + +// internal structure to manage a specific multipart upload to S3. +type multiuploader struct { + *uploader + wg sync.WaitGroup + m sync.Mutex + err error + uploadID string + parts completedParts +} + +// keeps track of a single chunk of data being sent to S3. +type chunk struct { + buf io.ReadSeeker + part []byte + num int64 +} + +// completedParts is a wrapper to make parts sortable by their part number, +// since S3 required this list to be sent in sorted order. +type completedParts []*s3.CompletedPart + +func (a completedParts) Len() int { return len(a) } +func (a completedParts) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a completedParts) Less(i, j int) bool { return *a[i].PartNumber < *a[j].PartNumber } + +// upload will perform a multipart upload using the firstBuf buffer containing +// the first chunk of data. +func (u *multiuploader) upload(firstBuf io.ReadSeeker, firstPart []byte) (*UploadOutput, error) { + params := &s3.CreateMultipartUploadInput{} + awsutil.Copy(params, u.in) + + // Create the multipart + resp, err := u.cfg.S3.CreateMultipartUploadWithContext(u.ctx, params, u.cfg.RequestOptions...) + if err != nil { + return nil, err + } + u.uploadID = *resp.UploadId + + // Create the workers + ch := make(chan chunk, u.cfg.Concurrency) + for i := 0; i < u.cfg.Concurrency; i++ { + u.wg.Add(1) + go u.readChunk(ch) + } + + // Send part 1 to the workers + var num int64 = 1 + ch <- chunk{buf: firstBuf, part: firstPart, num: num} + + // Read and queue the rest of the parts + for u.geterr() == nil && err == nil { + num++ + // This upload exceeded maximum number of supported parts, error now. + if num > int64(u.cfg.MaxUploadParts) || num > int64(MaxUploadParts) { + var msg string + if num > int64(u.cfg.MaxUploadParts) { + msg = fmt.Sprintf("exceeded total allowed configured MaxUploadParts (%d). Adjust PartSize to fit in this limit", + u.cfg.MaxUploadParts) + } else { + msg = fmt.Sprintf("exceeded total allowed S3 limit MaxUploadParts (%d). Adjust PartSize to fit in this limit", + MaxUploadParts) + } + u.seterr(awserr.New("TotalPartsExceeded", msg, nil)) + break + } + + var reader io.ReadSeeker + var nextChunkLen int + var part []byte + reader, nextChunkLen, part, err = u.nextReader() + + if err != nil && err != io.EOF { + u.seterr(awserr.New( + "ReadRequestBody", + "read multipart upload data failed", + err)) + break + } + + if nextChunkLen == 0 { + // No need to upload empty part, if file was empty to start + // with empty single part would of been created and never + // started multipart upload. + break + } + + ch <- chunk{buf: reader, part: part, num: num} + } + + // Close the channel, wait for workers, and complete upload + close(ch) + u.wg.Wait() + complete := u.complete() + + if err := u.geterr(); err != nil { + return nil, &multiUploadError{ + awsError: awserr.New( + "MultipartUpload", + "upload multipart failed", + err), + uploadID: u.uploadID, + } + } + return &UploadOutput{ + Location: aws.StringValue(complete.Location), + VersionID: complete.VersionId, + UploadID: u.uploadID, + }, nil +} + +// readChunk runs in worker goroutines to pull chunks off of the ch channel +// and send() them as UploadPart requests. +func (u *multiuploader) readChunk(ch chan chunk) { + defer u.wg.Done() + for { + data, ok := <-ch + + if !ok { + break + } + + if u.geterr() == nil { + if err := u.send(data); err != nil { + u.seterr(err) + } + } + } +} + +// send performs an UploadPart request and keeps track of the completed +// part information. +func (u *multiuploader) send(c chunk) error { + params := &s3.UploadPartInput{ + Bucket: u.in.Bucket, + Key: u.in.Key, + Body: c.buf, + UploadId: &u.uploadID, + SSECustomerAlgorithm: u.in.SSECustomerAlgorithm, + SSECustomerKey: u.in.SSECustomerKey, + PartNumber: &c.num, + } + resp, err := u.cfg.S3.UploadPartWithContext(u.ctx, params, u.cfg.RequestOptions...) + // put the byte array back into the pool to conserve memory + u.bufferPool.Put(c.part) + if err != nil { + return err + } + + n := c.num + completed := &s3.CompletedPart{ETag: resp.ETag, PartNumber: &n} + + u.m.Lock() + u.parts = append(u.parts, completed) + u.m.Unlock() + + return nil +} + +// geterr is a thread-safe getter for the error object +func (u *multiuploader) geterr() error { + u.m.Lock() + defer u.m.Unlock() + + return u.err +} + +// seterr is a thread-safe setter for the error object +func (u *multiuploader) seterr(e error) { + u.m.Lock() + defer u.m.Unlock() + + u.err = e +} + +// fail will abort the multipart unless LeavePartsOnError is set to true. +func (u *multiuploader) fail() { + if u.cfg.LeavePartsOnError { + return + } + + params := &s3.AbortMultipartUploadInput{ + Bucket: u.in.Bucket, + Key: u.in.Key, + UploadId: &u.uploadID, + } + _, err := u.cfg.S3.AbortMultipartUploadWithContext(u.ctx, params, u.cfg.RequestOptions...) + if err != nil { + logMessage(u.cfg.S3, aws.LogDebug, fmt.Sprintf("failed to abort multipart upload, %v", err)) + } +} + +// complete successfully completes a multipart upload and returns the response. +func (u *multiuploader) complete() *s3.CompleteMultipartUploadOutput { + if u.geterr() != nil { + u.fail() + return nil + } + + // Parts must be sorted in PartNumber order. + sort.Sort(u.parts) + + params := &s3.CompleteMultipartUploadInput{ + Bucket: u.in.Bucket, + Key: u.in.Key, + UploadId: &u.uploadID, + MultipartUpload: &s3.CompletedMultipartUpload{Parts: u.parts}, + } + resp, err := u.cfg.S3.CompleteMultipartUploadWithContext(u.ctx, params, u.cfg.RequestOptions...) + if err != nil { + u.seterr(err) + u.fail() + } + + return resp +} From ab4bb74e1747ae1835e45bd3866eb306c38da3ad Mon Sep 17 00:00:00 2001 From: TangBin Date: Sat, 17 Nov 2018 10:26:19 +0800 Subject: [PATCH 15/29] make fmt --- pkg/util/aws/securitygroup.go | 6 +++--- pkg/util/aws/storagecache.go | 4 ++-- pkg/util/aws/vpc.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/util/aws/securitygroup.go b/pkg/util/aws/securitygroup.go index 29399d61b0..3fc04daf36 100644 --- a/pkg/util/aws/securitygroup.go +++ b/pkg/util/aws/securitygroup.go @@ -127,7 +127,7 @@ func (self *SRegion) addSecurityGroupRule(secGrpId string, rule *secrules.Securi _, err = self.ec2Client.AuthorizeSecurityGroupEgress(params) } - if err != nil && strings.Contains(err.Error(),"InvalidPermission.Duplicate") { + if err != nil && strings.Contains(err.Error(), "InvalidPermission.Duplicate") { log.Debugf("addSecurityGroupRule %s %s", rule.Direction, err.Error()) return nil } @@ -321,8 +321,8 @@ func (self *SRegion) syncSecgroupRules(secgroupId string, rules []secrules.Secur sort.Sort(secrules.SecurityRuleSet(rules)) sort.Sort(secrules.SecurityRuleSet(secgroup.Permissions)) - log.Debugf("local security rules %s",rules) - log.Debugf("remote security rules %s",secgroup.Permissions) + log.Debugf("local security rules %s", rules) + log.Debugf("remote security rules %s", secgroup.Permissions) i, j := 0, 0 for i < len(rules) || j < len(secgroup.Permissions) { diff --git a/pkg/util/aws/storagecache.go b/pkg/util/aws/storagecache.go index eaed1175b2..b1a42471bd 100644 --- a/pkg/util/aws/storagecache.go +++ b/pkg/util/aws/storagecache.go @@ -150,8 +150,8 @@ func (self *SStoragecache) uploadImage(userCred mcclient.TokenCredential, imageI // uploader to aws s3 input := &s3manager.UploadInput{ Bucket: &bucketName, - Key: &imageId, - Body: reader, + Key: &imageId, + Body: reader, } awsSession, err := self.region.getAwsSession() diff --git a/pkg/util/aws/vpc.go b/pkg/util/aws/vpc.go index 72b35b3a80..eb0ed59eab 100644 --- a/pkg/util/aws/vpc.go +++ b/pkg/util/aws/vpc.go @@ -108,7 +108,7 @@ func (self *SVpc) GetManagerId() string { } func (self *SVpc) Delete() error { - // 删除vpc会同步删除关联的安全组 + // 删除vpc会同步删除关联的安全组 return self.region.DeleteVpc(self.VpcId) } From a49747bebe6e6271938dd8cd4573d4e5ad1d12f2 Mon Sep 17 00:00:00 2001 From: TangBin Date: Sat, 17 Nov 2018 16:07:06 +0800 Subject: [PATCH 16/29] rebuild root fix --- pkg/util/aws/instance.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/util/aws/instance.go b/pkg/util/aws/instance.go index 4d693c6ac3..bfc699822d 100644 --- a/pkg/util/aws/instance.go +++ b/pkg/util/aws/instance.go @@ -5,8 +5,8 @@ import ( "time" "github.com/aws/aws-sdk-go/service/ec2" - "github.com/coredns/coredns/plugin/pkg/log" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/pkg/util/osprofile" @@ -420,7 +420,6 @@ func (self *SRegion) GetInstances(zoneId string, ids []string, offset int, limit log.Errorf("GetInstances fail %s", err) return nil, 0, err } - instances := []SInstance{} for _, reservation := range res.Reservations { for _, instance := range reservation.Instances { @@ -473,8 +472,16 @@ func (self *SRegion) GetInstances(zoneId string, ids []string, offset int, limit productCodes = append(productCodes, *p.ProductCodeId) } + szone, err := self.getZoneById(*instance.Placement.AvailabilityZone) + if err != nil { + return nil, 0, err + } + + host := szone.getHost() + sinstance := SInstance{ RegionId: self.RegionId, + host: host, ZoneId: *instance.Placement.AvailabilityZone, InstanceId: *instance.InstanceId, ImageId: *instance.ImageId, From 6b14c8d95f52ae209bf3847640e6cabdb4601b46 Mon Sep 17 00:00:00 2001 From: TangBin Date: Sat, 17 Nov 2018 16:21:31 +0800 Subject: [PATCH 17/29] remove useless log --- pkg/util/aws/instance.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/util/aws/instance.go b/pkg/util/aws/instance.go index bfc699822d..02ff642841 100644 --- a/pkg/util/aws/instance.go +++ b/pkg/util/aws/instance.go @@ -423,7 +423,6 @@ func (self *SRegion) GetInstances(zoneId string, ids []string, offset int, limit instances := []SInstance{} for _, reservation := range res.Reservations { for _, instance := range reservation.Instances { - log.Debugf("GetInstances %s", instance.String()) if err := FillZero(instance); err != nil { return nil, 0, err } From 69d38d333ad0437f26000aba1db7d28c12f7c148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=88=E8=BD=A9?= Date: Mon, 19 Nov 2018 15:04:03 +0800 Subject: [PATCH 18/29] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E7=BC=BA=E5=B0=91=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/util/azure/resourcesku.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/util/azure/resourcesku.go b/pkg/util/azure/resourcesku.go index b26207fe74..2c6e6e80af 100644 --- a/pkg/util/azure/resourcesku.go +++ b/pkg/util/azure/resourcesku.go @@ -2,6 +2,7 @@ package azure import ( "fmt" + "yunion.io/x/pkg/utils" ) @@ -108,7 +109,7 @@ func (self *SAzureClient) ListResourceSkus() ([]SResourceSku, error) { url := fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Compute/skus?api-version=2017-09-01", self.subscriptionId) skus := make([]SResourceSku, 0) for { - body, err := jsonRequest(cli, "GET", self.domain, url, "") + body, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "") if err != nil { return nil, err } From 7f51e6d919b153c3892ea9942e192a1857f59c79 Mon Sep 17 00:00:00 2001 From: TangBin Date: Tue, 20 Nov 2018 10:33:19 +0800 Subject: [PATCH 19/29] fix confilict --- cmd/climc/shell/cloudmeta.go | 16 ++++++++++++++++ pkg/mcclient/modules/managers.go | 8 ++++++++ pkg/mcclient/modules/mod_cloudmeta.go | 13 +++++++++++++ pkg/mcclient/modules/resource.go | 2 +- pkg/mcclient/options/servers.go | 2 +- 5 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 cmd/climc/shell/cloudmeta.go create mode 100644 pkg/mcclient/modules/mod_cloudmeta.go diff --git a/cmd/climc/shell/cloudmeta.go b/cmd/climc/shell/cloudmeta.go new file mode 100644 index 0000000000..6fa020a639 --- /dev/null +++ b/cmd/climc/shell/cloudmeta.go @@ -0,0 +1,16 @@ +package shell + +import ( + "yunion.io/x/onecloud/pkg/mcclient" +) + +func init() { + type CloudmetaOptions struct { + PROVIDER_ID string `help:"provider_id"` + REGION_ID string `help:"region_id"` + ZONE_ID string `help:"zone_id"` + } + R(&CloudmetaOptions{}, "instance-type-list", "query backend service for its version", func(s *mcclient.ClientSession, args *CloudmetaOptions) error { + return nil + }) +} diff --git a/pkg/mcclient/modules/managers.go b/pkg/mcclient/modules/managers.go index cf51952d23..ed6e17a0c4 100644 --- a/pkg/mcclient/modules/managers.go +++ b/pkg/mcclient/modules/managers.go @@ -139,3 +139,11 @@ func NewWebsocketManager(keyword, keywordPlural string, columns, adminColumns [] serviceType: "websocket"}, Keyword: keyword, KeywordPlural: keywordPlural} } + +func NewCloudmetaManager(keyword, keywordPlural string, columns, adminColumns []string) ResourceManager { + return ResourceManager{ + BaseManager: BaseManager{columns: columns, + adminColumns: adminColumns, + serviceType: "cloudmeta"}, + Keyword: keyword, KeywordPlural: keywordPlural} +} \ No newline at end of file diff --git a/pkg/mcclient/modules/mod_cloudmeta.go b/pkg/mcclient/modules/mod_cloudmeta.go new file mode 100644 index 0000000000..e1f5ca497e --- /dev/null +++ b/pkg/mcclient/modules/mod_cloudmeta.go @@ -0,0 +1,13 @@ +package modules + +var ( + Cloudmeta ResourceManager +) + +func init() { + Cloudmeta = NewCloudmetaManager("cloudmeta", "cloudmetas", + []string{}, + []string{}) + + register(&Cloudmeta) +} diff --git a/pkg/mcclient/modules/resource.go b/pkg/mcclient/modules/resource.go index 65528750c4..35399fa2a2 100644 --- a/pkg/mcclient/modules/resource.go +++ b/pkg/mcclient/modules/resource.go @@ -297,7 +297,7 @@ func (this *ResourceManager) params2Body(s *mcclient.ClientSession, params jsonu return body } -func (this *ResourceManager) Create(session *mcclient.ClientSession, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { +func (this *ResourceManager)Create(session *mcclient.ClientSession, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { return this.CreateInContexts(session, params, nil) } diff --git a/pkg/mcclient/options/servers.go b/pkg/mcclient/options/servers.go index 89924038bf..dd486bb278 100644 --- a/pkg/mcclient/options/servers.go +++ b/pkg/mcclient/options/servers.go @@ -18,7 +18,7 @@ type ServerListOptions struct { Gpu *bool `help:"Show gpu servers"` Secgroup string `help:"Secgroup ID or Name"` AdminSecgroup string `help:"AdminSecgroup ID or Name"` - Hypervisor string `help:"Show server of hypervisor" choices:"kvm|esxi|container|baremetal|aliyun|azure"` + Hypervisor string `help:"Show server of hypervisor" choices:"kvm|esxi|container|baremetal|aliyun|azure|aws"` Manager string `help:"Show servers imported from manager"` Region string `help:"Show servers in cloudregion"` WithEip *bool `help:"Show Servers with EIP"` From 26cd84726218330ddfa1111d3fdbcd5e57e00ce2 Mon Sep 17 00:00:00 2001 From: TangBin Date: Tue, 20 Nov 2018 15:27:17 +0800 Subject: [PATCH 20/29] add tranlation --- pkg/httperrors/errors.go | 46 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/pkg/httperrors/errors.go b/pkg/httperrors/errors.go index 1891b64131..60887fd075 100644 --- a/pkg/httperrors/errors.go +++ b/pkg/httperrors/errors.go @@ -2,7 +2,6 @@ package httperrors import ( "fmt" - "yunion.io/x/onecloud/pkg/util/httputils" ) @@ -11,13 +10,56 @@ func NewJsonClientError(code int, title string, msg string, error httputils.Erro return &err } +func msgToTemplate(msg string) string { + // 将%s %d之类格式化字符串转换成{0}、{1}格式 + // 注意: 1.不支持复杂类型的转换例如%.2f , %[1]d, % x + // 2.原始msg中如果包含{0},{1}形式的字符串同样会引发错误。 + // 在抛出error msg时应注意避免 + fmtstr := false + lst := []rune(msg) + lastIndex := len(lst) - 1 + temp := []rune{} + index := 0 + for i, c := range lst { + switch c { + case '%': + if fmtstr || i == lastIndex { + temp = append(temp, c) + fmtstr = false + } else { + fmtstr = true + } + case 'v', 'T', 't', 'b', 'c', 'd', 'o', 'q', 'x', 'X', 'U', 'e', 'E', 'f', 'F', 'g', 'G', 's', 'p': + if fmtstr { + i := []rune(fmt.Sprintf("%d", index)) + temp = append(temp, '{') + temp = append(temp, i...) + temp = append(temp, '}') + index++ + fmtstr = false + } else { + temp = append(temp, c) + } + + default: + if fmtstr { + temp = append(temp, '%') + } + temp = append(temp, c) + fmtstr = false + } + } + + return string(temp) +} + func errorMessage(msg string, params ...interface{}) (string, httputils.Error) { fields := make([]string, len(params)) for i, v := range params { fields[i] = fmt.Sprint(v) } - error := httputils.Error{Id: msg, Fields: fields} + error := httputils.Error{Id: msgToTemplate(msg), Fields: fields} if len(params) > 0 { msg = fmt.Sprintf(msg, params...) } From e64b71818b9f32f49ece1accce7adf011d12a4e1 Mon Sep 17 00:00:00 2001 From: wanyaoqi Date: Tue, 20 Nov 2018 21:29:43 +0800 Subject: [PATCH 21/29] region: support create backup guest; scheduler: support backup guest schedule --- cmd/climc/shell/servers.go | 12 + pkg/appsrv/dispatcher/dispatcher.go | 2 + pkg/cloudcommon/db/opslog.go | 25 +- pkg/compute/guestdrivers/baremetals.go | 7 +- pkg/compute/guestdrivers/base.go | 4 + pkg/compute/guestdrivers/container.go | 2 +- pkg/compute/guestdrivers/kvm.go | 19 +- pkg/compute/guestdrivers/managedvirtual.go | 4 - pkg/compute/guestdrivers/virtualization.go | 12 +- pkg/compute/models/disks.go | 64 +- pkg/compute/models/guest_actions.go | 1779 +++++++++++++++++ pkg/compute/models/guestdrivers.go | 3 +- pkg/compute/models/guests.go | 1758 +--------------- pkg/compute/models/hosts.go | 4 +- pkg/compute/tasks/disk_base_task.go | 12 +- pkg/compute/tasks/disk_batch_create_task.go | 5 +- pkg/compute/tasks/disk_create_task.go | 73 +- pkg/compute/tasks/disk_delete_task.go | 21 +- pkg/compute/tasks/guest_backup_tasks.go | 244 +++ pkg/compute/tasks/guest_batch_create_task.go | 16 +- pkg/compute/tasks/guest_create_disk_task.go | 30 + pkg/compute/tasks/guest_create_task.go | 6 +- pkg/compute/tasks/guest_delete_task.go | 31 +- pkg/compute/tasks/guest_deploy_task.go | 72 +- .../tasks/guest_detach_all_disks_task.go | 3 + pkg/compute/tasks/guest_live_migrate_task.go | 56 +- pkg/compute/tasks/guest_start_task.go | 56 +- pkg/compute/tasks/guest_stop_task.go | 19 +- pkg/compute/tasks/guest_syncstatus_task.go | 2 + pkg/compute/tasks/guest_undeploy_task.go | 18 +- pkg/compute/tasks/schedule.go | 96 +- pkg/mcclient/options/servers.go | 2 + pkg/scheduler/api/sched.go | 34 +- pkg/scheduler/cache/candidate/hosts.go | 21 +- pkg/scheduler/core/generic_scheduler.go | 47 +- .../data_manager/candidate_manager.go | 11 +- pkg/scheduler/db/models/guest.go | 1 + pkg/scheduler/db/models/storage.go | 17 +- pkg/scheduler/handler/backup_helper.go | 126 ++ pkg/scheduler/handler/handler.go | 16 +- pkg/scheduler/manager/manager.go | 4 +- pkg/scheduler/manager/scheduler.go | 4 +- pkg/scheduler/manager/task_queue.go | 15 +- 43 files changed, 2924 insertions(+), 1829 deletions(-) create mode 100644 pkg/compute/models/guest_actions.go create mode 100644 pkg/compute/tasks/guest_backup_tasks.go create mode 100644 pkg/scheduler/handler/backup_helper.go diff --git a/cmd/climc/shell/servers.go b/cmd/climc/shell/servers.go index 56a82adbac..98e1099bd8 100644 --- a/cmd/climc/shell/servers.go +++ b/cmd/climc/shell/servers.go @@ -139,6 +139,18 @@ func init() { return nil }) + R(&options.ServerIdsOptions{}, "server-switch-to-backup", "Switch geust master to backup host", func(s *mcclient.ClientSession, opts *options.ServerIdsOptions) error { + ret := modules.Servers.BatchPerformAction(s, opts.ID, "switch-to-backup", nil) + printBatchResults(ret, modules.Servers.GetColumns(s)) + return nil + }) + + R(&options.ServerIdsOptions{}, "server-create-backup", "Create backup guest", func(s *mcclient.ClientSession, opts *options.ServerIdsOptions) error { + ret := modules.Servers.BatchPerformAction(s, opts.ID, "create-backup", nil) + printBatchResults(ret, modules.Servers.GetColumns(s)) + return nil + }) + R(&options.ServerStopOptions{}, "server-stop", "Stop servers", func(s *mcclient.ClientSession, opts *options.ServerStopOptions) error { params, err := options.StructToParams(opts) if err != nil { diff --git a/pkg/appsrv/dispatcher/dispatcher.go b/pkg/appsrv/dispatcher/dispatcher.go index e4dc1122ef..2b77757f53 100644 --- a/pkg/appsrv/dispatcher/dispatcher.go +++ b/pkg/appsrv/dispatcher/dispatcher.go @@ -304,6 +304,8 @@ func deleteHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) fmt.Sprintf("No request key: %s", manager.Keyword())) return } + } else { + data = jsonutils.NewDict() } result, err := manager.Delete(ctx, params[""], mergeQueryParams(params, query, ""), data) if err != nil { diff --git a/pkg/cloudcommon/db/opslog.go b/pkg/cloudcommon/db/opslog.go index cae57239f9..264e87f1d5 100644 --- a/pkg/cloudcommon/db/opslog.go +++ b/pkg/cloudcommon/db/opslog.go @@ -27,11 +27,16 @@ const ( ACT_ATTACH = "attach" ACT_DETACH = "detach" - ACT_UPDATE_STATUS = "updatestatus" + ACT_START_CREATE_BACKUP = "start_create_backup" + ACT_CREATE_BACKUP = "create_backup" + ACT_CREATE_BACKUP_FAILED = "create_backup_failed" - ACT_STARTING = "starting" - ACT_START = "start" - ACT_START_FAIL = "start_fail" + ACT_UPDATE_STATUS = "updatestatus" + ACT_STARTING = "starting" + ACT_START = "start" + ACT_START_FAIL = "start_fail" + ACT_BACKUP_START = "backup_start" + ACT_BACKUP_START_FAILED = "backup_start_fail" ACT_STOPPING = "stopping" ACT_STOP = "stop" @@ -49,6 +54,9 @@ const ( ACT_SAVE = "save" ACT_SAVE_FAIL = "save_fail" + ACT_SWITCHED = "switched" + ACT_SWITCH_FAILED = "switch_failed" + ACT_SNAPSHOTING = "snapshoting" ACT_SNAPSHOT_STREAM = "snapshot_stream" ACT_SNAPSHOT_DONE = "snapshot" @@ -63,9 +71,12 @@ const ( ACT_DISK_CLEAN_UP_SNAPSHOTS = "disk_clean_up_snapshots" ACT_DISK_CLEAN_UP_SNAPSHOTS_FAIL = "disk_clean_up_snapshots_fail" - ACT_ALLOCATING = "allocating" - ACT_ALLOCATE = "allocate" - ACT_ALLOCATE_FAIL = "alloc_fail" + ACT_ALLOCATING = "allocating" + ACT_BACKUP_ALLOCATING = "backup_allocating" + ACT_ALLOCATE = "allocate" + ACT_BACKUP_ALLOCATE = "backup_allocate" + ACT_ALLOCATE_FAIL = "alloc_fail" + ACT_BACKUP_ALLOCATE_FAIL = "backup_alloc_fail" ACT_DELOCATING = "delocating" ACT_DELOCATE = "delocate" diff --git a/pkg/compute/guestdrivers/baremetals.go b/pkg/compute/guestdrivers/baremetals.go index ef42f3f573..240ccbf2c4 100644 --- a/pkg/compute/guestdrivers/baremetals.go +++ b/pkg/compute/guestdrivers/baremetals.go @@ -175,8 +175,11 @@ func (self *SBaremetalGuestDriver) RequestStartOnHost(ctx context.Context, guest return host.BaremetalSyncRequest(ctx, "POST", url, headers, config) } -func (self *SBaremetalGuestDriver) RequestStopGuestForDelete(ctx context.Context, guest *models.SGuest, task taskman.ITask) error { - host := guest.GetHost() +func (self *SBaremetalGuestDriver) RequestStopGuestForDelete(ctx context.Context, guest *models.SGuest, + host *models.SHost, task taskman.ITask) error { + if host == nil { + host = guest.GetHost() + } guestStatus, _ := task.GetParams().GetString("guest_status") overridePendingDelete := jsonutils.QueryBoolean(task.GetParams(), "override_pending_delete", false) purge := jsonutils.QueryBoolean(task.GetParams(), "purge", false) diff --git a/pkg/compute/guestdrivers/base.go b/pkg/compute/guestdrivers/base.go index 8d9207b7c2..b3aed3eb01 100644 --- a/pkg/compute/guestdrivers/base.go +++ b/pkg/compute/guestdrivers/base.go @@ -184,6 +184,10 @@ func (self *SBaseGuestDriver) RequestReloadDiskSnapshot(ctx context.Context, gue return fmt.Errorf("Not Implement") } +func (self *SBaseGuestDriver) RequestSyncToBackup(ctx context.Context, guest *models.SGuest, task taskman.ITask) error { + return fmt.Errorf("Not Implement") +} + func (self *SBaseGuestDriver) getTaskRequestHeader(task taskman.ITask) http.Header { header := http.Header{} header.Set(mcclient.AUTH_TOKEN, task.GetUserCred().GetTokenString()) diff --git a/pkg/compute/guestdrivers/container.go b/pkg/compute/guestdrivers/container.go index 3d57546971..5718a65521 100644 --- a/pkg/compute/guestdrivers/container.go +++ b/pkg/compute/guestdrivers/container.go @@ -87,7 +87,7 @@ func (self *SContainerDriver) OnGuestDeployTaskDataReceived(ctx context.Context, return nil } -func (self *SContainerDriver) RequestStopGuestForDelete(ctx context.Context, guest *models.SGuest, task taskman.ITask) error { +func (self *SContainerDriver) RequestStopGuestForDelete(ctx context.Context, guest *models.SGuest, host *models.SHost, task taskman.ITask) error { // do nothing, call next stage task.ScheduleRun(nil) return nil diff --git a/pkg/compute/guestdrivers/kvm.go b/pkg/compute/guestdrivers/kvm.go index 1cb9d0d586..9f61b6b96d 100644 --- a/pkg/compute/guestdrivers/kvm.go +++ b/pkg/compute/guestdrivers/kvm.go @@ -164,7 +164,7 @@ func (self *SKVMGuestDriver) RequestUndeployGuestOnHost(ctx context.Context, gue body := jsonutils.NewDict() // XXXXXXXX - if guest.HostId != host.Id { + if guest.HostId != host.Id && guest.BackupHostId != host.Id { body.Set("migrated", jsonutils.JSONTrue) } _, res, err := httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "DELETE", url, header, body, false) @@ -283,10 +283,6 @@ func (self *SKVMGuestDriver) ValidateResizeDisk(guest *models.SGuest, disk *mode return nil } -func (self *SKVMGuestDriver) RequestDeleteDetachedDisk(ctx context.Context, disk *models.SDisk, task taskman.ITask, isPurge bool) error { - return disk.StartDiskDeleteTask(ctx, task.GetUserCred(), task.GetTaskId(), isPurge) -} - func (self *SKVMGuestDriver) RequestSyncConfigOnHost(ctx context.Context, guest *models.SGuest, host *models.SHost, task taskman.ITask) error { desc := guest.GetDriver().GetJsonDescAtHost(ctx, guest, host) body := jsonutils.NewDict() @@ -324,3 +320,16 @@ func (self *SKVMGuestDriver) RequestRebuildRootDisk(ctx context.Context, guest * subtask.ScheduleRun(nil) return nil } + +func (self *SKVMGuestDriver) RequestSyncToBackup(ctx context.Context, guest *models.SGuest, task taskman.ITask) error { + body := jsonutils.NewDict() + body.Set("backup_nbd_server_uri", jsonutils.NewString(guest.GetMetadata("backup_nbd_server_uri", task.GetUserCred()))) + host := guest.GetHost() + url := fmt.Sprintf("%s/server/%s/drive-mirror", host.ManagerUri, guest.Id) + header := self.getTaskRequestHeader(task) + _, _, err := httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "POST", url, header, body, false) + if err != nil { + return err + } + return nil +} diff --git a/pkg/compute/guestdrivers/managedvirtual.go b/pkg/compute/guestdrivers/managedvirtual.go index 30dbc10618..6c27c3484b 100644 --- a/pkg/compute/guestdrivers/managedvirtual.go +++ b/pkg/compute/guestdrivers/managedvirtual.go @@ -271,10 +271,6 @@ func (self *SManagedVirtualizedGuestDriver) DoGuestCreateDisksTask(ctx context.C return nil } -func (self *SManagedVirtualizedGuestDriver) RequestDeleteDetachedDisk(ctx context.Context, disk *models.SDisk, task taskman.ITask, isPurge bool) error { - return disk.StartDiskDeleteTask(ctx, task.GetUserCred(), task.GetTaskId(), isPurge) -} - type SManagedVMChangeConfig struct { InstanceId string Cpu int diff --git a/pkg/compute/guestdrivers/virtualization.go b/pkg/compute/guestdrivers/virtualization.go index 51ba216d1b..f72a47eb81 100644 --- a/pkg/compute/guestdrivers/virtualization.go +++ b/pkg/compute/guestdrivers/virtualization.go @@ -143,6 +143,11 @@ func (self *SVirtualizedGuestDriver) StartGuestRestartTask(guest *models.SGuest, return nil } +func (self *SVirtualizedGuestDriver) RequestDeleteDetachedDisk(ctx context.Context, disk *models.SDisk, task taskman.ITask, isPurge bool) error { + return disk.StartDiskDeleteTask(ctx, task.GetUserCred(), task.GetTaskId(), isPurge, + jsonutils.QueryBoolean(task.GetParams(), "override_pending_delete", false)) +} + func (self *SVirtualizedGuestDriver) OnGuestDeployTaskComplete(ctx context.Context, guest *models.SGuest, task taskman.ITask) error { if jsonutils.QueryBoolean(task.GetParams(), "restart", false) { task.SetStage("OnDeployStartGuestComplete", nil) @@ -161,8 +166,11 @@ func (self *SVirtualizedGuestDriver) StartGuestSyncstatusTask(guest *models.SGue return nil } -func (self *SVirtualizedGuestDriver) RequestStopGuestForDelete(ctx context.Context, guest *models.SGuest, task taskman.ITask) error { - host := guest.GetHost() +func (self *SVirtualizedGuestDriver) RequestStopGuestForDelete(ctx context.Context, guest *models.SGuest, + host *models.SHost, task taskman.ITask) error { + if host == nil { + host = guest.GetHost() + } if host != nil && host.Enabled && host.HostStatus == models.HOST_ONLINE { return guest.StartGuestStopTask(ctx, task.GetUserCred(), true, task.GetTaskId()) } diff --git a/pkg/compute/models/disks.go b/pkg/compute/models/disks.go index c0a992900b..55338fbfb5 100644 --- a/pkg/compute/models/disks.go +++ b/pkg/compute/models/disks.go @@ -32,18 +32,20 @@ import ( ) const ( - DISK_INIT = "init" - DISK_REBUILD = "rebuild" - DISK_ALLOC_FAILED = "alloc_failed" - DISK_STARTALLOC = "start_alloc" - DISK_ALLOCATING = "allocating" - DISK_READY = "ready" - DISK_RESET = "reset" - DISK_DEALLOC = "deallocating" - DISK_DEALLOC_FAILED = "dealloc_failed" - DISK_UNKNOWN = "unknown" - DISK_DETACHING = "detaching" - DISK_ATTACHING = "attaching" + DISK_INIT = "init" + DISK_REBUILD = "rebuild" + DISK_ALLOC_FAILED = "alloc_failed" + DISK_STARTALLOC = "start_alloc" + DISK_BACKUP_STARTALLOC = "backup_start_alloc" + DISK_BACKUP_ALLOC_FAILED = "backup_alloc_failed" + DISK_ALLOCATING = "allocating" + DISK_READY = "ready" + DISK_RESET = "reset" + DISK_DEALLOC = "deallocating" + DISK_DEALLOC_FAILED = "dealloc_failed" + DISK_UNKNOWN = "unknown" + DISK_DETACHING = "detaching" + DISK_ATTACHING = "attaching" DISK_START_SAVE = "start_save" DISK_SAVING = "saving" @@ -93,7 +95,8 @@ type SDisk struct { AutoDelete bool `nullable:"false" default:"false" get:"user" update:"user"` // Column(Boolean, nullable=False, default=False) - StorageId string `width:"128" charset:"ascii" nullable:"true" list:"admin"` // Column(VARCHAR(ID_LENGTH, charset='ascii'), nullable=True) + StorageId string `width:"128" charset:"ascii" nullable:"true" list:"admin" create:"required"` // Column(VARCHAR(ID_LENGTH, charset='ascii'), nullable=False) + BackupStorageId string `width:"128" charset:"ascii" nullable:"true" list:"admin" create:"required"` // # backing template id and type TemplateId string `width:"256" charset:"ascii" nullable:"true" list:"user"` // Column(VARCHAR(ID_LENGTH, charset='ascii'), nullable=True) @@ -725,6 +728,11 @@ func (self *SDisk) GetPathAtHost(host *SHost) string { hostStorage := host.GetHoststorageOfId(self.StorageId) if hostStorage != nil { return path.Join(hostStorage.MountPoint, self.Id) + } else if len(self.BackupStorageId) > 0 { + hostStorage = host.GetHoststorageOfId(self.BackupStorageId) + if hostStorage != nil { + return path.Join(hostStorage.MountPoint, self.Id) + } } return "" } @@ -1148,11 +1156,12 @@ func (self *SDisk) PerformPurge(ctx context.Context, userCred mcclient.TokenCred if err != nil { return nil, err } - return nil, self.StartDiskDeleteTask(ctx, userCred, "", true) + return nil, self.StartDiskDeleteTask(ctx, userCred, "", true, false) } func (self *SDisk) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { - return self.StartDiskDeleteTask(ctx, userCred, "", false) + return self.StartDiskDeleteTask(ctx, userCred, "", false, + jsonutils.QueryBoolean(query, "override_pending_delete", false)) } func (self *SDisk) getMoreDetails(extra *jsonutils.JSONDict) *jsonutils.JSONDict { @@ -1209,11 +1218,14 @@ func (self *SDisk) StartDiskResizeTask(ctx context.Context, userCred mcclient.To return nil } -func (self *SDisk) StartDiskDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, isPurge bool) error { +func (self *SDisk) StartDiskDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, isPurge, overridePendingDelete bool) error { params := jsonutils.NewDict() if isPurge { params.Add(jsonutils.JSONTrue, "purge") } + if overridePendingDelete { + params.Add(jsonutils.JSONTrue, "override_pending_delete") + } task, err := taskman.TaskManager.NewTask(ctx, "DiskDeleteTask", self, userCred, params, parentTaskId, "", nil) if err != nil { log.Errorf("%s", err) @@ -1250,6 +1262,14 @@ func (self *SDisk) SetDiskReady(ctx context.Context, userCred mcclient.TokenCred } } +func (self *SDisk) SwitchToBackup() error { + _, err := self.GetModelManager().TableSpec().Update(self, func() error { + self.StorageId, self.BackupStorageId = self.BackupStorageId, self.StorageId + return nil + }) + return err +} + func (self *SDisk) ClearHostSchedCache() error { storage := self.GetStorage() hosts := storage.GetAllAttachingHosts() @@ -1349,7 +1369,7 @@ func (manager *SDiskManager) CleanPendingDeleteDisks(ctx context.Context, userCr return } for i := 0; i < len(disks); i += 1 { - disks[i].StartDiskDeleteTask(ctx, userCred, "", false) + disks[i].StartDiskDeleteTask(ctx, userCred, "", false, false) } } @@ -1392,3 +1412,13 @@ func (manager *SDiskManager) AutoDiskSnapshot(ctx context.Context, userCred mccl guests[0].StartDiskSnapshot(ctx, userCred, disk.Id, snap.Id) } } + +func (disk *SDisk) StratCreateBackupTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + if task, err := taskman.TaskManager.NewTask(ctx, "DiskCreateBackupTask", disk, userCred, nil, parentTaskId, "", nil); err != nil { + log.Errorf(err.Error()) + return err + } else { + task.ScheduleRun(nil) + } + return nil +} diff --git a/pkg/compute/models/guest_actions.go b/pkg/compute/models/guest_actions.go new file mode 100644 index 0000000000..adf3dce2bc --- /dev/null +++ b/pkg/compute/models/guest_actions.go @@ -0,0 +1,1779 @@ +package models + +import ( + "context" + "database/sql" + "fmt" + "net/http" + "strconv" + "strings" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/compute/options" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/httputils" + "yunion.io/x/onecloud/pkg/util/logclient" + "yunion.io/x/onecloud/pkg/util/seclib2" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/util/fileutils" + "yunion.io/x/pkg/util/regutils" + "yunion.io/x/pkg/utils" + "yunion.io/x/sqlchemy" +) + +func (self *SGuest) AllowGetDetailsVnc(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) GetDetailsVnc(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if utils.IsInStringArray(self.Status, []string{VM_RUNNING, VM_BLOCK_STREAM}) { + host := self.GetHost() + if host == nil { + return nil, httperrors.NewInternalServerError("Host missing") + } + retval, err := self.GetDriver().GetGuestVncInfo(userCred, self, host) + if err != nil { + return nil, err + } + retval.Add(jsonutils.NewString(self.Id), "id") + return retval, nil + } else { + return jsonutils.NewDict(), nil + } +} + +func (self *SGuest) AllowGetDetailsMonitor(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) GetDetailsMonitor(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if utils.IsInStringArray(self.Status, []string{VM_RUNNING, VM_BLOCK_STREAM}) { + cmd, err := query.GetString("command") + if err != nil { + return nil, err + } + return self.SendMonitorCommand(ctx, userCred, cmd) + } + return nil, httperrors.NewInvalidStatusError("Cannot send command in status %s", self.Status) +} + +func (self *SGuest) AllowGetDetailsDesc(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) GetDetailsDesc(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) { + host := self.GetHost() + if host == nil { + return nil, httperrors.NewInvalidStatusError("No host for server") + } + desc := self.GetDriver().GetJsonDescAtHost(ctx, self, host) + return desc, nil +} + +func (self *SGuest) StartInsertIsoTask(ctx context.Context, imageId string, hostId string, userCred mcclient.TokenCredential, parentTaskId string) error { + self.insertIso(imageId) + + data := jsonutils.NewDict() + data.Add(jsonutils.NewString(imageId), "image_id") + data.Add(jsonutils.NewString(hostId), "host_id") + + task, err := taskman.TaskManager.NewTask(ctx, "GuestInsertIsoTask", self, userCred, data, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + +func (self *SGuest) StartGueststartTask(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, parentTaskId string) error { + self.SetStatus(userCred, VM_START_START, "") + task, err := taskman.TaskManager.NewTask(ctx, "GuestStartTask", self, userCred, data, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + +func (self *SGuest) StartGuestCreateTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict, pendingUsage quotas.IQuota, parentTaskId string) error { + return self.GetDriver().StartGuestCreateTask(self, ctx, userCred, params, pendingUsage, parentTaskId) +} + +func (self *SGuest) AllowPerformReset(ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformReset(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, + data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + isHard := jsonutils.QueryBoolean(data, "is_hard", false) + if self.Status == VM_RUNNING || self.Status == VM_STOP_FAILED { + self.GetDriver().StartGuestResetTask(self, ctx, userCred, isHard, "") + return nil, nil + } + return nil, httperrors.NewInvalidStatusError("Cannot reset VM in status %s", self.Status) +} + +func (self *SGuest) AllowPerformDiskSnapshot(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformDiskSnapshot(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if !utils.IsInStringArray(self.Status, []string{VM_RUNNING, VM_READY}) { + return nil, httperrors.NewInvalidStatusError("Cannot do snapshot when VM in status %s", self.Status) + } + diskId, err := data.GetString("disk_id") + if err != nil { + return nil, httperrors.NewBadRequestError(err.Error()) + } + name, err := data.GetString("name") + if err != nil { + return nil, httperrors.NewBadRequestError(err.Error()) + } + err = ValidateSnapshotName(self.Hypervisor, name, userCred.GetProjectId()) + if err != nil { + return nil, httperrors.NewBadRequestError(err.Error()) + } + if self.GetGuestDisk(diskId) == nil { + return nil, httperrors.NewNotFoundError("Guest disk %s not found", diskId) + } + if self.GetHypervisor() == HYPERVISOR_KVM { + q := SnapshotManager.Query() + cnt := q.Filter(sqlchemy.AND(sqlchemy.Equals(q.Field("disk_id"), diskId), + sqlchemy.Equals(q.Field("created_by"), MANUAL), + sqlchemy.Equals(q.Field("fake_deleted"), false))).Count() + if cnt >= options.Options.DefaultMaxManualSnapshotCount { + return nil, httperrors.NewBadRequestError("Disk %s snapshot full, cannot take any more", diskId) + } + pendingUsage := &SQuota{Snapshot: 1} + err = QuotaManager.CheckSetPendingQuota(ctx, userCred, self.ProjectId, pendingUsage) + if err != nil { + return nil, httperrors.NewBadRequestError("Check set pending quota error %s", err) + } + snapshot, err := SnapshotManager.CreateSnapshot(ctx, userCred, MANUAL, diskId, self.Id, "", name) + QuotaManager.CancelPendingUsage(ctx, userCred, self.ProjectId, nil, pendingUsage) + if err != nil { + return nil, err + } + err = self.StartDiskSnapshot(ctx, userCred, diskId, snapshot.Id) + return nil, err + } else { + snapshot, err := SnapshotManager.CreateSnapshot(ctx, userCred, MANUAL, diskId, self.Id, "", name) + if err != nil { + return nil, err + } + err = self.StartDiskSnapshot(ctx, userCred, diskId, snapshot.Id) + return nil, err + } + +} + +func (self *SGuest) StartDiskSnapshot(ctx context.Context, userCred mcclient.TokenCredential, diskId, snapshotId string) error { + self.SetStatus(userCred, VM_START_SNAPSHOT, "StartDiskSnapshot") + params := jsonutils.NewDict() + params.Set("disk_id", jsonutils.NewString(diskId)) + params.Set("snapshot_id", jsonutils.NewString(snapshotId)) + return self.GetDriver().StartGuestDiskSnapshotTask(ctx, userCred, self, params) +} + +func (self *SGuest) AllowPerformStop(ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformStop(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, + data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + isForce := jsonutils.QueryBoolean(data, "is_force", false) + if utils.IsInStringArray(self.Status, []string{VM_RUNNING, VM_STOP_FAILED}) || (isForce && self.Status == VM_STOPPING) { + return nil, self.StartGuestStopTask(ctx, userCred, isForce, "") + } else { + return nil, httperrors.NewInvalidStatusError("Cannot stop server in status %s", self.Status) + } +} + +func (self *SGuest) StartGuestStopTask(ctx context.Context, userCred mcclient.TokenCredential, isForce bool, parentTaskId string) error { + if len(parentTaskId) == 0 { + self.SetStatus(userCred, VM_START_STOP, "") + } + params := jsonutils.NewDict() + if isForce { + params.Add(jsonutils.JSONTrue, "is_force") + } + if len(parentTaskId) > 0 { + params.Add(jsonutils.JSONTrue, "subtask") + } + return self.GetDriver().StartGuestStopTask(self, ctx, userCred, params, parentTaskId) +} + +func (self *SGuest) AllowPerformRestart(ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformRestart(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + isForce := jsonutils.QueryBoolean(data, "is_force", false) + if utils.IsInStringArray(self.Status, []string{VM_RUNNING, VM_STOP_FAILED}) || (isForce && self.Status == VM_STOPPING) { + return nil, self.GetDriver().StartGuestRestartTask(self, ctx, userCred, isForce, "") + } else { + return nil, httperrors.NewInvalidStatusError("Cannot do restart server in status %s", self.Status) + } +} + +func (self *SGuest) AllowPerformSendkeys(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformSendkeys(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if self.Hypervisor != HYPERVISOR_KVM { + return nil, httperrors.NewUnsupportOperationError("Not allow for hypervisor %s", self.Hypervisor) + } + if self.Status != VM_RUNNING { + return nil, httperrors.NewInvalidStatusError("Cannot send keys in status %s", self.Status) + } + keys, err := data.GetString("keys") + if err != nil { + return nil, err + } + err = self.VerifySendKeys(keys) + if err != nil { + return nil, httperrors.NewBadRequestError(err.Error()) + } + cmd := fmt.Sprintf("sendkey %s", keys) + duration, err := data.Int("duration") + if err == nil { + cmd = fmt.Sprintf("%s %d", cmd, duration) + } + _, err = self.SendMonitorCommand(ctx, userCred, cmd) + return nil, err +} + +func (self *SGuest) VerifySendKeys(keyStr string) error { + keys := strings.Split(keyStr, "-") + for _, key := range keys { + if !self.IsLegalKey(key) { + return fmt.Errorf("Unknown key '%s'", key) + } + } + return nil +} + +func (self *SGuest) IsLegalKey(key string) bool { + singleKeys := "1234567890abcdefghijklmnopqrstuvwxyz" + legalKeys := []string{"ctrl", "ctrl_r", "alt", "alt_r", "shift", "shift_r", + "delete", "esc", "insert", "print", "spc", + "f1", "f2", "f3", "f4", "f5", "f6", + "f7", "f8", "f9", "f10", "f11", "f12", + "home", "pgup", "pgdn", "end", + "up", "down", "left", "right", + "tab", "minus", "equal", "backspace", "backslash", + "bracket_left", "bracket_right", "backslash", + "semicolon", "apostrophe", "grave_accent", "ret", + "comma", "dot", "slash", + "caps_lock", "num_lock", "scroll_lock"} + if len(key) > 1 && !utils.IsInStringArray(key, legalKeys) { + return false + } else if len(key) == 1 && !strings.Contains(singleKeys, key) { + return false + } + return true +} + +func (self *SGuest) SendMonitorCommand(ctx context.Context, userCred mcclient.TokenCredential, cmd string) (jsonutils.JSONObject, error) { + host := self.GetHost() + url := fmt.Sprintf("%s/servers/%s/monitor", host.ManagerUri, self.Id) + header := http.Header{} + header.Add("X-Auth-Token", userCred.GetTokenString()) + body := jsonutils.NewDict() + body.Add(jsonutils.NewString(cmd), "cmd") + _, res, err := httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "POST", url, header, body, false) + if err != nil { + return nil, err + } + ret := res.(*jsonutils.JSONDict) + return ret, nil +} + +func (self *SGuest) AllowPerformAssociateEip(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformAssociateEip(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if !utils.IsInStringArray(self.Status, []string{VM_READY, VM_RUNNING}) { + return nil, httperrors.NewInvalidStatusError("cannot associate eip in status %s", self.Status) + } + + eip, err := self.GetEip() + if err != nil { + log.Errorf("Fail to get Eip %s", err) + return nil, httperrors.NewGeneralError(err) + } + if eip != nil { + return nil, httperrors.NewInvalidStatusError("already associate with eip") + } + eipStr := jsonutils.GetAnyString(data, []string{"eip", "eip_id"}) + if len(eipStr) == 0 { + return nil, httperrors.NewInputParameterError("missing eip or eip_id") + } + eipObj, err := ElasticipManager.FetchByIdOrName(userCred, eipStr) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError("eip %s not found", eipStr) + } else { + return nil, httperrors.NewGeneralError(err) + } + } + + eip = eipObj.(*SElasticip) + eipRegion := eip.GetRegion() + instRegion := self.getRegion() + + if eip.Mode == EIP_MODE_INSTANCE_PUBLICIP { + return nil, httperrors.NewUnsupportOperationError("fixed eip cannot be associated") + } + + eipVm := eip.GetAssociateVM() + if eipVm != nil { + return nil, httperrors.NewConflictError("eip has been associated") + } + + if eipRegion.Id != instRegion.Id { + return nil, httperrors.NewInputParameterError("cannot associate eip and instance in different region") + } + + host := self.GetHost() + if host == nil { + return nil, httperrors.NewInputParameterError("server host is not found???") + } + + if host.ManagerId != eip.ManagerId { + return nil, httperrors.NewInputParameterError("cannot associate eip and instance in different provider") + } + + self.SetStatus(userCred, VM_ASSOCIATE_EIP, "associate eip") + + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(self.ExternalId), "instance_external_id") + params.Add(jsonutils.NewString(self.Id), "instance_id") + params.Add(jsonutils.NewString(EIP_ASSOCIATE_TYPE_SERVER), "instance_type") + + err = eip.StartEipAssociateTask(ctx, userCred, params) + + return nil, err +} + +func (self *SGuest) AllowPerformDissociateEip(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformDissociateEip(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + eip, err := self.GetEip() + if err != nil { + log.Errorf("Fail to get Eip %s", err) + return nil, httperrors.NewGeneralError(err) + } + if eip == nil { + return nil, httperrors.NewInvalidStatusError("No eip to dissociate") + } + + self.SetStatus(userCred, VM_DISSOCIATE_EIP, "associate eip") + + err = eip.StartEipDissociateTask(ctx, userCred, "") + if err != nil { + log.Errorf("fail to start dissociate task %s", err) + return nil, httperrors.NewGeneralError(err) + } + return nil, nil +} + +func (self *SGuest) AllowPerformCreateEip(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformCreateEip(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + bw, err := data.Int("bandwidth") + if err != nil { + return nil, httperrors.NewInputParameterError("Missing bandwidth") + } + + chargeType, _ := data.GetString("charge_type") + if len(chargeType) == 0 { + chargeType = EIP_CHARGE_TYPE_DEFAULT + } + + if len(self.ExternalId) == 0 { + return nil, httperrors.NewInvalidStatusError("Not a managed VM") + } + host := self.GetHost() + if host == nil { + return nil, httperrors.NewInvalidStatusError("No host???") + } + + _, err = host.GetDriver() + if err != nil { + return nil, httperrors.NewInvalidStatusError("No valid cloud provider") + } + + region := host.GetRegion() + if region == nil { + return nil, httperrors.NewInvalidStatusError("No cloudregion???") + } + + err = ElasticipManager.allocateEipAndAssociateVM(ctx, userCred, self, int(bw), chargeType, host.ManagerId, region.Id) + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + + self.SetStatus(userCred, VM_ASSOCIATE_EIP, "allocate and associate EIP") + + return nil, nil +} + +func (self *SGuest) AllowPerformUserData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformUserData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + userData, err := data.GetString("user_data") + if err != nil { + return nil, httperrors.NewInputParameterError("missing user_data %s", err) + } + err = self.setUserData(ctx, userCred, userData) + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + if len(self.HostId) > 0 { + err = self.StartSyncTask(ctx, userCred, false, "") + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + } + return nil, nil +} + +func (self *SGuest) AllowPerformAssignSecgroup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) AllowPerformRevokeSecgroup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformRevokeSecgroup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if !utils.IsInStringArray(self.Status, []string{VM_READY, VM_RUNNING, VM_SUSPEND}) { + return nil, httperrors.NewInputParameterError("Cannot revoke security rules in status %s", self.Status) + } else { + if _, err := self.GetModelManager().TableSpec().Update(self, func() error { + self.SecgrpId = "default" + return nil + }); err != nil { + return nil, err + } + if err := self.StartSyncTask(ctx, userCred, true, ""); err != nil { + return nil, err + } + } + return nil, nil +} + +func (self *SGuest) PerformAssignSecgroup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if !utils.IsInStringArray(self.Status, []string{VM_READY, VM_RUNNING, VM_SUSPEND}) { + logclient.AddActionLog(self, logclient.ACT_VM_ASSIGNSECGROUP, "Cannot assign security rules in status "+self.Status, userCred, false) + return nil, httperrors.NewInputParameterError("Cannot assign security rules in status %s", self.Status) + } else { + if secgrp, err := data.GetString("secgrp"); err != nil { + logclient.AddActionLog(self, logclient.ACT_VM_ASSIGNSECGROUP, err, userCred, false) + return nil, err + } else if sg, err := SecurityGroupManager.FetchByIdOrName(userCred, secgrp); err != nil { + msg := fmt.Sprintf("SecurityGroup %s not found", secgrp) + logclient.AddActionLog(self, logclient.ACT_VM_ASSIGNSECGROUP, msg, userCred, false) + return nil, httperrors.NewNotFoundError("SecurityGroup %s not found", secgrp) + } else { + if _, err := self.GetModelManager().TableSpec().Update(self, func() error { + self.SecgrpId = sg.GetId() + return nil + }); err != nil { + logclient.AddActionLog(self, logclient.ACT_VM_ASSIGNSECGROUP, err, userCred, false) + return nil, err + } + if err := self.StartSyncTask(ctx, userCred, true, ""); err != nil { + logclient.AddActionLog(self, logclient.ACT_VM_ASSIGNSECGROUP, err, userCred, false) + return nil, err + } + } + } + logclient.AddActionLog(self, logclient.ACT_VM_ASSIGNSECGROUP, nil, userCred, true) + return nil, nil +} + +func (self *SGuest) AllowPerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsAdmin(userCred) +} + +func (self *SGuest) PerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + err := self.ValidateDeleteCondition(ctx) + if err != nil { + return nil, err + } + host := self.GetHost() + if host != nil && host.Enabled { + return nil, httperrors.NewInvalidStatusError("Cannot purge server on enabled host") + } + err = self.StartDeleteGuestTask(ctx, userCred, "", true, false) + return nil, err +} + +func (self *SGuest) setKeypairId(userCred mcclient.TokenCredential, keypairId string) error { + diff, err := self.GetModelManager().TableSpec().Update(self, func() error { + self.KeypairId = keypairId + return nil + }) + if err != nil { + db.OpsLog.LogEvent(self, db.ACT_UPDATE, diff, userCred) + } + return err +} + +func (self *SGuest) AllowPerformRebuildRoot(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformRebuildRoot(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + imageId, _ := data.GetString("image_id") + + rebuildStatus, err := self.GetDriver().GetRebuildRootStatus() + if err != nil { + return nil, httperrors.NewInputParameterError(err.Error()) + } + + if !utils.IsInStringArray(self.Status, rebuildStatus) { + return nil, httperrors.NewInvalidStatusError("Cannot reset root in status %s", self.Status) + } + + if !data.Contains("image_id") { + gdc := self.CategorizeDisks() + imageId = gdc.Root.GetTemplateId() + if len(imageId) == 0 { + return nil, httperrors.NewBadRequestError("No template for root disk") + } + img, err := CachedimageManager.getImageInfo(ctx, userCred, imageId, false) + if err != nil { + return nil, httperrors.NewBadRequestError("Template %s not accessible: %s", imageId, err.Error()) + } + osType, _ := img.Properties["os_type"] + osName := self.GetMetadata("os_name", userCred) + if len(osName) == 0 && len(osType) == 0 && strings.ToLower(osType) != strings.ToLower(osName) { + return nil, httperrors.NewBadRequestError("Cannot switch OS between %s-%s", osName, osType) + } + } + + autoStart := jsonutils.QueryBoolean(data, "auto_start", false) + var needStop = false + if self.Status == VM_RUNNING { + needStop = true + } + resetPasswd := jsonutils.QueryBoolean(data, "reset_password", true) + passwd, _ := data.GetString("password") + if len(passwd) > 0 { + if !seclib2.MeetComplxity(passwd) { + return nil, httperrors.NewWeakPasswordError() + } + } + + keypairStr := jsonutils.GetAnyString(data, []string{"keypair", "keypair_id"}) + if len(keypairStr) > 0 { + keypairObj, err := KeypairManager.FetchByIdOrName(userCred, keypairStr) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError("keypair %s not found", keypairStr) + } else { + return nil, httperrors.NewGeneralError(err) + } + } + if self.KeypairId != keypairObj.GetId() { + err = self.setKeypairId(userCred, keypairObj.GetId()) + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + } + } + + return nil, self.StartRebuildRootTask(ctx, userCred, imageId, needStop, autoStart, passwd, resetPasswd) +} + +func (self *SGuest) StartRebuildRootTask(ctx context.Context, userCred mcclient.TokenCredential, imageId string, needStop, autoStart bool, passwd string, resetPasswd bool) error { + data := jsonutils.NewDict() + data.Set("image_id", jsonutils.NewString(imageId)) + if needStop { + data.Set("need_stop", jsonutils.JSONTrue) + } + if autoStart { + data.Set("auto_start", jsonutils.JSONTrue) + } + if resetPasswd { + data.Set("reset_password", jsonutils.JSONTrue) + } else { + data.Set("reset_password", jsonutils.JSONFalse) + } + if len(passwd) > 0 { + data.Set("password", jsonutils.NewString(passwd)) + } + if self.GetHypervisor() == HYPERVISOR_BAREMETAL { + task, err := taskman.TaskManager.NewTask(ctx, "BaremetalServerRebuildRootTask", self, userCred, data, "", "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + } else { + task, err := taskman.TaskManager.NewTask(ctx, "GuestRebuildRootTask", self, userCred, data, "", "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + } + return nil +} + +func (self *SGuest) DetachDisk(ctx context.Context, disk *SDisk, userCred mcclient.TokenCredential) { + guestdisk := self.GetGuestDisk(disk.Id) + if guestdisk != nil { + guestdisk.Detach(ctx, userCred) + } +} + +func (self *SGuest) AllowPerformCreatedisk(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformCreatedisk(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + var diskIdx, diskSize = 0, 0 + disksConf := jsonutils.NewDict() + diskSizes := make(map[string]int, 0) + diskSeq := fmt.Sprintf("disk.%d", diskIdx) + for data.Contains(diskSeq) { + diskDef, _ := data.Get(diskSeq) + diskInfo, err := parseDiskInfo(ctx, userCred, diskDef) + if err != nil { + logclient.AddActionLog(self, logclient.ACT_CREATE, err.Error(), userCred, false) + return nil, httperrors.NewBadRequestError(err.Error()) + } + if len(diskInfo.Backend) == 0 { + diskInfo.Backend = self.getDefaultStorageType() + } + disksConf.Set(diskSeq, jsonutils.Marshal(diskInfo)) + if _, ok := diskSizes[diskInfo.Backend]; !ok { + diskSizes[diskInfo.Backend] = diskInfo.Size + } else { + diskSizes[diskInfo.Backend] += diskInfo.Size + } + diskSize += diskInfo.Size + diskIdx += 1 + diskSeq = fmt.Sprintf("disk.%d", diskIdx) + } + if diskIdx == 0 { + logclient.AddActionLog(self, logclient.ACT_CREATE, "No Disk Info Provided", userCred, false) + return nil, httperrors.NewBadRequestError("No Disk Info Provided") + } + host := self.GetHost() + if host == nil { + logclient.AddActionLog(self, logclient.ACT_CREATE, "No valid host", userCred, false) + return nil, httperrors.NewBadRequestError("No valid host") + } + for backend, size := range diskSizes { + storage := host.GetLeastUsedStorage(backend) + if storage == nil { + logclient.AddActionLog(self, logclient.ACT_CREATE, "No valid storage on current host", userCred, false) + return nil, httperrors.NewBadRequestError("No valid storage on current host") + } + if storage.GetCapacity() < size { + logclient.AddActionLog(self, logclient.ACT_CREATE, "Not eough storage space on current host", userCred, false) + return nil, httperrors.NewBadRequestError("Not eough storage space on current host") + } + } + pendingUsage := &SQuota{ + Storage: diskSize, + } + err := QuotaManager.CheckSetPendingQuota(ctx, userCred, self.ProjectId, pendingUsage) + if err != nil { + logclient.AddActionLog(self, logclient.ACT_CREATE, err.Error(), userCred, false) + return nil, httperrors.NewBadRequestError(err.Error()) + } + + lockman.LockObject(ctx, host) + defer lockman.ReleaseObject(ctx, host) + + err = self.CreateDisksOnHost(ctx, userCred, host, disksConf, pendingUsage) + if err != nil { + QuotaManager.CancelPendingUsage(ctx, userCred, self.ProjectId, nil, pendingUsage) + logclient.AddActionLog(self, logclient.ACT_CREATE, err.Error(), userCred, false) + return nil, httperrors.NewBadRequestError(err.Error()) + } + err = self.StartGuestCreateDiskTask(ctx, userCred, disksConf, "") + return nil, err +} + +func (self *SGuest) StartGuestCreateDiskTask(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "GuestCreateDiskTask", self, userCred, data, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + +func (self *SGuest) AllowPerformDetachdisk(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformDetachdisk(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + diskId, err := data.GetString("disk_id") + if err != nil { + return nil, err + } + keepDisk := jsonutils.QueryBoolean(data, "keep_disk", false) + iDisk, err := DiskManager.FetchByIdOrName(userCred, diskId) + if err != nil { + return nil, err + } + disk := iDisk.(*SDisk) + if disk != nil { + if self.isAttach2Disk(disk) { + if disk.DiskType == DISK_TYPE_SYS { + return nil, httperrors.NewUnsupportOperationError("Cannot detach sys disk") + } + detachDiskStatus, err := self.GetDriver().GetDetachDiskStatus() + if err != nil { + return nil, err + } + if keepDisk && !self.GetDriver().CanKeepDetachDisk() { + return nil, httperrors.NewInputParameterError("Cannot keep detached disk") + } + if utils.IsInStringArray(self.Status, detachDiskStatus) { + if disk.Status == DISK_INIT { + disk.SetStatus(userCred, DISK_DETACHING, "") + } + taskData := jsonutils.NewDict() + taskData.Add(jsonutils.NewString(disk.Id), "disk_id") + taskData.Add(jsonutils.NewBool(keepDisk), "keep_disk") + self.GetDriver().StartGuestDetachdiskTask(ctx, userCred, self, taskData, "") + return nil, nil + } else { + return nil, httperrors.NewInvalidStatusError("Server in %s not able to detach disk", self.Status) + } + } else { + return nil, httperrors.NewInvalidStatusError("Disk %s not attached", diskId) + } + } + return nil, httperrors.NewResourceNotFoundError("Disk %s not found", diskId) +} + +func (self *SGuest) AllowPerformDetachIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return userCred.IsSystemAdmin() +} + +func (self *SGuest) PerformDetachIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if self.Hypervisor != HYPERVISOR_KVM { + return nil, httperrors.NewNotAcceptableError("Not allow for hypervisor %s", self.Hypervisor) + } + if self.Status != VM_READY { + msg := "Only allowed to attach isolated device when guest is ready" + logclient.AddActionLog(self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false) + return nil, httperrors.NewInvalidStatusError(msg) + } + device, err := data.GetString("device") + if err != nil { + msg := "Missing isolated device" + logclient.AddActionLog(self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false) + return nil, httperrors.NewBadRequestError(msg) + } + iDev, err := IsolatedDeviceManager.FetchByIdOrName(userCred, device) + if err != nil { + msg := fmt.Sprintf("Isolated device %s not found", device) + logclient.AddActionLog(self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false) + return nil, httperrors.NewBadRequestError(msg) + } + dev := iDev.(*SIsolatedDevice) + host := self.GetHost() + lockman.LockObject(ctx, host) + defer lockman.ReleaseObject(ctx, host) + err = self.detachIsolateDevice(userCred, dev) + return nil, err +} + +func (self *SGuest) detachIsolateDevice(userCred mcclient.TokenCredential, dev *SIsolatedDevice) error { + if dev.GuestId != self.Id { + msg := "Isolated device is not attached to this guest" + logclient.AddActionLog(self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false) + return httperrors.NewBadRequestError(msg) + } + _, err := self.GetModelManager().TableSpec().Update(dev, func() error { + dev.GuestId = "" + return nil + }) + if err != nil { + return err + } + db.OpsLog.LogEvent(self, db.ACT_GUEST_DETACH_ISOLATED_DEVICE, dev.GetShortDesc(), userCred) + return nil +} + +func (self *SGuest) AllowPerformAttachIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return userCred.IsSystemAdmin() +} + +func (self *SGuest) PerformAttachIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if self.Hypervisor != HYPERVISOR_KVM { + return nil, httperrors.NewNotAcceptableError("Not allow for hypervisor %s", self.Hypervisor) + } + if self.Status != VM_READY { + msg := "Only allowed to attach isolated device when guest is ready" + logclient.AddActionLog(self, logclient.ACT_GUEST_ATTACH_ISOLATED_DEVICE, msg, userCred, false) + return nil, httperrors.NewInvalidStatusError(msg) + } + device, err := data.GetString("device") + if err != nil { + msg := "Missing isolated device" + logclient.AddActionLog(self, logclient.ACT_GUEST_ATTACH_ISOLATED_DEVICE, msg, userCred, false) + return nil, httperrors.NewBadRequestError(msg) + } + iDev, err := IsolatedDeviceManager.FetchByIdOrName(userCred, device) + if err != nil { + msg := fmt.Sprintf("Isolated device %s not found", device) + logclient.AddActionLog(self, logclient.ACT_GUEST_ATTACH_ISOLATED_DEVICE, msg, userCred, false) + return nil, httperrors.NewBadRequestError(msg) + } + dev := iDev.(*SIsolatedDevice) + host := self.GetHost() + lockman.LockObject(ctx, host) + defer lockman.ReleaseObject(ctx, host) + err = self.attachIsolatedDevice(userCred, dev) + var msg string + if err != nil { + msg = err.Error() + } + logclient.AddActionLog(self, logclient.ACT_GUEST_ATTACH_ISOLATED_DEVICE, msg, userCred, err == nil) + return nil, err +} + +func (self *SGuest) AllowPerformDetachnetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformDetachnetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if self.Status != VM_READY { + return nil, httperrors.NewInvalidStatusError("Cannot detach network in status %s", self.Status) + } + reserve := jsonutils.QueryBoolean(data, "reserve", false) + netId, err := data.GetString("net_id") + if err != nil { + return nil, httperrors.NewBadRequestError(err.Error()) + } + iNetwork, err := NetworkManager.FetchById(netId) + if err != nil { + return nil, httperrors.NewNotFoundError("Network %s not found", netId) + } + network := iNetwork.(*SNetwork) + err = self.detachNetwork(ctx, userCred, network, reserve, true) + return nil, err +} + +func (self *SGuest) AllowPerformAttachnetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformAttachnetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if self.Status == VM_READY { + // owner_cred = self.get_owner_user_cred() >.< + netDesc, err := data.Get("net_desc") + if err != nil { + return nil, httperrors.NewBadRequestError(err.Error()) + } + conf, err := parseNetworkInfo(userCred, netDesc) + if err != nil { + return nil, err + } + err = isValidNetworkInfo(userCred, conf) + if err != nil { + return nil, httperrors.NewBadRequestError(err.Error()) + } + var inicCnt, enicCnt, ibw, ebw int + if isExitNetworkInfo(conf) { + enicCnt = 1 + ebw = conf.BwLimit + } else { + inicCnt = 1 + ibw = conf.BwLimit + } + pendingUsage := &SQuota{ + Port: inicCnt, + Eport: enicCnt, + Bw: ibw, + Ebw: ebw, + } + projectId := self.GetOwnerProjectId() + err = QuotaManager.CheckSetPendingQuota(ctx, userCred, projectId, pendingUsage) + if err != nil { + return nil, httperrors.NewOutOfQuotaError(err.Error()) + } + host := self.GetHost() + err = self.attach2NetworkDesc(ctx, userCred, host, conf, pendingUsage) + if err != nil { + QuotaManager.CancelPendingUsage(ctx, userCred, projectId, nil, pendingUsage) + return nil, httperrors.NewBadRequestError(err.Error()) + } + host.ClearSchedDescCache() + err = self.StartGuestDeployTask(ctx, userCred, nil, "deploy", "") + return nil, err + } + return nil, httperrors.NewBadRequestError("Cannot attach network in status %s", self.Status) +} + +func (self *SGuest) AllowPerformChangeBandwidth(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) || userCred.IsSystemAdmin() +} + +func (self *SGuest) PerformChangeBandwidth(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if utils.IsInStringArray(self.Status, []string{VM_READY, VM_RUNNING}) { + guestnics := self.GetNetworks() + index, err := data.Int("index") + if err != nil || index > int64(len(guestnics)) { + logclient.AddActionLog(self, logclient.ACT_VM_CHANGE_BANDWIDTH, "Index Not fount or out of NIC index", userCred, false) + return nil, httperrors.NewBadRequestError("Index Not fount or out of NIC index") + } + bandwidth, err := data.Int("bandwidth") + if err != nil || bandwidth < 0 { + logclient.AddActionLog(self, logclient.ACT_VM_CHANGE_BANDWIDTH, "Bandwidth must non-negative", userCred, false) + return nil, httperrors.NewBadRequestError("Bandwidth must be non-negative") + } + guestnic := &guestnics[index] + if guestnic.BwLimit != int(bandwidth) { + GuestnetworkManager.TableSpec().Update(guestnic, func() error { + guestnic.BwLimit = int(bandwidth) + return nil + }) + err := self.StartSyncTask(ctx, userCred, false, "") + logclient.AddActionLog(self, logclient.ACT_VM_CHANGE_BANDWIDTH, err, userCred, err == nil) + return nil, err + } + return nil, nil + } + msg := fmt.Sprintf("Cannot change bandwidth in status %s", self.Status) + logclient.AddActionLog(self, logclient.ACT_VM_CHANGE_BANDWIDTH, msg, userCred, false) + return nil, httperrors.NewBadRequestError(msg) +} + +func (self *SGuest) AllowPerformChangeConfig(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) || self.IsAdmin(userCred) +} + +func (self *SGuest) PerformChangeConfig(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + changeStatus, err := self.GetDriver().GetChangeConfigStatus() + if err != nil { + return nil, httperrors.NewInputParameterError(err.Error()) + } + if !utils.IsInStringArray(self.Status, changeStatus) { + return nil, httperrors.NewInvalidStatusError("Cannot change config in %s", self.Status) + } + if !self.GetDriver().AllowReconfigGuest() { + return nil, httperrors.NewInvalidStatusError("Not allow to change config") + } + host := self.GetHost() + if host == nil { + return nil, httperrors.NewInvalidStatusError("No valid host") + } + var addCpu, addMem int + confs := jsonutils.NewDict() + vcpuCount, err := data.GetString("vcpu_count") + if err == nil { + nVcpu, err := strconv.ParseInt(vcpuCount, 10, 0) + if err != nil { + return nil, httperrors.NewBadRequestError("Params vcpu_count parse error") + } + err = confs.Add(jsonutils.NewInt(nVcpu), "vcpu_count") + if err != nil { + return nil, httperrors.NewBadRequestError("Params vcpu_count parse error") + } + addCpu = int(nVcpu - int64(self.VcpuCount)) + } + vmemSize, err := data.GetString("vmem_size") + if err == nil { + if !regutils.MatchSize(vmemSize) { + return nil, httperrors.NewBadRequestError("Memory size must be number[+unit], like 256M, 1G or 256") + } + nVmem, err := fileutils.GetSizeMb(vmemSize, 'M', 1024) + if err != nil { + httperrors.NewBadRequestError("Params vmem_size parse error") + } + err = confs.Add(jsonutils.NewInt(int64(nVmem)), "vmem_size") + if err != nil { + return nil, httperrors.NewBadRequestError("Params vmem_size parse error") + } + addMem = nVmem - self.VmemSize + } + disks := self.GetDisks() + var addDisk int + var diskIdx = 1 + var newDiskIdx = 0 + var diskSizes = make(map[string]int, 0) + var newDisks = jsonutils.NewDict() + var resizeDisks = jsonutils.NewArray() + for { + diskNum := fmt.Sprintf("disk.%d", diskIdx) + diskDesc, err := data.Get(diskNum) + if err != nil { + break + } + diskConf, err := parseDiskInfo(ctx, userCred, diskDesc) + if err != nil { + return nil, httperrors.NewBadRequestError("Parse disk info error: %s", err) + } + if len(diskConf.Backend) == 0 { + diskConf.Backend = self.getDefaultStorageType() + } + if diskConf.Size > 0 { + if diskIdx >= len(disks) { + newDisks.Add(jsonutils.Marshal(diskConf), fmt.Sprintf("disk.%d", newDiskIdx)) + newDiskIdx += 1 + addDisk += diskConf.Size + storage := host.GetLeastUsedStorage(diskConf.Backend) + if storage == nil { + return nil, httperrors.NewResourceNotReadyError("host not connect storage %s", diskConf.Backend) + } + _, ok := diskSizes[storage.Id] + if !ok { + diskSizes[storage.Id] = 0 + } + diskSizes[storage.Id] = diskSizes[storage.Id] + diskConf.Size + } else { + disk := disks[diskIdx].GetDisk() + oldSize := disk.DiskSize + if diskConf.Size < oldSize { + return nil, httperrors.NewInputParameterError("Cannot reduce disk size") + } else if diskConf.Size > oldSize { + arr := jsonutils.NewArray(jsonutils.NewString(disks[diskIdx].DiskId), jsonutils.NewInt(int64(diskConf.Size))) + resizeDisks.Add(arr) + addDisk += diskConf.Size - oldSize + storage := disks[diskIdx].GetDisk().GetStorage() + _, ok := diskSizes[storage.Id] + if !ok { + diskSizes[storage.Id] = 0 + } + diskSizes[storage.Id] = diskSizes[storage.Id] + diskConf.Size - oldSize + } + } + } + diskIdx += 1 + } + + provider, e := self.GetHost().GetDriver() + /*if e != nil { + log.Errorf("Get Provider Error: %s", e) + return nil, httperrors.NewInsufficientResourceError("Provider Not Found") + }*/ + + if e != nil || !provider.IsPublicCloud() { + for storageId, needSize := range diskSizes { + iStorage, err := StorageManager.FetchById(storageId) + if err != nil { + return nil, httperrors.NewBadRequestError("Fetch storage error: %s", err) + } + storage := iStorage.(*SStorage) + if storage.GetFreeCapacity() < needSize { + return nil, httperrors.NewInsufficientResourceError("Not enough free space") + } + } + } else { + log.Debugf("Skip storage free capacity validating for public cloud: %s", provider.GetName()) + } + + if newDisks.Length() > 0 { + confs.Add(newDisks, "create") + } + if resizeDisks.Length() > 0 { + confs.Add(resizeDisks, "resize") + } + if jsonutils.QueryBoolean(data, "auto_start", false) { + confs.Add(jsonutils.NewBool(true), "auto_start") + } + + log.Debugf("%s", confs.String()) + + pendingUsage := &SQuota{} + if addCpu > 0 { + pendingUsage.Cpu = addCpu + } + if addMem > 0 { + pendingUsage.Memory = addMem + } + if addDisk > 0 { + pendingUsage.Storage = addDisk + } + if !pendingUsage.IsEmpty() { + err := QuotaManager.CheckSetPendingQuota(ctx, userCred, userCred.GetProjectId(), pendingUsage) + if err != nil { + return nil, httperrors.NewBadRequestError("Check set pending quota error %s", err) + } + } + if newDisks.Length() > 0 { + err := self.CreateDisksOnHost(ctx, userCred, host, newDisks, pendingUsage) + if err != nil { + QuotaManager.CancelPendingUsage(ctx, userCred, self.ProjectId, nil, pendingUsage) + return nil, httperrors.NewBadRequestError("Create disk on host error: %s", err) + } + } + self.StartChangeConfigTask(ctx, userCred, confs, "", pendingUsage) + return nil, nil +} + +func (self *SGuest) StartChangeConfigTask(ctx context.Context, userCred mcclient.TokenCredential, + data *jsonutils.JSONDict, parentTaskId string, pendingUsage quotas.IQuota) error { + self.SetStatus(userCred, VM_CHANGE_FLAVOR, "") + task, err := taskman.TaskManager.NewTask(ctx, "GuestChangeConfigTask", self, userCred, data, parentTaskId, "", pendingUsage) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + +func (model *SGuest) AllowPerformCancelDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return userCred.IsSystemAdmin() +} + +func (self *SGuest) PerformCancelDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if self.PendingDeleted { + err := self.DoCancelPendingDelete(ctx, userCred) + return nil, err + } + return nil, nil +} + +func (self *SGuest) DoCancelPendingDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + for _, guestdisk := range self.GetDisks() { + disk := guestdisk.GetDisk() + disk.DoCancelPendingDelete(ctx, userCred) + } + return self.SVirtualResourceBase.DoCancelPendingDelete(ctx, userCred) +} + +func (self *SGuest) StartUndeployGuestTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, targetHostId string) error { + data := jsonutils.NewDict() + if len(targetHostId) > 0 { + data.Add(jsonutils.NewString(targetHostId), "target_host_id") + } + task, err := taskman.TaskManager.NewTask(ctx, "GuestUndeployTask", self, userCred, data, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + +func (self *SGuest) AllowPerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + self.SetStatus(userCred, VM_SYNCING_STATUS, "perform_syncstatus") + err := self.StartSyncstatus(ctx, userCred, "") + return nil, err +} + +func (self *SGuest) StartSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + return self.GetDriver().StartGuestSyncstatusTask(self, ctx, userCred, parentTaskId) +} + +func (self *SGuest) isNotRunningStatus(status string) bool { + if status == VM_READY || status == VM_SUSPEND { + return true + } + return false +} + +func (self *SGuest) PerformStatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + preStatus := self.Status + _, err := self.SVirtualResourceBase.PerformStatus(ctx, userCred, query, data) + if err != nil { + return nil, err + } + if preStatus != self.Status && !self.isNotRunningStatus(preStatus) && self.isNotRunningStatus(self.Status) { + db.OpsLog.LogEvent(self, db.ACT_STOP, "", userCred) + if self.Status == VM_READY && !self.DisableDelete.Bool() && self.ShutdownBehavior == SHUTDOWN_TERMINATE { + err = self.StartAutoDeleteGuestTask(ctx, userCred, "") + return nil, err + } + } + return nil, nil +} + +func (self *SGuest) StartAutoDeleteGuestTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + db.OpsLog.LogEvent(self, db.ACT_DELETE, "auto-delete after stop", userCred) + return self.StartDeleteGuestTask(ctx, userCred, parentTaskId, false, false) +} + +func (self *SGuest) StartDeleteGuestTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, isPurge bool, overridePendingDelete bool) error { + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(self.Status), "guest_status") + if isPurge { + params.Add(jsonutils.JSONTrue, "purge") + } + if overridePendingDelete { + params.Add(jsonutils.JSONTrue, "override_pending_delete") + } + self.SetStatus(userCred, VM_START_DELETE, "") + return self.GetDriver().StartDeleteGuestTask(ctx, userCred, self, params, parentTaskId) +} + +func (self *SGuest) AllowPerformSaveImage(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformSaveImage(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if !utils.IsInStringArray(self.Status, []string{VM_READY}) { + return nil, httperrors.NewInputParameterError("Cannot save image in status %s", self.Status) + } else if !data.Contains("name") { + return nil, httperrors.NewInputParameterError("Image name is required") + } else if disks := self.CategorizeDisks(); disks.Root == nil { + return nil, httperrors.NewInputParameterError("No root image") + } else { + kwargs := data.(*jsonutils.JSONDict) + restart := self.Status == VM_RUNNING + properties := jsonutils.NewDict() + if notes, err := data.GetString("notes"); err != nil && len(notes) > 0 { + properties.Add(jsonutils.NewString(notes), "notes") + } + properties.Add(jsonutils.NewString(self.OsType), "os_type") + kwargs.Add(properties, "properties") + kwargs.Add(jsonutils.NewBool(restart), "restart") + + lockman.LockObject(ctx, disks.Root) + defer lockman.ReleaseObject(ctx, disks.Root) + + if imageId, err := disks.Root.PrepareSaveImage(ctx, userCred, kwargs); err != nil { + return nil, err + } else { + kwargs.Add(jsonutils.NewString(imageId), "image_id") + } + return nil, self.StartGuestSaveImage(ctx, userCred, kwargs, "") + } +} + +func (self *SGuest) StartGuestSaveImage(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, parentTaskId string) error { + return self.GetDriver().StartGuestSaveImage(ctx, userCred, self, data, parentTaskId) +} + +func (self *SGuest) AllowPerformSync(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformSync(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if err := self.StartSyncTask(ctx, userCred, false, ""); err != nil { + return nil, err + } + return nil, nil +} + +func (self *SGuest) GetQemuVersion(userCred mcclient.TokenCredential) string { + return self.GetMetadata("__qemu_version", userCred) +} + +// if qemuVer >= compareVer return true +func (self *SGuest) CheckQemuVersion(qemuVer, compareVer string) bool { + if len(qemuVer) == 0 { + return false + } + + compareVersion := strings.Split(compareVer, ".") + guestVersion := strings.Split(qemuVer, ".") + var i = 0 + for ; i < len(guestVersion); i++ { + if i >= len(compareVersion) { + return true + } + v, _ := strconv.ParseInt(guestVersion[i], 10, 0) + compareV, _ := strconv.ParseInt(compareVersion[i], 10, 0) + if v < compareV { + return false + } else if v > compareV { + return true + } + } + if i < len(compareVersion)-1 { + return false + } + return true +} + +func (self *SGuest) AllowPerformMigrate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformMigrate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if self.GetHypervisor() != HYPERVISOR_KVM { + return nil, httperrors.NewNotAcceptableError("Not allow for hypervisor %s", self.GetHypervisor()) + } + isRescueMode := jsonutils.QueryBoolean(data, "rescue_mode", false) + if !isRescueMode && self.Status != VM_READY { + return nil, httperrors.NewServerStatusError("Cannot normal migrate guest in status %s, try rescue mode or server-live-migrate?", self.Status) + } + if isRescueMode { + guestDisks := self.GetDisks() + for _, guestDisk := range guestDisks { + if utils.IsInStringArray( + guestDisk.GetDisk().GetStorage().StorageType, STORAGE_LOCAL_TYPES) { + return nil, httperrors.NewBadRequestError("Rescue mode requires all disk store in shared storages") + } + } + } + devices := self.GetIsolatedDevices() + if devices != nil && len(devices) > 0 { + return nil, httperrors.NewBadRequestError("Cannot migrate with isolated devices") + } + var preferHostId string + preferHost, _ := data.GetString("prefer_host") + if len(preferHost) > 0 { + if !userCred.IsSystemAdmin() { + return nil, httperrors.NewBadRequestError("Only system admin can assign host") + } + iHost, _ := HostManager.FetchByIdOrName(userCred, preferHost) + if iHost == nil { + return nil, httperrors.NewBadRequestError("Host %s not found", preferHost) + } + host := iHost.(*SHost) + preferHostId = host.Id + } + err := self.StartMigrateTask(ctx, userCred, isRescueMode, self.Status, preferHostId, "") + return nil, err +} + +func (self *SGuest) StartMigrateTask(ctx context.Context, userCred mcclient.TokenCredential, isRescueMode bool, guestStatus, preferHostId, parentTaskId string) error { + data := jsonutils.NewDict() + if isRescueMode { + data.Set("is_rescue_mode", jsonutils.JSONTrue) + } + if len(preferHostId) > 0 { + data.Set("prefer_host_id", jsonutils.NewString(preferHostId)) + } + data.Set("guest_status", jsonutils.NewString(guestStatus)) + if task, err := taskman.TaskManager.NewTask(ctx, "GuestMigrateTask", self, userCred, data, parentTaskId, "", nil); err != nil { + log.Errorf(err.Error()) + return err + } else { + task.ScheduleRun(nil) + } + return nil +} + +func (self *SGuest) AllowPerformLiveMigrate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformLiveMigrate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if self.GetHypervisor() != HYPERVISOR_KVM { + return nil, httperrors.NewNotAcceptableError("Not allow for hypervisor %s", self.GetHypervisor()) + } + imageId := self.GetDisks()[0].GetDisk().TemplateId + image, err := CachedimageManager.GetImageById(ctx, userCred, imageId, false) + if err != nil { + return nil, err + } + if image.DiskFormat != "qcow2" { + return nil, httperrors.NewBadRequestError("Live migrate only support image fromat qocw2") + } + if utils.IsInStringArray(self.Status, []string{VM_RUNNING, VM_SUSPEND}) { + cdrom := self.getCdrom() + if cdrom != nil && len(cdrom.ImageId) > 0 { + return nil, httperrors.NewBadRequestError("Cannot migrate with cdrom") + } + devices := self.GetIsolatedDevices() + if devices != nil && len(devices) > 0 { + return nil, httperrors.NewBadRequestError("Cannot migrate with isolated devices") + } + if !self.CheckQemuVersion(self.GetQemuVersion(userCred), "1.1.2") { + return nil, httperrors.NewBadRequestError("Cannot do live migrate, too low qemu version") + } + var preferHostId string + preferHost, _ := data.GetString("prefer_host") + if len(preferHost) > 0 { + if !userCred.IsSystemAdmin() { + return nil, httperrors.NewBadRequestError("Only system admin can assign host") + } + iHost, _ := HostManager.FetchByIdOrName(userCred, preferHost) + if iHost == nil { + return nil, httperrors.NewBadRequestError("Host %s not found", preferHost) + } + host := iHost.(*SHost) + preferHostId = host.Id + } + err := self.StartGuestLiveMigrateTask(ctx, userCred, self.Status, preferHostId, "") + return nil, err + } + return nil, httperrors.NewBadRequestError("Cannot live migrate in status %s", self.Status) +} + +func (self *SGuest) StartGuestLiveMigrateTask(ctx context.Context, userCred mcclient.TokenCredential, guestStatus, preferHostId, parentTaskId string) error { + self.SetStatus(userCred, VM_START_MIGRATE, "") + data := jsonutils.NewDict() + if len(preferHostId) > 0 { + data.Set("prefer_host_id", jsonutils.NewString(preferHostId)) + } + data.Set("guest_status", jsonutils.NewString(guestStatus)) + if task, err := taskman.TaskManager.NewTask(ctx, "GuestLiveMigrateTask", self, userCred, data, parentTaskId, "", nil); err != nil { + log.Errorf(err.Error()) + return err + } else { + task.ScheduleRun(nil) + } + return nil +} + +func (self *SGuest) AllowPerformDeploy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformDeploy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + kwargs, ok := data.(*jsonutils.JSONDict) + if !ok { + return nil, fmt.Errorf("Parse query body error") + } + + // 变更密码/密钥时需要Restart才能生效。更新普通字段不需要Restart + doRestart := false + if kwargs.Contains("__delete_keypair__") || kwargs.Contains("keypair") { + doRestart = true + var kpId string + + if kwargs.Contains("keypair") { + keypair, _ := kwargs.GetString("keypair") + iKp, err := KeypairManager.FetchByIdOrName(userCred, keypair) + if err != nil { + return nil, err + } + if iKp == nil { + return nil, fmt.Errorf("Fetch keypair error") + } + kp := iKp.(*SKeypair) + kpId = kp.Id + } + + if self.KeypairId != kpId { + okey := self.getKeypair() + if okey != nil { + kwargs.Set("delete_public_key", jsonutils.NewString(okey.PublicKey)) + } + + self.GetModelManager().TableSpec().Update(self, func() error { + self.KeypairId = kpId + return nil + }) + + kwargs.Set("reset_password", jsonutils.JSONTrue) + } + } + + deployStatus, err := self.GetDriver().GetDeployStatus() + if err != nil { + return nil, httperrors.NewInputParameterError(err.Error()) + } + + if utils.IsInStringArray(self.Status, deployStatus) { + if doRestart && self.Status == VM_RUNNING { + kwargs.Set("restart", jsonutils.JSONTrue) + } + err := self.StartGuestDeployTask(ctx, userCred, kwargs, "deploy", "") + if err != nil { + return nil, err + } + return nil, nil + } + + return nil, httperrors.NewServerStatusError("Cannot deploy in status %s", self.Status) +} + +func (self *SGuest) StartGuestDeployTask(ctx context.Context, userCred mcclient.TokenCredential, kwargs *jsonutils.JSONDict, action string, parentTaskId string) error { + self.SetStatus(userCred, VM_START_DEPLOY, "") + if kwargs == nil { + kwargs = jsonutils.NewDict() + } + kwargs.Add(jsonutils.NewString(action), "deploy_action") + task, err := taskman.TaskManager.NewTask(ctx, "GuestDeployTask", self, userCred, kwargs, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + +func (self *SGuest) AllowPerformAttachdisk(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) ValidateAttachDisk(ctx context.Context, disk *SDisk) error { + storage := disk.GetStorage() + if provider := storage.GetCloudprovider(); provider != nil { + host := self.GetHost() + if provider.Id != host.ManagerId { + return httperrors.NewInputParameterError("Disk %s and guest not belong to the same account", disk.Name) + } + if storage.ZoneId != host.ZoneId { + return httperrors.NewInputParameterError("Disk %s and guest not belong to the same zone", disk.Name) + } + } + + if disk.isAttached() { + return httperrors.NewInputParameterError("Disk %s has been attached", disk.Name) + } + if len(disk.GetPathAtHost(self.GetHost())) == 0 { + return httperrors.NewInputParameterError("Disk %s not belong the guest's host", disk.Name) + } + if disk.Status != DISK_READY { + return httperrors.NewInputParameterError("Disk in %s not able to attach", disk.Status) + } + guestStatus, err := self.GetDriver().GetAttachDiskStatus() + if err != nil { + return err + } + if !utils.IsInStringArray(self.Status, guestStatus) { + return httperrors.NewInputParameterError("Guest %s not support attach disk in status %s", self.Name, self.Status) + } + return nil +} + +func (self *SGuest) PerformAttachdisk(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + diskId, err := data.GetString("disk_id") + if err != nil { + return nil, err + } + disk, err := DiskManager.FetchByIdOrName(userCred, diskId) + if err != nil { + return nil, err + } + if disk == nil { + return nil, httperrors.NewResourceNotFoundError("Disk %s not found", diskId) + } + if err := self.ValidateAttachDisk(ctx, disk.(*SDisk)); err != nil { + return nil, err + } + + taskData := data.(*jsonutils.JSONDict) + taskData.Set("disk_id", jsonutils.NewString(disk.GetId())) + + if err := self.GetDriver().StartGuestAttachDiskTask(ctx, userCred, self, taskData, ""); err != nil { + return nil, err + } + return nil, nil +} + +func (self *SGuest) StartSyncTask(ctx context.Context, userCred mcclient.TokenCredential, fw_only bool, parentTaskId string) error { + if !utils.IsInStringArray(self.Status, []string{VM_READY, VM_RUNNING}) { + return httperrors.NewResourceBusyError("Cannot sync in status %s", self.Status) + } + data := jsonutils.NewDict() + if fw_only { + data.Add(jsonutils.JSONTrue, "fw_only") + } else if err := self.SetStatus(userCred, VM_SYNC_CONFIG, ""); err != nil { + log.Errorf(err.Error()) + return err + } + if task, err := taskman.TaskManager.NewTask(ctx, "GuestSyncConfTask", self, userCred, data, parentTaskId, "", nil); err != nil { + log.Errorf(err.Error()) + return err + } else { + task.ScheduleRun(nil) + } + return nil +} + +func (self *SGuest) AllowPerformSuspend(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformSuspend(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if self.Status == VM_RUNNING { + err := self.StartSuspendTask(ctx, userCred, "") + return nil, err + } + return nil, httperrors.NewInvalidStatusError("Cannot suspend VM in status %s", self.Status) +} + +func (self *SGuest) StartSuspendTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + err := self.SetStatus(userCred, VM_SUSPEND, "do suspend") + if err != nil { + return err + } + return self.GetDriver().StartSuspendTask(ctx, userCred, self, nil, parentTaskId) +} + +func (self *SGuest) AllowPerformStart(ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformStart(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, + data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if utils.IsInStringArray(self.Status, []string{VM_READY, VM_START_FAILED, VM_SAVE_DISK_FAILED, VM_SUSPEND}) { + if self.isAllDisksReady() { + var kwargs *jsonutils.JSONDict + if data != nil { + kwargs = data.(*jsonutils.JSONDict) + } + err := self.GetDriver().PerformStart(ctx, userCred, self, kwargs) + return nil, err + } else { + return nil, httperrors.NewInvalidStatusError("Some disk not ready") + } + } else { + return nil, httperrors.NewInvalidStatusError("Cannot do start server in status %s", self.Status) + } +} + +func (self *SGuest) SwitchToBackup() error { + _, err := self.GetModelManager().TableSpec().Update(self, func() error { + self.HostId, self.BackupHostId = self.BackupHostId, self.HostId + return nil + }) + return err +} + +func (self *SGuest) AllowPerformSwitchToBackup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsAdmin(userCred) +} + +func (self *SGuest) PerformSwitchToBackup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if self.Status == VM_BLOCK_STREAM { + return nil, httperrors.NewBadRequestError("Cannot swith to backup when guest in status %s", self.Status) + } + if len(self.BackupHostId) == 0 { + return nil, httperrors.NewBadRequestError("Guest no backup host") + } + if task, err := taskman.TaskManager.NewTask(ctx, "GuestSwitchToBackupTask", self, userCred, nil, "", "", nil); err != nil { + log.Errorf(err.Error()) + return nil, err + } else { + task.ScheduleRun(nil) + } + return nil, nil +} + +func (manager *SGuestManager) AllowPerformDirtyServerStart(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return userCred.IsSystemAdmin() || userCred.IsAdmin() +} + +func (manager *SGuestManager) PerformDirtyServerStart(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + guestId, err := data.GetString("guest_id") + if err != nil { + return nil, httperrors.NewBadRequestError("Missing guest_id") + } + guest := manager.FetchGuestById(guestId) + if guest == nil { + return nil, httperrors.NewNotFoundError("Guest %s not found", guestId) + } + hostId, _ := data.GetString("host_id") + if len(hostId) == 0 { + return nil, httperrors.NewBadGatewayError("Missing host_id or host id is nil?") + } + + if guest.HostId == hostId { + // master guest + err := guest.StartGueststartTask(ctx, userCred, nil, "") + return nil, err + } else if guest.BackupHostId == hostId { + // slave guest + err := guest.GuestStartAndSyncToBackup(ctx, userCred, nil, "") + return nil, err + } else if guest.BackupHostId != hostId { + // abandon guest + err := guest.StartUndeployGuestTask(ctx, userCred, "", hostId) + return nil, err + } + return nil, nil +} + +func (guest *SGuest) GuestStartAndSyncToBackup(ctx context.Context, userCred mcclient.TokenCredential, + data *jsonutils.JSONDict, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "GuestStartAndSyncToBackupTask", guest, userCred, data, parentTaskId, "", nil) + if err != nil { + log.Errorf(err.Error()) + return err + } else { + task.ScheduleRun(nil) + } + return nil +} + +func (self *SGuest) AllowPerformCreateBackup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsAdmin(userCred) +} + +func (self *SGuest) PerformCreateBackup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if len(self.BackupHostId) > 0 { + return nil, httperrors.NewBadRequestError("Already have create backup server") + } + if self.getDefaultStorageType() != STORAGE_LOCAL { + return nil, httperrors.NewBadRequestError("Cannot create backup with shared storage") + } + if self.Hypervisor != HYPERVISOR_KVM { + return nil, httperrors.NewBadRequestError("Backup only support hypervisor kvm") + } + if len(self.GetIsolatedDevices()) > 0 { + return nil, httperrors.NewBadRequestError("Cannot create backup with isolated degices") + } + if self.GuestDisksHasSnapshot() { + return nil, httperrors.NewBadRequestError("Cannot create backup with snapshot") + } + + req := self.getGuestBackupResourceRequirements(ctx, userCred) + err := QuotaManager.CheckSetPendingQuota(ctx, userCred, self.GetOwnerProjectId(), &req) + if err != nil { + return nil, err + } + + params := data.(*jsonutils.JSONDict) + params.Set("guest_status", jsonutils.NewString(self.Status)) + task, err := taskman.TaskManager.NewTask(ctx, "GuestCreateBackupTask", self, userCred, params, "", "", &req) + if err != nil { + QuotaManager.CancelPendingUsage(ctx, userCred, self.ProjectId, nil, &req) + log.Errorf(err.Error()) + return nil, err + } else { + task.ScheduleRun(nil) + } + return nil, nil +} + +func (self *SGuest) CreateBackupDisks(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + if task, err := taskman.TaskManager.NewTask(ctx, "GuestCreateBackupDisksTask", self, userCred, nil, parentTaskId, "", nil); err != nil { + log.Errorf(err.Error()) + return err + } else { + task.ScheduleRun(nil) + } + return nil +} + +func (self *SGuest) StartCreateBackup(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, kwargs *jsonutils.JSONDict) error { + if kwargs == nil { + kwargs = jsonutils.NewDict() + } + kwargs.Add(jsonutils.NewString("create"), "deploy_action") + if task, err := taskman.TaskManager.NewTask(ctx, "GuestDeployBackupTask", self, userCred, kwargs, parentTaskId, "", nil); err != nil { + log.Errorf(err.Error()) + return err + } else { + task.ScheduleRun(nil) + } + return nil +} diff --git a/pkg/compute/models/guestdrivers.go b/pkg/compute/models/guestdrivers.go index d8045e6511..08daa21d7d 100644 --- a/pkg/compute/models/guestdrivers.go +++ b/pkg/compute/models/guestdrivers.go @@ -67,7 +67,7 @@ type IGuestDriver interface { StartGuestSaveImage(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest, params *jsonutils.JSONDict, parentTaskId string) error - RequestStopGuestForDelete(ctx context.Context, guest *SGuest, task taskman.ITask) error + RequestStopGuestForDelete(ctx context.Context, guest *SGuest, host *SHost, task taskman.ITask) error RequestDetachDisksFromGuestForDelete(ctx context.Context, guest *SGuest, task taskman.ITask) error @@ -110,6 +110,7 @@ type IGuestDriver interface { RequestDiskSnapshot(ctx context.Context, guest *SGuest, task taskman.ITask, snapshotId, diskId string) error RequestDeleteSnapshot(ctx context.Context, guest *SGuest, task taskman.ITask, params *jsonutils.JSONDict) error RequestReloadDiskSnapshot(ctx context.Context, guest *SGuest, task taskman.ITask, params *jsonutils.JSONDict) error + RequestSyncToBackup(ctx context.Context, guest *SGuest, task taskman.ITask) error } var guestDrivers map[string]IGuestDriver diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index 0212771866..9094577216 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -6,7 +6,6 @@ import ( "database/sql" "encoding/base64" "fmt" - "net/http" "strconv" "strings" "time" @@ -28,19 +27,14 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" - "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" - "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/options" + "yunion.io/x/onecloud/pkg/compute/sshkeys" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/auth" - "yunion.io/x/onecloud/pkg/util/httputils" - "yunion.io/x/onecloud/pkg/util/logclient" "yunion.io/x/onecloud/pkg/util/seclib2" - - "yunion.io/x/onecloud/pkg/compute/options" - "yunion.io/x/onecloud/pkg/compute/sshkeys" ) const ( @@ -66,6 +60,12 @@ const ( VM_STOPPING = "stopping" VM_STOP_FAILED = "stop_fail" // # = running + VM_BACKUP_STARTING = "backup_starting" + VM_BACKUP_CREATING = "backup_creating" + VM_BACKUP_CREATE_FAILED = "backup_create_fail" + VM_DEPLOYING_BACKUP = "deploying_backup" + VM_DEPLOYING_BACKUP_FAILED = "deploging_backup_fail" + VM_ATTACH_DISK_FAILED = "attach_disk_fail" VM_DETACH_DISK_FAILED = "detach_disk_fail" @@ -92,7 +92,7 @@ const ( VM_START_SNAPSHOT = "snapshot_start" VM_SNAPSHOT = "snapshot" VM_SNAPSHOT_DELETE = "snapshot_delete" - VM_SNAPSHOT_STREAM = "block_stream" + VM_BLOCK_STREAM = "block_stream" VM_SNAPSHOT_SUCC = "snapshot_succ" VM_SNAPSHOT_FAILED = "snapshot_failed" @@ -134,7 +134,7 @@ const ( HYPERVISOR_DEFAULT = HYPERVISOR_ALIYUN ) -var VM_RUNNING_STATUS = []string{VM_START_START, VM_STARTING, VM_RUNNING, VM_SNAPSHOT_STREAM} +var VM_RUNNING_STATUS = []string{VM_START_START, VM_STARTING, VM_RUNNING, VM_BLOCK_STREAM} var VM_CREATING_STATUS = []string{VM_CREATE_NETWORK, VM_CREATE_DISK, VM_START_DEPLOY, VM_DEPLOYING} var HYPERVISORS = []string{HYPERVISOR_KVM, HYPERVISOR_BAREMETAL, HYPERVISOR_ESXI, HYPERVISOR_CONTAINER, HYPERVISOR_ALIYUN, HYPERVISOR_AZURE, HYPERVISOR_AWS, HYPERVISOR_QCLOUD} @@ -198,7 +198,8 @@ type SGuest struct { KeypairId string `width:"36" charset:"ascii" nullable:"true" list:"user" create:"optional"` // Column(VARCHAR(36, charset='ascii'), nullable=True) - HostId string `width:"36" charset:"ascii" nullable:"true" list:"admin" get:"admin"` // Column(VARCHAR(36, charset='ascii'), nullable=True) + HostId string `width:"36" charset:"ascii" nullable:"true" list:"admin" get:"admin"` // Column(VARCHAR(36, charset='ascii'), nullable=True) + BackupHostId string `width:"36" charset:"ascii" nullable:"true" list:"admin" get:"admin"` Vga string `width:"36" charset:"ascii" nullable:"true" list:"user" update:"user" create:"optional"` // Column(VARCHAR(36, charset='ascii'), nullable=True) Vdi string `width:"36" charset:"ascii" nullable:"true" list:"user" update:"user" create:"optional"` // Column(VARCHAR(36, charset='ascii'), nullable=True) @@ -248,7 +249,12 @@ func (manager *SGuestManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQ if host == nil { return nil, httperrors.NewResourceNotFoundError("host %s not found", hostFilter) } - q = q.Equals("host_id", host.GetId()) + if jsonutils.QueryBoolean(queryDict, "get_backup_guests_on_host", false) { + q.Filter(sqlchemy.OR(sqlchemy.Equals(q.Field("host_id"), host.GetId()), + sqlchemy.Equals(q.Field("backup_host_id"), host.GetId()))) + } else { + q = q.Equals("host_id", host.GetId()) + } } secgrpFilter, _ := queryDict.GetString("secgroup") @@ -514,6 +520,15 @@ func (guest *SGuest) SetHostId(hostId string) error { return err } +func (guest *SGuest) SetHostIdWithBackup(master, slave string) error { + _, err := guest.GetModelManager().TableSpec().Update(guest, func() error { + guest.HostId = master + guest.BackupHostId = slave + return nil + }) + return err +} + func (guest *SGuest) ValidateResizeDisk(disk *SDisk, storage *SStorage) error { return guest.GetDriver().ValidateResizeDisk(guest, disk, storage) } @@ -696,6 +711,9 @@ func (manager *SGuestManager) ValidateCreateData(ctx context.Context, userCred m } for idx := 0; data.Contains(fmt.Sprintf("isolated_device.%d", idx)); idx += 1 { + if jsonutils.QueryBoolean(data, "backup", false) { + return nil, httperrors.NewBadRequestError("Cannot create backup with isolated device") + } devJson, err := data.Get(fmt.Sprintf("isolated_device.%d", idx)) if err != nil { return nil, httperrors.NewInputParameterError("invalid isolated device description %s", err) @@ -780,7 +798,8 @@ func (manager *SGuestManager) ValidateCreateData(ctx context.Context, userCred m data.Add(jsonutils.NewInt(int64(vcpuCount)), "vcpu_count") if !jsonutils.QueryBoolean(data, "is_system", false) { - err = manager.checkCreateQuota(ctx, userCred, ownerProjId, data) + err = manager.checkCreateQuota(ctx, userCred, ownerProjId, data, + jsonutils.QueryBoolean(data, "backup", false)) if err != nil { return nil, err } @@ -790,8 +809,8 @@ func (manager *SGuestManager) ValidateCreateData(ctx context.Context, userCred m return data, nil } -func (manager *SGuestManager) checkCreateQuota(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, data *jsonutils.JSONDict) error { - req := getGuestResourceRequirements(ctx, userCred, data, 1) +func (manager *SGuestManager) checkCreateQuota(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, data *jsonutils.JSONDict, hasBackup bool) error { + req := getGuestResourceRequirements(ctx, userCred, data, 1, hasBackup) err := QuotaManager.CheckSetPendingQuota(ctx, userCred, ownerProjId, &req) if err != nil { return httperrors.NewOutOfQuotaError(err.Error()) @@ -816,7 +835,7 @@ func (self *SGuest) checkUpdateQuota(ctx context.Context, userCred mcclient.Toke return err } -func getGuestResourceRequirements(ctx context.Context, userCred mcclient.TokenCredential, data jsonutils.JSONObject, count int) SQuota { +func getGuestResourceRequirements(ctx context.Context, userCred mcclient.TokenCredential, data jsonutils.JSONObject, count int, hasBackup bool) SQuota { vcpuCount, _ := data.Int("vcpu_count") if vcpuCount == 0 { vcpuCount = 1 @@ -852,6 +871,11 @@ func getGuestResourceRequirements(ctx context.Context, userCred mcclient.TokenCr iBw += netConfig.BwLimit } } + if hasBackup { + vcpuCount = vcpuCount * 2 + vmemSize = vmemSize * 2 + diskSize = diskSize * 2 + } return SQuota{ Cpu: int(vcpuCount) * count, Memory: int(vmemSize) * count, @@ -864,6 +888,15 @@ func getGuestResourceRequirements(ctx context.Context, userCred mcclient.TokenCr } } +func (guest *SGuest) getGuestBackupResourceRequirements(ctx context.Context, userCred mcclient.TokenCredential) SQuota { + guestDisksSize := guest.getDiskSize() + return SQuota{ + Cpu: int(guest.VcpuCount), + Memory: guest.VmemSize, + Storage: guestDisksSize, + } +} + func (guest *SGuest) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data jsonutils.JSONObject) { guest.SVirtualResourceBase.PostCreate(ctx, userCred, ownerProjId, query, data) tags := []string{"cpu_bound", "io_bound", "io_hardlimit"} @@ -893,7 +926,8 @@ func (guest *SGuest) setApptags(ctx context.Context, appTags []string, userCred } func (manager *SGuestManager) OnCreateComplete(ctx context.Context, items []db.IModel, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) { - pendingUsage := getGuestResourceRequirements(ctx, userCred, data, len(items)) + pendingUsage := getGuestResourceRequirements(ctx, userCred, data, len(items), + jsonutils.QueryBoolean(data, "backup", false)) RunBatchCreateTask(ctx, items, userCred, data, pendingUsage, "GuestBatchCreateTask") } @@ -1765,352 +1799,6 @@ func (self *SGuest) attach2Disk(disk *SDisk, userCred mcclient.TokenCredential, return err } -func (self *SGuest) AllowPerformSaveImage(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) PerformSaveImage(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if !utils.IsInStringArray(self.Status, []string{VM_READY}) { - return nil, httperrors.NewInputParameterError("Cannot save image in status %s", self.Status) - } else if !data.Contains("name") { - return nil, httperrors.NewInputParameterError("Image name is required") - } else if disks := self.CategorizeDisks(); disks.Root == nil { - return nil, httperrors.NewInputParameterError("No root image") - } else { - kwargs := data.(*jsonutils.JSONDict) - restart := self.Status == VM_RUNNING - properties := jsonutils.NewDict() - if notes, err := data.GetString("notes"); err != nil && len(notes) > 0 { - properties.Add(jsonutils.NewString(notes), "notes") - } - properties.Add(jsonutils.NewString(self.OsType), "os_type") - kwargs.Add(properties, "properties") - kwargs.Add(jsonutils.NewBool(restart), "restart") - - lockman.LockObject(ctx, disks.Root) - defer lockman.ReleaseObject(ctx, disks.Root) - - if imageId, err := disks.Root.PrepareSaveImage(ctx, userCred, kwargs); err != nil { - return nil, err - } else { - kwargs.Add(jsonutils.NewString(imageId), "image_id") - } - return nil, self.StartGuestSaveImage(ctx, userCred, kwargs, "") - } -} - -func (self *SGuest) StartGuestSaveImage(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, parentTaskId string) error { - return self.GetDriver().StartGuestSaveImage(ctx, userCred, self, data, parentTaskId) -} - -func (self *SGuest) AllowPerformSync(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) PerformSync(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if err := self.StartSyncTask(ctx, userCred, false, ""); err != nil { - return nil, err - } - return nil, nil -} - -func (self *SGuest) GetQemuVersion(userCred mcclient.TokenCredential) string { - return self.GetMetadata("__qemu_version", userCred) -} - -// if qemuVer >= compareVer return true -func (self *SGuest) CheckQemuVersion(qemuVer, compareVer string) bool { - if len(qemuVer) == 0 { - return false - } - - compareVersion := strings.Split(compareVer, ".") - guestVersion := strings.Split(qemuVer, ".") - var i = 0 - for ; i < len(guestVersion); i++ { - if i >= len(compareVersion) { - return true - } - v, _ := strconv.ParseInt(guestVersion[i], 10, 0) - compareV, _ := strconv.ParseInt(compareVersion[i], 10, 0) - if v < compareV { - return false - } else if v > compareV { - return true - } - } - if i < len(compareVersion)-1 { - return false - } - return true -} - -func (self *SGuest) AllowPerformMigrate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) PerformMigrate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if self.GetHypervisor() != HYPERVISOR_KVM { - return nil, httperrors.NewNotAcceptableError("Not allow for hypervisor %s", self.GetHypervisor()) - } - isRescueMode := jsonutils.QueryBoolean(data, "rescue_mode", false) - if !isRescueMode && self.Status != VM_READY { - return nil, httperrors.NewServerStatusError("Cannot normal migrate guest in status %s, try rescue mode or server-live-migrate?", self.Status) - } - if isRescueMode { - guestDisks := self.GetDisks() - for _, guestDisk := range guestDisks { - if utils.IsInStringArray( - guestDisk.GetDisk().GetStorage().StorageType, STORAGE_LOCAL_TYPES) { - return nil, httperrors.NewBadRequestError("Rescue mode requires all disk store in shared storages") - } - } - } - devices := self.GetIsolatedDevices() - if devices != nil && len(devices) > 0 { - return nil, httperrors.NewBadRequestError("Cannot migrate with isolated devices") - } - var preferHostId string - preferHost, _ := data.GetString("prefer_host") - if len(preferHost) > 0 { - if !userCred.IsSystemAdmin() { - return nil, httperrors.NewBadRequestError("Only system admin can assign host") - } - iHost, _ := HostManager.FetchByIdOrName(userCred, preferHost) - if iHost == nil { - return nil, httperrors.NewBadRequestError("Host %s not found", preferHost) - } - host := iHost.(*SHost) - preferHostId = host.Id - } - err := self.StartMigrateTask(ctx, userCred, isRescueMode, self.Status, preferHostId, "") - return nil, err -} - -func (self *SGuest) StartMigrateTask(ctx context.Context, userCred mcclient.TokenCredential, isRescueMode bool, guestStatus, preferHostId, parentTaskId string) error { - data := jsonutils.NewDict() - if isRescueMode { - data.Set("is_rescue_mode", jsonutils.JSONTrue) - } - if len(preferHostId) > 0 { - data.Set("prefer_host_id", jsonutils.NewString(preferHostId)) - } - data.Set("guest_status", jsonutils.NewString(guestStatus)) - if task, err := taskman.TaskManager.NewTask(ctx, "GuestMigrateTask", self, userCred, data, parentTaskId, "", nil); err != nil { - log.Errorf(err.Error()) - return err - } else { - task.ScheduleRun(nil) - } - return nil -} - -func (self *SGuest) AllowPerformLiveMigrate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) PerformLiveMigrate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if self.GetHypervisor() != HYPERVISOR_KVM { - return nil, httperrors.NewNotAcceptableError("Not allow for hypervisor %s", self.GetHypervisor()) - } - imageId := self.GetDisks()[0].GetDisk().TemplateId - image, err := CachedimageManager.GetImageById(ctx, userCred, imageId, false) - if err != nil { - return nil, err - } - if image.DiskFormat != "qcow2" { - return nil, httperrors.NewBadRequestError("Live migrate only support image fromat qocw2") - } - if utils.IsInStringArray(self.Status, []string{VM_RUNNING, VM_SUSPEND}) { - cdrom := self.getCdrom() - if cdrom != nil && len(cdrom.ImageId) > 0 { - return nil, httperrors.NewBadRequestError("Cannot migrate with cdrom") - } - devices := self.GetIsolatedDevices() - if devices != nil && len(devices) > 0 { - return nil, httperrors.NewBadRequestError("Cannot migrate with isolated devices") - } - if !self.CheckQemuVersion(self.GetQemuVersion(userCred), "1.1.2") { - return nil, httperrors.NewBadRequestError("Cannot do live migrate, too low qemu version") - } - var preferHostId string - preferHost, _ := data.GetString("prefer_host") - if len(preferHost) > 0 { - if !userCred.IsSystemAdmin() { - return nil, httperrors.NewBadRequestError("Only system admin can assign host") - } - iHost, _ := HostManager.FetchByIdOrName(userCred, preferHost) - if iHost == nil { - return nil, httperrors.NewBadRequestError("Host %s not found", preferHost) - } - host := iHost.(*SHost) - preferHostId = host.Id - } - err := self.StartGuestLiveMigrateTask(ctx, userCred, self.Status, preferHostId, "") - return nil, err - } - return nil, httperrors.NewBadRequestError("Cannot live migrate in status %s", self.Status) -} - -func (self *SGuest) StartGuestLiveMigrateTask(ctx context.Context, userCred mcclient.TokenCredential, guestStatus, preferHostId, parentTaskId string) error { - self.SetStatus(userCred, VM_START_MIGRATE, "") - data := jsonutils.NewDict() - if len(preferHostId) > 0 { - data.Set("prefer_host_id", jsonutils.NewString(preferHostId)) - } - data.Set("guest_status", jsonutils.NewString(guestStatus)) - if task, err := taskman.TaskManager.NewTask(ctx, "GuestLiveMigrateTask", self, userCred, data, parentTaskId, "", nil); err != nil { - log.Errorf(err.Error()) - return err - } else { - task.ScheduleRun(nil) - } - return nil -} - -func (self *SGuest) AllowPerformDeploy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) PerformDeploy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - kwargs, ok := data.(*jsonutils.JSONDict) - if !ok { - return nil, fmt.Errorf("Parse query body error") - } - - // 变更密码/密钥时需要Restart才能生效。更新普通字段不需要Restart, Azure需要在运行状态下操作 - doRestart := false - if kwargs.Contains("__delete_keypair__") || kwargs.Contains("keypair") { - doRestart = self.GetDriver().IsNeedRestartForResetLoginInfo() - var kpId string - - if kwargs.Contains("keypair") { - keypair, _ := kwargs.GetString("keypair") - iKp, err := KeypairManager.FetchByIdOrName(userCred, keypair) - if err != nil { - return nil, err - } - if iKp == nil { - return nil, fmt.Errorf("Fetch keypair error") - } - kp := iKp.(*SKeypair) - kpId = kp.Id - } - - if self.KeypairId != kpId { - okey := self.getKeypair() - if okey != nil { - kwargs.Set("delete_public_key", jsonutils.NewString(okey.PublicKey)) - } - - self.GetModelManager().TableSpec().Update(self, func() error { - self.KeypairId = kpId - return nil - }) - - kwargs.Set("reset_password", jsonutils.JSONTrue) - } - } - - deployStatus, err := self.GetDriver().GetDeployStatus() - if err != nil { - return nil, httperrors.NewInputParameterError(err.Error()) - } - - if utils.IsInStringArray(self.Status, deployStatus) { - if doRestart && self.Status == VM_RUNNING { - kwargs.Set("restart", jsonutils.JSONTrue) - } - err := self.StartGuestDeployTask(ctx, userCred, kwargs, "deploy", "") - if err != nil { - return nil, err - } - return nil, nil - } - - return nil, httperrors.NewServerStatusError("Cannot deploy in status %s", self.Status) -} - -func (self *SGuest) AllowPerformAttachdisk(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) ValidateAttachDisk(ctx context.Context, disk *SDisk) error { - storage := disk.GetStorage() - if provider := storage.GetCloudprovider(); provider != nil { - host := self.GetHost() - if provider.Id != host.ManagerId { - return httperrors.NewInputParameterError("Disk %s and guest not belong to the same account", disk.Name) - } - if storage.ZoneId != host.ZoneId { - return httperrors.NewInputParameterError("Disk %s and guest not belong to the same zone", disk.Name) - } - } - - if disk.isAttached() { - return httperrors.NewInputParameterError("Disk %s has been attached", disk.Name) - } - if len(disk.GetPathAtHost(self.GetHost())) == 0 { - return httperrors.NewInputParameterError("Disk %s not belong the guest's host", disk.Name) - } - if disk.Status != DISK_READY { - return httperrors.NewInputParameterError("Disk in %s not able to attach", disk.Status) - } - guestStatus, err := self.GetDriver().GetAttachDiskStatus() - if err != nil { - return err - } - if !utils.IsInStringArray(self.Status, guestStatus) { - return httperrors.NewInputParameterError("Guest %s not support attach disk in status %s", self.Name, self.Status) - } - return nil -} - -func (self *SGuest) PerformAttachdisk(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - diskId, err := data.GetString("disk_id") - if err != nil { - return nil, err - } - disk, err := DiskManager.FetchByIdOrName(userCred, diskId) - if err != nil { - return nil, err - } - if disk == nil { - return nil, httperrors.NewResourceNotFoundError("Disk %s not found", diskId) - } - if err := self.ValidateAttachDisk(ctx, disk.(*SDisk)); err != nil { - return nil, err - } - - taskData := data.(*jsonutils.JSONDict) - taskData.Set("disk_id", jsonutils.NewString(disk.GetId())) - - if err := self.GetDriver().StartGuestAttachDiskTask(ctx, userCred, self, taskData, ""); err != nil { - return nil, err - } - return nil, nil -} - -func (self *SGuest) StartSyncTask(ctx context.Context, userCred mcclient.TokenCredential, fw_only bool, parentTaskId string) error { - if !utils.IsInStringArray(self.Status, []string{VM_READY, VM_RUNNING}) { - return httperrors.NewResourceBusyError("Cannot sync in status %s", self.Status) - } - data := jsonutils.NewDict() - if fw_only { - data.Add(jsonutils.JSONTrue, "fw_only") - } else if err := self.SetStatus(userCred, VM_SYNC_CONFIG, ""); err != nil { - log.Errorf(err.Error()) - return err - } - if task, err := taskman.TaskManager.NewTask(ctx, "GuestSyncConfTask", self, userCred, data, parentTaskId, "", nil); err != nil { - log.Errorf(err.Error()) - return err - } else { - task.ScheduleRun(nil) - } - return nil -} - type sSyncDiskPair struct { disk *SDisk vdisk cloudprovider.ICloudDisk @@ -2201,11 +1889,14 @@ func filterGuestByRange(q *sqlchemy.SQuery, rangeObj db.IStandaloneModel, hostTy } type SGuestCountStat struct { - TotalGuestCount int - TotalCpuCount int - TotalMemSize int - TotalDiskSize int - TotalIsolatedCount int + TotalGuestCount int + TotalCpuCount int + TotalMemSize int + TotalDiskSize int + TotalIsolatedCount int + TotalBackupCpuCount int + TotalBackupMemSize int + TotalBackupDiskSize int } func totalGuestResourceCount( @@ -2227,6 +1918,7 @@ func totalGuestResourceCount( diskQuery = diskQuery.GroupBy(guestdisks.Field("guest_id")) diskSubQuery := diskQuery.SubQuery() + diskBackupSubQuery := diskQuery.IsNotEmpty("backup_storage_id").SubQuery() isolated := IsolatedDeviceManager.Query().SubQuery() @@ -2237,14 +1929,20 @@ func totalGuestResourceCount( isoDevSubQuery := isoDevQuery.SubQuery() guests := GuestManager.Query().SubQuery() + guestBackupSubQuery := GuestManager.Query("id", "vcpu_count", "vmem_size").IsNotEmpty("backup_host_id").SubQuery() q := guests.Query(sqlchemy.COUNT("total_guest_count"), sqlchemy.SUM("total_cpu_count", guests.Field("vcpu_count")), sqlchemy.SUM("total_mem_size", guests.Field("vmem_size")), sqlchemy.SUM("total_disk_size", diskSubQuery.Field("guest_disk_size")), - sqlchemy.SUM("total_isolated_count", isoDevSubQuery.Field("device_sum"))) + sqlchemy.SUM("total_isolated_count", isoDevSubQuery.Field("device_sum")), + sqlchemy.SUM("total_backup_disk_size", diskBackupSubQuery.Field("guest_disk_size")), + sqlchemy.SUM("total_backup_cpu_count", guestBackupSubQuery.Field("vcpu_count")), + sqlchemy.SUM("total_backup_mem_size", guestBackupSubQuery.Field("vmem_size"))) q = q.LeftJoin(diskSubQuery, sqlchemy.Equals(diskSubQuery.Field("guest_id"), guests.Field("id"))) + q = q.LeftJoin(guestBackupSubQuery, sqlchemy.Equals(guestBackupSubQuery.Field("id"), q.Field("id"))) + q = q.LeftJoin(diskBackupSubQuery, sqlchemy.Equals(diskBackupSubQuery.Field("guest_id"), guests.Field("id"))) q = q.LeftJoin(isoDevSubQuery, sqlchemy.Equals(isoDevSubQuery.Field("guest_id"), guests.Field("id"))) @@ -2273,6 +1971,9 @@ func totalGuestResourceCount( if err != nil { log.Errorf("%s", err) } + stat.TotalCpuCount += stat.TotalBackupCpuCount + stat.TotalMemSize += stat.TotalBackupMemSize + stat.TotalDiskSize += stat.TotalBackupDiskSize return stat } @@ -2395,6 +2096,18 @@ func (self *SGuest) createDiskOnHost(ctx context.Context, userCred mcclient.Toke if err != nil { return nil, err } + if len(self.BackupHostId) > 0 { + backupHost := HostManager.FetchHostById(self.BackupHostId) + backupStorage := self.GetDriver().ChooseHostStorage(backupHost, diskConfig.Backend) + _, err = disk.GetModelManager().TableSpec().Update(disk, func() error { + disk.BackupStorageId = backupStorage.Id + return nil + }) + if err != nil { + log.Errorf("Disk save backup storage error") + return disk, err + } + } err = self.attach2Disk(disk, userCred, diskConfig.Driver, diskConfig.Cache, diskConfig.Mountpoint) return disk, err } @@ -2453,10 +2166,6 @@ func (self *SGuest) JoinGroups(userCred mcclient.TokenCredential, params *jsonut // TODO } -func (self *SGuest) StartGuestCreateTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict, pendingUsage quotas.IQuota, parentTaskId string) error { - return self.GetDriver().StartGuestCreateTask(self, ctx, userCred, params, pendingUsage, parentTaskId) -} - type SGuestDiskCategory struct { Root *SDisk Swap []*SDisk @@ -2509,20 +2218,6 @@ func (self *SGuest) CategorizeNics() SGuestNicCategory { return netCat } -func (self *SGuest) StartGuestDeployTask(ctx context.Context, userCred mcclient.TokenCredential, kwargs *jsonutils.JSONDict, action string, parentTaskId string) error { - self.SetStatus(userCred, VM_START_DEPLOY, "") - if kwargs == nil { - kwargs = jsonutils.NewDict() - } - kwargs.Add(jsonutils.NewString(action), "deploy_action") - task, err := taskman.TaskManager.NewTask(ctx, "GuestDeployTask", self, userCred, kwargs, parentTaskId, "", nil) - if err != nil { - return err - } - task.ScheduleRun(nil) - return nil -} - func (self *SGuest) NotifyServerEvent(event string, priority string, loginInfo bool) error { meta, err := self.GetAllMetadata(nil) if err != nil { @@ -2568,20 +2263,6 @@ func (self *SGuest) NotifyAdminServerEvent(ctx context.Context, event string, pr return notifyclient.Notify(options.Options.NotifyAdminUser, event, priority, kwargs) } -func (self *SGuest) StartGuestStopTask(ctx context.Context, userCred mcclient.TokenCredential, isForce bool, parentTaskId string) error { - if len(parentTaskId) == 0 { - self.SetStatus(userCred, VM_START_STOP, "") - } - params := jsonutils.NewDict() - if isForce { - params.Add(jsonutils.JSONTrue, "is_force") - } - if len(parentTaskId) > 0 { - params.Add(jsonutils.JSONTrue, "subtask") - } - return self.GetDriver().StartGuestStopTask(self, ctx, userCred, params, parentTaskId) -} - func (self *SGuest) insertIso(imageId string) bool { cdrom := self.getCdrom() return cdrom.insertIso(imageId) @@ -2607,736 +2288,12 @@ func (self *SGuest) GetDetailsIso(userCred mcclient.TokenCredential) jsonutils.J return desc } -func (self *SGuest) StartInsertIsoTask(ctx context.Context, imageId string, hostId string, userCred mcclient.TokenCredential, parentTaskId string) error { - self.insertIso(imageId) - - data := jsonutils.NewDict() - data.Add(jsonutils.NewString(imageId), "image_id") - data.Add(jsonutils.NewString(hostId), "host_id") - - task, err := taskman.TaskManager.NewTask(ctx, "GuestInsertIsoTask", self, userCred, data, parentTaskId, "", nil) - if err != nil { - return err +func (self *SGuest) GetKeypairPublicKey() string { + keypair := self.getKeypair() + if keypair != nil { + return keypair.PublicKey } - task.ScheduleRun(nil) - return nil -} - -func (self *SGuest) StartGueststartTask(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, parentTaskId string) error { - self.SetStatus(userCred, VM_START_START, "") - task, err := taskman.TaskManager.NewTask(ctx, "GuestStartTask", self, userCred, data, parentTaskId, "", nil) - if err != nil { - return err - } - task.ScheduleRun(nil) - return nil -} - -func (self *SGuest) StartGuestCreateDiskTask(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, parentTaskId string) error { - task, err := taskman.TaskManager.NewTask(ctx, "GuestCreateDiskTask", self, userCred, data, parentTaskId, "", nil) - if err != nil { - return err - } - task.ScheduleRun(nil) - return nil -} - -func (self *SGuest) StartSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { - return self.GetDriver().StartGuestSyncstatusTask(self, ctx, userCred, parentTaskId) -} - -func (self *SGuest) StartAutoDeleteGuestTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { - db.OpsLog.LogEvent(self, db.ACT_DELETE, "auto-delete after stop", userCred) - return self.StartDeleteGuestTask(ctx, userCred, parentTaskId, false, false) -} - -func (self *SGuest) StartDeleteGuestTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, isPurge bool, overridePendingDelete bool) error { - params := jsonutils.NewDict() - params.Add(jsonutils.NewString(self.Status), "guest_status") - if isPurge { - params.Add(jsonutils.JSONTrue, "purge") - } - if overridePendingDelete { - params.Add(jsonutils.JSONTrue, "override_pending_delete") - } - self.SetStatus(userCred, VM_START_DELETE, "") - return self.GetDriver().StartDeleteGuestTask(ctx, userCred, self, params, parentTaskId) -} - -func (self *SGuest) AllowPerformAssignSecgroup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) AllowPerformRevokeSecgroup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) PerformRevokeSecgroup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if !utils.IsInStringArray(self.Status, []string{VM_READY, VM_RUNNING, VM_SUSPEND}) { - return nil, httperrors.NewInputParameterError("Cannot revoke security rules in status %s", self.Status) - } else { - if _, err := self.GetModelManager().TableSpec().Update(self, func() error { - self.SecgrpId = "default" - return nil - }); err != nil { - return nil, err - } - if err := self.StartSyncTask(ctx, userCred, true, ""); err != nil { - return nil, err - } - } - return nil, nil -} - -func (self *SGuest) PerformAssignSecgroup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if !utils.IsInStringArray(self.Status, []string{VM_READY, VM_RUNNING, VM_SUSPEND}) { - logclient.AddActionLog(self, logclient.ACT_VM_ASSIGNSECGROUP, "Cannot assign security rules in status "+self.Status, userCred, false) - return nil, httperrors.NewInputParameterError("Cannot assign security rules in status %s", self.Status) - } else { - if secgrp, err := data.GetString("secgrp"); err != nil { - logclient.AddActionLog(self, logclient.ACT_VM_ASSIGNSECGROUP, err, userCred, false) - return nil, err - } else if sg, err := SecurityGroupManager.FetchByIdOrName(userCred, secgrp); err != nil { - msg := fmt.Sprintf("SecurityGroup %s not found", secgrp) - logclient.AddActionLog(self, logclient.ACT_VM_ASSIGNSECGROUP, msg, userCred, false) - return nil, httperrors.NewNotFoundError("SecurityGroup %s not found", secgrp) - } else { - if _, err := self.GetModelManager().TableSpec().Update(self, func() error { - self.SecgrpId = sg.GetId() - return nil - }); err != nil { - logclient.AddActionLog(self, logclient.ACT_VM_ASSIGNSECGROUP, err, userCred, false) - return nil, err - } - if err := self.StartSyncTask(ctx, userCred, true, ""); err != nil { - logclient.AddActionLog(self, logclient.ACT_VM_ASSIGNSECGROUP, err, userCred, false) - return nil, err - } - } - } - logclient.AddActionLog(self, logclient.ACT_VM_ASSIGNSECGROUP, nil, userCred, true) - return nil, nil -} - -func (self *SGuest) AllowPerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return self.IsAdmin(userCred) -} - -func (self *SGuest) PerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - err := self.ValidateDeleteCondition(ctx) - if err != nil { - return nil, err - } - host := self.GetHost() - if host != nil && host.Enabled { - return nil, httperrors.NewInvalidStatusError("Cannot purge server on enabled host") - } - err = self.StartDeleteGuestTask(ctx, userCred, "", true, false) - return nil, err -} - -func (self *SGuest) setKeypairId(userCred mcclient.TokenCredential, keypairId string) error { - diff, err := self.GetModelManager().TableSpec().Update(self, func() error { - self.KeypairId = keypairId - return nil - }) - if err != nil { - db.OpsLog.LogEvent(self, db.ACT_UPDATE, diff, userCred) - } - return err -} - -func (self *SGuest) AllowPerformRebuildRoot(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) PerformRebuildRoot(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - imageId, _ := data.GetString("image_id") - - rebuildStatus, err := self.GetDriver().GetRebuildRootStatus() - if err != nil { - return nil, httperrors.NewInputParameterError(err.Error()) - } - - if !utils.IsInStringArray(self.Status, rebuildStatus) { - return nil, httperrors.NewInvalidStatusError("Cannot reset root in status %s", self.Status) - } - - if !data.Contains("image_id") { - gdc := self.CategorizeDisks() - imageId = gdc.Root.GetTemplateId() - if len(imageId) == 0 { - return nil, httperrors.NewBadRequestError("No template for root disk") - } - img, err := CachedimageManager.getImageInfo(ctx, userCred, imageId, false) - if err != nil { - return nil, httperrors.NewBadRequestError("Template %s not accessible: %s", imageId, err.Error()) - } - osType, _ := img.Properties["os_type"] - osName := self.GetMetadata("os_name", userCred) - if len(osName) == 0 && len(osType) == 0 && strings.ToLower(osType) != strings.ToLower(osName) { - return nil, httperrors.NewBadRequestError("Cannot switch OS between %s-%s", osName, osType) - } - } - - autoStart := jsonutils.QueryBoolean(data, "auto_start", false) - var needStop = false - if self.Status == VM_RUNNING { - needStop = true - } - resetPasswd := jsonutils.QueryBoolean(data, "reset_password", true) - passwd, _ := data.GetString("password") - if len(passwd) > 0 { - if !seclib2.MeetComplxity(passwd) { - return nil, httperrors.NewWeakPasswordError() - } - } - - keypairStr := jsonutils.GetAnyString(data, []string{"keypair", "keypair_id"}) - if len(keypairStr) > 0 { - keypairObj, err := KeypairManager.FetchByIdOrName(userCred, keypairStr) - if err != nil { - if err == sql.ErrNoRows { - return nil, httperrors.NewResourceNotFoundError("keypair %s not found", keypairStr) - } else { - return nil, httperrors.NewGeneralError(err) - } - } - if self.KeypairId != keypairObj.GetId() { - err = self.setKeypairId(userCred, keypairObj.GetId()) - if err != nil { - return nil, httperrors.NewGeneralError(err) - } - } - } - - return nil, self.StartRebuildRootTask(ctx, userCred, imageId, needStop, autoStart, passwd, resetPasswd) -} - -func (self *SGuest) StartRebuildRootTask(ctx context.Context, userCred mcclient.TokenCredential, imageId string, needStop, autoStart bool, passwd string, resetPasswd bool) error { - data := jsonutils.NewDict() - data.Set("image_id", jsonutils.NewString(imageId)) - if needStop { - data.Set("need_stop", jsonutils.JSONTrue) - } - if autoStart { - data.Set("auto_start", jsonutils.JSONTrue) - } - if resetPasswd { - data.Set("reset_password", jsonutils.JSONTrue) - } else { - data.Set("reset_password", jsonutils.JSONFalse) - } - if len(passwd) > 0 { - data.Set("password", jsonutils.NewString(passwd)) - } - if self.GetHypervisor() == HYPERVISOR_BAREMETAL { - task, err := taskman.TaskManager.NewTask(ctx, "BaremetalServerRebuildRootTask", self, userCred, data, "", "", nil) - if err != nil { - return err - } - task.ScheduleRun(nil) - } else { - task, err := taskman.TaskManager.NewTask(ctx, "GuestRebuildRootTask", self, userCred, data, "", "", nil) - if err != nil { - return err - } - task.ScheduleRun(nil) - } - return nil -} - -func (self *SGuest) DetachDisk(ctx context.Context, disk *SDisk, userCred mcclient.TokenCredential) { - guestdisk := self.GetGuestDisk(disk.Id) - if guestdisk != nil { - guestdisk.Detach(ctx, userCred) - } -} - -func (self *SGuest) AllowPerformCreatedisk(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) PerformCreatedisk(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - var diskIdx, diskSize = 0, 0 - disksConf := jsonutils.NewDict() - diskSizes := make(map[string]int, 0) - diskSeq := fmt.Sprintf("disk.%d", diskIdx) - for data.Contains(diskSeq) { - diskDef, _ := data.Get(diskSeq) - diskInfo, err := parseDiskInfo(ctx, userCred, diskDef) - if err != nil { - logclient.AddActionLog(self, logclient.ACT_CREATE, err.Error(), userCred, false) - return nil, httperrors.NewBadRequestError(err.Error()) - } - if len(diskInfo.Backend) == 0 { - diskInfo.Backend = self.getDefaultStorageType() - } - disksConf.Set(diskSeq, jsonutils.Marshal(diskInfo)) - if _, ok := diskSizes[diskInfo.Backend]; !ok { - diskSizes[diskInfo.Backend] = diskInfo.Size - } else { - diskSizes[diskInfo.Backend] += diskInfo.Size - } - diskSize += diskInfo.Size - diskIdx += 1 - diskSeq = fmt.Sprintf("disk.%d", diskIdx) - } - if diskIdx == 0 { - logclient.AddActionLog(self, logclient.ACT_CREATE, "No Disk Info Provided", userCred, false) - return nil, httperrors.NewBadRequestError("No Disk Info Provided") - } - host := self.GetHost() - if host == nil { - logclient.AddActionLog(self, logclient.ACT_CREATE, "No valid host", userCred, false) - return nil, httperrors.NewBadRequestError("No valid host") - } - for backend, size := range diskSizes { - storage := host.GetLeastUsedStorage(backend) - if storage == nil { - logclient.AddActionLog(self, logclient.ACT_CREATE, "No valid storage on current host", userCred, false) - return nil, httperrors.NewBadRequestError("No valid storage on current host") - } - if storage.GetCapacity() < size { - logclient.AddActionLog(self, logclient.ACT_CREATE, "Not eough storage space on current host", userCred, false) - return nil, httperrors.NewBadRequestError("Not eough storage space on current host") - } - } - pendingUsage := &SQuota{ - Storage: diskSize, - } - err := QuotaManager.CheckSetPendingQuota(ctx, userCred, self.ProjectId, pendingUsage) - if err != nil { - logclient.AddActionLog(self, logclient.ACT_CREATE, err.Error(), userCred, false) - return nil, httperrors.NewBadRequestError(err.Error()) - } - - lockman.LockObject(ctx, host) - defer lockman.ReleaseObject(ctx, host) - - err = self.CreateDisksOnHost(ctx, userCred, host, disksConf, pendingUsage) - if err != nil { - QuotaManager.CancelPendingUsage(ctx, userCred, self.ProjectId, nil, pendingUsage) - logclient.AddActionLog(self, logclient.ACT_CREATE, err.Error(), userCred, false) - return nil, httperrors.NewBadRequestError(err.Error()) - } - err = self.StartGuestCreateDiskTask(ctx, userCred, disksConf, "") - return nil, err -} - -func (self *SGuest) AllowPerformDetachdisk(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) PerformDetachdisk(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - diskId, err := data.GetString("disk_id") - if err != nil { - return nil, err - } - keepDisk := jsonutils.QueryBoolean(data, "keep_disk", false) - iDisk, err := DiskManager.FetchByIdOrName(userCred, diskId) - if err != nil { - return nil, err - } - disk := iDisk.(*SDisk) - if disk != nil { - if self.isAttach2Disk(disk) { - if disk.DiskType == DISK_TYPE_SYS { - return nil, httperrors.NewUnsupportOperationError("Cannot detach sys disk") - } - detachDiskStatus, err := self.GetDriver().GetDetachDiskStatus() - if err != nil { - return nil, err - } - if keepDisk && !self.GetDriver().CanKeepDetachDisk() { - return nil, httperrors.NewInputParameterError("Cannot keep detached disk") - } - if utils.IsInStringArray(self.Status, detachDiskStatus) { - if disk.Status == DISK_INIT { - disk.SetStatus(userCred, DISK_DETACHING, "") - } - taskData := jsonutils.NewDict() - taskData.Add(jsonutils.NewString(disk.Id), "disk_id") - taskData.Add(jsonutils.NewBool(keepDisk), "keep_disk") - self.GetDriver().StartGuestDetachdiskTask(ctx, userCred, self, taskData, "") - return nil, nil - } else { - return nil, httperrors.NewInvalidStatusError("Server in %s not able to detach disk", self.Status) - } - } else { - return nil, httperrors.NewInvalidStatusError("Disk %s not attached", diskId) - } - } - return nil, httperrors.NewResourceNotFoundError("Disk %s not found", diskId) -} - -func (self *SGuest) AllowPerformDetachIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return userCred.IsSystemAdmin() -} - -func (self *SGuest) PerformDetachIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if self.Hypervisor != HYPERVISOR_KVM { - return nil, httperrors.NewNotAcceptableError("Not allow for hypervisor %s", self.Hypervisor) - } - if self.Status != VM_READY { - msg := "Only allowed to attach isolated device when guest is ready" - logclient.AddActionLog(self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false) - return nil, httperrors.NewInvalidStatusError(msg) - } - device, err := data.GetString("device") - if err != nil { - msg := "Missing isolated device" - logclient.AddActionLog(self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false) - return nil, httperrors.NewBadRequestError(msg) - } - iDev, err := IsolatedDeviceManager.FetchByIdOrName(userCred, device) - if err != nil { - msg := fmt.Sprintf("Isolated device %s not found", device) - logclient.AddActionLog(self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false) - return nil, httperrors.NewBadRequestError(msg) - } - dev := iDev.(*SIsolatedDevice) - host := self.GetHost() - lockman.LockObject(ctx, host) - defer lockman.ReleaseObject(ctx, host) - err = self.detachIsolateDevice(userCred, dev) - return nil, err -} - -func (self *SGuest) detachIsolateDevice(userCred mcclient.TokenCredential, dev *SIsolatedDevice) error { - if dev.GuestId != self.Id { - msg := "Isolated device is not attached to this guest" - logclient.AddActionLog(self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false) - return httperrors.NewBadRequestError(msg) - } - _, err := self.GetModelManager().TableSpec().Update(dev, func() error { - dev.GuestId = "" - return nil - }) - if err != nil { - return err - } - db.OpsLog.LogEvent(self, db.ACT_GUEST_DETACH_ISOLATED_DEVICE, dev.GetShortDesc(), userCred) - return nil -} - -func (self *SGuest) AllowPerformAttachIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return userCred.IsSystemAdmin() -} - -func (self *SGuest) PerformAttachIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if self.Hypervisor != HYPERVISOR_KVM { - return nil, httperrors.NewNotAcceptableError("Not allow for hypervisor %s", self.Hypervisor) - } - if self.Status != VM_READY { - msg := "Only allowed to attach isolated device when guest is ready" - logclient.AddActionLog(self, logclient.ACT_GUEST_ATTACH_ISOLATED_DEVICE, msg, userCred, false) - return nil, httperrors.NewInvalidStatusError(msg) - } - device, err := data.GetString("device") - if err != nil { - msg := "Missing isolated device" - logclient.AddActionLog(self, logclient.ACT_GUEST_ATTACH_ISOLATED_DEVICE, msg, userCred, false) - return nil, httperrors.NewBadRequestError(msg) - } - iDev, err := IsolatedDeviceManager.FetchByIdOrName(userCred, device) - if err != nil { - msg := fmt.Sprintf("Isolated device %s not found", device) - logclient.AddActionLog(self, logclient.ACT_GUEST_ATTACH_ISOLATED_DEVICE, msg, userCred, false) - return nil, httperrors.NewBadRequestError(msg) - } - dev := iDev.(*SIsolatedDevice) - host := self.GetHost() - lockman.LockObject(ctx, host) - defer lockman.ReleaseObject(ctx, host) - err = self.attachIsolatedDevice(userCred, dev) - var msg string - if err != nil { - msg = err.Error() - } - logclient.AddActionLog(self, logclient.ACT_GUEST_ATTACH_ISOLATED_DEVICE, msg, userCred, err == nil) - return nil, err -} - -func (self *SGuest) AllowPerformDetachnetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) PerformDetachnetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if self.Status != VM_READY { - return nil, httperrors.NewInvalidStatusError("Cannot detach network in status %s", self.Status) - } - reserve := jsonutils.QueryBoolean(data, "reserve", false) - netId, err := data.GetString("net_id") - if err != nil { - return nil, httperrors.NewBadRequestError(err.Error()) - } - iNetwork, err := NetworkManager.FetchById(netId) - if err != nil { - return nil, httperrors.NewNotFoundError("Network %s not found", netId) - } - network := iNetwork.(*SNetwork) - err = self.detachNetwork(ctx, userCred, network, reserve, true) - return nil, err -} - -func (self *SGuest) AllowPerformAttachnetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) PerformAttachnetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if self.Status == VM_READY { - // owner_cred = self.get_owner_user_cred() >.< - netDesc, err := data.Get("net_desc") - if err != nil { - return nil, httperrors.NewBadRequestError(err.Error()) - } - conf, err := parseNetworkInfo(userCred, netDesc) - if err != nil { - return nil, err - } - err = isValidNetworkInfo(userCred, conf) - if err != nil { - return nil, httperrors.NewBadRequestError(err.Error()) - } - var inicCnt, enicCnt, ibw, ebw int - if isExitNetworkInfo(conf) { - enicCnt = 1 - ebw = conf.BwLimit - } else { - inicCnt = 1 - ibw = conf.BwLimit - } - pendingUsage := &SQuota{ - Port: inicCnt, - Eport: enicCnt, - Bw: ibw, - Ebw: ebw, - } - projectId := self.GetOwnerProjectId() - err = QuotaManager.CheckSetPendingQuota(ctx, userCred, projectId, pendingUsage) - if err != nil { - return nil, httperrors.NewOutOfQuotaError(err.Error()) - } - host := self.GetHost() - err = self.attach2NetworkDesc(ctx, userCred, host, conf, pendingUsage) - if err != nil { - QuotaManager.CancelPendingUsage(ctx, userCred, projectId, nil, pendingUsage) - return nil, httperrors.NewBadRequestError(err.Error()) - } - host.ClearSchedDescCache() - err = self.StartGuestDeployTask(ctx, userCred, nil, "deploy", "") - return nil, err - } - return nil, httperrors.NewBadRequestError("Cannot attach network in status %s", self.Status) -} - -func (self *SGuest) AllowPerformChangeBandwidth(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) || userCred.IsSystemAdmin() -} - -func (self *SGuest) PerformChangeBandwidth(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if utils.IsInStringArray(self.Status, []string{VM_READY, VM_RUNNING}) { - guestnics := self.GetNetworks() - index, err := data.Int("index") - if err != nil || index > int64(len(guestnics)) { - logclient.AddActionLog(self, logclient.ACT_VM_CHANGE_BANDWIDTH, "Index Not fount or out of NIC index", userCred, false) - return nil, httperrors.NewBadRequestError("Index Not fount or out of NIC index") - } - bandwidth, err := data.Int("bandwidth") - if err != nil || bandwidth < 0 { - logclient.AddActionLog(self, logclient.ACT_VM_CHANGE_BANDWIDTH, "Bandwidth must non-negative", userCred, false) - return nil, httperrors.NewBadRequestError("Bandwidth must be non-negative") - } - guestnic := &guestnics[index] - if guestnic.BwLimit != int(bandwidth) { - GuestnetworkManager.TableSpec().Update(guestnic, func() error { - guestnic.BwLimit = int(bandwidth) - return nil - }) - err := self.StartSyncTask(ctx, userCred, false, "") - logclient.AddActionLog(self, logclient.ACT_VM_CHANGE_BANDWIDTH, err, userCred, err == nil) - return nil, err - } - return nil, nil - } - msg := fmt.Sprintf("Cannot change bandwidth in status %s", self.Status) - logclient.AddActionLog(self, logclient.ACT_VM_CHANGE_BANDWIDTH, msg, userCred, false) - return nil, httperrors.NewBadRequestError(msg) -} - -func (self *SGuest) AllowPerformChangeConfig(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) || self.IsAdmin(userCred) -} - -func (self *SGuest) PerformChangeConfig(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - changeStatus, err := self.GetDriver().GetChangeConfigStatus() - if err != nil { - return nil, httperrors.NewInputParameterError(err.Error()) - } - if !utils.IsInStringArray(self.Status, changeStatus) { - return nil, httperrors.NewInvalidStatusError("Cannot change config in %s", self.Status) - } - if !self.GetDriver().AllowReconfigGuest() { - return nil, httperrors.NewInvalidStatusError("Not allow to change config") - } - host := self.GetHost() - if host == nil { - return nil, httperrors.NewInvalidStatusError("No valid host") - } - var addCpu, addMem int - confs := jsonutils.NewDict() - vcpuCount, err := data.GetString("vcpu_count") - if err == nil { - nVcpu, err := strconv.ParseInt(vcpuCount, 10, 0) - if err != nil { - return nil, httperrors.NewBadRequestError("Params vcpu_count parse error") - } - err = confs.Add(jsonutils.NewInt(nVcpu), "vcpu_count") - if err != nil { - return nil, httperrors.NewBadRequestError("Params vcpu_count parse error") - } - addCpu = int(nVcpu - int64(self.VcpuCount)) - } - vmemSize, err := data.GetString("vmem_size") - if err == nil { - if !regutils.MatchSize(vmemSize) { - return nil, httperrors.NewBadRequestError("Memory size must be number[+unit], like 256M, 1G or 256") - } - nVmem, err := fileutils.GetSizeMb(vmemSize, 'M', 1024) - if err != nil { - httperrors.NewBadRequestError("Params vmem_size parse error") - } - err = confs.Add(jsonutils.NewInt(int64(nVmem)), "vmem_size") - if err != nil { - return nil, httperrors.NewBadRequestError("Params vmem_size parse error") - } - addMem = nVmem - self.VmemSize - } - disks := self.GetDisks() - var addDisk int - var diskIdx = 1 - var newDiskIdx = 0 - var diskSizes = make(map[string]int, 0) - var newDisks = jsonutils.NewDict() - var resizeDisks = jsonutils.NewArray() - for { - diskNum := fmt.Sprintf("disk.%d", diskIdx) - diskDesc, err := data.Get(diskNum) - if err != nil { - break - } - diskConf, err := parseDiskInfo(ctx, userCred, diskDesc) - if err != nil { - return nil, httperrors.NewBadRequestError("Parse disk info error: %s", err) - } - if len(diskConf.Backend) == 0 { - diskConf.Backend = self.getDefaultStorageType() - } - if diskConf.Size > 0 { - if diskIdx >= len(disks) { - newDisks.Add(jsonutils.Marshal(diskConf), fmt.Sprintf("disk.%d", newDiskIdx)) - newDiskIdx += 1 - addDisk += diskConf.Size - storage := host.GetLeastUsedStorage(diskConf.Backend) - if storage == nil { - return nil, httperrors.NewResourceNotReadyError("host not connect storage %s", diskConf.Backend) - } - _, ok := diskSizes[storage.Id] - if !ok { - diskSizes[storage.Id] = 0 - } - diskSizes[storage.Id] = diskSizes[storage.Id] + diskConf.Size - } else { - disk := disks[diskIdx].GetDisk() - oldSize := disk.DiskSize - if diskConf.Size < oldSize { - return nil, httperrors.NewInputParameterError("Cannot reduce disk size") - } else if diskConf.Size > oldSize { - arr := jsonutils.NewArray(jsonutils.NewString(disks[diskIdx].DiskId), jsonutils.NewInt(int64(diskConf.Size))) - resizeDisks.Add(arr) - addDisk += diskConf.Size - oldSize - storage := disks[diskIdx].GetDisk().GetStorage() - _, ok := diskSizes[storage.Id] - if !ok { - diskSizes[storage.Id] = 0 - } - diskSizes[storage.Id] = diskSizes[storage.Id] + diskConf.Size - oldSize - } - } - } - diskIdx += 1 - } - - provider, e := self.GetHost().GetDriver() - /*if e != nil { - log.Errorf("Get Provider Error: %s", e) - return nil, httperrors.NewInsufficientResourceError("Provider Not Found") - }*/ - - if e != nil || !provider.IsPublicCloud() { - for storageId, needSize := range diskSizes { - iStorage, err := StorageManager.FetchById(storageId) - if err != nil { - return nil, httperrors.NewBadRequestError("Fetch storage error: %s", err) - } - storage := iStorage.(*SStorage) - if storage.GetFreeCapacity() < needSize { - return nil, httperrors.NewInsufficientResourceError("Not enough free space") - } - } - } else { - log.Debugf("Skip storage free capacity validating for public cloud: %s", provider.GetName()) - } - - if newDisks.Length() > 0 { - confs.Add(newDisks, "create") - } - if resizeDisks.Length() > 0 { - confs.Add(resizeDisks, "resize") - } - if jsonutils.QueryBoolean(data, "auto_start", false) { - confs.Add(jsonutils.NewBool(true), "auto_start") - } - - log.Debugf("%s", confs.String()) - - pendingUsage := &SQuota{} - if addCpu > 0 { - pendingUsage.Cpu = addCpu - } - if addMem > 0 { - pendingUsage.Memory = addMem - } - if addDisk > 0 { - pendingUsage.Storage = addDisk - } - if !pendingUsage.IsEmpty() { - err := QuotaManager.CheckSetPendingQuota(ctx, userCred, userCred.GetProjectId(), pendingUsage) - if err != nil { - return nil, httperrors.NewBadRequestError("Check set pending quota error %s", err) - } - } - if newDisks.Length() > 0 { - err := self.CreateDisksOnHost(ctx, userCred, host, newDisks, pendingUsage) - if err != nil { - QuotaManager.CancelPendingUsage(ctx, userCred, self.ProjectId, nil, pendingUsage) - return nil, httperrors.NewBadRequestError("Create disk on host error: %s", err) - } - } - self.StartChangeConfigTask(ctx, userCred, confs, "", pendingUsage) - return nil, nil -} - -func (self *SGuest) StartChangeConfigTask(ctx context.Context, userCred mcclient.TokenCredential, - data *jsonutils.JSONDict, parentTaskId string, pendingUsage quotas.IQuota) error { - self.SetStatus(userCred, VM_CHANGE_FLAVOR, "") - task, err := taskman.TaskManager.NewTask(ctx, "GuestChangeConfigTask", self, userCred, data, parentTaskId, "", pendingUsage) - if err != nil { - return err - } - task.ScheduleRun(nil) - return nil + return "" } func (self *SGuest) DoPendingDelete(ctx context.Context, userCred mcclient.TokenCredential) { @@ -3356,39 +2313,6 @@ func (self *SGuest) DoPendingDelete(ctx context.Context, userCred mcclient.Token self.SVirtualResourceBase.DoPendingDelete(ctx, userCred) } -func (model *SGuest) AllowPerformCancelDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return userCred.IsSystemAdmin() -} - -func (self *SGuest) PerformCancelDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if self.PendingDeleted { - err := self.DoCancelPendingDelete(ctx, userCred) - return nil, err - } - return nil, nil -} - -func (self *SGuest) DoCancelPendingDelete(ctx context.Context, userCred mcclient.TokenCredential) error { - for _, guestdisk := range self.GetDisks() { - disk := guestdisk.GetDisk() - disk.DoCancelPendingDelete(ctx, userCred) - } - return self.SVirtualResourceBase.DoCancelPendingDelete(ctx, userCred) -} - -func (self *SGuest) StartUndeployGuestTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, targetHostId string) error { - data := jsonutils.NewDict() - if len(targetHostId) > 0 { - data.Add(jsonutils.NewString(targetHostId), "target_host_id") - } - task, err := taskman.TaskManager.NewTask(ctx, "GuestUndeployTask", self, userCred, data, parentTaskId, "", nil) - if err != nil { - return err - } - task.ScheduleRun(nil) - return nil -} - func (self *SGuest) LeaveAllGroups(userCred mcclient.TokenCredential) { groupGuests := make([]SGroupguest, 0) q := GroupguestManager.Query() @@ -3479,39 +2403,6 @@ func (self *SGuest) DeleteAllDisksInDB(ctx context.Context, userCred mcclient.To return nil } -func (self *SGuest) AllowPerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) PerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - self.SetStatus(userCred, VM_SYNCING_STATUS, "perform_syncstatus") - err := self.StartSyncstatus(ctx, userCred, "") - return nil, err -} - -func (self *SGuest) isNotRunningStatus(status string) bool { - if status == VM_READY || status == VM_SUSPEND { - return true - } - return false -} - -func (self *SGuest) PerformStatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - preStatus := self.Status - _, err := self.SVirtualResourceBase.PerformStatus(ctx, userCred, query, data) - if err != nil { - return nil, err - } - if preStatus != self.Status && !self.isNotRunningStatus(preStatus) && self.isNotRunningStatus(self.Status) { - db.OpsLog.LogEvent(self, db.ACT_STOP, "", userCred) - if self.Status == VM_READY && !self.DisableDelete.Bool() && self.ShutdownBehavior == SHUTDOWN_TERMINATE { - err = self.StartAutoDeleteGuestTask(ctx, userCred, "") - return nil, err - } - } - return nil, nil -} - type SDeployConfig struct { Path string Action string @@ -3629,16 +2520,6 @@ func (self *SGuest) getExtraOptions() jsonutils.JSONObject { return self.GetMetadataJson("extra_options", nil) } -/* -func (self *SGuest) GetFlavor() *SFlav { - -} - -func (self *SGuest) getFlavorName() string { - f := self.GetFlavor() -} -*/ - func (self *SGuest) GetJsonDescAtHypervisor(ctx context.Context, host *SHost) *jsonutils.JSONDict { desc := jsonutils.NewDict() @@ -3655,6 +2536,16 @@ func (self *SGuest) GetJsonDescAtHypervisor(ctx context.Context, host *SHost) *j desc.Add(jsonutils.NewString(self.getBios()), "bios") desc.Add(jsonutils.NewString(self.BootOrder), "boot_order") + if len(self.BackupHostId) > 0 { + if self.HostId == host.Id { + desc.Set("is_master", jsonutils.JSONTrue) + desc.Set("host_id", jsonutils.NewString(self.HostId)) + } else if self.BackupHostId == host.Id { + desc.Set("is_slave", jsonutils.JSONTrue) + desc.Set("host_id", jsonutils.NewString(self.BackupHostId)) + } + } + // isolated devices isolatedDevs := IsolatedDeviceManager.generateJsonDescForGuest(self) desc.Add(jsonutils.NewArray(isolatedDevs...), "isolated_devices") @@ -4143,163 +3034,6 @@ func (self *SGuest) isAllDisksReady() bool { return ready } -func (self *SGuest) AllowPerformSuspend(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) PerformSuspend(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if self.Status == VM_RUNNING { - err := self.StartSuspendTask(ctx, userCred, "") - return nil, err - } - return nil, httperrors.NewInvalidStatusError("Cannot suspend VM in status %s", self.Status) -} - -func (self *SGuest) StartSuspendTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { - err := self.SetStatus(userCred, VM_SUSPEND, "do suspend") - if err != nil { - return err - } - return self.GetDriver().StartSuspendTask(ctx, userCred, self, nil, parentTaskId) -} - -func (self *SGuest) AllowPerformStart(ctx context.Context, - userCred mcclient.TokenCredential, - query jsonutils.JSONObject, - data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) PerformStart(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, - data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if utils.IsInStringArray(self.Status, []string{VM_READY, VM_START_FAILED, VM_SAVE_DISK_FAILED, VM_SUSPEND}) { - if self.isAllDisksReady() { - var kwargs *jsonutils.JSONDict - if data != nil { - kwargs = data.(*jsonutils.JSONDict) - } - err := self.GetDriver().PerformStart(ctx, userCred, self, kwargs) - return nil, err - } else { - return nil, httperrors.NewInvalidStatusError("Some disk not ready") - } - } else { - return nil, httperrors.NewInvalidStatusError("Cannot do start server in status %s", self.Status) - } -} - -func (self *SGuest) AllowPerformReset(ctx context.Context, - userCred mcclient.TokenCredential, - query jsonutils.JSONObject, - data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) PerformReset(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, - data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - isHard := jsonutils.QueryBoolean(data, "is_hard", false) - if self.Status == VM_RUNNING || self.Status == VM_STOP_FAILED { - self.GetDriver().StartGuestResetTask(self, ctx, userCred, isHard, "") - return nil, nil - } - return nil, httperrors.NewInvalidStatusError("Cannot reset VM in status %s", self.Status) -} - -func (self *SGuest) AllowPerformDiskSnapshot(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) PerformDiskSnapshot(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if !utils.IsInStringArray(self.Status, []string{VM_RUNNING, VM_READY}) { - return nil, httperrors.NewInvalidStatusError("Cannot do snapshot when VM in status %s", self.Status) - } - diskId, err := data.GetString("disk_id") - if err != nil { - return nil, httperrors.NewBadRequestError(err.Error()) - } - name, err := data.GetString("name") - if err != nil { - return nil, httperrors.NewBadRequestError(err.Error()) - } - err = ValidateSnapshotName(self.Hypervisor, name, userCred.GetProjectId()) - if err != nil { - return nil, httperrors.NewBadRequestError(err.Error()) - } - if self.GetGuestDisk(diskId) == nil { - return nil, httperrors.NewNotFoundError("Guest disk %s not found", diskId) - } - if self.GetHypervisor() == HYPERVISOR_KVM { - q := SnapshotManager.Query() - cnt := q.Filter(sqlchemy.AND(sqlchemy.Equals(q.Field("disk_id"), diskId), - sqlchemy.Equals(q.Field("created_by"), MANUAL), - sqlchemy.Equals(q.Field("fake_deleted"), false))).Count() - if cnt >= options.Options.DefaultMaxManualSnapshotCount { - return nil, httperrors.NewBadRequestError("Disk %s snapshot full, cannot take any more", diskId) - } - pendingUsage := &SQuota{Snapshot: 1} - err = QuotaManager.CheckSetPendingQuota(ctx, userCred, self.ProjectId, pendingUsage) - if err != nil { - return nil, httperrors.NewBadRequestError("Check set pending quota error %s", err) - } - snapshot, err := SnapshotManager.CreateSnapshot(ctx, userCred, MANUAL, diskId, self.Id, "", name) - QuotaManager.CancelPendingUsage(ctx, userCred, self.ProjectId, nil, pendingUsage) - if err != nil { - return nil, err - } - err = self.StartDiskSnapshot(ctx, userCred, diskId, snapshot.Id) - return nil, err - } else { - snapshot, err := SnapshotManager.CreateSnapshot(ctx, userCred, MANUAL, diskId, self.Id, "", name) - if err != nil { - return nil, err - } - err = self.StartDiskSnapshot(ctx, userCred, diskId, snapshot.Id) - return nil, err - } - -} - -func (self *SGuest) StartDiskSnapshot(ctx context.Context, userCred mcclient.TokenCredential, diskId, snapshotId string) error { - self.SetStatus(userCred, VM_START_SNAPSHOT, "StartDiskSnapshot") - params := jsonutils.NewDict() - params.Set("disk_id", jsonutils.NewString(diskId)) - params.Set("snapshot_id", jsonutils.NewString(snapshotId)) - return self.GetDriver().StartGuestDiskSnapshotTask(ctx, userCred, self, params) -} - -func (self *SGuest) AllowPerformStop(ctx context.Context, - userCred mcclient.TokenCredential, - query jsonutils.JSONObject, - data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) PerformStop(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, - data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - isForce := jsonutils.QueryBoolean(data, "is_force", false) - if utils.IsInStringArray(self.Status, []string{VM_RUNNING, VM_STOP_FAILED}) || (isForce && self.Status == VM_STOPPING) { - return nil, self.StartGuestStopTask(ctx, userCred, isForce, "") - } else { - return nil, httperrors.NewInvalidStatusError("Cannot do start server in status %s", self.Status) - } -} - -func (self *SGuest) AllowPerformRestart(ctx context.Context, - userCred mcclient.TokenCredential, - query jsonutils.JSONObject, - data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) PerformRestart(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - isForce := jsonutils.QueryBoolean(data, "is_force", false) - if utils.IsInStringArray(self.Status, []string{VM_RUNNING, VM_STOP_FAILED}) || (isForce && self.Status == VM_STOPPING) { - return nil, self.GetDriver().StartGuestRestartTask(self, ctx, userCred, isForce, "") - } else { - return nil, httperrors.NewInvalidStatusError("Cannot do restart server in status %s", self.Status) - } -} - /* TODO @@ -4320,137 +3054,6 @@ def start_guest_sched_start_task(self, user_cred, data=None, */ -func (self *SGuest) AllowGetDetailsVnc(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) GetDetailsVnc(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if utils.IsInStringArray(self.Status, []string{VM_RUNNING, VM_SNAPSHOT_STREAM}) { - host := self.GetHost() - if host == nil { - return nil, httperrors.NewInternalServerError("Host missing") - } - retval, err := self.GetDriver().GetGuestVncInfo(userCred, self, host) - if err != nil { - return nil, err - } - retval.Add(jsonutils.NewString(self.Id), "id") - return retval, nil - } else { - return jsonutils.NewDict(), nil - } -} - -func (self *SGuest) AllowGetDetailsMonitor(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) GetDetailsMonitor(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if utils.IsInStringArray(self.Status, []string{VM_RUNNING, VM_SNAPSHOT_STREAM}) { - cmd, err := query.GetString("command") - if err != nil { - return nil, err - } - return self.SendMonitorCommand(ctx, userCred, cmd) - } - return nil, httperrors.NewInvalidStatusError("Cannot send command in status %s", self.Status) -} - -func (self *SGuest) AllowGetDetailsDesc(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) GetDetailsDesc(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) { - host := self.GetHost() - if host == nil { - return nil, httperrors.NewInvalidStatusError("No host for server") - } - desc := self.GetDriver().GetJsonDescAtHost(ctx, self, host) - return desc, nil -} - -func (self *SGuest) AllowPerformSendkeys(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) PerformSendkeys(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if self.Hypervisor != HYPERVISOR_KVM { - return nil, httperrors.NewUnsupportOperationError("Not allow for hypervisor %s", self.Hypervisor) - } - if self.Status != VM_RUNNING { - return nil, httperrors.NewInvalidStatusError("Cannot send keys in status %s", self.Status) - } - keys, err := data.GetString("keys") - if err != nil { - return nil, err - } - err = self.VerifySendKeys(keys) - if err != nil { - return nil, httperrors.NewBadRequestError(err.Error()) - } - cmd := fmt.Sprintf("sendkey %s", keys) - duration, err := data.Int("duration") - if err == nil { - cmd = fmt.Sprintf("%s %d", cmd, duration) - } - _, err = self.SendMonitorCommand(ctx, userCred, cmd) - return nil, err -} - -func (self *SGuest) VerifySendKeys(keyStr string) error { - keys := strings.Split(keyStr, "-") - for _, key := range keys { - if !self.IsLegalKey(key) { - return fmt.Errorf("Unknown key '%s'", key) - } - } - return nil -} - -func (self *SGuest) IsLegalKey(key string) bool { - singleKeys := "1234567890abcdefghijklmnopqrstuvwxyz" - legalKeys := []string{"ctrl", "ctrl_r", "alt", "alt_r", "shift", "shift_r", - "delete", "esc", "insert", "print", "spc", - "f1", "f2", "f3", "f4", "f5", "f6", - "f7", "f8", "f9", "f10", "f11", "f12", - "home", "pgup", "pgdn", "end", - "up", "down", "left", "right", - "tab", "minus", "equal", "backspace", "backslash", - "bracket_left", "bracket_right", "backslash", - "semicolon", "apostrophe", "grave_accent", "ret", - "comma", "dot", "slash", - "caps_lock", "num_lock", "scroll_lock"} - if len(key) > 1 && !utils.IsInStringArray(key, legalKeys) { - return false - } else if len(key) == 1 && !strings.Contains(singleKeys, key) { - return false - } - return true -} - -func (self *SGuest) SendMonitorCommand(ctx context.Context, userCred mcclient.TokenCredential, cmd string) (jsonutils.JSONObject, error) { - host := self.GetHost() - url := fmt.Sprintf("%s/servers/%s/monitor", host.ManagerUri, self.Id) - header := http.Header{} - header.Add("X-Auth-Token", userCred.GetTokenString()) - body := jsonutils.NewDict() - body.Add(jsonutils.NewString(cmd), "cmd") - _, res, err := httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "POST", url, header, body, false) - if err != nil { - return nil, err - } - ret := res.(*jsonutils.JSONDict) - return ret, nil -} - -func (self *SGuest) GetKeypairPublicKey() string { - keypair := self.getKeypair() - if keypair != nil { - return keypair.PublicKey - } - return "" -} - func (manager *SGuestManager) GetIpInProjectWithName(projectId, name string, isExitOnly bool) []string { guestnics := GuestnetworkManager.Query().SubQuery() guests := manager.Query().SubQuery() @@ -4629,141 +3232,6 @@ func (self *SGuest) GetIVM() (cloudprovider.ICloudVM, error) { return ihost.GetIVMById(self.ExternalId) } -func (self *SGuest) AllowPerformAssociateEip(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) PerformAssociateEip(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if !utils.IsInStringArray(self.Status, []string{VM_READY, VM_RUNNING}) { - return nil, httperrors.NewInvalidStatusError("cannot associate eip in status %s", self.Status) - } - - eip, err := self.GetEip() - if err != nil { - log.Errorf("Fail to get Eip %s", err) - return nil, httperrors.NewGeneralError(err) - } - if eip != nil { - return nil, httperrors.NewInvalidStatusError("already associate with eip") - } - eipStr := jsonutils.GetAnyString(data, []string{"eip", "eip_id"}) - if len(eipStr) == 0 { - return nil, httperrors.NewInputParameterError("missing eip or eip_id") - } - eipObj, err := ElasticipManager.FetchByIdOrName(userCred, eipStr) - if err != nil { - if err == sql.ErrNoRows { - return nil, httperrors.NewResourceNotFoundError("eip %s not found", eipStr) - } else { - return nil, httperrors.NewGeneralError(err) - } - } - - eip = eipObj.(*SElasticip) - eipRegion := eip.GetRegion() - instRegion := self.getRegion() - - if eip.Mode == EIP_MODE_INSTANCE_PUBLICIP { - return nil, httperrors.NewUnsupportOperationError("fixed eip cannot be associated") - } - - eipVm := eip.GetAssociateVM() - if eipVm != nil { - return nil, httperrors.NewConflictError("eip has been associated") - } - - if eipRegion.Id != instRegion.Id { - return nil, httperrors.NewInputParameterError("cannot associate eip and instance in different region") - } - - host := self.GetHost() - if host == nil { - return nil, httperrors.NewInputParameterError("server host is not found???") - } - - if host.ManagerId != eip.ManagerId { - return nil, httperrors.NewInputParameterError("cannot associate eip and instance in different provider") - } - - self.SetStatus(userCred, VM_ASSOCIATE_EIP, "associate eip") - - params := jsonutils.NewDict() - params.Add(jsonutils.NewString(self.ExternalId), "instance_external_id") - params.Add(jsonutils.NewString(self.Id), "instance_id") - params.Add(jsonutils.NewString(EIP_ASSOCIATE_TYPE_SERVER), "instance_type") - - err = eip.StartEipAssociateTask(ctx, userCred, params) - - return nil, err -} - -func (self *SGuest) AllowPerformDissociateEip(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) PerformDissociateEip(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - eip, err := self.GetEip() - if err != nil { - log.Errorf("Fail to get Eip %s", err) - return nil, httperrors.NewGeneralError(err) - } - if eip == nil { - return nil, httperrors.NewInvalidStatusError("No eip to dissociate") - } - - self.SetStatus(userCred, VM_DISSOCIATE_EIP, "associate eip") - - err = eip.StartEipDissociateTask(ctx, userCred, "") - if err != nil { - log.Errorf("fail to start dissociate task %s", err) - return nil, httperrors.NewGeneralError(err) - } - return nil, nil -} - -func (self *SGuest) AllowPerformCreateEip(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) PerformCreateEip(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - bw, err := data.Int("bandwidth") - if err != nil { - return nil, httperrors.NewInputParameterError("Missing bandwidth") - } - - chargeType, _ := data.GetString("charge_type") - if len(chargeType) == 0 { - chargeType = EIP_CHARGE_TYPE_DEFAULT - } - - if len(self.ExternalId) == 0 { - return nil, httperrors.NewInvalidStatusError("Not a managed VM") - } - host := self.GetHost() - if host == nil { - return nil, httperrors.NewInvalidStatusError("No host???") - } - - _, err = host.GetDriver() - if err != nil { - return nil, httperrors.NewInvalidStatusError("No valid cloud provider") - } - - region := host.GetRegion() - if region == nil { - return nil, httperrors.NewInvalidStatusError("No cloudregion???") - } - - err = ElasticipManager.allocateEipAndAssociateVM(ctx, userCred, self, int(bw), chargeType, host.ManagerId, region.Id) - if err != nil { - return nil, httperrors.NewGeneralError(err) - } - - self.SetStatus(userCred, VM_ASSOCIATE_EIP, "allocate and associate EIP") - - return nil, nil -} - func (self *SGuest) DeleteEip(ctx context.Context, userCred mcclient.TokenCredential) error { eip, err := self.GetEip() if err != nil { @@ -4824,28 +3292,6 @@ func (self *SGuest) setUserData(ctx context.Context, userCred mcclient.TokenCred return nil } -func (self *SGuest) AllowPerformUserData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) -} - -func (self *SGuest) PerformUserData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - userData, err := data.GetString("user_data") - if err != nil { - return nil, httperrors.NewInputParameterError("missing user_data %s", err) - } - err = self.setUserData(ctx, userCred, userData) - if err != nil { - return nil, httperrors.NewGeneralError(err) - } - if len(self.HostId) > 0 { - err = self.StartSyncTask(ctx, userCred, false, "") - if err != nil { - return nil, httperrors.NewGeneralError(err) - } - } - return nil, nil -} - func (self *SGuest) getSchedDesc() jsonutils.JSONObject { desc := jsonutils.NewDict() @@ -4945,3 +3391,13 @@ func (self *SGuest) FillNetSchedDesc(desc *jsonutils.JSONDict) { guestNetworks[i].NetworkId, guestNetworks[i].IpAddr))) } } + +func (self *SGuest) GuestDisksHasSnapshot() bool { + guestDisks := self.GetDisks() + for i := 0; i < len(guestDisks); i++ { + if SnapshotManager.GetDiskSnapshotCount(guestDisks[i].DiskId) > 0 { + return true + } + } + return false +} diff --git a/pkg/compute/models/hosts.go b/pkg/compute/models/hosts.go index 79028ba701..056ded6afa 100644 --- a/pkg/compute/models/hosts.go +++ b/pkg/compute/models/hosts.go @@ -1686,7 +1686,9 @@ func (self *SHost) getGuestsResource(status string) *SHostGuestResourceUsage { q := guests.Query(sqlchemy.COUNT("guest_count"), sqlchemy.SUM("guest_vcpu_count", guests.Field("vcpu_count")), sqlchemy.SUM("guest_vmem_size", guests.Field("vmem_size"))) - q = q.Equals("host_id", self.Id) + cond := sqlchemy.OR(sqlchemy.Equals(q.Field("host_id"), self.Id), + sqlchemy.Equals(q.Field("backup_host_id"), self.Id)) + q = q.Filter(cond) if len(status) > 0 { q = q.Equals("status", status) } diff --git a/pkg/compute/tasks/disk_base_task.go b/pkg/compute/tasks/disk_base_task.go index 083f78d85d..872dc93587 100644 --- a/pkg/compute/tasks/disk_base_task.go +++ b/pkg/compute/tasks/disk_base_task.go @@ -34,8 +34,7 @@ func (self *SDiskBaseTask) finalReleasePendingUsage(ctx context.Context) { } } -func (self *SDiskBaseTask) CleanHostSchedCache(disk *models.SDisk) { - storage := disk.GetStorage() +func (self *SDiskBaseTask) CleanStorageSchedCache(storage *models.SStorage) { if hosts := storage.GetAllAttachingHosts(); hosts == nil { log.Errorf("get attaching host error") } else { @@ -46,3 +45,12 @@ func (self *SDiskBaseTask) CleanHostSchedCache(disk *models.SDisk) { } } } + +func (self *SDiskBaseTask) CleanHostSchedCache(disk *models.SDisk) { + storage := disk.GetStorage() + self.CleanStorageSchedCache(storage) + if len(disk.BackupStorageId) > 0 { + bkStorage := models.StorageManager.FetchStorageById(disk.BackupStorageId) + self.CleanStorageSchedCache(bkStorage) + } +} diff --git a/pkg/compute/tasks/disk_batch_create_task.go b/pkg/compute/tasks/disk_batch_create_task.go index 26b4d273fd..d7cbd8e9c4 100644 --- a/pkg/compute/tasks/disk_batch_create_task.go +++ b/pkg/compute/tasks/disk_batch_create_task.go @@ -13,7 +13,7 @@ import ( ) type DiskBatchCreateTask struct { - taskman.STask + SSchedTask } func init() { @@ -44,7 +44,8 @@ func (self *DiskBatchCreateTask) OnInit(ctx context.Context, objs []db.IStandalo StartScheduleObjects(ctx, self, toSchedDisks) } -func (self *DiskBatchCreateTask) OnScheduleFailCallback(obj IScheduleModel) { +func (self *DiskBatchCreateTask) OnScheduleFailCallback(obj IScheduleModel, reason string) { + self.SSchedTask.OnScheduleFailCallback(obj, reason) disk := obj.(*models.SDisk) log.Errorf("Schedule disk %s failed", disk.Name) } diff --git a/pkg/compute/tasks/disk_create_task.go b/pkg/compute/tasks/disk_create_task.go index be0e2635ed..a4a101c83e 100644 --- a/pkg/compute/tasks/disk_create_task.go +++ b/pkg/compute/tasks/disk_create_task.go @@ -2,6 +2,7 @@ package tasks import ( "context" + "fmt" "yunion.io/x/jsonutils" "yunion.io/x/log" @@ -15,23 +16,31 @@ type DiskCreateTask struct { SDiskBaseTask } -func init() { - taskman.RegisterTask(DiskCreateTask{}) -} - func (self *DiskCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { disk := obj.(*models.SDisk) storagecache := disk.GetStorage().GetStoragecache() imageId := disk.GetTemplateId() if len(imageId) > 0 { - self.SetStage("on_storage_cache_image_complete", nil) + if len(disk.BackupStorageId) > 0 { + self.SetStage("OnMasterStorageCacheImageComplete", nil) + } else { + self.SetStage("OnStorageCacheImageComplete", nil) + } storagecache.StartImageCacheTask(ctx, self.UserCred, imageId, false, self.GetTaskId()) } else { self.OnStorageCacheImageComplete(ctx, disk, nil) } } +func (self *DiskCreateTask) OnMasterStorageCacheImageComplete(ctx context.Context, disk *models.SDisk, data jsonutils.JSONObject) { + storage := models.StorageManager.FetchStorageById(disk.BackupStorageId) + storagecache := storage.GetStoragecache() + imageId := disk.GetTemplateId() + self.SetStage("OnStorageCacheImageComplete", nil) + storagecache.StartImageCacheTask(ctx, self.UserCred, imageId, false, self.GetTaskId()) +} + func (self *DiskCreateTask) OnStorageCacheImageComplete(ctx context.Context, disk *models.SDisk, data jsonutils.JSONObject) { rebuild, _ := self.GetParams().Bool("rebuild") snapshot, _ := self.GetParams().GetString("snapshot") @@ -42,12 +51,34 @@ func (self *DiskCreateTask) OnStorageCacheImageComplete(ctx context.Context, dis host := storage.GetMasterHost() db.OpsLog.LogEvent(disk, db.ACT_ALLOCATING, disk.GetShortDesc(), self.GetUserCred()) disk.SetStatus(self.GetUserCred(), models.DISK_STARTALLOC, "") - self.SetStage("on_disk_ready", nil) + if len(disk.BackupStorageId) > 0 { + self.SetStage("OnMasterStorageCreateDiskComplete", nil) + } else { + self.SetStage("OnDiskReady", nil) + } if err := disk.StartAllocate(ctx, host, storage, self.GetTaskId(), self.GetUserCred(), rebuild, snapshot, self); err != nil { self.OnStartAllocateFailed(ctx, disk, jsonutils.NewString(err.Error())) } } +func (self *DiskCreateTask) OnMasterStorageCreateDiskComplete(ctx context.Context, disk *models.SDisk, data jsonutils.JSONObject) { + rebuild, _ := self.GetParams().Bool("rebuild") + snapshot, _ := self.GetParams().GetString("snapshot") + storage := models.StorageManager.FetchStorageById(disk.BackupStorageId) + host := storage.GetMasterHost() + db.OpsLog.LogEvent(disk, db.ACT_BACKUP_ALLOCATING, disk.GetShortDesc(), self.GetUserCred()) + disk.SetStatus(self.UserCred, models.DISK_BACKUP_STARTALLOC, "") + self.SetStage("OnDiskReady", nil) + if err := disk.StartAllocate(ctx, host, storage, self.GetTaskId(), self.GetUserCred(), rebuild, snapshot, self); err != nil { + self.OnBackupAllocateFailed(ctx, disk, jsonutils.NewString(fmt.Sprintf("Backup disk alloctate failed: %s", err.Error()))) + } +} + +func (self *DiskCreateTask) OnBackupAllocateFailed(ctx context.Context, disk *models.SDisk, data jsonutils.JSONObject) { + disk.SetStatus(self.UserCred, models.DISK_BACKUP_ALLOC_FAILED, data.String()) + self.SetStageFailed(ctx, data.String()) +} + func (self *DiskCreateTask) OnStartAllocateFailed(ctx context.Context, disk *models.SDisk, data jsonutils.JSONObject) { disk.SetStatus(self.UserCred, models.DISK_ALLOC_FAILED, data.String()) self.SetStageFailed(ctx, data.String()) @@ -74,3 +105,33 @@ func (self *DiskCreateTask) OnDiskReadyFailed(ctx context.Context, disk *models. disk.SetStatus(self.UserCred, models.DISK_ALLOC_FAILED, data.String()) self.SetStageFailed(ctx, data.String()) } + +type DiskCreateBackupTask struct { + DiskCreateTask +} + +func (self *DiskCreateBackupTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + disk := obj.(*models.SDisk) + storage := models.StorageManager.FetchStorageById(disk.BackupStorageId) + storagecache := storage.GetStoragecache() + imageId := disk.GetTemplateId() + if len(imageId) > 0 { + self.SetStage("OnMasterStorageCreateDiskComplete", nil) + storagecache.StartImageCacheTask(ctx, self.UserCred, imageId, false, self.GetTaskId()) + } else { + self.OnMasterStorageCreateDiskComplete(ctx, disk, nil) + } +} + +func (self *DiskCreateBackupTask) OnDiskReady(ctx context.Context, disk *models.SDisk, data jsonutils.JSONObject) { + bkStorage := models.StorageManager.FetchStorageById(disk.BackupStorageId) + self.CleanStorageSchedCache(bkStorage) + disk.SetStatus(self.UserCred, models.DISK_READY, "") + db.OpsLog.LogEvent(disk, db.ACT_BACKUP_ALLOCATE, disk.GetShortDesc(), self.UserCred) + self.SetStageComplete(ctx, nil) +} + +func init() { + taskman.RegisterTask(DiskCreateTask{}) + taskman.RegisterTask(DiskCreateBackupTask{}) +} diff --git a/pkg/compute/tasks/disk_delete_task.go b/pkg/compute/tasks/disk_delete_task.go index ff6c4f76c1..590a6e264a 100644 --- a/pkg/compute/tasks/disk_delete_task.go +++ b/pkg/compute/tasks/disk_delete_task.go @@ -2,13 +2,13 @@ package tasks import ( "context" + "fmt" "yunion.io/x/jsonutils" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/compute/options" - "yunion.io/x/onecloud/pkg/httperrors" ) type DiskDeleteTask struct { @@ -51,15 +51,30 @@ func (self *DiskDeleteTask) startDeleteDisk(ctx context.Context, disk *models.SD if isPurge { self.OnGuestDiskDeleteSucc(ctx, disk, nil) } else { - self.SetStage("on_guest_disk_delete_succ", nil) + if len(disk.BackupStorageId) > 0 { + self.SetStage("OnMasterStorageDeleteDiskSucc", nil) + } else { + self.SetStage("OnGuestDiskDeleteSucc", nil) + } if host == nil { - self.OnGuestDiskDeleteFailed(ctx, disk, httperrors.NewNotFoundError("fail to find master host")) + self.OnGuestDiskDeleteFailed(ctx, disk, fmt.Errorf("fail to find master host")) } else if err := host.GetHostDriver().RequestDeallocateDiskOnHost(host, storage, disk, self); err != nil { self.OnGuestDiskDeleteFailed(ctx, disk, err) } } } +func (self *DiskDeleteTask) OnMasterStorageDeleteDiskSucc(ctx context.Context, disk *models.SDisk, data jsonutils.JSONObject) { + self.SetStage("OnGuestDiskDeleteSucc", nil) + storage := models.StorageManager.FetchStorageById(disk.BackupStorageId) + host := storage.GetMasterHost() + if host == nil { + self.OnGuestDiskDeleteFailed(ctx, disk, fmt.Errorf("backup storage %s fail to find master host", disk.BackupStorageId)) + } else if err := host.GetHostDriver().RequestDeallocateDiskOnHost(host, storage, disk, self); err != nil { + self.OnGuestDiskDeleteFailed(ctx, disk, err) + } +} + func (self *DiskDeleteTask) startPendingDeleteDisk(ctx context.Context, disk *models.SDisk) { disk.DoPendingDelete(ctx, self.UserCred) self.SetStageComplete(ctx, nil) diff --git a/pkg/compute/tasks/guest_backup_tasks.go b/pkg/compute/tasks/guest_backup_tasks.go new file mode 100644 index 0000000000..4bc6425fc4 --- /dev/null +++ b/pkg/compute/tasks/guest_backup_tasks.go @@ -0,0 +1,244 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/pkg/utils" +) + +type GuestSwitchToBackupTask struct { + SGuestBaseTask +} + +/* +0. ensure master guest stopped +1. stop backup guest +2. switch guest master host to backup host +3. start guest with new master +*/ +func (self *GuestSwitchToBackupTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + guest := obj.(*models.SGuest) + host := guest.GetHost() + self.Params.Set("is_force", jsonutils.JSONTrue) + self.SetStage("OnEnsureMasterGuestStoped", nil) + err := guest.GetDriver().RequestStopOnHost(ctx, guest, host, self) + if err != nil { + // In case of master host crash + self.OnEnsureMasterGuestStoped(ctx, guest, nil) + } +} + +func (self *GuestSwitchToBackupTask) OnEnsureMasterGuestStoped(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + backupHost := models.HostManager.FetchHostById(guest.BackupHostId) + self.Params.Set("is_force", jsonutils.JSONTrue) + self.SetStage("OnBackupGuestStoped", nil) + err := guest.GetDriver().RequestStopOnHost(ctx, guest, backupHost, self) + if err != nil { + self.SetStageFailed(ctx, fmt.Sprintf("Stop backup guest error: %s", err)) + } +} + +func (self *GuestSwitchToBackupTask) OnBackupGuestStoped(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + disks := guest.GetDisks() + for i := 0; i < len(disks); i++ { + disk := disks[i].GetDisk() + err := disk.SwitchToBackup() + if err != nil { + if i > 0 { + for j := 0; j < i; j++ { + disk = disks[j].GetDisk() + disk.SwitchToBackup() + } + } + db.OpsLog.LogEvent(guest, db.ACT_SWITCH_FAILED, fmt.Sprintf("Switch to backup disk error: %s", err), self.UserCred) + self.SetStageFailed(ctx, fmt.Sprintf("Switch to backup disk error: %s", err)) + } + } + err := guest.SwitchToBackup() + if err != nil { + db.OpsLog.LogEvent(guest, db.ACT_SWITCH_FAILED, fmt.Sprintf("Switch to backup guest error: %s", err), self.UserCred) + self.SetStageFailed(ctx, fmt.Sprintf("Switch to backup guest error: %s", err)) + } + db.OpsLog.LogEvent(guest, db.ACT_SWITCHED, fmt.Sprintf("Switch to backup guest error: %s", err), self.UserCred) + self.SetStage("OnNewMasterStarted", nil) + guest.StartGueststartTask(ctx, self.UserCred, nil, self.GetTaskId()) +} + +func (self *GuestSwitchToBackupTask) OnNewMasterStarted(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + self.SetStageComplete(ctx, nil) +} + +/********************* GuestStartAndSyncToBackupTask *********************/ + +type GuestStartAndSyncToBackupTask struct { + SGuestBaseTask +} + +func (self *GuestStartAndSyncToBackupTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + guest := obj.(*models.SGuest) + self.SetStage("OnCheckTemplete", nil) + self.checkTemplete(ctx, guest) +} + +func (self *GuestStartAndSyncToBackupTask) checkTemplete(ctx context.Context, guest *models.SGuest) { + diskCat := guest.CategorizeDisks() + if diskCat.Root != nil && len(diskCat.Root.GetTemplateId()) > 0 { + err := guest.GetDriver().CheckDiskTemplateOnStorage(ctx, self.UserCred, diskCat.Root.GetTemplateId(), + diskCat.Root.BackupStorageId, self) + if err != nil { + self.SetStageFailed(ctx, err.Error()) + } + } else { + self.OnCheckTemplete(ctx, guest, nil) + } +} + +func (self *GuestStartAndSyncToBackupTask) OnCheckTemplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + self.SetStage("OnStartBackupGuest", nil) + host := models.HostManager.FetchHostById(guest.BackupHostId) + guest.GetDriver().RequestStartOnHost(ctx, guest, host, self.UserCred, self) +} + +func (self *GuestStartAndSyncToBackupTask) OnStartBackupGuest(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + db.OpsLog.LogEvent(guest, db.ACT_BACKUP_START, "", self.UserCred) + if utils.IsInStringArray(guest.Status, models.VM_RUNNING_STATUS) { + self.SetStage("OnRequestSyncToBackup", nil) + err := guest.GetDriver().RequestSyncToBackup(ctx, guest, self) + if err != nil { + self.SetStageFailed(ctx, fmt.Sprintf("Guest Request Sync to backup failed %s", err)) + } + } else { + self.SetStageComplete(ctx, nil) + } +} + +func (self *GuestStartAndSyncToBackupTask) OnStartBackupGuestFailed(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + db.OpsLog.LogEvent(guest, db.ACT_BACKUP_START_FAILED, "", self.UserCred) + self.SetStageFailed(ctx, data.String()) +} + +func (self *GuestStartAndSyncToBackupTask) OnRequestSyncToBackup(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + self.SetStageComplete(ctx, nil) +} + +type GuestCreateBackupTask struct { + SSchedTask +} + +func (self *GuestCreateBackupTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + StartScheduleObjects(ctx, self, []db.IStandaloneModel{obj}) +} + +func (self *GuestCreateBackupTask) OnStartSchedule(obj IScheduleModel) { + guest := obj.(*models.SGuest) + guest.SetStatus(self.UserCred, models.VM_BACKUP_CREATING, "") + db.OpsLog.LogEvent(guest, db.ACT_START_CREATE_BACKUP, "", self.UserCred) +} + +func (self *GuestCreateBackupTask) GetSchedParams() *jsonutils.JSONDict { + obj := self.GetObject() + guest := obj.(*models.SGuest) + schedDesc := guest.ToSchedDesc() + if self.Params.Contains("prefer_host_id") { + preferHostId, _ := self.Params.Get("prefer_host_id") + schedDesc.Set("prefer_host_id", preferHostId) + } + return schedDesc +} + +func (self *GuestCreateBackupTask) OnScheduleFailCallback(obj IScheduleModel, reason string) { + // do nothing +} + +func (self *GuestCreateBackupTask) OnScheduleFailed(ctx context.Context, reason string) { + obj := self.GetObject() + guest := obj.(*models.SGuest) + self.TaskFailed(ctx, guest, reason) +} + +func (self *GuestCreateBackupTask) SaveScheduleResult(ctx context.Context, obj IScheduleModel, targetHostId string) { + guest := obj.(*models.SGuest) + targetHost := models.HostManager.FetchHostById(targetHostId) + if targetHost == nil { + self.TaskFailed(ctx, guest, "target host not found?") + return + } + guest.SetHostIdWithBackup(guest.HostId, targetHostId) + db.OpsLog.LogEvent(guest, db.ACT_CREATE_BACKUP, fmt.Sprintf("guest backup start create on host %s", targetHostId), self.UserCred) + + // backup disk only support disk backend local + storage := guest.GetDriver().ChooseHostStorage(targetHost, models.STORAGE_LOCAL) + if storage == nil { + self.TaskFailed(ctx, guest, "Get backup storage error") + return + } + self.StartCreateBackupDisks(ctx, guest, storage.Id) +} + +func (self *GuestCreateBackupTask) StartCreateBackupDisks(ctx context.Context, guest *models.SGuest, storageId string) { + guestDisks := guest.GetDisks() + for i := 0; i < len(guestDisks); i++ { + disk := guestDisks[i].GetDisk() + disk.GetModelManager().TableSpec().Update(disk, func() error { + disk.BackupStorageId = storageId + return nil + }) + } + self.SetStage("OnCreateBackupDisks", nil) + err := guest.CreateBackupDisks(ctx, self.UserCred, self.GetTaskId()) + if err != nil { + self.TaskFailed(ctx, guest, err.Error()) + } +} + +func (self *GuestCreateBackupTask) OnCreateBackupDisks(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + self.SetStage("OnCreateBackup", nil) + guest.StartCreateBackup(ctx, self.UserCred, self.GetTaskId(), nil) +} + +func (self *GuestCreateBackupTask) OnCreateBackupDisksFailed(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + self.TaskFailed(ctx, guest, fmt.Sprintf("Create Backup Disks failed %s", data.String())) +} + +func (self *GuestCreateBackupTask) OnCreateBackupFailed(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + self.TaskFailed(ctx, guest, fmt.Sprintf("Deploy Backup failed %s", data.String())) +} + +func (self *GuestCreateBackupTask) OnCreateBackup(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + guestStatus, _ := self.Params.GetString("guest_status") + guest.SetStatus(self.UserCred, guestStatus, "") + if utils.IsInStringArray(guestStatus, models.VM_RUNNING_STATUS) { + self.OnGuestStart(ctx, guest, nil) + } else { + self.SetStageComplete(ctx, nil) + } +} + +func (self *GuestCreateBackupTask) OnGuestStart(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + self.SetStage("OnSyncToBackup", nil) + err := guest.GuestStartAndSyncToBackup(ctx, self.UserCred, nil, self.GetTaskId()) + if err != nil { + self.SetStageFailed(ctx, fmt.Sprintf("Guest sycn to backup error %s", err.Error())) + } +} + +func (self *GuestCreateBackupTask) OnSyncToBackup(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + self.SetStageComplete(ctx, nil) +} + +func (self *GuestCreateBackupTask) TaskFailed(ctx context.Context, guest *models.SGuest, reason string) { + guest.SetStatus(self.UserCred, models.VM_BACKUP_CREATE_FAILED, reason) + db.OpsLog.LogEvent(guest, db.ACT_CREATE_BACKUP_FAILED, reason, self.UserCred) + self.SetStageFailed(ctx, reason) +} + +func init() { + taskman.RegisterTask(GuestSwitchToBackupTask{}) + taskman.RegisterTask(GuestStartAndSyncToBackupTask{}) + taskman.RegisterTask(GuestCreateBackupTask{}) +} diff --git a/pkg/compute/tasks/guest_batch_create_task.go b/pkg/compute/tasks/guest_batch_create_task.go index 8df1e5cbab..cdce52c9af 100644 --- a/pkg/compute/tasks/guest_batch_create_task.go +++ b/pkg/compute/tasks/guest_batch_create_task.go @@ -13,7 +13,7 @@ import ( ) type GuestBatchCreateTask struct { - taskman.STask + SSchedTask } func init() { @@ -24,13 +24,20 @@ func (self *GuestBatchCreateTask) OnInit(ctx context.Context, objs []db.IStandal StartScheduleObjects(ctx, self, objs) } -func (self *GuestBatchCreateTask) OnScheduleFailCallback(obj IScheduleModel) { +func (self *GuestBatchCreateTask) OnScheduleFailCallback(obj IScheduleModel, reason string) { + self.SSchedTask.OnScheduleFailCallback(obj, reason) guest := obj.(*models.SGuest) if guest.DisableDelete.IsTrue() { guest.SetDisableDelete(false) } } +func (self *GuestBatchCreateTask) SaveScheduleResultWithBackup(ctx context.Context, obj IScheduleModel, master, slave string) { + guest := obj.(*models.SGuest) + guest.SetHostIdWithBackup(master, slave) + self.SaveScheduleResult(ctx, obj, master) +} + func (self *GuestBatchCreateTask) SaveScheduleResult(ctx context.Context, obj IScheduleModel, hostId string) { var err error guest := obj.(*models.SGuest) @@ -39,7 +46,10 @@ func (self *GuestBatchCreateTask) SaveScheduleResult(ctx context.Context, obj IS if err != nil { log.Errorf("GetPendingUsage fail %s", err) } - guest.SetHostId(hostId) + if len(guest.HostId) == 0 { + guest.SetHostId(hostId) + } + quotaCpuMem := models.SQuota{Cpu: int(guest.VcpuCount), Memory: guest.VmemSize} err = models.QuotaManager.CancelPendingUsage(ctx, self.UserCred, guest.ProjectId, &pendingUsage, "aCpuMem) self.SetPendingUsage(&pendingUsage) diff --git a/pkg/compute/tasks/guest_create_disk_task.go b/pkg/compute/tasks/guest_create_disk_task.go index 7514bb17de..b4f221b1d3 100644 --- a/pkg/compute/tasks/guest_create_disk_task.go +++ b/pkg/compute/tasks/guest_create_disk_task.go @@ -226,7 +226,37 @@ func (self *ManagedGuestCreateDiskTask) AttachManagedDisks(ctx context.Context, self.SetStageComplete(ctx, nil) } +type GuestCreateBackupDisksTask struct { + SGuestBaseTask +} + +func (self *GuestCreateBackupDisksTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + guest := obj.(*models.SGuest) + self.CreateBackups(ctx, guest, nil) +} + +func (self *GuestCreateBackupDisksTask) CreateBackups(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + body := jsonutils.NewDict() + var diskIndex int64 = 0 + if self.Params.Contains("disk_index") { + diskIndex, _ = self.Params.Int("disk_index") + } + body.Set("disk_index", jsonutils.NewInt(diskIndex+1)) + self.SetStage("CreateBackups", body) + + guestDisks := guest.GetDisks() + if int(diskIndex) == len(guestDisks) { + self.SetStageComplete(ctx, nil) + } else { + err := guestDisks[diskIndex].GetDisk().StratCreateBackupTask(ctx, self.UserCred, self.GetTaskId()) + if err != nil { + self.SetStageFailed(ctx, err.Error()) + } + } +} + func init() { + taskman.RegisterTask(GuestCreateBackupDisksTask{}) taskman.RegisterTask(GuestCreateDiskTask{}) taskman.RegisterTask(KVMGuestCreateDiskTask{}) taskman.RegisterTask(ManagedGuestCreateDiskTask{}) diff --git a/pkg/compute/tasks/guest_create_task.go b/pkg/compute/tasks/guest_create_task.go index 0906f65072..82dec22877 100644 --- a/pkg/compute/tasks/guest_create_task.go +++ b/pkg/compute/tasks/guest_create_task.go @@ -42,7 +42,7 @@ func (self *GuestCreateTask) OnWaitGuestNetworksReady(ctx context.Context, obj d func (self *GuestCreateTask) OnGuestNetworkReady(ctx context.Context, guest *models.SGuest) { guest.SetStatus(self.UserCred, models.VM_CREATE_DISK, "") - self.SetStage("on_disk_prepared", nil) + self.SetStage("OnDiskPrepared", nil) guest.GetDriver().RequestGuestCreateAllDisks(ctx, guest, self) } @@ -58,7 +58,7 @@ func (self *GuestCreateTask) OnDiskPrepared(ctx context.Context, obj db.IStandal guest := obj.(*models.SGuest) cdrom, _ := self.Params.GetString("cdrom") if len(cdrom) > 0 { - self.SetStage("on_cdrom_prepared", nil) + self.SetStage("OnCdromPrepared", nil) guest.GetDriver().RequestGuestCreateInsertIso(ctx, cdrom, guest, self) } else { self.OnCdromPrepared(ctx, obj, data) @@ -83,7 +83,7 @@ func (self *GuestCreateTask) OnCdromPreparedFailed(ctx context.Context, obj db.I } func (self *GuestCreateTask) StartDeployGuest(ctx context.Context, guest *models.SGuest) { - self.SetStage("on_deploy_guest_desc_complete", nil) + self.SetStage("OnDeployGuestDescComplete", nil) guest.StartGuestDeployTask(ctx, self.UserCred, self.Params, "create", self.GetId()) } diff --git a/pkg/compute/tasks/guest_delete_task.go b/pkg/compute/tasks/guest_delete_task.go index b6f7293118..73d527cdb6 100644 --- a/pkg/compute/tasks/guest_delete_task.go +++ b/pkg/compute/tasks/guest_delete_task.go @@ -30,16 +30,35 @@ func (self *GuestDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel self.OnGuestStopComplete(ctx, obj, data) return } - self.SetStage("on_guest_stop_complete", nil) - err := guest.GetDriver().RequestStopGuestForDelete(ctx, guest, self) + if len(guest.BackupHostId) > 0 { + self.SetStage("OnMasterHostStopGuestComplete", nil) + if err := guest.GetDriver().RequestStopGuestForDelete(ctx, guest, nil, self); err != nil { + log.Errorf("RequestStopGuestForDelete fail %s", err) + self.OnMasterHostStopGuestComplete(ctx, guest, nil) + } + } else { + self.SetStage("OnGuestStopComplete", nil) + if err := guest.GetDriver().RequestStopGuestForDelete(ctx, guest, nil, self); err != nil { + log.Errorf("RequestStopGuestForDelete fail %s", err) + self.OnGuestStopComplete(ctx, guest, nil) + } + } +} + +func (self *GuestDeleteTask) OnMasterHostStopGuestComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + self.SetStage("OnGuestStopComplete", nil) + host := models.HostManager.FetchHostById(guest.BackupHostId) + err := guest.GetDriver().RequestStopGuestForDelete(ctx, guest, host, self) if err != nil { log.Errorf("RequestStopGuestForDelete fail %s", err) - // errMsg := jsonutils.NewString(err.Error()) - // self.OnGuestStopCompleteFailed(ctx, obj, errMsg) - self.OnGuestStopComplete(ctx, obj, data) + self.OnGuestStopComplete(ctx, guest, nil) } } +func (self *GuestDeleteTask) OnMasterHostStopGuestCompleteFailed(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + self.OnGuestStopComplete(ctx, guest, nil) // ignore stop error +} + func (self *GuestDeleteTask) OnGuestStopComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { guest := obj.(*models.SGuest) @@ -119,7 +138,7 @@ func (self *GuestDeleteTask) OnSyncGuestConfCompleteFailed(ctx context.Context, func (self *GuestDeleteTask) StartDeleteGuest(ctx context.Context, guest *models.SGuest) { // No snapshot - self.SetStage("on_guest_detach_disks_complete", nil) + self.SetStage("OnGuestDetachDisksComplete", nil) guest.GetDriver().RequestDetachDisksFromGuestForDelete(ctx, guest, self) } diff --git a/pkg/compute/tasks/guest_deploy_task.go b/pkg/compute/tasks/guest_deploy_task.go index b2156a763a..c5f4a51a23 100644 --- a/pkg/compute/tasks/guest_deploy_task.go +++ b/pkg/compute/tasks/guest_deploy_task.go @@ -16,10 +16,6 @@ type GuestDeployTask struct { SGuestBaseTask } -func init() { - taskman.RegisterTask(GuestDeployTask{}) -} - func (self *GuestDeployTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { guest := obj.(*models.SGuest) if !guest.IsNetworkAllocated() { @@ -31,25 +27,41 @@ func (self *GuestDeployTask) OnInit(ctx context.Context, obj db.IStandaloneModel func (self *GuestDeployTask) OnGuestNetworkReady(ctx context.Context, guest *models.SGuest) { if jsonutils.QueryBoolean(self.Params, "restart", false) { - self.SetStage("on_deploy_wait_server_stop", nil) + self.SetStage("OnDeployWaitServerStop", nil) guest.StartGuestStopTask(ctx, self.UserCred, false, self.GetTaskId()) } else { self.OnDeployWaitServerStop(ctx, guest, nil) } } -func (self *GuestDeployTask) OnDeployWaitServerStop(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { - guest := obj.(*models.SGuest) +func (self *GuestDeployTask) OnDeployWaitServerStop(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + self.SetStage("OnDeployGuestComplete", nil) targetHostId, _ := self.Params.GetString("target_host_id") if len(targetHostId) == 0 { - targetHostId = guest.HostId + if len(guest.BackupHostId) > 0 { + self.SetStage("OnSlaveHostDeployComplete", nil) + self.DeployBackup(ctx, guest, nil) + return + } else { + targetHostId = guest.HostId + } } host := models.HostManager.FetchHostById(targetHostId) - self.StartDeployGuestOnHost(ctx, guest, host) + self.DeployOnHost(ctx, guest, host) } -func (self *GuestDeployTask) StartDeployGuestOnHost(ctx context.Context, guest *models.SGuest, host *models.SHost) { - self.SetStage("on_deploy_guest_complete", nil) +func (self *GuestDeployTask) DeployBackup(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + host := models.HostManager.FetchHostById(guest.BackupHostId) + err := guest.GetDriver().RequestDeployGuestOnHost(ctx, guest, host, self) + if err != nil { + log.Errorf("request_deploy_guest_on_host %s", err) + self.OnDeployGuestFail(ctx, guest, err) + } else { + guest.SetStatus(self.UserCred, models.VM_DEPLOYING_BACKUP, "") + } +} + +func (self *GuestDeployTask) DeployOnHost(ctx context.Context, guest *models.SGuest, host *models.SHost) { err := guest.GetDriver().RequestDeployGuestOnHost(ctx, guest, host, self) if err != nil { log.Errorf("request_deploy_guest_on_host %s", err) @@ -59,6 +71,22 @@ func (self *GuestDeployTask) StartDeployGuestOnHost(ctx context.Context, guest * } } +func (self *GuestDeployTask) OnSlaveHostDeployComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + host := guest.GetHost() + self.SetStage("OnDeployGuestComplete", nil) + self.DeployOnHost(ctx, guest, host) +} + +func (self *GuestDeployTask) OnSlaveHostDeployCompleteFailed(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + guest.SetStatus(self.UserCred, models.VM_DEPLOYING_BACKUP_FAILED, "") + self.SetStage("OnUndeployBackupGuest", nil) + guest.StartUndeployGuestTask(ctx, self.UserCred, self.GetId(), guest.BackupHostId) +} + +func (self *GuestDeployTask) OnUndeployBackupGuest(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + self.SetStageFailed(ctx, "deploy backup failed") +} + func (self *GuestDeployTask) OnDeployGuestFail(ctx context.Context, guest *models.SGuest, err error) { guest.SetStatus(self.UserCred, models.VM_DEPLOY_FAILED, err.Error()) self.SetStageFailed(ctx, err.Error()) @@ -108,3 +136,25 @@ func (self *GuestDeployTask) OnDeployStartGuestComplete(ctx context.Context, obj func (self *GuestDeployTask) OnDeployGuestSyncstatusComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { self.SetStageComplete(ctx, nil) } + +type GuestDeployBackupTask struct { + GuestDeployTask +} + +func (self *GuestDeployBackupTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + guest := obj.(*models.SGuest) + if len(guest.BackupHostId) == 0 { + self.SetStageFailed(ctx, "Guest dosen't have backup host") + } + self.SetStage("OnDeployGuestComplete", nil) + self.DeployBackup(ctx, guest, nil) +} + +func (self *GuestDeployBackupTask) OnDeployGuestComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + self.SetStageComplete(ctx, nil) +} + +func init() { + taskman.RegisterTask(GuestDeployTask{}) + taskman.RegisterTask(GuestDeployBackupTask{}) +} diff --git a/pkg/compute/tasks/guest_detach_all_disks_task.go b/pkg/compute/tasks/guest_detach_all_disks_task.go index 6be7da967f..cb5fa8f9ee 100644 --- a/pkg/compute/tasks/guest_detach_all_disks_task.go +++ b/pkg/compute/tasks/guest_detach_all_disks_task.go @@ -40,6 +40,9 @@ func (self *GuestDetachAllDisksTask) OnDiskDeleteComplete(ctx context.Context, o if purge { taskData.Add(jsonutils.JSONTrue, "purge") } + if jsonutils.QueryBoolean(self.Params, "override_pending_delete", false) { + taskData.Add(jsonutils.JSONTrue, "override_pending_delete") + } disk := guestdisk.GetDisk() storage := disk.GetStorage() if storage.IsLocal() { diff --git a/pkg/compute/tasks/guest_live_migrate_task.go b/pkg/compute/tasks/guest_live_migrate_task.go index 7a36a4255b..d94d0de4c3 100644 --- a/pkg/compute/tasks/guest_live_migrate_task.go +++ b/pkg/compute/tasks/guest_live_migrate_task.go @@ -12,14 +12,11 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" "yunion.io/x/onecloud/pkg/compute/models" - "yunion.io/x/onecloud/pkg/compute/options" - "yunion.io/x/onecloud/pkg/mcclient/auth" - "yunion.io/x/onecloud/pkg/mcclient/modules" "yunion.io/x/onecloud/pkg/util/httputils" ) type GuestMigrateTask struct { - SGuestBaseTask + SSchedTask } type GuestLiveMigrateTask struct { @@ -32,45 +29,38 @@ func init() { } func (self *GuestMigrateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { - guest := obj.(*models.SGuest) - guest.SetStatus(self.UserCred, models.VM_MIGRATING, "") - db.OpsLog.LogEvent(guest, db.ACT_MIGRATING, "", self.UserCred) - self.FindCandidataTarget(ctx, guest) + StartScheduleObjects(ctx, self, []db.IStandaloneModel{obj}) } -func (self *GuestMigrateTask) FindCandidataTarget(ctx context.Context, guest *models.SGuest) { +func (self *GuestMigrateTask) GetSchedParams() *jsonutils.JSONDict { + obj := self.GetObject() + guest := obj.(*models.SGuest) schedDesc := guest.ToSchedDesc() if self.Params.Contains("prefer_host_id") { preferHostId, _ := self.Params.Get("prefer_host_id") schedDesc.Set("prefer_host_id", preferHostId) } - s := auth.GetAdminSession(options.Options.Region, "") - results, err := modules.SchedManager.DoSchedule(s, schedDesc, 1) - if err != nil { - self.TaskFailed(ctx, guest, fmt.Sprintf("Do schedule error %s", err)) - } else { - self.OnScheduleComplete(ctx, guest, results) - } + return schedDesc } -func (self *GuestMigrateTask) OnScheduleComplete(ctx context.Context, guest *models.SGuest, results []jsonutils.JSONObject) { - if len(results) != 1 { - self.TaskFailed(ctx, guest, "Schedule failed") - return - } - var targetHostId string - if results[0].Contains("candidate") { - targetHostId, _ = results[0].GetString("candidate", "id") - } else if results[0].Contains("error") { - msg, _ := results[0].Get("error") - self.TaskFailed(ctx, guest, msg.String()) - return - } else { - msg := fmt.Sprintf("Unknown scheduler result %s", results[0]) - self.TaskFailed(ctx, guest, msg) - return - } +func (self *GuestMigrateTask) OnStartSchedule(obj IScheduleModel) { + guest := obj.(*models.SGuest) + guest.SetStatus(self.UserCred, models.VM_MIGRATING, "") + db.OpsLog.LogEvent(guest, db.ACT_MIGRATING, "", self.UserCred) +} +func (self *GuestMigrateTask) OnScheduleFailCallback(obj IScheduleModel, reason string) { + // do nothing +} + +func (self *GuestMigrateTask) OnScheduleFailed(ctx context.Context, reason string) { + obj := self.GetObject() + guest := obj.(*models.SGuest) + self.TaskFailed(ctx, guest, reason) +} + +func (self *GuestMigrateTask) SaveScheduleResult(ctx context.Context, obj IScheduleModel, targetHostId string) { + guest := obj.(*models.SGuest) targetHost := models.HostManager.FetchHostById(targetHostId) if targetHost == nil { self.TaskFailed(ctx, guest, "target host not found?") diff --git a/pkg/compute/tasks/guest_start_task.go b/pkg/compute/tasks/guest_start_task.go index 2eb1ca3db7..7830a62a3d 100644 --- a/pkg/compute/tasks/guest_start_task.go +++ b/pkg/compute/tasks/guest_start_task.go @@ -2,6 +2,7 @@ package tasks import ( "context" + "fmt" "yunion.io/x/jsonutils" "yunion.io/x/onecloud/pkg/cloudcommon/db" @@ -26,13 +27,27 @@ func (self *GuestStartTask) OnInit(ctx context.Context, obj db.IStandaloneModel, func (self *GuestStartTask) checkTemplate(ctx context.Context, guest *models.SGuest) { diskCat := guest.CategorizeDisks() if diskCat.Root != nil && len(diskCat.Root.GetTemplateId()) > 0 { - self.SetStage("on_start_template_ready", nil) + if len(guest.BackupHostId) > 0 { + self.SetStage("OnMasterHostTemplateReady", nil) + } else { + self.SetStage("OnStartTemplateReady", nil) + } guest.GetDriver().CheckDiskTemplateOnStorage(ctx, self.UserCred, diskCat.Root.GetTemplateId(), diskCat.Root.StorageId, self) } else { self.startStart(ctx, guest) } } +func (self *GuestStartTask) OnMasterHostTemplateReady(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + self.SetStage("OnStartTemplateReady", nil) + diskCat := guest.CategorizeDisks() + err := guest.GetDriver().CheckDiskTemplateOnStorage(ctx, self.UserCred, diskCat.Root.GetTemplateId(), + diskCat.Root.BackupStorageId, self) + if err != nil { + self.SetStageFailed(ctx, err.Error()) + } +} + func (self *GuestStartTask) OnStartTemplateReady(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { guest := obj.(*models.SGuest) self.startStart(ctx, guest) @@ -40,7 +55,15 @@ func (self *GuestStartTask) OnStartTemplateReady(ctx context.Context, obj db.ISt func (self *GuestStartTask) startStart(ctx context.Context, guest *models.SGuest) { db.OpsLog.LogEvent(guest, db.ACT_STARTING, nil, self.UserCred) - self.SetStage("on_start_complete", nil) + if len(guest.BackupHostId) > 0 { + self.RequestStartBacking(ctx, guest) + } else { + self.RequestStart(ctx, guest) + } +} + +func (self *GuestStartTask) RequestStart(ctx context.Context, guest *models.SGuest) { + self.SetStage("OnStartComplete", nil) host := guest.GetHost() guest.SetStatus(self.UserCred, models.VM_STARTING, "") result, err := guest.GetDriver().RequestStartOnHost(ctx, guest, host, self.UserCred, self) @@ -55,6 +78,35 @@ func (self *GuestStartTask) startStart(ctx context.Context, guest *models.SGuest } } +func (self *GuestStartTask) RequestStartBacking(ctx context.Context, guest *models.SGuest) { + self.SetStage("OnStartBackupGuestComplete", nil) + host := models.HostManager.FetchHostById(guest.BackupHostId) + guest.SetStatus(self.UserCred, models.VM_BACKUP_STARTING, "") + result, err := guest.GetDriver().RequestStartOnHost(ctx, guest, host, self.UserCred, self) + if err != nil { + self.onStartGuestFailed(ctx, guest, err) + } else { + if result != nil && jsonutils.QueryBoolean(result, "is_running", false) { + self.OnStartBackupGuestComplete(ctx, guest, nil) + } + } +} + +func (self *GuestStartTask) OnStartBackupGuestComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + if data != nil { + nbdServerPort, err := data.Int("nbd_server_port") + if err == nil { + backupHost := models.HostManager.FetchHostById(guest.BackupHostId) + nbdServerUri := fmt.Sprintf("nbd:%s:%d", backupHost.AccessIp, nbdServerPort) + guest.SetMetadata(ctx, "backup_nbd_server_uri", nbdServerUri, self.UserCred) + } else { + self.onStartGuestFailed(ctx, guest, fmt.Errorf("Start backup guest result missing nbd_server_port")) + return + } + } + self.RequestStart(ctx, guest) +} + func (self *GuestStartTask) OnStartComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { guest := obj.(*models.SGuest) db.OpsLog.LogEvent(guest, db.ACT_START, guest.GetShortDesc(), self.UserCred) diff --git a/pkg/compute/tasks/guest_stop_task.go b/pkg/compute/tasks/guest_stop_task.go index 1368f24082..18968d4c3a 100644 --- a/pkg/compute/tasks/guest_stop_task.go +++ b/pkg/compute/tasks/guest_stop_task.go @@ -39,7 +39,7 @@ func (self *GuestStopTask) stopGuest(ctx context.Context, guest *models.SGuest) if !self.isSubtask() { guest.SetStatus(self.UserCred, models.VM_STOPPING, "") } - self.SetStage("on_guest_stop_task_complete", nil) + self.SetStage("OnMasterStopTaskComplete", nil) err := guest.GetDriver().RequestStopOnHost(ctx, guest, host, self) if err != nil { log.Errorf("RequestStopOnHost fail %s", err) @@ -47,8 +47,21 @@ func (self *GuestStopTask) stopGuest(ctx context.Context, guest *models.SGuest) } } -func (self *GuestStopTask) OnGuestStopTaskComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { - guest := obj.(*models.SGuest) +func (self *GuestStopTask) OnMasterStopTaskComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + if len(guest.BackupHostId) > 0 { + host := models.HostManager.FetchHostById(guest.BackupHostId) + self.SetStage("OnGuestStopTaskComplete", nil) + err := guest.GetDriver().RequestStopOnHost(ctx, guest, host, self) + if err != nil { + log.Errorf("RequestStopOnHost fail %s", err) + self.OnStopGuestFail(ctx, guest, err) + } + } else { + self.OnGuestStopTaskComplete(ctx, guest, data) + } +} + +func (self *GuestStopTask) OnGuestStopTaskComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { if !self.isSubtask() { guest.SetStatus(self.UserCred, models.VM_READY, "") } diff --git a/pkg/compute/tasks/guest_syncstatus_task.go b/pkg/compute/tasks/guest_syncstatus_task.go index 18849cc4e0..a2c1af6551 100644 --- a/pkg/compute/tasks/guest_syncstatus_task.go +++ b/pkg/compute/tasks/guest_syncstatus_task.go @@ -47,6 +47,8 @@ func (self *GuestSyncstatusTask) OnGetStatusSucc(ctx context.Context, guest *mod statusStr = models.VM_SUSPEND case cloudprovider.CloudVMStatusStopped: statusStr = models.VM_READY + case models.VM_BLOCK_STREAM: + break default: statusStr = models.VM_UNKNOWN } diff --git a/pkg/compute/tasks/guest_undeploy_task.go b/pkg/compute/tasks/guest_undeploy_task.go index eb3f789e8a..f10cd631a4 100644 --- a/pkg/compute/tasks/guest_undeploy_task.go +++ b/pkg/compute/tasks/guest_undeploy_task.go @@ -22,7 +22,11 @@ func init() { func (self *GuestUndeployTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { guest := obj.(*models.SGuest) targetHostId, _ := self.Params.GetString("target_host_id") + self.SetStage("OnGuestUndeployComplete", nil) if len(targetHostId) == 0 { + if len(guest.BackupHostId) > 0 { + self.SetStage("OnMasetHostUndeployGuestComplete", nil) + } targetHostId = guest.HostId } var host *models.SHost @@ -30,7 +34,19 @@ func (self *GuestUndeployTask) OnInit(ctx context.Context, obj db.IStandaloneMod host = models.HostManager.FetchHostById(targetHostId) } if host != nil { - self.SetStage("on_guest_undeploy_complete", nil) + err := guest.GetDriver().RequestUndeployGuestOnHost(ctx, guest, host, self) + if err != nil { + self.OnStartDeleteGuestFail(ctx, err) + } + } else { + self.SetStageComplete(ctx, nil) + } +} + +func (self *GuestUndeployTask) OnMasetHostUndeployGuestComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) { + self.SetStage("OnGuestUndeployComplete", nil) + host := models.HostManager.FetchHostById(guest.BackupHostId) + if host != nil { err := guest.GetDriver().RequestUndeployGuestOnHost(ctx, guest, host, self) if err != nil { self.OnStartDeleteGuestFail(ctx, err) diff --git a/pkg/compute/tasks/schedule.go b/pkg/compute/tasks/schedule.go index a21e19ee5a..5ca761bf30 100644 --- a/pkg/compute/tasks/schedule.go +++ b/pkg/compute/tasks/schedule.go @@ -10,6 +10,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/compute/options" @@ -31,14 +32,52 @@ type IScheduleModel interface { type IScheduleTask interface { GetUserCred() mcclient.TokenCredential - GetParams() *jsonutils.JSONDict + GetSchedParams() *jsonutils.JSONDict GetPendingUsage(quota quotas.IQuota) error - SetStage(stageName string, data *jsonutils.JSONDict) SetStageFailed(ctx context.Context, reason string) - OnScheduleFailCallback(obj IScheduleModel) + + OnStartSchedule(obj IScheduleModel) + OnScheduleFailCallback(obj IScheduleModel, reason string) OnScheduleComplete(ctx context.Context, items []db.IStandaloneModel, data *jsonutils.JSONDict) SaveScheduleResult(ctx context.Context, obj IScheduleModel, hostId string) + SaveScheduleResultWithBackup(ctx context.Context, obj IScheduleModel, master, slave string) + OnScheduleFailed(ctx context.Context, reason string) +} + +type SSchedTask struct { + taskman.STask +} + +func (self *SSchedTask) GetSchedParams() *jsonutils.JSONDict { + return self.GetParams() +} + +func (self *SSchedTask) OnStartSchedule(obj IScheduleModel) { + db.OpsLog.LogEvent(obj, db.ACT_ALLOCATING, nil, self.GetUserCred()) + obj.SetStatus(self.GetUserCred(), SCHEDULE, "") +} + +func (self *SSchedTask) OnScheduleFailCallback(obj IScheduleModel, reason string) { + obj.SetStatus(self.GetUserCred(), SCHEDULE_FAILED, reason) + db.OpsLog.LogEvent(obj, db.ACT_ALLOCATE_FAIL, reason, self.GetUserCred()) + notifyclient.NotifySystemError(obj.GetId(), obj.GetName(), SCHEDULE_FAILED, reason) +} + +func (self *SSchedTask) OnScheduleComplete(ctx context.Context, items []db.IStandaloneModel, data *jsonutils.JSONDict) { + self.SetStageComplete(ctx, nil) +} + +func (self *SSchedTask) SaveScheduleResult(ctx context.Context, obj IScheduleModel, hostId string) { + // ... +} + +func (self *SSchedTask) SaveScheduleResultWithBackup(ctx context.Context, obj IScheduleModel, master, slave string) { + // ... +} + +func (self *SSchedTask) OnScheduleFailed(ctx context.Context, reason string) { + self.SetStageFailed(ctx, reason) } func StartScheduleObjects( @@ -50,8 +89,7 @@ func StartScheduleObjects( for i, obj := range objs { schedObj := obj.(IScheduleModel) schedObjs[i] = schedObj - db.OpsLog.LogEvent(schedObj, db.ACT_ALLOCATING, nil, task.GetUserCred()) - schedObj.SetStatus(task.GetUserCred(), SCHEDULE, "") + task.OnStartSchedule(schedObj) } doScheduleObjects(ctx, task, schedObjs) } @@ -61,12 +99,13 @@ func doScheduleObjects( task IScheduleTask, objs []IScheduleModel, ) { - schedtags := models.ApplySchedPolicies(task.GetParams()) + parmas := task.GetSchedParams() + schedtags := models.ApplySchedPolicies(parmas) task.SetStage("OnScheduleComplete", schedtags) s := auth.GetAdminSession(options.Options.Region, "") - results, err := modules.SchedManager.DoSchedule(s, task.GetParams(), len(objs)) + results, err := modules.SchedManager.DoSchedule(s, parmas, len(objs)) if err != nil { onSchedulerRequestFail(ctx, task, objs, fmt.Sprintf("Scheduler fail: %s", err)) return @@ -81,7 +120,7 @@ func cancelPendingUsage(ctx context.Context, task IScheduleTask) { log.Errorf("Taks GetPendingUsage fail %s", err) return } - ownerProjectId, _ := task.GetParams().GetString("owner_tenant_id") + ownerProjectId, _ := task.GetSchedParams().GetString("owner_tenant_id") err = models.QuotaManager.CancelPendingUsage(ctx, task.GetUserCred(), ownerProjectId, &pendingUsage, &pendingUsage) if err != nil { log.Errorf("cancelpendingusage error %s", err) @@ -95,13 +134,13 @@ func onSchedulerRequestFail( reason string, ) { for _, obj := range objs { - onScheduleFail(ctx, task, obj, reason) + onObjScheduleFail(ctx, task, obj, reason) } - task.SetStageFailed(ctx, fmt.Sprintf("Schedule failed: %s", reason)) + task.OnScheduleFailed(ctx, fmt.Sprintf("Schedule failed: %s", reason)) cancelPendingUsage(ctx, task) } -func onScheduleFail( +func onObjScheduleFail( ctx context.Context, task IScheduleTask, obj IScheduleModel, @@ -114,11 +153,7 @@ func onScheduleFail( if len(msg) > 0 { reason = fmt.Sprintf("%s: %s", reason, msg) } - - obj.SetStatus(task.GetUserCred(), SCHEDULE_FAILED, reason) - db.OpsLog.LogEvent(obj, db.ACT_ALLOCATE_FAIL, reason, task.GetUserCred()) - notifyclient.NotifySystemError(obj.GetId(), obj.GetName(), SCHEDULE_FAILED, reason) - task.OnScheduleFailCallback(obj) + task.OnScheduleFailCallback(obj, reason) } func onSchedulerResults( @@ -131,25 +166,46 @@ func onSchedulerResults( for idx := 0; idx < len(objs); idx += 1 { obj := objs[idx] result := results[idx] - if result.Contains("candidate") { + if result.Contains("candidate", "id") { hostId, _ := result.GetString("candidate", "id") onScheduleSucc(ctx, task, obj, hostId) succCount += 1 + } else if result.Contains("candidate", "master_id") { + master, _ := result.GetString("candidate", "master_id") + slave, _ := result.GetString("candidate", "slave_id") + if len(master) == 0 || len(slave) == 0 { + onObjScheduleFail(ctx, task, obj, "Scheduler candidates not match") + } else { + onMasterSlaveScheduleSucc(ctx, task, obj, master, slave) + } } else if result.Contains("error") { msg, _ := result.Get("error") - onScheduleFail(ctx, task, obj, fmt.Sprintf("%s", msg)) + onObjScheduleFail(ctx, task, obj, fmt.Sprintf("%s", msg)) } else { msg := fmt.Sprintf("Unknown scheduler result %s", result) - onScheduleFail(ctx, task, obj, msg) + onObjScheduleFail(ctx, task, obj, msg) return } } if succCount == 0 { - task.SetStageFailed(ctx, "Schedule failed") + task.OnScheduleFailed(ctx, "Schedule failed") } cancelPendingUsage(ctx, task) } +func onMasterSlaveScheduleSucc( + ctx context.Context, + task IScheduleTask, + obj IScheduleModel, + master, slave string, +) { + lockman.LockObject(ctx, obj) + defer lockman.ReleaseObject(ctx, obj) + task.SaveScheduleResultWithBackup(ctx, obj, master, slave) + models.HostManager.ClearSchedDescCache(master) + models.HostManager.ClearSchedDescCache(slave) +} + func onScheduleSucc( ctx context.Context, task IScheduleTask, diff --git a/pkg/mcclient/options/servers.go b/pkg/mcclient/options/servers.go index 89924038bf..c0ee0be921 100644 --- a/pkg/mcclient/options/servers.go +++ b/pkg/mcclient/options/servers.go @@ -108,6 +108,8 @@ type ServerCreateOptions struct { AllowDelete *bool `help:"Unlock server to allow deleting" json:"-"` ShutdownBehavior string `help:"Behavior after VM server shutdown, stop or terminate server" metavar:"" choices:"stop|terminate"` AutoStart *bool `help:"Auto start server after it is created"` + Backup *bool `help:"Create server with backup server" json:"backup"` + BackupHost string `help:"Perfered host where virtual backup server should be created" json:"prefer_backup_host"` Deploy []string `help:"Specify deploy files in virtual server file system" json:"-"` Group []string `help:"Group of virtual server"` Project string `help:"'Owner project ID or Name" json:"tenant"` diff --git a/pkg/scheduler/api/sched.go b/pkg/scheduler/api/sched.go index a9b4da4a2e..3cd2645521 100644 --- a/pkg/scheduler/api/sched.go +++ b/pkg/scheduler/api/sched.go @@ -101,6 +101,12 @@ type SchedData struct { // baremental BaremetalDiskConfigs []*baremetal.BaremetalDiskConfig `json:"baremetal_disk_config"` + + // vm backup schedule + // Schedule server with backup server + Backup bool `json:"backup"` + // Backup server should be scheduled + BackupHostID string `json:"backup_host_id"` } func NewSchedData(sjson *simplejson.Json, count int64, byTest bool) (*SchedData, error) { @@ -150,6 +156,18 @@ func NewSchedData(sjson *simplejson.Json, count int64, byTest bool) (*SchedData, } } + if backupObj, ok := sjson.CheckGet("backup"); ok { + if backup, err := backupObj.Bool(); err == nil && backup { + data.Backup = true + } + } + + if backupHostID, ok := sjson.CheckGet("prefer_backup_host_id"); ok { + if backHost, err := backupHostID.String(); err == nil { + data.BackupHostID = backHost + } + } + data.Candidates = candidates err := data.reviseSchedType(sjson) @@ -245,7 +263,9 @@ func (s *SchedData) reviseSchedType(sjson *simplejson.Json) error { } func (d *SchedData) SkipDirtyMarkHost() bool { - return d.IsPublicCloudProvider() || d.IsContainer || d.Hypervisor == SchedTypeContainer + skipByHypervisor := d.IsPublicCloudProvider() || d.IsContainer || d.Hypervisor == SchedTypeContainer + skipByBackup := d.Backup + return skipByHypervisor || skipByBackup } func (d *SchedData) IsPublicCloudProvider() bool { @@ -807,13 +827,8 @@ type SchedResult struct { Items []SchedResultItem `json:"scheduler"` } -type candidateResult struct { - ID string `json:"id"` - Name string `json:"name"` -} - type SchedSuccItem struct { - Candidate SchedNormalResultItem `json:"candidate"` + Candidate interface{} `json:"candidate"` } type SchedErrItem struct { @@ -826,6 +841,11 @@ type SchedNormalResultItem struct { Data map[string]interface{} `json:"data"` } +type SchedBackupResultItem struct { + MasterID string `json:"master_id"` + SlaveID string `json:"slave_id"` +} + type SchedTestResult struct { Data interface{} `json:"data"` Total int64 `json:"total"` diff --git a/pkg/scheduler/cache/candidate/hosts.go b/pkg/scheduler/cache/candidate/hosts.go index f353bd08e6..841418e9b0 100644 --- a/pkg/scheduler/cache/candidate/hosts.go +++ b/pkg/scheduler/cache/candidate/hosts.go @@ -195,7 +195,8 @@ type HostBuilder struct { storageDict map[string]interface{} storageStatesSizeDict map[string]map[string]interface{} - hostGuests map[string][]interface{} + hostGuests map[string][]interface{} + hostBackupGuests map[string][]interface{} groupGuests []interface{} groups []interface{} @@ -681,6 +682,19 @@ func (b *HostBuilder) setGuests(ids []string, errMessageChannel chan error) { errMessageChannel <- err return } + + hostBackupGuests, err := utils.GroupBy(guests, func(obj interface{}) (string, error) { + gst, ok := obj.(*models.Guest) + if !ok { + return "", utils.ConvertError(obj, "*models.Guest") + } + return gst.BackupHostID, nil + }) + if err != nil { + errMessageChannel <- err + return + } + guestDict, err := utils.ToDict(guests, func(obj interface{}) (string, error) { gst, ok := obj.(*models.Guest) if !ok { @@ -695,6 +709,7 @@ func (b *HostBuilder) setGuests(ids []string, errMessageChannel chan error) { b.guestIDs = guestIDs b.guests = guests b.hostGuests = hostGuests + b.hostBackupGuests = hostBackupGuests b.guestDict = guestDict return } @@ -1021,6 +1036,10 @@ func (b *HostBuilder) fillGuestsResourceInfo(desc *HostDesc, host *models.Host) if !ok { guestsOnHost = []interface{}{} } + backupGuestsOnHost, ok := b.hostBackupGuests[host.ID] + if ok { + guestsOnHost = append(guestsOnHost, backupGuestsOnHost...) + } for _, gst := range guestsOnHost { guest := gst.(*models.Guest) diff --git a/pkg/scheduler/core/generic_scheduler.go b/pkg/scheduler/core/generic_scheduler.go index c7775dfb10..bf02d7145f 100644 --- a/pkg/scheduler/core/generic_scheduler.go +++ b/pkg/scheduler/core/generic_scheduler.go @@ -1,6 +1,7 @@ package core import ( + "encoding/json" "fmt" "sort" "strings" @@ -85,7 +86,7 @@ func NewGenericScheduler(s Scheduler) (*GenericScheduler, error) { return g, nil } -func (g *GenericScheduler) Schedule(unit *Unit, candidates []Candidater) ([]*SchedResultItem, error) { +func (g *GenericScheduler) Schedule(unit *Unit, candidates []Candidater) (*SchedResultItemList, error) { startTime := time.Now() defer func() { log.V(4).Infof("Schedule cost time: %v", time.Since(startTime)) @@ -157,7 +158,7 @@ func (g *GenericScheduler) Schedule(unit *Unit, candidates []Candidater) ([]*Sch g.DirtySelectedCandidates(selectedCandidates) } - return resultItems, nil + return &SchedResultItemList{Unit: unit, Data: resultItems}, nil } func newSchedResultByCtx(u *Unit, count int64, c Candidater) *SchedResultItem { @@ -270,26 +271,38 @@ func (its SchedResultItemList) Len() int { return len(its.Data) } +func (its *SchedResultItemList) Swap(i, j int) { + its.Data[i], its.Data[j] = its.Data[j], its.Data[i] +} + func (its SchedResultItemList) Less(i, j int) bool { it1, it2 := its.Data[i], its.Data[j] - ctx := its.Unit + return it1.Capacity < it2.Capacity + /* + ctx := its.Unit - m := func(c int64) int64 { - if c > 0 { - return 1 + m := func(c int64) int64 { + if c > 0 { + return 1 + } + return 0 } - return 0 - } - v := func(count, capacity, score int64) int64 { - return (m(count) << 42) | (m(capacity) << 21) | score - } + v := func(count, capacity, score int64) int64 { + return (m(count) << 42) | (m(capacity) << 21) | score + } - count1, count2 := it1.Count, it2.Count - capacity1, capacity2 := ctx.GetCapacity(it1.ID), ctx.GetCapacity(it2.ID) - score1, score2 := int64(ctx.GetScore(it1.ID)), int64(ctx.GetScore(it2.ID)) + count1, count2 := it1.Count, it2.Count + capacity1, capacity2 := ctx.GetCapacity(it1.ID), ctx.GetCapacity(it2.ID) + score1, score2 := int64(ctx.GetScore(it1.ID)), int64(ctx.GetScore(it2.ID)) - return v(count1, capacity1, score1) < v(count2, capacity2, score2) + return v(count1, capacity1, score1) < v(count2, capacity2, score2) + */ +} + +func (its SchedResultItemList) String() string { + bytes, _ := json.Marshal(its.Data) + return string(bytes) } type SelectedCandidate struct { @@ -301,6 +314,10 @@ func (s SelectedCandidate) Index() (string, error) { return s.Candidate.IndexKey(), nil } +func (s SelectedCandidate) GetCount() uint64 { + return uint64(s.Count) +} + // SelectHosts takes a prioritized list of candidates and then picks // a group of hosts func SelectHosts(unit *Unit, priorityList HostPriorityList) ([]*SelectedCandidate, error) { diff --git a/pkg/scheduler/data_manager/candidate_manager.go b/pkg/scheduler/data_manager/candidate_manager.go index 367145aa59..8ec27d8b11 100644 --- a/pkg/scheduler/data_manager/candidate_manager.go +++ b/pkg/scheduler/data_manager/candidate_manager.go @@ -335,10 +335,13 @@ func (cm *CandidateManager) ReloadAll(resType string) ([]interface{}, error) { return impl.ReloadAll() } -func (cm *CandidateManager) SetCandidatesDirty(scs []*core.SelectedCandidate) { - for _, sc := range scs { - cm.dirtyPool.Add(sc, uint64(sc.Count)) - } +type IDirtyPoolItem interface { + ttlpool.Item + GetCount() uint64 +} + +func (cm *CandidateManager) SetCandidateDirty(item IDirtyPoolItem) { + cm.dirtyPool.Add(item, item.GetCount()) } func (cm *CandidateManager) CleanDirtyCandidatesOnce(keys []string) { diff --git a/pkg/scheduler/db/models/guest.go b/pkg/scheduler/db/models/guest.go index c600242a1e..a84a4e6651 100644 --- a/pkg/scheduler/db/models/guest.go +++ b/pkg/scheduler/db/models/guest.go @@ -119,6 +119,7 @@ type Guest struct { ShutdownBehavior string `json:"shutdown_behavior,omitempty" gorm:"column:shutdown_behavior"` KeypairID string `json:"keypair_id,omitempty" gorm:"column:keypair_id"` HostID string `json:"host_id,omitempty" gorm:"column:host_id"` + BackupHostID string `json:"backup_host_id,omitempty" gorm:"column:backup_host_id"` VNCPort int64 `json:"vnc_port,omitempty" gorm:"column:vnc_port"` VGA string `json:"vga" gorm:"column:vga"` FlavorID string `json:"flavor_id,omitempty" gorm:"column:flavor_id"` diff --git a/pkg/scheduler/db/models/storage.go b/pkg/scheduler/db/models/storage.go index d9b54fa918..d21235eeb3 100644 --- a/pkg/scheduler/db/models/storage.go +++ b/pkg/scheduler/db/models/storage.go @@ -6,6 +6,8 @@ import ( "github.com/jinzhu/gorm" + "yunion.io/x/log" + o "yunion.io/x/onecloud/cmd/scheduler/options" ) @@ -65,7 +67,20 @@ func GetStorageCapacities(storageIDs []string) ([]StorageCapacity, error) { Select("storage_id, status, sum(disk_size) as total_size"). Where(fmt.Sprintf("storage_id in ('%s') and deleted=0", strings.Join(storageIDs, "','"))). Group("storage_id, status").Scan(&results).Error - return results, err + if err != nil { + return nil, err + } + backupResults := make([]StorageCapacity, 0) + err = Disks.DB().Table(disksTable). + Select("backup_storage_id as storage_id, status, sum(disk_size) as total_size"). + Where(fmt.Sprintf("storage_id in ('%s') and deleted=0", strings.Join(storageIDs, "','"))). + Group("storage_id, status").Scan(&backupResults).Error + if err != nil { + log.Errorf("Get backup storage error: %v", err) + return results, nil + } + results = append(results, backupResults...) + return results, nil } func (s Storage) OverCommitBound() float64 { diff --git a/pkg/scheduler/handler/backup_helper.go b/pkg/scheduler/handler/backup_helper.go new file mode 100644 index 0000000000..483259f6db --- /dev/null +++ b/pkg/scheduler/handler/backup_helper.go @@ -0,0 +1,126 @@ +package handler + +import ( + "fmt" + "sort" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/scheduler/api" + "yunion.io/x/onecloud/pkg/scheduler/core" + schedman "yunion.io/x/onecloud/pkg/scheduler/manager" +) + +func transToBackupSchedResult(result *core.SchedResultItemList, preferBackupHost string, count int64) interface{} { + // clean each result sched result item's count + for _, item := range result.Data { + item.Count = 0 + } + + apiResults := newBackupSchedResult(result, preferBackupHost, count) + return regionResponse(apiResults) +} + +func newBackupSchedResult(result *core.SchedResultItemList, preferBackupHost string, count int64) []api.SchedResultItem { + apiResults := make([]api.SchedResultItem, 0) + for i := 0; i < int(count); i++ { + log.V(10).Debugf("Select backup host from result: %s", result) + target, err := getSchedBackupResult(result, preferBackupHost) + if err != nil { + apiResults = append(apiResults, api.SchedErrItem{Error: err.Error()}) + continue + } + apiResults = append(apiResults, api.SchedSuccItem{Candidate: target}) + } + return apiResults +} + +func getSchedBackupResult(result *core.SchedResultItemList, preferBackupHost string) (*api.SchedBackupResultItem, error) { + masterHost := selectMasterHost(result.Data, preferBackupHost) + if masterHost == nil { + return nil, fmt.Errorf("Can't find master host") + } + backupHost := selectBackupHost(masterHost.ID, preferBackupHost, result.Data) + if backupHost == nil { + return nil, fmt.Errorf("Can't find backup host by master %s", masterHost.ID) + } + + markHostUsed(masterHost) + markHostUsed(backupHost) + sort.Sort(sort.Reverse(result)) + + ret := &api.SchedBackupResultItem{ + MasterID: masterHost.ID, + SlaveID: backupHost.ID, + } + return ret, nil +} + +func markHostUsed(host *core.SchedResultItem) { + host.Count++ + host.Capacity-- + setHostDirty(host) +} + +// selectMasterID find master host id run VM +// return nil if not found +func selectMasterHost(result []*core.SchedResultItem, preferBackupHost string) *core.SchedResultItem { + if len(result) == 0 { + return nil + } + host := result[0] + if host.Capacity >= 1 && host.ID != preferBackupHost { + return host + } + if len(result) == 1 { + return nil + } + restHosts := result[1:] + return selectMasterHost(restHosts, preferBackupHost) +} + +func selectBackupHost(masterID, preferBackupHost string, result []*core.SchedResultItem) *core.SchedResultItem { + if len(result) == 0 { + return nil + } + firstHost := result[0] + if canHostAsBackup(masterID, preferBackupHost, firstHost) { + return firstHost + } + if len(result) == 1 { + return nil + } + restHosts := result[1:] + return selectBackupHost(masterID, preferBackupHost, restHosts) +} + +func canHostAsBackup(masterID, preferBackupHost string, host *core.SchedResultItem) bool { + if host.ID == masterID { + return false + } + if host.Capacity == 0 { + return false + } + if preferBackupHost != "" { + if host.ID != preferBackupHost { + return false + } + } + return true +} + +type dirtyItemAdapter struct { + *core.SchedResultItem +} + +func (a *dirtyItemAdapter) Index() (string, error) { + return a.ID, nil +} + +func (a *dirtyItemAdapter) GetCount() uint64 { + return uint64(a.Count) +} + +func setHostDirty(host *core.SchedResultItem) { + schedman.GetCandidateManager().SetCandidateDirty(&dirtyItemAdapter{SchedResultItem: host}) +} diff --git a/pkg/scheduler/handler/handler.go b/pkg/scheduler/handler/handler.go index 2278596810..89ef8bcdc6 100644 --- a/pkg/scheduler/handler/handler.go +++ b/pkg/scheduler/handler/handler.go @@ -108,10 +108,10 @@ func doSchedulerTest(c *gin.Context) { c.JSON(http.StatusOK, transToSchedTestResult(result, schedInfo.SuggestionLimit)) } -func transToSchedTestResult(result []*core.SchedResultItem, limit int64) interface{} { +func transToSchedTestResult(result *core.SchedResultItemList, limit int64) interface{} { return &api.SchedTestResult{ - Data: result, - Total: int64(len(result)), + Data: result.Data, + Total: int64(result.Len()), Limit: limit, Offset: 0, } @@ -258,7 +258,15 @@ func doSyncSchedule(c *gin.Context) { return } - c.JSON(http.StatusOK, transToRegionSchedResult(result, schedInfo.Data.Count)) + count := schedInfo.Data.Count + var resp interface{} + if schedInfo.Data.Backup { + resp = transToBackupSchedResult(result, schedInfo.Data.BackupHostID, count) + } else { + resp = transToRegionSchedResult(result.Data, count) + } + + c.JSON(http.StatusOK, resp) } func transToRegionSchedResult(result []*core.SchedResultItem, count int64) interface{} { diff --git a/pkg/scheduler/manager/manager.go b/pkg/scheduler/manager/manager.go index 5949af00eb..b4f09f7155 100644 --- a/pkg/scheduler/manager/manager.go +++ b/pkg/scheduler/manager/manager.go @@ -87,7 +87,7 @@ func (sm *SchedulerManager) start() { } } -func (sm *SchedulerManager) schedule(info *api.SchedInfo) ([]*core.SchedResultItem, error) { +func (sm *SchedulerManager) schedule(info *api.SchedInfo) (*core.SchedResultItemList, error) { log.V(10).Infof("SchedulerManager do schedule, input: %#v", info) task, err := sm.TaskManager.AddTask(sm, info) if err != nil { @@ -112,7 +112,7 @@ func NewSessionID() string { // Schedule process the request data that is scheduled for dispatch and complements // the session information. -func Schedule(info *api.SchedInfo) ([]*core.SchedResultItem, error) { +func Schedule(info *api.SchedInfo) (*core.SchedResultItemList, error) { if len(info.SessionID) == 0 { info.SessionID = NewSessionID() } diff --git a/pkg/scheduler/manager/scheduler.go b/pkg/scheduler/manager/scheduler.go index cde65e3b9d..37b855f264 100644 --- a/pkg/scheduler/manager/scheduler.go +++ b/pkg/scheduler/manager/scheduler.go @@ -137,7 +137,9 @@ func (s *BaseScheduler) BeforePredicate() error { } func (s *BaseScheduler) DirtySelectedCandidates(scs []*core.SelectedCandidate) { - s.CandidateManager().SetCandidatesDirty(scs) + for _, sc := range scs { + s.CandidateManager().SetCandidateDirty(sc) + } } // GuestScheduler for guest type schedule diff --git a/pkg/scheduler/manager/task_queue.go b/pkg/scheduler/manager/task_queue.go index 0a413c8703..125ab24e21 100644 --- a/pkg/scheduler/manager/task_queue.go +++ b/pkg/scheduler/manager/task_queue.go @@ -30,7 +30,7 @@ type TaskExecutor struct { callback TaskExecuteCallback unit *core.Unit - resultItems []*core.SchedResultItem + resultItems *core.SchedResultItemList resultError error logs []string completed bool @@ -63,7 +63,7 @@ func (te *TaskExecutor) Execute() { } } -func (te *TaskExecutor) execute() ([]*core.SchedResultItem, error) { +func (te *TaskExecutor) execute() (*core.SchedResultItemList, error) { scheduler := te.scheduler genericScheduler, err := core.NewGenericScheduler(scheduler.(core.Scheduler)) if err != nil { @@ -92,7 +92,7 @@ func (te *TaskExecutor) Kill() { } } -func (te *TaskExecutor) GetResult() ([]*core.SchedResultItem, error) { +func (te *TaskExecutor) GetResult() (*core.SchedResultItemList, error) { return te.resultItems, te.resultError } @@ -254,7 +254,7 @@ type Task struct { waitCh chan struct{} completedCount int - resultItems []*core.SchedResultItem + resultItems *core.SchedResultItemList resultError error } @@ -266,7 +266,6 @@ func NewTask(manager *SchedulerManager, schedInfo *api.SchedInfo) *Task { taskExecutors: []*TaskExecutor{}, lock: sync.Mutex{}, waitCh: make(chan struct{}), - resultItems: []*core.SchedResultItem{}, resultError: nil, } } @@ -312,7 +311,7 @@ func (t *Task) onTaskCompleted(taskExecutor *TaskExecutor) { t.resultError = taskExecutor.resultError t.onError() } else { - t.resultItems = append(t.resultItems, taskExecutor.resultItems...) + t.resultItems = taskExecutor.resultItems t.completedCount += 1 if t.completedCount >= len(t.taskExecutors) { t.onCompleted() @@ -349,12 +348,12 @@ func (t *Task) onCompleted() { close(t.waitCh) } -func (t *Task) Wait() ([]*core.SchedResultItem, error) { +func (t *Task) Wait() (*core.SchedResultItemList, error) { log.V(10).Infof("Task wait...") <-t.waitCh return t.GetResult() } -func (t *Task) GetResult() ([]*core.SchedResultItem, error) { +func (t *Task) GetResult() (*core.SchedResultItemList, error) { return t.resultItems, t.resultError } From 6dc3d879c7ffdb61a2cf27eb8bd94879635b73d9 Mon Sep 17 00:00:00 2001 From: TangBin Date: Wed, 21 Nov 2018 11:32:36 +0800 Subject: [PATCH 22/29] add test cases --- pkg/httperrors/errors.go | 20 ++++++++++---------- pkg/httperrors/errors_test.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/pkg/httperrors/errors.go b/pkg/httperrors/errors.go index 60887fd075..cc252d37e6 100644 --- a/pkg/httperrors/errors.go +++ b/pkg/httperrors/errors.go @@ -1,6 +1,7 @@ package httperrors import ( + "bytes" "fmt" "yunion.io/x/onecloud/pkg/util/httputils" ) @@ -18,39 +19,38 @@ func msgToTemplate(msg string) string { fmtstr := false lst := []rune(msg) lastIndex := len(lst) - 1 - temp := []rune{} + temp := bytes.Buffer{} index := 0 for i, c := range lst { switch c { case '%': if fmtstr || i == lastIndex { - temp = append(temp, c) + temp.WriteRune(c) fmtstr = false } else { fmtstr = true } case 'v', 'T', 't', 'b', 'c', 'd', 'o', 'q', 'x', 'X', 'U', 'e', 'E', 'f', 'F', 'g', 'G', 's', 'p': if fmtstr { - i := []rune(fmt.Sprintf("%d", index)) - temp = append(temp, '{') - temp = append(temp, i...) - temp = append(temp, '}') + temp.WriteRune('{') + temp.WriteString(fmt.Sprintf("%d", index)) + temp.WriteRune('}') index++ fmtstr = false } else { - temp = append(temp, c) + temp.WriteRune(c) } default: if fmtstr { - temp = append(temp, '%') + temp.WriteRune('%') } - temp = append(temp, c) + temp.WriteRune(c) fmtstr = false } } - return string(temp) + return temp.String() } func errorMessage(msg string, params ...interface{}) (string, httputils.Error) { diff --git a/pkg/httperrors/errors_test.go b/pkg/httperrors/errors_test.go index 051bc74d9a..a126b7fc71 100644 --- a/pkg/httperrors/errors_test.go +++ b/pkg/httperrors/errors_test.go @@ -50,3 +50,37 @@ func TestVariadic(t *testing.T) { }) } } + +func TestMsgToTemplate(t *testing.T) { + cases := []struct { + name string + msg string + params []interface{} + out string + }{ + { + name: "non-empty msg to template", + msg: "%% baremetals %s delete.time %d%", + out: "% baremetals {0} delete.time {1}%", + }, + { + name: "empty msg to template", + msg: "", + out: "", + }, + { + name: "non-empty with zh-utf8 characters msg to template", + msg: "%% baremetals %s 中文%d ¥%%", + out: "% baremetals {0} 中文{1} ¥%", + }, + } + + for _, c := range cases{ + t.Run(c.name, func(t *testing.T) { + resp := msgToTemplate(c.msg) + if resp != c.out { + t.Errorf("want %s, got %s", c.out, resp) + } + }) + } +} \ No newline at end of file From 0ae3ebdb6c7bf49c8a59d2f1f43ec8f105a24bae Mon Sep 17 00:00:00 2001 From: Yousong Zhou Date: Tue, 20 Nov 2018 13:14:48 +0000 Subject: [PATCH 23/29] aliyun: rename jsonRequest to ecsRequest --- pkg/util/aliyun/aliyun.go | 8 ++++---- pkg/util/aliyun/region.go | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/util/aliyun/aliyun.go b/pkg/util/aliyun/aliyun.go index 79fb5b905e..bc85f1fd17 100644 --- a/pkg/util/aliyun/aliyun.go +++ b/pkg/util/aliyun/aliyun.go @@ -40,7 +40,7 @@ func NewAliyunClient(providerId string, providerName string, accessKey string, s return &client, nil } -func jsonRequest(client *sdk.Client, apiName string, params map[string]string) (jsonutils.JSONObject, error) { +func ecsRequest(client *sdk.Client, apiName string, params map[string]string) (jsonutils.JSONObject, error) { return _jsonRequest(client, "ecs.aliyuncs.com", ALIYUN_API_VERSION, apiName, params) } @@ -83,16 +83,16 @@ func (self *SAliyunClient) getDefaultClient() (*sdk.Client, error) { return sdk.NewClientWithAccessKey(ALIYUN_DEFAULT_REGION, self.accessKey, self.secret) } -func (self *SAliyunClient) jsonRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { +func (self *SAliyunClient) ecsRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { cli, err := self.getDefaultClient() if err != nil { return nil, err } - return jsonRequest(cli, apiName, params) + return ecsRequest(cli, apiName, params) } func (self *SAliyunClient) fetchRegions() error { - body, err := self.jsonRequest("DescribeRegions", map[string]string{"AcceptLanguage": "zh-CN"}) + body, err := self.ecsRequest("DescribeRegions", map[string]string{"AcceptLanguage": "zh-CN"}) if err != nil { log.Errorf("fetchRegions fail %s", err) return err diff --git a/pkg/util/aliyun/region.go b/pkg/util/aliyun/region.go index fecb4d90cb..b4f9faeb76 100644 --- a/pkg/util/aliyun/region.go +++ b/pkg/util/aliyun/region.go @@ -79,11 +79,11 @@ func (self *SRegion) GetOssClient() (*oss.Client, error) { } func (self *SRegion) ecsRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { - cli, err := self.getEcsClient() + client, err := self.getEcsClient() if err != nil { return nil, err } - return jsonRequest(cli, apiName, params) + return _jsonRequest(client, "ecs.aliyuncs.com", ALIYUN_API_VERSION, apiName, params) } ///////////////////////////////////////////////////////////////////////////// From 7e7f124efe47f45c4c677eaf847dbf3e3f52b32d Mon Sep 17 00:00:00 2001 From: Yousong Zhou Date: Tue, 20 Nov 2018 13:28:25 +0000 Subject: [PATCH 24/29] aliyun: region: rename ecsClient to sdkClient --- pkg/util/aliyun/region.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/util/aliyun/region.go b/pkg/util/aliyun/region.go index b4f9faeb76..e2c0905591 100644 --- a/pkg/util/aliyun/region.go +++ b/pkg/util/aliyun/region.go @@ -17,7 +17,7 @@ import ( type SRegion struct { client *SAliyunClient - ecsClient *sdk.Client + sdkClient *sdk.Client ossClient *oss.Client RegionId string @@ -44,15 +44,15 @@ func (self *SRegion) GetMetadata() *jsonutils.JSONDict { return nil } -func (self *SRegion) getEcsClient() (*sdk.Client, error) { - if self.ecsClient == nil { +func (self *SRegion) getSdkClient() (*sdk.Client, error) { + if self.sdkClient == nil { cli, err := sdk.NewClientWithAccessKey(self.RegionId, self.client.accessKey, self.client.secret) if err != nil { return nil, err } - self.ecsClient = cli + self.sdkClient = cli } - return self.ecsClient, nil + return self.sdkClient, nil } // oss endpoint @@ -79,7 +79,7 @@ func (self *SRegion) GetOssClient() (*oss.Client, error) { } func (self *SRegion) ecsRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { - client, err := self.getEcsClient() + client, err := self.getSdkClient() if err != nil { return nil, err } From cd0a1b87ef8c1e939135f54f92a07c6cc5ce7caa Mon Sep 17 00:00:00 2001 From: Yousong Zhou Date: Tue, 20 Nov 2018 13:28:48 +0000 Subject: [PATCH 25/29] aliyun: add SVpc.apiRequest() method --- pkg/util/aliyun/aliyun.go | 3 ++- pkg/util/aliyun/vpc.go | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/pkg/util/aliyun/aliyun.go b/pkg/util/aliyun/aliyun.go index bc85f1fd17..b2011e9cb1 100644 --- a/pkg/util/aliyun/aliyun.go +++ b/pkg/util/aliyun/aliyun.go @@ -16,7 +16,8 @@ const ( ALIYUN_DEFAULT_REGION = "cn-hangzhou" - ALIYUN_API_VERSION = "2014-05-26" + ALIYUN_API_VERSION = "2014-05-26" + ALIYUN_API_VERSION_VPC = "2016-04-28" ALIYUN_BSS_API_VERSION = "2017-12-14" diff --git a/pkg/util/aliyun/vpc.go b/pkg/util/aliyun/vpc.go index 91951cf029..341bab020c 100644 --- a/pkg/util/aliyun/vpc.go +++ b/pkg/util/aliyun/vpc.go @@ -45,6 +45,14 @@ type SVpc struct { VpcName string } +func (self *SVpc) apiRequest(action string, params map[string]string) (jsonutils.JSONObject, error) { + client, err := self.region.getSdkClient() + if err != nil { + return nil, err + } + return _jsonRequest(client, "vpc.aliyuncs.com", ALIYUN_API_VERSION_VPC, action, params) +} + func (self *SVpc) GetMetadata() *jsonutils.JSONDict { return nil } From 2c430b4430e4e1b30659630f33a50bea029b4532 Mon Sep 17 00:00:00 2001 From: Yousong Zhou Date: Sat, 17 Nov 2018 13:15:03 +0000 Subject: [PATCH 26/29] routetables: initial version --- cmd/climc/shell/routetables.go | 117 +++++++++++ pkg/compute/handlers.go | 1 + pkg/compute/models/routetables.go | 254 ++++++++++++++++++++++++ pkg/mcclient/modules/mod_routetables.go | 28 +++ pkg/mcclient/options/routetables.go | 133 +++++++++++++ 5 files changed, 533 insertions(+) create mode 100644 cmd/climc/shell/routetables.go create mode 100644 pkg/compute/models/routetables.go create mode 100644 pkg/mcclient/modules/mod_routetables.go create mode 100644 pkg/mcclient/options/routetables.go diff --git a/cmd/climc/shell/routetables.go b/cmd/climc/shell/routetables.go new file mode 100644 index 0000000000..32f0207830 --- /dev/null +++ b/cmd/climc/shell/routetables.go @@ -0,0 +1,117 @@ +package shell + +import ( + "fmt" + "strings" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + printRouteTableList := func(list *modules.ListResult, columns []string) { + data := list.Data + for _, jsonObj := range data { + jd := jsonObj.(*jsonutils.JSONDict) + routesObj, err := jd.GetArray("routes") + if err != nil { + continue + } + routes := []string{} + for _, routeObj := range routesObj { + typ, _ := routeObj.GetString("type") + cidr, _ := routeObj.GetString("cidr") + next_hop_type, _ := routeObj.GetString("next_hop_type") + next_hop, _ := routeObj.GetString("next_hop") + route := fmt.Sprintf("%8s: %18s %s", typ, cidr, next_hop_type) + if len(next_hop) > 0 { + route += fmt.Sprintf(":%s", next_hop) + } + routes = append(routes, route) + } + s := strings.Join(routes, "\n") + jd.Set("routes", jsonutils.NewString(s)) + } + printList(list, columns) + } + + R(&options.RouteTableCreateOptions{}, "routetable-create", "Create routetable", func(s *mcclient.ClientSession, opts *options.RouteTableCreateOptions) error { + params, err := opts.Params() + if err != nil { + return err + } + routetable, err := modules.RouteTables.Create(s, params) + if err != nil { + return err + } + printObjectRecursive(routetable) + return nil + }) + R(&options.RouteTableGetOptions{}, "routetable-show", "Show routetable", func(s *mcclient.ClientSession, opts *options.RouteTableGetOptions) error { + routetable, err := modules.RouteTables.Get(s, opts.ID, nil) + if err != nil { + return err + } + printObjectRecursive(routetable) + return nil + }) + R(&options.RouteTableListOptions{}, "routetable-list", "List routetables", func(s *mcclient.ClientSession, opts *options.RouteTableListOptions) error { + params, err := options.ListStructToParams(opts) + if err != nil { + return err + } + result, err := modules.RouteTables.List(s, params) + if err != nil { + return err + } + printRouteTableList(result, modules.RouteTables.GetColumns(s)) + return nil + }) + R(&options.RouteTableUpdateOptions{}, "routetable-update", "Update routetable", func(s *mcclient.ClientSession, opts *options.RouteTableUpdateOptions) error { + params, err := opts.Params() + if err != nil { + return err + } + routetable, err := modules.RouteTables.Update(s, opts.ID, params) + if err != nil { + return err + } + printObjectRecursive(routetable) + return nil + }) + R(&options.RouteTableAddRoutesOptions{}, "routetable-add-routes", "Add routes to routetable", func(s *mcclient.ClientSession, opts *options.RouteTableAddRoutesOptions) error { + params, err := opts.Params() + if err != nil { + return err + } + routetable, err := modules.RouteTables.PerformAction(s, opts.ID, "add-routes", params) + if err != nil { + return err + } + printObjectRecursive(routetable) + return nil + }) + R(&options.RouteTableDelRoutesOptions{}, "routetable-del-routes", "Del routes to routetable", func(s *mcclient.ClientSession, opts *options.RouteTableDelRoutesOptions) error { + params, err := opts.Params() + if err != nil { + return err + } + routetable, err := modules.RouteTables.PerformAction(s, opts.ID, "del-routes", params) + if err != nil { + return err + } + printObjectRecursive(routetable) + return nil + }) + R(&options.RouteTableDeleteOptions{}, "routetable-delete", "Show routetable", func(s *mcclient.ClientSession, opts *options.RouteTableDeleteOptions) error { + routetable, err := modules.RouteTables.Delete(s, opts.ID, nil) + if err != nil { + return err + } + printObjectRecursive(routetable) + return nil + }) +} diff --git a/pkg/compute/handlers.go b/pkg/compute/handlers.go index 6d7ed755f0..ad2ec0a97a 100644 --- a/pkg/compute/handlers.go +++ b/pkg/compute/handlers.go @@ -71,6 +71,7 @@ func InitHandlers(app *appsrv.Application) { models.LoadbalancerCertificateManager, models.LoadbalancerAclManager, models.LoadbalancerAgentManager, + models.RouteTableManager, models.SchedpolicyManager, models.DynamicschedtagManager, diff --git a/pkg/compute/models/routetables.go b/pkg/compute/models/routetables.go new file mode 100644 index 0000000000..a79e59219c --- /dev/null +++ b/pkg/compute/models/routetables.go @@ -0,0 +1,254 @@ +package models + +import ( + "context" + "net" + "reflect" + "strings" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/gotypes" + "yunion.io/x/sqlchemy" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/validators" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" +) + +type SRoute struct { + Type string + Cidr string + NextHopType string + NextHopId string +} + +func (route *SRoute) Validate(data *jsonutils.JSONDict) error { + if strings.Index(route.Cidr, "/") > 0 { + _, ipNet, err := net.ParseCIDR(route.Cidr) + if err != nil { + return err + } + // normalize from 192.168.1.3/24 to 192.168.1.0/24 + route.Cidr = ipNet.String() + } else { + ip := net.ParseIP(route.Cidr).To4() + if ip == nil { + return httperrors.NewInputParameterError("invalid addr %s", route.Cidr) + } + } + return nil +} + +type SRoutes []*SRoute + +func (routes *SRoutes) String() string { + return jsonutils.Marshal(routes).String() +} +func (routes *SRoutes) IsZero() bool { + if len([]*SRoute(*routes)) == 0 { + return true + } + return false +} + +func (routes *SRoutes) Validate(data *jsonutils.JSONDict) error { + found := map[string]bool{} + for _, route := range *routes { + if err := route.Validate(data); err != nil { + return err + } + if _, ok := found[route.Cidr]; ok { + // error so that the user has a chance to deal with comments + return httperrors.NewInputParameterError("duplicate route cidr %s", route.Cidr) + } + // TODO aliyun: check overlap with System type route + found[route.Cidr] = true + } + return nil +} + +type SRouteTableManager struct { + db.SVirtualResourceBaseManager +} + +var RouteTableManager *SRouteTableManager + +func init() { + gotypes.RegisterSerializable(reflect.TypeOf(&SRoutes{}), func() gotypes.ISerializable { + return &SRoutes{} + }) + RouteTableManager = &SRouteTableManager{ + SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager( + SRouteTable{}, + "route_tables_tbl", + "route_table", + "route_tables", + ), + } +} + +type SRouteTable struct { + db.SVirtualResourceBase + + VpcId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required"` + CloudregionId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"optional"` + Type string `width:"16" charset:"ascii" nullable:"false" list:"user"` + Routes *SRoutes `list:"user" update:"user" create:"required"` +} + +func (man *SRouteTableManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) { + q, err := man.SVirtualResourceBaseManager.ListItemFilter(ctx, q, userCred, query) + if err != nil { + return nil, err + } + userProjId := userCred.GetProjectId() + data := query.(*jsonutils.JSONDict) + for _, key := range []string{"vpc", "cloudregion"} { + v := validators.NewModelIdOrNameValidator(key, key, userProjId) + v.Optional(true) + q, err = v.QueryFilter(q, data) + if err != nil { + return nil, err + } + } + return q, nil +} + +func (man *SRouteTableManager) validateRoutes(data *jsonutils.JSONDict, update bool) (*jsonutils.JSONDict, error) { + routes := SRoutes{} + routesV := validators.NewStructValidator("routes", &routes) + if update { + routesV.Optional(true) + } + err := routesV.Validate(data) + if err != nil { + return nil, err + } + return data, nil +} + +func (man *SRouteTableManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + data, err := man.validateRoutes(data, false) + if err != nil { + return nil, err + } + vpcV := validators.NewModelIdOrNameValidator("vpc", "vpc", ownerProjId) + if err := vpcV.Validate(data); err != nil { + return nil, err + } + vpc := vpcV.Model.(*SVpc) + cloudregion := vpc.GetRegion() + if cloudregion == nil { + return nil, httperrors.NewConflictError("failed fetching cloudregion of vpc %s(%s)", vpc.Name, vpc.Id) + } + data.Set("cloudregion_id", jsonutils.NewString(cloudregion.Id)) + return man.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data) +} + +func (rt *SRouteTable) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + data, err := RouteTableManager.validateRoutes(data, true) + if err != nil { + return nil, err + } + return rt.SVirtualResourceBase.ValidateUpdateData(ctx, userCred, query, data) +} + +func (rt *SRouteTable) AllowPerformAddRoutes(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) bool { + return rt.IsOwner(userCred) || userCred.IsSystemAdmin() +} + +func (rt *SRouteTable) AllowPerformDelRoutes(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) bool { + return rt.AllowPerformAddRoutes(ctx, userCred, query, data) +} + +// PerformAddRoutes patches acl entries by adding then deleting the specified acls. +// This is intended mainly for command line operations. +func (rt *SRouteTable) PerformAddRoutes(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + routes := gotypes.DeepCopy(rt.Routes).(SRoutes) + { + adds := SRoutes{} + addsV := validators.NewStructValidator("routes", &adds) + addsV.Optional(true) + err := addsV.Validate(data) + if err != nil { + return nil, err + } + for _, add := range adds { + found := false + for _, route := range routes { + if route.Cidr == add.Cidr { + found = true + break + } + } + if !found { + routes = append(routes, add) + } + } + } + _, err := rt.GetModelManager().TableSpec().Update(rt, func() error { + rt.Routes = &routes + return nil + }) + if err != nil { + return nil, err + } + return nil, nil +} + +func (rt *SRouteTable) PerformDelRoutes(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + routes := gotypes.DeepCopy(rt.Routes).(SRoutes) + { + cidrs := []string{} + err := data.Unmarshal(&cidrs, "cidrs") + if err != nil { + return nil, httperrors.NewInputParameterError("unmarshaling cidrs failed: %s", err) + } + for _, cidr := range cidrs { + for i := len(routes) - 1; i >= 0; i-- { + route := routes[i] + if route.Type == "system" { + continue + } + if route.Cidr == cidr { + routes = append(routes[:i], routes[i+1:]...) + break + } + } + } + } + _, err := rt.GetModelManager().TableSpec().Update(rt, func() error { + rt.Routes = &routes + return nil + }) + if err != nil { + return nil, err + } + return nil, nil +} + +func (rt *SRouteTable) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { + extra := rt.SVirtualResourceBase.GetCustomizeColumns(ctx, userCred, query) + vpcM, err := VpcManager.FetchById(rt.VpcId) + if err != nil { + log.Errorf("route table %s(%s): fetch vpc (%s) error: %s", + rt.Name, rt.Id, rt.VpcId, err) + return extra + } + cloudregionM, err := CloudregionManager.FetchById(rt.CloudregionId) + if err != nil { + log.Errorf("route table %s(%s): fetch cloud region (%s) error: %s", + rt.Name, rt.Id, rt.CloudregionId, err) + return extra + } + extra.Set("vpc", jsonutils.NewString(vpcM.GetName())) + extra.Set("cloudregion", jsonutils.NewString(cloudregionM.GetName())) + return extra +} + +func (rt *SRouteTable) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { + extra := rt.GetCustomizeColumns(ctx, userCred, query) + return extra +} diff --git a/pkg/mcclient/modules/mod_routetables.go b/pkg/mcclient/modules/mod_routetables.go new file mode 100644 index 0000000000..12f965ecad --- /dev/null +++ b/pkg/mcclient/modules/mod_routetables.go @@ -0,0 +1,28 @@ +package modules + +type RouteTableManager struct { + ResourceManager +} + +var ( + RouteTables RouteTableManager +) + +func init() { + RouteTables = RouteTableManager{ + NewComputeManager( + "route_table", + "route_tables", + []string{ + "id", + "name", + "type", + "vpc", + "vpc_id", + "routes", + }, + []string{"tenant"}, + ), + } + registerCompute(&RouteTables) +} diff --git a/pkg/mcclient/options/routetables.go b/pkg/mcclient/options/routetables.go new file mode 100644 index 0000000000..c214d824b8 --- /dev/null +++ b/pkg/mcclient/options/routetables.go @@ -0,0 +1,133 @@ +package options + +import ( + "fmt" + + "yunion.io/x/jsonutils" +) + +type Route struct { + Type string + Cidr string + NextHopType string + NextHopId string +} + +type Routes []*Route + +type RoutesOptions struct { + RouteType []string + RouteCidr []string + RouteNextHopType []string + RouteNextHopId []string +} + +func (opts *RoutesOptions) Params() (jsonutils.JSONObject, error) { + len0 := len(opts.RouteType) + len1 := len(opts.RouteCidr) + if len0 != len1 || len0 != len(opts.RouteNextHopType) || len1 != len(opts.RouteNextHopId) { + return nil, fmt.Errorf("there must be equal number of options of --route-xxx") + } + routes := []*Route{} + for i := 0; i < len0; i++ { + routes = append(routes, &Route{ + Type: opts.RouteType[i], + Cidr: opts.RouteCidr[i], + NextHopType: opts.RouteNextHopType[i], + NextHopId: opts.RouteNextHopId[i], + }) + } + routesJson := jsonutils.Marshal(routes) + return routesJson, nil +} + +type RouteTableCreateOptions struct { + NAME string + Vpc string + + RoutesOptions +} + +func (opts *RouteTableCreateOptions) Params() (*jsonutils.JSONDict, error) { + params, err := optionsStructToParams(opts) + if err != nil { + return nil, err + } + routesJson, err := opts.RoutesOptions.Params() + if err != nil { + return nil, err + } + params.Set("routes", routesJson) + return params, nil +} + +type RouteTableGetOptions struct { + ID string +} + +type RouteTableUpdateOptions struct { + ID string `json:"-"` + Name string + + RoutesOptions +} + +func (opts *RouteTableUpdateOptions) Params() (*jsonutils.JSONDict, error) { + params, err := optionsStructToParams(opts) + if err != nil { + return nil, err + } + if len(opts.RouteCidr) != 0 { + routesJson, err := opts.RoutesOptions.Params() + if err != nil { + return nil, err + } + params.Set("routes", routesJson) + } + return params, nil +} + +type RouteTableAddRoutesOptions struct { + ID string `json:"-"` + + RoutesOptions +} + +func (opts *RouteTableAddRoutesOptions) Params() (*jsonutils.JSONDict, error) { + if len(opts.RouteCidr) == 0 { + return nil, fmt.Errorf("nothing to add") + } + routesJson, err := opts.RoutesOptions.Params() + if err != nil { + return nil, err + } + params := jsonutils.NewDict() + params.Set("routes", routesJson) + return params, nil +} + +type RouteTableDelRoutesOptions struct { + ID string `json:"-"` + + RouteCidr []string +} + +func (opts *RouteTableDelRoutesOptions) Params() (*jsonutils.JSONDict, error) { + if len(opts.RouteCidr) == 0 { + return nil, fmt.Errorf("nothing to del") + } + params := jsonutils.NewDict() + params.Set("cidrs", jsonutils.Marshal(opts.RouteCidr)) + return params, nil +} + +type RouteTableDeleteOptions struct { + ID string +} + +type RouteTableListOptions struct { + Vpc string + Cloudregion string + + BaseListOptions +} From 593219e0103030b781b9d9ac7b9745327d539b41 Mon Sep 17 00:00:00 2001 From: Yousong Zhou Date: Fri, 23 Nov 2018 03:05:04 +0000 Subject: [PATCH 27/29] =?UTF-8?q?aliyun:=20=E5=90=8C=E6=AD=A5routetables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/cloudprovider/resources.go | 19 ++ pkg/compute/models/routetables.go | 122 +++++++++++ .../tasks/cloud_provider_sync_info_task.go | 19 ++ pkg/util/aliyun/routetable.go | 200 +++++++++++++++++- pkg/util/aliyun/vpc.go | 33 ++- pkg/util/aws/vpc.go | 5 + pkg/util/azure/classic_vpc.go | 5 + pkg/util/azure/vpc.go | 5 + pkg/util/qcloud/vpc.go | 5 + 9 files changed, 403 insertions(+), 10 deletions(-) diff --git a/pkg/cloudprovider/resources.go b/pkg/cloudprovider/resources.go index c5c8ccde7c..b95751ee11 100644 --- a/pkg/cloudprovider/resources.go +++ b/pkg/cloudprovider/resources.go @@ -221,6 +221,24 @@ type ICloudSecurityGroup interface { GetRules() ([]secrules.SecurityRule, error) } +type ICloudRouteTable interface { + ICloudResource + GetManagerId() string + + GetDescription() string + GetRegionId() string + GetVpcId() string + GetType() string + GetIRoutes() ([]ICloudRoute, error) +} + +type ICloudRoute interface { + GetType() string + GetCidr() string + GetNextHopType() string + GetNextHop() string +} + type ICloudDisk interface { ICloudResource IBillingResource @@ -266,6 +284,7 @@ type ICloudVpc interface { // GetStatus() string GetIWires() ([]ICloudWire, error) GetISecurityGroups() ([]ICloudSecurityGroup, error) + GetIRouteTables() ([]ICloudRouteTable, error) GetManagerId() string diff --git a/pkg/compute/models/routetables.go b/pkg/compute/models/routetables.go index a79e59219c..b14498f131 100644 --- a/pkg/compute/models/routetables.go +++ b/pkg/compute/models/routetables.go @@ -9,10 +9,12 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/gotypes" + "yunion.io/x/pkg/util/compare" "yunion.io/x/sqlchemy" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/validators" + "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" ) @@ -91,6 +93,7 @@ func init() { type SRouteTable struct { db.SVirtualResourceBase + SManagedResourceBase VpcId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required"` CloudregionId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"optional"` @@ -250,5 +253,124 @@ func (rt *SRouteTable) GetCustomizeColumns(ctx context.Context, userCred mcclien func (rt *SRouteTable) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { extra := rt.GetCustomizeColumns(ctx, userCred, query) + extra = rt.SManagedResourceBase.getExtraDetails(ctx, extra) return extra } + +func (man *SRouteTableManager) SyncRouteTables(ctx context.Context, userCred mcclient.TokenCredential, vpc *SVpc, cloudRouteTables []cloudprovider.ICloudRouteTable) ([]SRouteTable, []cloudprovider.ICloudRouteTable, compare.SyncResult) { + localRouteTables := make([]SRouteTable, 0) + remoteRouteTables := make([]cloudprovider.ICloudRouteTable, 0) + syncResult := compare.SyncResult{} + + dbRouteTables := []SRouteTable{} + if err := db.FetchModelObjects(man, man.Query(), &dbRouteTables); err != nil { + syncResult.Error(err) + return nil, nil, syncResult + } + removed := make([]SRouteTable, 0) + commondb := make([]SRouteTable, 0) + commonext := make([]cloudprovider.ICloudRouteTable, 0) + added := make([]cloudprovider.ICloudRouteTable, 0) + if false { + for _, rt := range cloudRouteTables { + log.Errorf("%s, %s", rt.GetName(), rt.GetGlobalId()) + routes, err := rt.GetIRoutes() + if err != nil { + log.Errorf("get routes err: %s", err) + continue + } + for i, route := range routes { + log.Errorf("route %d: %#v ", i, route) + } + } + } + if err := compare.CompareSets(dbRouteTables, cloudRouteTables, &removed, &commondb, &commonext, &added); err != nil { + syncResult.Error(err) + return nil, nil, syncResult + } + + for i := 0; i < len(commondb); i += 1 { + err := commondb[i].SyncWithCloudRouteTable(userCred, vpc, commonext[i]) + if err != nil { + syncResult.UpdateError(err) + continue + } + localRouteTables = append(localRouteTables, commondb[i]) + remoteRouteTables = append(remoteRouteTables, commonext[i]) + syncResult.Update() + } + + for i := 0; i < len(added); i += 1 { + routeTableNew, err := man.insertFromCloud(userCred, vpc, added[i]) + if err != nil { + syncResult.AddError(err) + continue + } + localRouteTables = append(localRouteTables, *routeTableNew) + remoteRouteTables = append(remoteRouteTables, added[i]) + syncResult.Add() + } + return localRouteTables, remoteRouteTables, syncResult +} + +func (man *SRouteTableManager) newRouteTableFromCloud(userCred mcclient.TokenCredential, vpc *SVpc, cloudRouteTable cloudprovider.ICloudRouteTable) (*SRouteTable, error) { + routes := []*SRoute{} + { + cloudRoutes, err := cloudRouteTable.GetIRoutes() + if err != nil { + return nil, err + } + for _, cloudRoute := range cloudRoutes { + route := &SRoute{ + Type: cloudRoute.GetType(), + Cidr: cloudRoute.GetCidr(), + NextHopType: cloudRoute.GetNextHopType(), + NextHopId: cloudRoute.GetNextHop(), + } + routes = append(routes, route) + } + } + routeTable := &SRouteTable{ + CloudregionId: vpc.CloudregionId, + VpcId: vpc.Id, + Type: cloudRouteTable.GetType(), + Routes: (*SRoutes)(&routes), + } + routeTable.Name = cloudRouteTable.GetName() + routeTable.ManagerId = vpc.ManagerId + routeTable.ExternalId = cloudRouteTable.GetGlobalId() + routeTable.Description = cloudRouteTable.GetDescription() + routeTable.ProjectId = userCred.GetProjectId() + routeTable.SetModelManager(man) + return routeTable, nil +} + +func (man *SRouteTableManager) insertFromCloud(userCred mcclient.TokenCredential, vpc *SVpc, cloudRouteTable cloudprovider.ICloudRouteTable) (*SRouteTable, error) { + routeTable, err := man.newRouteTableFromCloud(userCred, vpc, cloudRouteTable) + if err != nil { + return nil, err + } + if err := man.TableSpec().Insert(routeTable); err != nil { + return nil, err + } + return routeTable, nil +} + +func (self *SRouteTable) SyncWithCloudRouteTable(userCred mcclient.TokenCredential, vpc *SVpc, cloudRouteTable cloudprovider.ICloudRouteTable) error { + man := self.GetModelManager().(*SRouteTableManager) + routeTable, err := man.newRouteTableFromCloud(userCred, vpc, cloudRouteTable) + if err != nil { + return err + } + _, err = man.TableSpec().Update(self, func() error { + self.CloudregionId = routeTable.CloudregionId + self.VpcId = vpc.Id + self.Type = routeTable.Type + self.Routes = routeTable.Routes + return nil + }) + if err != nil { + return err + } + return nil +} diff --git a/pkg/compute/tasks/cloud_provider_sync_info_task.go b/pkg/compute/tasks/cloud_provider_sync_info_task.go index 7fc8fbea5b..06dccfdd0a 100644 --- a/pkg/compute/tasks/cloud_provider_sync_info_task.go +++ b/pkg/compute/tasks/cloud_provider_sync_info_task.go @@ -209,6 +209,7 @@ func syncRegionVPCs(ctx context.Context, provider *models.SCloudprovider, task * for j := 0; j < len(localVpcs); j += 1 { syncVpcWires(ctx, provider, task, &localVpcs[j], remoteVpcs[j], syncRange) syncVpcSecGroup(ctx, provider, task, &localVpcs[j], remoteVpcs[j], syncRange) + syncVpcRouteTables(ctx, provider, task, &localVpcs[j], remoteVpcs[j], syncRange) } } @@ -230,6 +231,24 @@ func syncVpcSecGroup(ctx context.Context, provider *models.SCloudprovider, task } } +func syncVpcRouteTables(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, localVpc *models.SVpc, remoteVpc cloudprovider.ICloudVpc, syncRange *models.SSyncRange) { + routeTables, err := remoteVpc.GetIRouteTables() + if err != nil { + msg := fmt.Sprintf("GetIRouteTables for vpc %s failed %s", remoteVpc.GetId(), err) + log.Errorf(msg) + logSyncFailed(provider, task, msg) + return + } + _, _, result := models.RouteTableManager.SyncRouteTables(ctx, task.GetUserCred(), localVpc, routeTables) + msg := result.Result() + notes := fmt.Sprintf("SyncRouteTables for VPC %s result: %s", localVpc.Name, msg) + log.Infof(notes) + if result.IsError() { + logSyncFailed(provider, task, msg) + return + } +} + func syncVpcWires(ctx context.Context, provider *models.SCloudprovider, task taskman.ITask, localVpc *models.SVpc, remoteVpc cloudprovider.ICloudVpc, syncRange *models.SSyncRange) { wires, err := remoteVpc.GetIWires() if err != nil { diff --git a/pkg/util/aliyun/routetable.go b/pkg/util/aliyun/routetable.go index 65e8f6101e..6f37698cb2 100644 --- a/pkg/util/aliyun/routetable.go +++ b/pkg/util/aliyun/routetable.go @@ -1,7 +1,13 @@ package aliyun import ( + "fmt" "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/cloudprovider" ) // {"CreationTime":"2017-03-19T13:37:40Z","RouteEntrys":{"RouteEntry":[{"DestinationCidrBlock":"172.31.32.0/20","InstanceId":"","NextHopType":"local","NextHops":{"NextHop":[]},"RouteTableId":"vtb-j6c60lectdi80rk5xz43g","Status":"Available","Type":"System"},{"DestinationCidrBlock":"100.64.0.0/10","InstanceId":"","NextHopType":"service","NextHops":{"NextHop":[]},"RouteTableId":"vtb-j6c60lectdi80rk5xz43g","Status":"Available","Type":"System"}]},"RouteTableId":"vtb-j6c60lectdi80rk5xz43g","RouteTableType":"System","VRouterId":"vrt-j6c00qrol733dg36iq4qj"} @@ -11,23 +17,199 @@ type SNextHops struct { } type SRouteEntry struct { - DestinationCidrBlock string - InstanceId string - NextHopType string - NextHops SNextHops + routeTable *SRouteTable + RouteTableId string - Status string Type string + DestinationCidrBlock string + NextHopType string + InstanceId string + NextHops SNextHops +} + +func (route *SRouteEntry) GetType() string { + return route.Type +} + +func (route *SRouteEntry) GetCidr() string { + return route.DestinationCidrBlock +} + +func (route *SRouteEntry) GetNextHopType() string { + return route.NextHopType +} + +func (route *SRouteEntry) GetNextHop() string { + return route.InstanceId } type SRouteEntrys struct { - RouteEntry []SRouteEntry + RouteEntry []*SRouteEntry } type SRouteTable struct { - CreationTime time.Time - RouteEntrys SRouteEntrys + region *SRegion + vpc *SVpc + routes []cloudprovider.ICloudRoute + + VpcId string + CreationTime time.Time + RouteEntrys SRouteEntrys + VRouterId string + Description string + RouteTableId string + RouteTableName string RouteTableType string - VRouterId string + RouterId string + RouterType string + VSwitchIds SRouteTableVSwitchIds +} + +type SRouteTableVSwitchIds struct { + VSwitchId []string +} + +type sDescribeRouteTablesResponseRouteTables struct { + RouteTable []SRouteTable +} + +type sDescribeRouteTablesResponse struct { + RouteTables sDescribeRouteTablesResponseRouteTables + TotalCount int +} + +func (self *SRouteTable) GetDescription() string { + return self.Description +} + +func (self *SRouteTable) GetId() string { + return self.GetGlobalId() +} + +func (self *SRouteTable) GetGlobalId() string { + return self.RouteTableId +} + +func (self *SRouteTable) GetManagerId() string { + return self.region.client.providerId +} + +func (self *SRouteTable) GetName() string { + return self.RouteTableName +} + +func (self *SRouteTable) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SRouteTable) GetRegionId() string { + return self.region.RegionId +} + +func (self *SRouteTable) GetType() string { + return self.RouteTableType +} + +func (self *SRouteTable) GetVpcId() string { + return self.VpcId +} + +func (self *SRouteTable) GetIRoutes() ([]cloudprovider.ICloudRoute, error) { + if self.routes == nil { + err := self.fetchRoutes() + if err != nil { + return nil, err + } + } + return self.routes, nil +} + +func (self *SRouteTable) GetStatus() string { + return "" +} + +func (self *SRouteTable) IsEmulated() bool { + return false +} + +func (self *SRouteTable) Refresh() error { + return nil +} + +func (self *SRouteTable) fetchRoutes() error { + routes := make([]*SRouteEntry, 0) + for { + parts, total, err := self.RemoteGetRoutes(len(routes), 50) + if err != nil { + return err + } + routes = append(routes, parts...) + if len(routes) >= total { + break + } + } + self.routes = make([]cloudprovider.ICloudRoute, len(routes)) + for i := 0; i < len(routes); i++ { + routes[i].routeTable = self + self.routes[i] = routes[i] + } + return nil +} + +func (self *SRouteTable) RemoteGetRoutes(offset int, limit int) ([]*SRouteEntry, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + params := make(map[string]string) + params["RouteTableId"] = self.RouteTableId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + + body, err := self.region.ecsRequest("DescribeRouteTables", params) + if err != nil { + log.Errorf("RemoteGetRoutes fail %s", err) + return nil, 0, err + } + + resp := sDescribeRouteTablesResponse{} + err = body.Unmarshal(&resp) + if err != nil { + log.Errorf("Unmarshal routeEntrys fail %s", err) + return nil, 0, err + } + routeTables := resp.RouteTables.RouteTable + if len(routeTables) != 1 { + return nil, 0, fmt.Errorf("expecting 1 route table, got %d", len(routeTables)) + } + routeTable := routeTables[0] + return routeTable.RouteEntrys.RouteEntry, resp.TotalCount, nil +} + +func (self *SVpc) RemoteGetRouteTableList(offset int, limit int) ([]*SRouteTable, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + params := make(map[string]string) + params["VpcId"] = self.VpcId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + + body, err := self.apiRequest("DescribeRouteTableList", params) + if err != nil { + log.Errorf("RemoteGetRouteTableList fail %s", err) + return nil, 0, err + } + + routeTables := make([]*SRouteTable, 0) + err = body.Unmarshal(&routeTables, "RouterTableList", "RouterTableListType") + if err != nil { + log.Errorf("Unmarshal routeTables fail %s", err) + return nil, 0, err + } + for _, routeTable := range routeTables { + routeTable.region = self.region + } + total, _ := body.Int("TotalCount") + return routeTables, int(total), nil } diff --git a/pkg/util/aliyun/vpc.go b/pkg/util/aliyun/vpc.go index 341bab020c..b567df76d2 100644 --- a/pkg/util/aliyun/vpc.go +++ b/pkg/util/aliyun/vpc.go @@ -30,7 +30,8 @@ type SVpc struct { iwires []cloudprovider.ICloudWire - secgroups []cloudprovider.ICloudSecurityGroup + secgroups []cloudprovider.ICloudSecurityGroup + routeTables []cloudprovider.ICloudRouteTable CidrBlock string CreationTime time.Time @@ -191,6 +192,36 @@ func (self *SVpc) GetISecurityGroups() ([]cloudprovider.ICloudSecurityGroup, err return self.secgroups, nil } +func (self *SVpc) fetchRouteTables() error { + routeTables := make([]*SRouteTable, 0) + for { + parts, total, err := self.RemoteGetRouteTableList(len(routeTables), 50) + if err != nil { + return err + } + routeTables = append(routeTables, parts...) + if len(routeTables) >= total { + break + } + } + self.routeTables = make([]cloudprovider.ICloudRouteTable, len(routeTables)) + for i := 0; i < len(routeTables); i++ { + routeTables[i].vpc = self + self.routeTables[i] = routeTables[i] + } + return nil +} + +func (self *SVpc) GetIRouteTables() ([]cloudprovider.ICloudRouteTable, error) { + if self.routeTables == nil { + err := self.fetchRouteTables() + if err != nil { + return nil, err + } + } + return self.routeTables, nil +} + func (self *SVpc) GetManagerId() string { return self.region.client.providerId } diff --git a/pkg/util/aws/vpc.go b/pkg/util/aws/vpc.go index eb0ed59eab..ad9946ee7f 100644 --- a/pkg/util/aws/vpc.go +++ b/pkg/util/aws/vpc.go @@ -103,6 +103,11 @@ func (self *SVpc) GetISecurityGroups() ([]cloudprovider.ICloudSecurityGroup, err return self.secgroups, nil } +func (self *SVpc) GetIRouteTables() ([]cloudprovider.ICloudRouteTable, error) { + rts := []cloudprovider.ICloudRouteTable{} + return rts, nil +} + func (self *SVpc) GetManagerId() string { return self.region.client.providerId } diff --git a/pkg/util/azure/classic_vpc.go b/pkg/util/azure/classic_vpc.go index 6e369a6617..1cb6568441 100644 --- a/pkg/util/azure/classic_vpc.go +++ b/pkg/util/azure/classic_vpc.go @@ -140,6 +140,11 @@ func (self *SClassicVpc) GetISecurityGroups() ([]cloudprovider.ICloudSecurityGro return self.secgroups, nil } +func (self *SClassicVpc) GetIRouteTables() ([]cloudprovider.ICloudRouteTable, error) { + rts := []cloudprovider.ICloudRouteTable{} + return rts, nil +} + func (self *SClassicVpc) fetchWires() error { networks := make([]cloudprovider.ICloudNetwork, len(self.Properties.Subnets)) wire := SClassicWire{zone: self.region.izones[0].(*SZone), vpc: self} diff --git a/pkg/util/azure/vpc.go b/pkg/util/azure/vpc.go index 07060c4a4c..3d4bb02dff 100644 --- a/pkg/util/azure/vpc.go +++ b/pkg/util/azure/vpc.go @@ -148,6 +148,11 @@ func (self *SVpc) GetISecurityGroups() ([]cloudprovider.ICloudSecurityGroup, err return self.secgroups, nil } +func (self *SVpc) GetIRouteTables() ([]cloudprovider.ICloudRouteTable, error) { + rts := []cloudprovider.ICloudRouteTable{} + return rts, nil +} + func (self *SVpc) fetchWires() error { networks := make([]cloudprovider.ICloudNetwork, len(*self.Properties.Subnets)) if len(self.region.izones) == 0 { diff --git a/pkg/util/qcloud/vpc.go b/pkg/util/qcloud/vpc.go index 5aee1b7161..ba59f5609f 100644 --- a/pkg/util/qcloud/vpc.go +++ b/pkg/util/qcloud/vpc.go @@ -86,6 +86,11 @@ func (self *SVpc) GetISecurityGroups() ([]cloudprovider.ICloudSecurityGroup, err return isecgroups, nil } +func (self *SVpc) GetIRouteTables() ([]cloudprovider.ICloudRouteTable, error) { + rts := []cloudprovider.ICloudRouteTable{} + return rts, nil +} + func (self *SVpc) getWireByZoneId(zoneId string) *SWire { for i := 0; i <= len(self.iwires); i++ { wire := self.iwires[i].(*SWire) From 9c52c999a117efe08f2ba24fef56f26182351d81 Mon Sep 17 00:00:00 2001 From: wanyaoqi Date: Thu, 22 Nov 2018 19:09:40 +0800 Subject: [PATCH 28/29] support nfs storage --- cmd/climc/shell/storages.go | 42 +++- pkg/compute/guestdrivers/qcloud.go | 3 +- pkg/compute/models/disks.go | 3 +- pkg/compute/models/guestdisks.go | 3 +- pkg/compute/models/guests.go | 3 +- pkg/compute/models/hosts.go | 3 +- pkg/compute/models/hoststorages.go | 73 ++++++- pkg/compute/models/storagecaches.go | 17 ++ pkg/compute/models/storages.go | 292 ++++++++++++++++++++++++---- pkg/compute/options/options.go | 2 + 10 files changed, 381 insertions(+), 60 deletions(-) diff --git a/cmd/climc/shell/storages.go b/cmd/climc/shell/storages.go index de96c3bf29..5392b09073 100644 --- a/cmd/climc/shell/storages.go +++ b/cmd/climc/shell/storages.go @@ -99,17 +99,21 @@ func init() { }) type StorageCreateOptions struct { - NAME string `help:"Name of the Storage"` - Capacity int64 `help:"Capacity of the Storage"` - MediumType string `help:"Medium type, either ssd or rotate" choices:"ssd|rotate"` - StorageType string `help:"Storage type" choices:"local|nas|vsan|rbd|baremetal"` - MonHost string `helo:"Ceph mon_host config"` - Key string `helo:"Ceph key config"` - Pool string `helo:"Ceph Poll Name"` + NAME string `help:"Name of the Storage"` + ZONE string `help:"Zone id of storage"` + Capacity int64 `help:"Capacity of the Storage"` + MediumType string `help:"Medium type, either ssd or rotate" choices:"ssd|rotate"` + StorageType string `help:"Storage type" choices:"local|nas|vsan|rbd|nfs|baremetal"` + MonHost string `help:"Ceph mon_host config"` + Key string `help:"Ceph key config"` + Pool string `help:"Ceph Poll Name"` + NfsHost string `help:"NFS host"` + NfsSharedDir string `help:"NFS shared dir"` } R(&StorageCreateOptions{}, "storage-create", "Create a Storage", func(s *mcclient.ClientSession, args *StorageCreateOptions) error { params := jsonutils.NewDict() params.Add(jsonutils.NewString(args.NAME), "name") + params.Add(jsonutils.NewString(args.ZONE), "zone") params.Add(jsonutils.NewInt(args.Capacity), "capacity") params.Add(jsonutils.NewString(args.StorageType), "storage_type") params.Add(jsonutils.NewString(args.MediumType), "medium_type") @@ -120,6 +124,12 @@ func init() { params.Add(jsonutils.NewString(args.MonHost), "rbd_mon_host") params.Add(jsonutils.NewString(args.Key), "rbd_key") params.Add(jsonutils.NewString(args.Pool), "rbd_pool") + } else if args.StorageType == "nfs" { + if len(args.NfsHost) == 0 || len(args.NfsSharedDir) == 0 { + return fmt.Errorf("Storage type nfs missing conf host or shared dir") + } + params.Add(jsonutils.NewString(args.NfsHost), "nfs_host") + params.Add(jsonutils.NewString(args.NfsSharedDir), "nfs_shared_dir") } storage, err := modules.Storages.Create(s, params) if err != nil { @@ -168,6 +178,24 @@ func init() { return nil }) + R(&StorageShowOptions{}, "storage-online", "Online a storage", func(s *mcclient.ClientSession, args *StorageShowOptions) error { + result, err := modules.Storages.PerformAction(s, args.ID, "online", nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + + R(&StorageShowOptions{}, "storage-offline", "Offline a storage", func(s *mcclient.ClientSession, args *StorageShowOptions) error { + result, err := modules.Storages.PerformAction(s, args.ID, "offline", nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + type StorageCacheImageActionOptions struct { ID string `help:"ID or name of storage"` IMAGE string `help:"ID or name of image"` diff --git a/pkg/compute/guestdrivers/qcloud.go b/pkg/compute/guestdrivers/qcloud.go index cdcb19335f..7e9556c0e3 100644 --- a/pkg/compute/guestdrivers/qcloud.go +++ b/pkg/compute/guestdrivers/qcloud.go @@ -9,7 +9,6 @@ import ( "yunion.io/x/log" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/pkg/util/sysutils" "yunion.io/x/pkg/utils" "yunion.io/x/onecloud/pkg/cloudcommon/db" @@ -74,7 +73,7 @@ func (self *SQcloudGuestDriver) ValidateResizeDisk(guest *models.SGuest, disk *m if !utils.IsInStringArray(guest.Status, []string{models.VM_READY, models.VM_RUNNING}) { return fmt.Errorf("Cannot resize disk when guest in status %s", guest.Status) } - if utils.IsInStringArray(storage.StorageType, []string{sysutils.STORAGE_LOCAL_BASIC, sysutils.STORAGE_LOCAL_SSD}) { + if utils.IsInStringArray(storage.StorageType, []string{models.STORAGE_LOCAL_BASIC, models.STORAGE_LOCAL_SSD}) { return fmt.Errorf("Cannot resize %s disk", storage.StorageType) } return nil diff --git a/pkg/compute/models/disks.go b/pkg/compute/models/disks.go index 55338fbfb5..2319762a2b 100644 --- a/pkg/compute/models/disks.go +++ b/pkg/compute/models/disks.go @@ -15,7 +15,6 @@ import ( "yunion.io/x/pkg/util/fileutils" "yunion.io/x/pkg/util/osprofile" "yunion.io/x/pkg/util/regutils" - "yunion.io/x/pkg/util/sysutils" "yunion.io/x/pkg/util/timeutils" "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" @@ -1030,7 +1029,7 @@ func parseDiskInfo(ctx context.Context, userCred mcclient.TokenCredential, info diskConfig.Mountpoint = p } else if p == "autoextend" { diskConfig.Size = -1 - } else if utils.IsInStringArray(p, sysutils.STORAGE_TYPES) { + } else if utils.IsInStringArray(p, STORAGE_TYPES) { diskConfig.Backend = p } else if len(p) > 0 { if userCred == nil { diff --git a/pkg/compute/models/guestdisks.go b/pkg/compute/models/guestdisks.go index cb5834f328..0e61957918 100644 --- a/pkg/compute/models/guestdisks.go +++ b/pkg/compute/models/guestdisks.go @@ -146,7 +146,8 @@ func (self *SGuestdisk) GetJsonDescAtHost(host *SHost) jsonutils.JSONObject { } } storage := disk.GetStorage() - if host.HostType == HOST_TYPE_HYPERVISOR && disk.IsLocal() || (storage != nil && storage.StorageType == STORAGE_RBD) { + // XXX ??? + if host.HostType == HOST_TYPE_HYPERVISOR { desc.Add(jsonutils.NewString(disk.StorageId), "storage_id") localpath := disk.GetPathAtHost(host) if len(localpath) == 0 { diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index 92a2bf8257..9213938a3a 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -19,7 +19,6 @@ import ( "yunion.io/x/pkg/util/osprofile" "yunion.io/x/pkg/util/regutils" "yunion.io/x/pkg/util/secrules" - "yunion.io/x/pkg/util/sysutils" "yunion.io/x/pkg/util/timeutils" "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" @@ -2306,7 +2305,7 @@ func (self *SGuest) DoPendingDelete(ctx context.Context, userCred mcclient.Token for _, guestdisk := range self.GetDisks() { disk := guestdisk.GetDisk() storage := disk.GetStorage() - if utils.IsInStringArray(storage.StorageType, sysutils.LOCAL_STORAGE_TYPES) || utils.IsInStringArray(disk.DiskType, []string{DISK_TYPE_SYS, DISK_TYPE_SWAP}) || (utils.IsInStringArray(self.Hypervisor, PUBLIC_CLOUD_HYPERVISORS) && disk.AutoDelete) { + if utils.IsInStringArray(storage.StorageType, STORAGE_LOCAL_TYPES) || utils.IsInStringArray(disk.DiskType, []string{DISK_TYPE_SYS, DISK_TYPE_SWAP}) || (utils.IsInStringArray(self.Hypervisor, PUBLIC_CLOUD_HYPERVISORS) && disk.AutoDelete) { disk.DoPendingDelete(ctx, userCred) } else { self.DetachDisk(ctx, disk, userCred) diff --git a/pkg/compute/models/hosts.go b/pkg/compute/models/hosts.go index 52bc0c742d..c05f30ab93 100644 --- a/pkg/compute/models/hosts.go +++ b/pkg/compute/models/hosts.go @@ -17,7 +17,6 @@ import ( "yunion.io/x/pkg/util/fileutils" "yunion.io/x/pkg/util/netutils" "yunion.io/x/pkg/util/regutils" - "yunion.io/x/pkg/util/sysutils" "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" @@ -467,7 +466,7 @@ func (self *SHost) GetBaremetalstorage() *SHoststorage { q := hoststorages.Query() q = q.Join(storages, sqlchemy.AND(sqlchemy.Equals(storages.Field("id"), hoststorages.Field("storage_id")), sqlchemy.IsFalse(storages.Field("deleted")))) - q = q.Filter(sqlchemy.Equals(storages.Field("storage_type"), sysutils.STORAGE_BAREMETAL)) + q = q.Filter(sqlchemy.Equals(storages.Field("storage_type"), STORAGE_BAREMETAL)) q = q.Filter(sqlchemy.Equals(hoststorages.Field("host_id"), self.Id)) if q.Count() == 1 { hs := SHoststorage{} diff --git a/pkg/compute/models/hoststorages.go b/pkg/compute/models/hoststorages.go index 8da2193ddb..1591a2e823 100644 --- a/pkg/compute/models/hoststorages.go +++ b/pkg/compute/models/hoststorages.go @@ -3,12 +3,16 @@ package models import ( "context" "fmt" + "net/http" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/httputils" "yunion.io/x/pkg/tristate" + "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" ) @@ -97,12 +101,68 @@ func (manager *SHoststorageManager) ValidateCreateData(ctx context.Context, user return manager.SJointResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data) } +func (self *SHoststorage) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data jsonutils.JSONObject) { + self.SHostJointsBase.PostCreate(ctx, userCred, ownerProjId, query, data) + storage := self.GetStorage() + if !utils.IsInStringArray(storage.StorageType, STORAGE_LOCAL_TYPES) { + host := storage.GetMasterHost() + log.Infof("Attach SharedStorage[%s] on host %s ...", storage.Name, host.Name) + url := fmt.Sprintf("%s/storages/attach", host.ManagerUri) + headers := http.Header{} + headers.Set("X-Auth-Token", userCred.GetTokenString()) + body := jsonutils.NewDict() + body.Set("mount_point", jsonutils.NewString(self.MountPoint)) + body.Set("name", jsonutils.NewString(storage.Name)) + body.Set("storage_id", jsonutils.NewString(storage.Id)) + body.Set("storage_conf", storage.StorageConf) + body.Set("storage_type", jsonutils.NewString(storage.StorageType)) + if len(storage.StoragecacheId) > 0 { + storagecache := StoragecacheManager.FetchStoragecacheById(storage.StoragecacheId) + if storagecache != nil { + body.Set("imagecache_path", jsonutils.NewString( + storage.GetStorageCachePath(self.MountPoint, storagecache.Path))) + body.Set("sotragecache_id", jsonutils.NewString(storagecache.Id)) + } + } + _, _, err := httputils.JSONRequest(httputils.GetDefaultClient(), + ctx, "POST", url, headers, body, false) + if err != nil { + log.Errorf("Host Storage Post Create Error: %s", err) + return + } + self.SyncStorageStatus() + } +} + +func (self *SHoststorage) SyncStorageStatus() { + storage := self.GetStorage() + hostQuery := HostManager.Query().SubQuery() + count := HoststorageManager.Query().Join(hostQuery, + sqlchemy.AND(sqlchemy.Equals(hostQuery.Field("id"), self.HostId), + sqlchemy.Equals(hostQuery.Field("host_status"), "online"))).Count() + status := storage.Status + if count >= 1 { + status = STORAGE_ONLINE + } else { + status = STORAGE_OFFLINE + } + if status != storage.Status { + storage.GetModelManager().TableSpec().Update(storage, func() error { + storage.Status = status + return nil + }) + } +} + func (self *SHoststorage) getExtraDetails(extra *jsonutils.JSONDict) *jsonutils.JSONDict { host := self.GetHost() extra.Add(jsonutils.NewString(host.Name), "host") storage := self.GetStorage() extra.Add(jsonutils.NewString(storage.Name), "storage") extra.Add(jsonutils.NewInt(int64(storage.Capacity)), "capacity") + if storage.StorageConf != nil { + extra.Set("storage_conf", storage.StorageConf) + } used := storage.GetUsedCapacity(tristate.True) wasted := storage.GetUsedCapacity(tristate.False) extra.Add(jsonutils.NewInt(int64(used)), "used_capacity") @@ -110,12 +170,15 @@ func (self *SHoststorage) getExtraDetails(extra *jsonutils.JSONDict) *jsonutils. extra.Add(jsonutils.NewInt(int64(storage.Capacity-used-wasted)), "free_capacity") extra.Add(jsonutils.NewString(storage.StorageType), "storage_type") extra.Add(jsonutils.NewString(storage.MediumType), "medium_type") - if storage.Enabled { - extra.Add(jsonutils.JSONTrue, "enabled") - } else { - extra.Add(jsonutils.JSONFalse, "enabled") - } + extra.Add(jsonutils.NewBool(storage.Enabled), "enabled") extra.Add(jsonutils.NewFloat(float64(storage.GetOvercommitBound())), "cmtbound") + extra.Add(jsonutils.NewInt(int64(self.GetGuestDiskCount())), "guest_disk_count") + if len(storage.StoragecacheId) > 0 { + storagecache := StoragecacheManager.FetchStoragecacheById(storage.StoragecacheId) + if storagecache != nil { + extra.Set("imagecache_path", jsonutils.NewString(storage.GetStorageCachePath(self.MountPoint, storagecache.Path))) + } + } return extra } diff --git a/pkg/compute/models/storagecaches.go b/pkg/compute/models/storagecaches.go index 240a99782b..5d2d07624a 100644 --- a/pkg/compute/models/storagecaches.go +++ b/pkg/compute/models/storagecaches.go @@ -284,6 +284,23 @@ func (manager *SStoragecacheManager) ListItemFilter(ctx context.Context, q *sqlc return q, nil } +func (manager *SStoragecacheManager) FetchStoragecacheById(storageCacheId string) *SStoragecache { + iStorageCache, _ := manager.FetchById(storageCacheId) + if iStorageCache == nil { + return nil + } + return iStorageCache.(*SStoragecache) +} + +func (manager *SStoragecacheManager) GetCachePathById(storageCacheId string) string { + iStorageCache, _ := manager.FetchById(storageCacheId) + if iStorageCache == nil { + return "" + } + sc := iStorageCache.(*SStoragecache) + return sc.Path +} + func (self *SStoragecache) ValidateDeleteCondition(ctx context.Context) error { if self.getCachedImageCount() > 0 { return httperrors.NewNotEmptyError("storage cache not empty") diff --git a/pkg/compute/models/storages.go b/pkg/compute/models/storages.go index b7be546d7d..fb0d4e12c2 100644 --- a/pkg/compute/models/storages.go +++ b/pkg/compute/models/storages.go @@ -2,6 +2,10 @@ package models import ( "context" + "fmt" + "path" + "strings" + "time" "yunion.io/x/jsonutils" "yunion.io/x/log" @@ -10,31 +14,51 @@ import ( "yunion.io/x/onecloud/pkg/compute/options" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/logclient" "yunion.io/x/pkg/tristate" "yunion.io/x/pkg/util/compare" - "yunion.io/x/pkg/util/sysutils" "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" ) const ( - STORAGE_LOCAL = "local" - STORAGE_BAREMETAL = "baremetal" - STORAGE_SHEEPDOG = "sheepdog" - STORAGE_RBD = "rbd" - STORAGE_DOCKER = "docker" - STORAGE_NAS = "nas" - STORAGE_VSAN = "vsan" + STORAGE_LOCAL = "local" + STORAGE_BAREMETAL = "baremetal" + STORAGE_SHEEPDOG = "sheepdog" + STORAGE_RBD = "rbd" + STORAGE_DOCKER = "docker" + STORAGE_NAS = "nas" + STORAGE_VSAN = "vsan" + STORAGE_NFS = "nfs" + STORAGE_PUBLIC_CLOUD = "cloud" STORAGE_CLOUD_EFFICIENCY = "cloud_efficiency" STORAGE_CLOUD_SSD = "cloud_ssd" - STORAGE_EPHEMERAL_SSD = "ephemeral_ssd" - STORAGE_GP2_SSD = "gp2" // aws general purpose ssd - STORAGE_IO1_SSD = "io1" // aws Provisioned IOPS SSD - STORAGE_ST1_HDD = "st1" // aws Throughput Optimized HDD - STORAGE_SC1_SSD = "sc1" // aws Cold HDD - STORAGE_STANDARD_SSD = "standard" // aws Magnetic volumes + STORAGE_CLOUD_ESSD = "cloud_essd" //增强型(Enhanced)SSD 云盘 + //Azure hdd and ssd storagetype + STORAGE_STANDARD_GRS = "standard_grs" + STORAGE_STANDARD_LRS = "standard_lrs" + STORAGE_STANDARD_RAGRS = "standard_ragrs" + STORAGE_STANDARD_ZRS = "standard_zrs" + STORAGE_PREMIUM_LRS = "premium_lrs" + + // aws storage type + STORAGE_GP2_SSD = "gp2" // aws general purpose ssd + STORAGE_IO1_SSD = "io1" // aws Provisioned IOPS SSD + STORAGE_ST1_HDD = "st1" // aws Throughput Optimized HDD + STORAGE_SC1_SSD = "sc1" // aws Cold HDD + STORAGE_STANDARD_SSD = "standard" // aws Magnetic volumes + + // qcloud storage type + // STORAGE_CLOUD_SSD ="cloud_ssd" + STORAGE_LOCAL_BASIC = "local_basic" + STORAGE_LOCAL_SSD = "local_ssd" + STORAGE_CLOUD_BASIC = "cloud_basic" + STORAGE_CLOUD_PERMIUM = "cloud_permium" +) + +const ( STORAGE_ENABLED = "enabled" STORAGE_DISABLED = "disabled" STORAGE_OFFLINE = "offline" @@ -47,17 +71,26 @@ const ( var ( DISK_TYPES = []string{DISK_TYPE_ROTATE, DISK_TYPE_SSD, DISK_TYPE_HYBRID} - STORAGE_LOCAL_TYPES = []string{STORAGE_LOCAL, STORAGE_BAREMETAL, STORAGE_NAS} + STORAGE_LOCAL_TYPES = []string{STORAGE_LOCAL, STORAGE_BAREMETAL} STORAGE_SUPPORT_TYPES = STORAGE_LOCAL_TYPES STORAGE_ALL_TYPES = []string{ STORAGE_LOCAL, STORAGE_BAREMETAL, STORAGE_SHEEPDOG, STORAGE_RBD, STORAGE_DOCKER, STORAGE_NAS, STORAGE_VSAN, + STORAGE_NFS, } - STORAGE_LIMITED_TYPES = []string{STORAGE_LOCAL, STORAGE_BAREMETAL, STORAGE_NAS, STORAGE_RBD} + STORAGE_TYPES = []string{STORAGE_LOCAL, STORAGE_BAREMETAL, STORAGE_SHEEPDOG, + STORAGE_RBD, STORAGE_DOCKER, STORAGE_NAS, STORAGE_VSAN, STORAGE_NFS, + STORAGE_PUBLIC_CLOUD, STORAGE_CLOUD_SSD, STORAGE_CLOUD_ESSD, STORAGE_CLOUD_EFFICIENCY, + STORAGE_STANDARD_GRS, STORAGE_STANDARD_LRS, STORAGE_STANDARD_RAGRS, STORAGE_STANDARD_ZRS, STORAGE_PREMIUM_LRS, + STORAGE_GP2_SSD, STORAGE_IO1_SSD, STORAGE_ST1_HDD, STORAGE_SC1_SSD, STORAGE_STANDARD_SSD, + STORAGE_LOCAL_BASIC, STORAGE_LOCAL_SSD, STORAGE_CLOUD_BASIC, STORAGE_CLOUD_PERMIUM, + } + + STORAGE_LIMITED_TYPES = []string{STORAGE_LOCAL, STORAGE_BAREMETAL, STORAGE_NAS, STORAGE_RBD, STORAGE_NFS} ) type SStorageManager struct { - db.SEnabledStatusStandaloneResourceBaseManager + db.SStandaloneResourceBaseManager SInfrastructureManager } @@ -65,7 +98,7 @@ var StorageManager *SStorageManager func init() { StorageManager = &SStorageManager{ - SEnabledStatusStandaloneResourceBaseManager: db.NewEnabledStatusStandaloneResourceBaseManager( + SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager( SStorage{}, "storages_tbl", "storage", @@ -75,7 +108,7 @@ func init() { } type SStorage struct { - db.SEnabledStatusStandaloneResourceBase + db.SStandaloneResourceBase SInfrastructure SManagedResourceBase @@ -89,6 +122,9 @@ type SStorage struct { ZoneId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"admin_required"` StoragecacheId string `width:"36" charset:"ascii" nullable:"true" list:"admin" get:"admin"` + + Enabled bool `nullable:"false" default:"true" list:"user" create:"optional"` + Status string `width:"36" charset:"ascii" nullable:"false" default:"offline" list:"user" create:"optional"` } func (manager *SStorageManager) GetContextManager() []db.IModelManager { @@ -98,7 +134,12 @@ func (manager *SStorageManager) GetContextManager() []db.IModelManager { func (manager *SStorageManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { storageType, _ := data.GetString("storage_type") mediumType, _ := data.GetString("medium_type") - if !utils.IsInStringArray(storageType, STORAGE_ALL_TYPES) { + capacity, _ := data.Int("capacity") + if capacity <= 0 { + return nil, httperrors.NewInputParameterError("Invalid capacity") + } + data.Set("capacity", jsonutils.NewInt(capacity)) + if !utils.IsInStringArray(storageType, STORAGE_TYPES) { return nil, httperrors.NewInputParameterError("Invalid storage type %s", storageType) } if !utils.IsInStringArray(mediumType, DISK_TYPES) { @@ -113,27 +154,192 @@ func (manager *SStorageManager) ValidateCreateData(ctx context.Context, userCred return nil, httperrors.NewResourceNotFoundError("zone %s", zoneId) } data.Set("zone_id", jsonutils.NewString(zone.GetId())) - // TODO: ValidateRdbConfData - // if storageType == STORAGE_RBD { - // conf := jsonutils.NewDict() - // for k, v := range data.Value() { - // if strings.HasPrefix(k, fmt.Sprintf("%s_", storageType)) { - // k = k[len(storageType)+1:] - // if len(k) > 0 { - // conf.Set(k, v) - // } - // } - // } - // data.Set("capacity", manager.ValidateRdbConfData(conf)) - // } - return manager.SEnabledStatusStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data) + if storageType == STORAGE_RBD { + conf, err := manager.ValidateRbdConfData(data) + if err != nil { + return nil, httperrors.NewBadRequestError("Vaildata rbd conf error: %s", err.Error()) + } + data.Set("storage_conf", conf) + // data.Set("capacity", rbdConf) + } else if storageType == STORAGE_NFS { + conf, err := manager.ValidataNfsConfdata(data) + if err != nil { + return nil, httperrors.NewBadRequestError("Vaildata nfs conf error: %s", err.Error()) + } + data.Set("storage_conf", conf) + } + return manager.SStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data) +} + +func (manager *SStorageManager) ValidataNfsConfdata(data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + conf := jsonutils.NewDict() + if nfsHost, err := data.GetString("nfs_host"); err != nil { + return nil, httperrors.NewInputParameterError("Get nfs conf host error: %s", err.Error()) + } else { + conf.Set("nfs_host", jsonutils.NewString(nfsHost)) + } + if nfsSharedDir, err := data.GetString("nfs_shared_dir"); err != nil { + return nil, httperrors.NewInputParameterError("Get nfs conf shared dir error: %s", err.Error()) + } else { + conf.Set("nfs_shared_dir", jsonutils.NewString(nfsSharedDir)) + } + return conf, nil +} + +func (manager *SStorageManager) ValidateRbdConfData(data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + conf := jsonutils.NewDict() + for k, v := range data.Value() { + if strings.HasPrefix(k, fmt.Sprintf("%s_", STORAGE_RBD)) { + k = k[len(STORAGE_RBD)+1:] + if len(k) > 0 { + conf.Set(k, v) + } + } + } + requireFields := []string{"mon_host", "key", "pool"} + for _, field := range requireFields { + if !conf.Contains(field) { + return nil, httperrors.NewMissingParameterError(field) + } + } + storages := make([]SStorage, 0) + err := manager.Query().Equals("storage_type", STORAGE_RBD).All(&storages) + if err != nil { + return nil, err + } + for i := 0; i < len(storages); i++ { + if conf.Equals(storages[i].StorageConf) { + return nil, httperrors.NewDuplicateResourceError("This RBD Storage[%s/%s] has already exist", storages[i].Name, conf.String()) + } + } + // TODO??? ensure rbd pool can use and get capacity + return conf, nil } func (self *SStorage) ValidateDeleteCondition(ctx context.Context) error { if self.GetHostCount() > 0 || self.GetDiskCount() > 0 || self.GetSnapshotCount() > 0 { return httperrors.NewNotEmptyError("Not an empty storage provider") } - return self.SEnabledStatusStandaloneResourceBase.ValidateDeleteCondition(ctx) + return self.SStandaloneResourceBase.ValidateDeleteCondition(ctx) +} + +func (self *SStorage) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data jsonutils.JSONObject) { + self.SStandaloneResourceBase.PostCreate(ctx, userCred, ownerProjId, query, data) + if self.StorageType == STORAGE_RBD { + // TODO + } else if self.StorageType == STORAGE_NFS { + sc := &SStoragecache{} + sc.Path = options.Options.NfsDefaultImageCacheDir + sc.ExternalId = self.Id + sc.Name = "nfs" + self.Name + time.Now().String() + err := StoragecacheManager.TableSpec().Insert(sc) + if err != nil { + log.Errorln(err) + return + } + err = StoragecacheManager.Query().Equals("external_id", self.Id).First(sc) + if err != nil { + log.Errorln(err) + return + } + self.GetModelManager().TableSpec().Update(self, func() error { + self.StoragecacheId = sc.Id + return nil + }) + } +} + +func (self *SStorage) SetStatus(userCred mcclient.TokenCredential, status string, reason string) error { + if self.Status == status { + return nil + } + oldStatus := self.Status + _, err := self.GetModelManager().TableSpec().Update(self, func() error { + self.Status = status + return nil + }) + if err != nil { + return err + } + if userCred != nil { + notes := fmt.Sprintf("%s=>%s", oldStatus, status) + if len(reason) > 0 { + notes = fmt.Sprintf("%s: %s", notes, reason) + } + db.OpsLog.LogEvent(self, db.ACT_UPDATE_STATUS, notes, userCred) + if strings.Contains(notes, "fail") { + logclient.AddActionLog(self, logclient.ACT_VM_SYNC_STATUS, notes, userCred, false) + } + } + return nil +} + +func (self *SStorage) AllowPerformEnable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return userCred.IsSystemAdmin() +} + +func (self *SStorage) PerformEnable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if !self.Enabled { + _, err := self.GetModelManager().TableSpec().Update(self, func() error { + self.Enabled = true + return nil + }) + if err != nil { + log.Errorf("PerformEnable save update fail %s", err) + return nil, err + } + db.OpsLog.LogEvent(self, db.ACT_ENABLE, "", userCred) + } + return nil, nil +} + +func (self *SStorage) AllowPerformDisable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return userCred.IsSystemAdmin() +} + +func (self *SStorage) PerformDisable(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if self.Enabled { + _, err := self.GetModelManager().TableSpec().Update(self, func() error { + self.Enabled = false + return nil + }) + if err != nil { + log.Errorf("PerformDisable save update fail %s", err) + return nil, err + } + db.OpsLog.LogEvent(self, db.ACT_DISABLE, "", userCred) + } + return nil, nil +} + +func (self *SStorage) AllowPerformOnline(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return userCred.IsSystemAdmin() +} + +func (self *SStorage) PerformOnline(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if self.Status != STORAGE_ONLINE { + err := self.SetStatus(userCred, STORAGE_ONLINE, "") + if err != nil { + return nil, err + } + db.OpsLog.LogEvent(self, db.ACT_ONLINE, "", userCred) + } + return nil, nil +} + +func (self *SStorage) AllowPerformOffline(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return userCred.IsSystemAdmin() +} + +func (self *SStorage) PerformOffline(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if self.Status != STORAGE_OFFLINE { + err := self.SetStatus(userCred, STORAGE_OFFLINE, "") + if err != nil { + return nil, err + } + db.OpsLog.LogEvent(self, db.ACT_OFFLINE, "", userCred) + } + return nil, nil } func (self *SStorage) GetHostCount() int { @@ -156,6 +362,14 @@ func (manager *SStorageManager) AllowListItems(ctx context.Context, userCred mcc return true } +func (self *SStorage) GetStorageCachePath(mountPoint, imageCachePath string) string { + if self.StorageType == STORAGE_NFS { + return path.Join(mountPoint, imageCachePath) + } else { + return imageCachePath + } +} + func (self *SStorage) getMoreDetails(extra *jsonutils.JSONDict) *jsonutils.JSONDict { used := self.GetUsedCapacity(tristate.True) waste := self.GetUsedCapacity(tristate.False) @@ -176,7 +390,7 @@ func (self *SStorage) getMoreDetails(extra *jsonutils.JSONDict) *jsonutils.JSOND } func (self *SStorage) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { - extra := self.SEnabledStatusStandaloneResourceBase.GetCustomizeColumns(ctx, userCred, query) + extra := self.SStandaloneResourceBase.GetCustomizeColumns(ctx, userCred, query) return self.getMoreDetails(extra) } @@ -761,7 +975,7 @@ func (manager *SStorageManager) InitializeData() error { } func (manager *SStorageManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) { - q, err := manager.SStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query) + q, err := manager.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query) if err != nil { return nil, err } @@ -777,11 +991,11 @@ func (manager *SStorageManager) ListItemFilter(ctx context.Context, q *sqlchemy. } if jsonutils.QueryBoolean(query, "share", false) { - q = q.Filter(sqlchemy.NotIn(q.Field("storage_type"), sysutils.LOCAL_STORAGE_TYPES)) + q = q.Filter(sqlchemy.NotIn(q.Field("storage_type"), STORAGE_LOCAL_TYPES)) } if jsonutils.QueryBoolean(query, "local", false) { - q = q.Filter(sqlchemy.In(q.Field("storage_type"), sysutils.LOCAL_STORAGE_TYPES)) + q = q.Filter(sqlchemy.In(q.Field("storage_type"), STORAGE_LOCAL_TYPES)) } if jsonutils.QueryBoolean(query, "usable", false) { diff --git a/pkg/compute/options/options.go b/pkg/compute/options/options.go index 25d5937c55..b357663c43 100644 --- a/pkg/compute/options/options.go +++ b/pkg/compute/options/options.go @@ -65,6 +65,8 @@ type ComputeOptions struct { ConvertEsxiDefaultTemplate string `default:"Default template" help:"ESXI baremetal convert option"` ConvertKubeletDockerVolumeSize string `default:"256g" help:"Docker volume size"` + NfsDefaultImageCacheDir string `default:"image_cache"` + cloudcommon.DBOptions } From 71bca9927977f5cfa33f97e6d975176dc7c7bca8 Mon Sep 17 00:00:00 2001 From: wanyaoqi Date: Thu, 22 Nov 2018 20:02:18 +0800 Subject: [PATCH 29/29] make dep --- Gopkg.lock | 10 ++- pkg/compute/models/hoststorages.go | 26 ++++++- pkg/compute/models/storages.go | 8 ++- .../tasks/guest_detach_all_disks_task.go | 8 +-- pkg/compute/tasks/guest_detach_disk_task.go | 5 +- .../x/pkg/prettytable/prettytable.go | 14 +++- vendor/yunion.io/x/pkg/util/sysutils/doc.go | 1 - .../x/pkg/util/sysutils/storagetypes.go | 49 ------------- vendor/yunion.io/x/structarg/structarg.go | 70 +++++-------------- 9 files changed, 67 insertions(+), 124 deletions(-) delete mode 100644 vendor/yunion.io/x/pkg/util/sysutils/doc.go delete mode 100644 vendor/yunion.io/x/pkg/util/sysutils/storagetypes.go diff --git a/Gopkg.lock b/Gopkg.lock index 32230c7ea1..4c92a38082 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1331,7 +1331,7 @@ [[projects]] branch = "master" - digest = "1:bf987f904fd821c6395fefdab956d070596eb401657fff40420014a3a8e3f45c" + digest = "1:8c4d781aafefb667511de3e0e9fe02170519f63d75a69e34045bd417f25d5523" name = "yunion.io/x/pkg" packages = [ "gotypes", @@ -1355,7 +1355,6 @@ "util/secrules", "util/sets", "util/stringutils", - "util/sysutils", "util/timeutils", "util/trace", "util/ttlpool", @@ -1365,7 +1364,7 @@ "utils", ] pruneopts = "UT" - revision = "7614d751299a6703a05f757a13e6dc900332e31b" + revision = "883ea57a4b57d551bbf2fb7d1876d6ab9355c486" [[projects]] branch = "master" @@ -1377,11 +1376,11 @@ [[projects]] branch = "master" - digest = "1:f07a1ef9758f56186dd9039a8608bc9d537070a7c04479dbab6aeb42501001b7" + digest = "1:bbaf572e68e5dad4045e1e424af29838882c620df391fa1631dd8dc851c87a95" name = "yunion.io/x/structarg" packages = ["."] pruneopts = "UT" - revision = "e0cc2c73375327d401b100bd8988fa340c2687ee" + revision = "c95bf78846decd8dea379e925410f2895fb20076" [solve-meta] analyzer-name = "dep" @@ -1496,7 +1495,6 @@ "yunion.io/x/pkg/util/secrules", "yunion.io/x/pkg/util/sets", "yunion.io/x/pkg/util/stringutils", - "yunion.io/x/pkg/util/sysutils", "yunion.io/x/pkg/util/timeutils", "yunion.io/x/pkg/util/trace", "yunion.io/x/pkg/util/ttlpool", diff --git a/pkg/compute/models/hoststorages.go b/pkg/compute/models/hoststorages.go index 1591a2e823..2b234dd825 100644 --- a/pkg/compute/models/hoststorages.go +++ b/pkg/compute/models/hoststorages.go @@ -121,14 +121,35 @@ func (self *SHoststorage) PostCreate(ctx context.Context, userCred mcclient.Toke if storagecache != nil { body.Set("imagecache_path", jsonutils.NewString( storage.GetStorageCachePath(self.MountPoint, storagecache.Path))) - body.Set("sotragecache_id", jsonutils.NewString(storagecache.Id)) + body.Set("storagecache_id", jsonutils.NewString(storagecache.Id)) } } _, _, err := httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "POST", url, headers, body, false) if err != nil { log.Errorf("Host Storage Post Create Error: %s", err) - return + // panic(err) ??? + } + self.SyncStorageStatus() + } +} + +func (self *SHoststorage) PreDelete(ctx context.Context, userCred mcclient.TokenCredential) { + storage := self.GetStorage() + if !utils.IsInStringArray(storage.StorageType, STORAGE_LOCAL_TYPES) { + host := storage.GetMasterHost() + log.Infof("Attach SharedStorage[%s] on host %s ...", storage.Name, host.Name) + url := fmt.Sprintf("%s/storages/detach", host.ManagerUri) + headers := http.Header{} + headers.Set("X-Auth-Token", userCred.GetTokenString()) + body := jsonutils.NewDict() + body.Set("mount_point", jsonutils.NewString(self.MountPoint)) + body.Set("name", jsonutils.NewString(storage.Name)) + _, _, err := httputils.JSONRequest(httputils.GetDefaultClient(), + ctx, "POST", url, headers, body, false) + if err != nil { + log.Errorf("Host Storage Post Create Error: %s", err) + // panic(err) ??? } self.SyncStorageStatus() } @@ -177,6 +198,7 @@ func (self *SHoststorage) getExtraDetails(extra *jsonutils.JSONDict) *jsonutils. storagecache := StoragecacheManager.FetchStoragecacheById(storage.StoragecacheId) if storagecache != nil { extra.Set("imagecache_path", jsonutils.NewString(storage.GetStorageCachePath(self.MountPoint, storagecache.Path))) + extra.Set("storagecache_id", jsonutils.NewString(storagecache.Id)) } } return extra diff --git a/pkg/compute/models/storages.go b/pkg/compute/models/storages.go index fb0d4e12c2..37ca701ead 100644 --- a/pkg/compute/models/storages.go +++ b/pkg/compute/models/storages.go @@ -231,7 +231,7 @@ func (self *SStorage) PostCreate(ctx context.Context, userCred mcclient.TokenCre sc := &SStoragecache{} sc.Path = options.Options.NfsDefaultImageCacheDir sc.ExternalId = self.Id - sc.Name = "nfs" + self.Name + time.Now().String() + sc.Name = "nfs-" + self.Name + time.Now().Format("2006-01-02 15:04:05") err := StoragecacheManager.TableSpec().Insert(sc) if err != nil { log.Errorln(err) @@ -242,10 +242,14 @@ func (self *SStorage) PostCreate(ctx context.Context, userCred mcclient.TokenCre log.Errorln(err) return } - self.GetModelManager().TableSpec().Update(self, func() error { + _, err = self.GetModelManager().TableSpec().Update(self, func() error { self.StoragecacheId = sc.Id + self.Status = STORAGE_ONLINE return nil }) + if err != nil { + log.Errorln(err) + } } } diff --git a/pkg/compute/tasks/guest_detach_all_disks_task.go b/pkg/compute/tasks/guest_detach_all_disks_task.go index cb5fa8f9ee..c5c6a7d8ac 100644 --- a/pkg/compute/tasks/guest_detach_all_disks_task.go +++ b/pkg/compute/tasks/guest_detach_all_disks_task.go @@ -43,13 +43,7 @@ func (self *GuestDetachAllDisksTask) OnDiskDeleteComplete(ctx context.Context, o if jsonutils.QueryBoolean(self.Params, "override_pending_delete", false) { taskData.Add(jsonutils.JSONTrue, "override_pending_delete") } - disk := guestdisk.GetDisk() - storage := disk.GetStorage() - if storage.IsLocal() { - taskData.Add(jsonutils.JSONFalse, "keep_disk") - } else { - taskData.Add(jsonutils.JSONTrue, "keep_disk") - } + taskData.Add(jsonutils.JSONFalse, "keep_disk") task, err := taskman.TaskManager.NewTask(ctx, "GuestDetachDiskTask", guest, self.UserCred, taskData, self.GetTaskId(), "", nil) if err != nil { self.SetStageFailed(ctx, err.Error()) diff --git a/pkg/compute/tasks/guest_detach_disk_task.go b/pkg/compute/tasks/guest_detach_disk_task.go index 8b976b0444..676c12972a 100644 --- a/pkg/compute/tasks/guest_detach_disk_task.go +++ b/pkg/compute/tasks/guest_detach_disk_task.go @@ -91,15 +91,12 @@ func (self *GuestDetachDiskTask) OnSyncConfigComplete(ctx context.Context, guest db.OpsLog.LogEvent(disk, db.ACT_DELETE, "", self.UserCred) disk.RealDelete(ctx, self.UserCred) self.SetStageComplete(ctx, nil) - return - } - if !keepDisk && disk.GetGuestDiskCount() == 0 && disk.AutoDelete { + } else if (disk.Status != models.DISK_READY || !keepDisk) && disk.GetGuestDiskCount() == 0 && disk.AutoDelete { self.SetStage("on_disk_delete_complete", nil) db.OpsLog.LogEvent(disk, db.ACT_DELETE, "", self.UserCred) err := guest.GetDriver().RequestDeleteDetachedDisk(ctx, disk, self, purge) if err != nil { self.OnTaskFail(ctx, guest, disk, err) - return } } else { self.SetStageComplete(ctx, nil) diff --git a/vendor/yunion.io/x/pkg/prettytable/prettytable.go b/vendor/yunion.io/x/pkg/prettytable/prettytable.go index f5ee4f34f1..feba929161 100644 --- a/vendor/yunion.io/x/pkg/prettytable/prettytable.go +++ b/vendor/yunion.io/x/pkg/prettytable/prettytable.go @@ -3,6 +3,7 @@ package prettytable import ( "bytes" "strings" + "unicode" ) type AlignmentType uint8 @@ -102,6 +103,17 @@ func textLine(buf *bytes.Buffer, columns []ptColumn, widths []int) { } } +func runeDisplayWidth(r rune) int { + const puncts = "。,;:()、?《》" + if unicode.Is(unicode.Han, r) { + return 2 + } + if strings.ContainsRune(puncts, r) { + return 2 + } + return 1 +} + // cellDisplayWidth returns display width of the cell when printed as the // nthCol. prevWidth is the total display width (as return by this same func) // of previous cells in the same line @@ -116,7 +128,7 @@ func cellDisplayWidth(cell string, nthCol int, prevWidth int) int { for _, c := range line { incr := 0 if c != '\t' { - incr = 1 + incr = runeDisplayWidth(c) } else { // terminal with have the char TabWidth aligned incr = TabWidth - (x & (TabWidth - 1)) diff --git a/vendor/yunion.io/x/pkg/util/sysutils/doc.go b/vendor/yunion.io/x/pkg/util/sysutils/doc.go deleted file mode 100644 index 8294259502..0000000000 --- a/vendor/yunion.io/x/pkg/util/sysutils/doc.go +++ /dev/null @@ -1 +0,0 @@ -package sysutils // import "yunion.io/x/pkg/util/sysutils" diff --git a/vendor/yunion.io/x/pkg/util/sysutils/storagetypes.go b/vendor/yunion.io/x/pkg/util/sysutils/storagetypes.go deleted file mode 100644 index 3f9de2fe1b..0000000000 --- a/vendor/yunion.io/x/pkg/util/sysutils/storagetypes.go +++ /dev/null @@ -1,49 +0,0 @@ -package sysutils - -const ( - STORAGE_LOCAL = "local" - STORAGE_BAREMETAL = "baremetal" - STORAGE_SHEEPDOG = "sheepdog" - STORAGE_RBD = "rbd" - STORAGE_DOCKER = "docker" - STORAGE_NAS = "nas" - STORAGE_VSAN = "vsan" - - STORAGE_CLOUD = "cloud" - STORAGE_CLOUD_SSD = "cloud_ssd" - STORAGE_CLOUD_ESSD = "cloud_essd" //增强型(Enhanced)SSD 云盘 - STORAGE_CLOUD_EFFICIENCY = "cloud_efficiency" - - //Azure hdd and ssd storagetype - STORAGE_STANDARD_GRS = "standard_grs" - STORAGE_STANDARD_LRS = "standard_lrs" - STORAGE_STANDARD_RAGRS = "standard_ragrs" - STORAGE_STANDARD_ZRS = "standard_zrs" - STORAGE_PREMIUM_LRS = "premium_lrs" - - // aws storage type - STORAGE_GP2_SSD = "gp2" // aws general purpose ssd - STORAGE_IO1_SSD = "io1" // aws Provisioned IOPS SSD - STORAGE_ST1_HDD = "st1" // aws Throughput Optimized HDD - STORAGE_SC1_SSD = "sc1" // aws Cold HDD - STORAGE_STANDARD_SSD = "standard" // aws Magnetic volumes - - // qcloud storage type - // STORAGE_CLOUD_SSD ="cloud_ssd" - STORAGE_LOCAL_BASIC = "local_basic" - STORAGE_LOCAL_SSD = "local_ssd" - STORAGE_CLOUD_BASIC = "cloud_basic" - STORAGE_CLOUD_PERMIUM = "cloud_permium" -) - -var STORAGE_TYPES = []string{STORAGE_LOCAL, STORAGE_BAREMETAL, STORAGE_SHEEPDOG, - STORAGE_RBD, STORAGE_DOCKER, STORAGE_NAS, STORAGE_VSAN, - STORAGE_CLOUD, STORAGE_CLOUD_SSD, STORAGE_CLOUD_ESSD, STORAGE_CLOUD_EFFICIENCY, - STORAGE_STANDARD_GRS, STORAGE_STANDARD_LRS, STORAGE_STANDARD_RAGRS, STORAGE_STANDARD_ZRS, STORAGE_PREMIUM_LRS, - STORAGE_GP2_SSD, STORAGE_IO1_SSD, STORAGE_ST1_HDD, STORAGE_SC1_SSD, STORAGE_STANDARD_SSD, - STORAGE_LOCAL_BASIC, STORAGE_LOCAL_SSD, STORAGE_CLOUD_BASIC, STORAGE_CLOUD_PERMIUM, -} - -var LOCAL_STORAGE_TYPES = []string{STORAGE_LOCAL, STORAGE_BAREMETAL} - -// var SUPPORT = []string {support = [STORAGE_LOCAL, STORAGE_BAREMETAL, STORAGE_NAS] diff --git a/vendor/yunion.io/x/structarg/structarg.go b/vendor/yunion.io/x/structarg/structarg.go index e09e97cdad..ff749484c7 100644 --- a/vendor/yunion.io/x/structarg/structarg.go +++ b/vendor/yunion.io/x/structarg/structarg.go @@ -187,56 +187,6 @@ func (this *ArgumentParser) addStructArgument(tp reflect.Type, val reflect.Value return nil } -/*func findWord(str []byte, offset int) (string, int) { - var buffer bytes.Buffer - i := skipEmpty(str, offset) - if i >= len(str) { - return "", i - } - var endstr string - quote := false - if str[i] == '"' { - quote = true - endstr = "\"" - i++ - } else if str[i] == '\'' { - quote = true - endstr = "'" - i++ - } else { - endstr = " :,\t\n}]" - } - for i < len(str) { - if quote && str[i] == '\\' { - if i+1 < len(str) { - i++ - switch str[i] { - case 'n': - buffer.WriteByte('\n') - case 'r': - buffer.WriteByte('\r') - case 't': - buffer.WriteByte('\t') - default: - buffer.WriteByte(str[i]) - } - i++ - } else { - break - } - } else if strings.IndexByte(endstr, str[i]) >= 0 { // end - if quote { - i++ - } - break - } else { - buffer.WriteByte(str[i]) - i++ - } - } - return buffer.String(), i -}*/ - func (this *ArgumentParser) addArgument(f reflect.StructField, v reflect.Value) error { tagMap := utils.TagMap(f.Tag) help := tagMap[TAG_HELP] @@ -901,6 +851,14 @@ func (this *ArgumentParser) ParseArgs(args []string, ignore_unknown bool) error return err } +func isQuotedByChar(str string, quoteChar byte) bool { + return str[0] == quoteChar && str[len(str)-1] == quoteChar +} + +func isQuoted(str string) bool { + return isQuotedByChar(str, '"') || isQuotedByChar(str, '\'') +} + func (this *ArgumentParser) parseKeyValue(key, value string) error { arg := this.findOptionalArgument(key) if arg != nil { @@ -914,7 +872,15 @@ func (this *ArgumentParser) parseKeyValue(key, value string) error { } } } else { - return arg.SetValue(value) + if !isQuoted(value) { + value = fmt.Sprintf("\"%s\"", value) + } + values := utils.FindWords([]byte(value), 0) + if len(values) == 1 { + return arg.SetValue(values[0]) + } else { + log.Warningf("too many arguments %#v for %s", values, key) + } } } else { log.Warningf("Cannot find argument %s", key) @@ -964,7 +930,7 @@ func (this *ArgumentParser) ParseFile(filepath string) error { for scanner.Scan() { line := scanner.Text() line = strings.TrimSpace(removeComments(line)) - line = removeCharacters(line, `"'`) + // line = removeCharacters(line, `"'`) if len(line) > 0 { key, val, e := line2KeyValue(line) if e == nil {