diff --git a/pkg/apis/compute/filesystem.go b/pkg/apis/compute/filesystem.go index 092b5babae..32846b37e1 100644 --- a/pkg/apis/compute/filesystem.go +++ b/pkg/apis/compute/filesystem.go @@ -14,7 +14,11 @@ package compute -import "yunion.io/x/onecloud/pkg/apis" +import ( + "time" + + "yunion.io/x/onecloud/pkg/apis" +) const ( // 可用 @@ -70,6 +74,22 @@ type FileSystemCreateInput struct { // 订阅Id, 若传入network_id此参数可忽略 ManagerId string `json:"manager_id"` + + // 包年包月时间周期 + Duration string `json:"duration"` + + // 是否自动续费(仅包年包月时生效) + // default: false + AutoRenew bool `json:"auto_renew"` + + // 到期释放时间,仅后付费支持 + ExpiredAt time.Time `json:"expired_at"` + + // 计费方式 + // enum: postpaid, prepaid + BillingType string `json:"billing_type"` + // swagger:ignore + BillingCycle string `json:"billing_cycle"` } type FileSystemSyncstatusInput struct { diff --git a/pkg/apis/compute/nas_skus.go b/pkg/apis/compute/nas_skus.go index 7afd543fe2..76decd1b93 100644 --- a/pkg/apis/compute/nas_skus.go +++ b/pkg/apis/compute/nas_skus.go @@ -40,4 +40,7 @@ type NasSkuDetails struct { apis.EnabledStatusStandaloneResourceDetails CloudregionResourceInfo + + // 云环境 + CloudEnv string `json:"cloud_env"` } diff --git a/pkg/compute/models/access_group_caches.go b/pkg/compute/models/access_group_caches.go index e2c75a136b..2656db4dda 100644 --- a/pkg/compute/models/access_group_caches.go +++ b/pkg/compute/models/access_group_caches.go @@ -16,6 +16,7 @@ package models import ( "context" + "fmt" "yunion.io/x/jsonutils" "yunion.io/x/pkg/errors" @@ -564,6 +565,9 @@ func (manager *SAccessGroupCacheManager) Register(ctx context.Context, opts *SAc cache := &SAccessGroupCache{} cache.SetModelManager(manager, cache) cache.Name = opts.Name + if opts.NetworkType == api.NETWORK_TYPE_CLASSIC { + cache.Name = fmt.Sprintf("%s-%s", opts.Name, opts.NetworkType) + } cache.Description = opts.Desc cache.Status = api.ACCESS_GROUP_STATUS_CREATING cache.ManagerId = opts.ManagerId diff --git a/pkg/compute/models/access_group_rules.go b/pkg/compute/models/access_group_rules.go index ae84699844..a9d8b88e6f 100644 --- a/pkg/compute/models/access_group_rules.go +++ b/pkg/compute/models/access_group_rules.go @@ -77,6 +77,10 @@ func (self *SAccessGroupRule) GetId() string { return self.Id } +func (manager *SAccessGroupRuleManager) ResourceScope() rbacutils.TRbacScope { + return rbacutils.ScopeDomain +} + func (manager *SAccessGroupRuleManager) FetchUniqValues(ctx context.Context, data jsonutils.JSONObject) jsonutils.JSONObject { groupId, _ := data.GetString("access_group_id") return jsonutils.Marshal(map[string]string{"access_group_id": groupId}) diff --git a/pkg/compute/models/filesystem.go b/pkg/compute/models/filesystem.go index 5b561dc7a8..0407a2b0a7 100644 --- a/pkg/compute/models/filesystem.go +++ b/pkg/compute/models/filesystem.go @@ -20,18 +20,23 @@ import ( "time" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/util/compare" + "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" + billing_api "yunion.io/x/onecloud/pkg/apis/billing" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudcommon/validators" "yunion.io/x/onecloud/pkg/cloudprovider" + "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/billing" "yunion.io/x/onecloud/pkg/util/stringutils2" ) @@ -136,32 +141,43 @@ func (man *SFileSystemManager) ValidateCreateData(ctx context.Context, userCred if zone := network.GetZone(); zone != nil { input.ZoneId = zone.Id input.CloudregionId = zone.CloudregionId - } else { - zones, err := network.GetRegion().GetZones() - if err != nil { - return input, httperrors.NewGeneralError(errors.Wrapf(err, "GetZones")) - } - for _, zone := range zones { - if zone.Status == api.ZONE_ENABLE { - input.ZoneId = zone.Id - input.CloudregionId = zone.CloudregionId - break - } - } } - } else if len(input.ZoneId) > 0 { - _zone, err := validators.ValidateModel(userCred, ZoneManager, &input.ZoneId) - if err != nil { - return input, err - } - zone := _zone.(*SZone) - input.CloudregionId = zone.CloudregionId - } else { + } + if len(input.ZoneId) == 0 { return input, httperrors.NewMissingParameterError("zone_id") } + _zone, err := validators.ValidateModel(userCred, ZoneManager, &input.ZoneId) + if err != nil { + return input, err + } + zone := _zone.(*SZone) + region := zone.GetRegion() + input.CloudregionId = region.Id + if len(input.ManagerId) == 0 { return input, httperrors.NewMissingParameterError("manager_id") } + + if len(input.Duration) > 0 { + billingCycle, err := billing.ParseBillingCycle(input.Duration) + if err != nil { + return input, httperrors.NewInputParameterError("invalid duration %s", input.Duration) + } + + if !utils.IsInStringArray(input.BillingType, []string{billing_api.BILLING_TYPE_PREPAID, billing_api.BILLING_TYPE_POSTPAID}) { + input.BillingType = billing_api.BILLING_TYPE_PREPAID + } + + if input.BillingType == billing_api.BILLING_TYPE_PREPAID { + if !region.GetDriver().IsSupportedBillingCycle(billingCycle, man.KeywordPlural()) { + return input, httperrors.NewInputParameterError("unsupported duration %s", input.Duration) + } + } + tm := time.Time{} + input.BillingCycle = billingCycle.String() + input.ExpiredAt = billingCycle.EndAt(tm) + } + input.StatusInfrasResourceBaseCreateInput, err = man.SStatusInfrasResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.StatusInfrasResourceBaseCreateInput) if err != nil { return input, err @@ -534,3 +550,40 @@ func (self *SFileSystem) GetICloudFileSystem() (cloudprovider.ICloudFileSystem, } return iRegion.GetICloudFileSystemById(self.ExternalId) } + +func (manager *SFileSystemManager) getExpiredPostpaids() ([]SFileSystem, error) { + q := ListExpiredPostpaidResources(manager.Query(), options.Options.ExpiredPrepaidMaxCleanBatchSize) + + fs := make([]SFileSystem, 0) + err := db.FetchModelObjects(manager, q, &fs) + if err != nil { + return nil, errors.Wrapf(err, "db.FetchModelObjects") + } + return fs, nil +} + +func (self *SFileSystem) doExternalSync(ctx context.Context, userCred mcclient.TokenCredential) error { + iFs, err := self.GetICloudFileSystem() + if err != nil { + return errors.Wrapf(err, "GetICloudFileSystem") + } + return self.SyncWithCloudFileSystem(ctx, userCred, iFs) +} + +func (manager *SFileSystemManager) DeleteExpiredPostpaids(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) { + fss, err := manager.getExpiredPostpaids() + if err != nil { + log.Errorf("FileSystem getExpiredPostpaids error: %v", err) + return + } + for i := 0; i < len(fss); i += 1 { + if len(fss[i].ExternalId) > 0 { + err := fss[i].doExternalSync(ctx, userCred) + if err == nil && fss[i].IsValidPostPaid() { + continue + } + } + fss[i].DeletePreventionOff(&fss[i], userCred) + fss[i].StartDeleteTask(ctx, userCred, "") + } +} diff --git a/pkg/compute/models/mount_targets.go b/pkg/compute/models/mount_targets.go index 38f9d97496..c69c22fcfc 100644 --- a/pkg/compute/models/mount_targets.go +++ b/pkg/compute/models/mount_targets.go @@ -71,6 +71,10 @@ type SMountTarget struct { FileSystemId string `width:"36" charset:"ascii" nullable:"false" create:"required" index:"true" list:"user"` } +func (manager *SMountTargetManager) ResourceScope() rbacutils.TRbacScope { + return rbacutils.ScopeDomain +} + func (manager *SMountTargetManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { return db.IsDomainAllowCreate(userCred, manager) } @@ -341,6 +345,21 @@ func (manager *SMountTargetManager) ListItemExportKeys(ctx context.Context, return q, nil } +func (self *SMountTarget) ValidateDeleteCondition(ctx context.Context) error { + fs, err := self.GetFileSystem() + if err != nil { + return httperrors.NewGeneralError(errors.Wrapf(err, "GetFileSystem")) + } + region, err := fs.GetRegion() + if err != nil { + return httperrors.NewGeneralError(errors.Wrapf(err, "GetRegion")) + } + if region.Provider == api.CLOUD_PROVIDER_HUAWEI { + return httperrors.NewNotSupportedError("not allow to delete") + } + return self.SStatusStandaloneResourceBase.ValidateDeleteCondition(ctx) +} + func (self *SMountTarget) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { return nil } diff --git a/pkg/compute/models/nas_skus.go b/pkg/compute/models/nas_skus.go index 5501fd60ff..aaa62d319e 100644 --- a/pkg/compute/models/nas_skus.go +++ b/pkg/compute/models/nas_skus.go @@ -18,6 +18,7 @@ import ( "context" "database/sql" "fmt" + "strings" "yunion.io/x/jsonutils" "yunion.io/x/log" @@ -124,6 +125,8 @@ func (manager *SNasSkuManager) FetchCustomizeColumns( EnabledStatusStandaloneResourceDetails: stdRows[i], CloudregionResourceInfo: regRows[i], } + + rows[i].CloudEnv = strings.Split(regRows[i].RegionExternalId, "/")[0] } return rows diff --git a/pkg/compute/regiondrivers/aliyun.go b/pkg/compute/regiondrivers/aliyun.go index b2d95372d9..f451d45b80 100644 --- a/pkg/compute/regiondrivers/aliyun.go +++ b/pkg/compute/regiondrivers/aliyun.go @@ -974,7 +974,10 @@ func (self *SAliyunRegionDriver) ValidateCreateDBInstanceData(ctx context.Contex func (self *SAliyunRegionDriver) IsSupportedBillingCycle(bc billing.SBillingCycle, resource string) bool { switch resource { - case models.DBInstanceManager.KeywordPlural(), models.ElasticcacheManager.KeywordPlural(), models.NatGatewayManager.KeywordPlural(): + case models.DBInstanceManager.KeywordPlural(), + models.ElasticcacheManager.KeywordPlural(), + models.NatGatewayManager.KeywordPlural(), + models.FileSystemManager.KeywordPlural(): years := bc.GetYears() months := bc.GetMonths() if (years >= 1 && years <= 3) || (months >= 1 && months <= 9) { diff --git a/pkg/compute/service/service.go b/pkg/compute/service/service.go index e3c11d580c..8e73c5d026 100644 --- a/pkg/compute/service/service.go +++ b/pkg/compute/service/service.go @@ -128,6 +128,7 @@ func StartService() { cron.AddJobAtIntervals("CleanExpiredPostpaidDBInstances", time.Duration(opts.PrepaidExpireCheckSeconds)*time.Second, models.DBInstanceManager.DeleteExpiredPostpaids) cron.AddJobAtIntervals("CleanExpiredPostpaidServers", time.Duration(opts.PrepaidExpireCheckSeconds)*time.Second, models.GuestManager.DeleteExpiredPostpaidServers) cron.AddJobAtIntervals("CleanExpiredPostpaidNatGateways", time.Duration(opts.PrepaidExpireCheckSeconds)*time.Second, models.NatGatewayManager.DeleteExpiredPostpaids) + cron.AddJobAtIntervals("CleanExpiredPostpaidNas", time.Duration(opts.PrepaidExpireCheckSeconds)*time.Second, models.FileSystemManager.DeleteExpiredPostpaids) cron.AddJobAtIntervals("StartHostPingDetectionTask", time.Duration(opts.HostOfflineDetectionInterval)*time.Second, models.HostManager.PingDetectionTask) cron.AddJobAtIntervalsWithStartRun("CalculateQuotaUsages", time.Duration(opts.CalculateQuotaUsageIntervalSeconds)*time.Second, models.QuotaManager.CalculateQuotaUsages, true) diff --git a/pkg/compute/tasks/filesystem_create_task.go b/pkg/compute/tasks/filesystem_create_task.go index b534309046..5b50a7b001 100644 --- a/pkg/compute/tasks/filesystem_create_task.go +++ b/pkg/compute/tasks/filesystem_create_task.go @@ -85,7 +85,7 @@ func (self *FileSystemCreateTask) OnInit(ctx context.Context, obj db.IStandalone } db.SetExternalId(fs, self.GetUserCred(), iFs.GetGlobalId()) - cloudprovider.WaitStatus(iFs, api.NAS_STATUS_AVAILABLE, time.Second*5, time.Minute*3) + cloudprovider.WaitMultiStatus(iFs, []string{api.NAS_STATUS_AVAILABLE, api.NAS_STATUS_CREATE_FAILED}, time.Second*5, time.Minute*3) self.SetStage("OnSyncstatusComplete", nil) fs.StartSyncstatus(ctx, self.GetUserCred(), self.GetTaskId()) diff --git a/pkg/compute/tasks/filesystem_delete_task.go b/pkg/compute/tasks/filesystem_delete_task.go index 41d4aa3ca3..35840c97d5 100644 --- a/pkg/compute/tasks/filesystem_delete_task.go +++ b/pkg/compute/tasks/filesystem_delete_task.go @@ -60,6 +60,23 @@ func (self *FileSystemDeleteTask) OnInit(ctx context.Context, obj db.IStandalone self.taskFailed(ctx, fs, errors.Wrapf(err, "fs.GetICloudFileSystem")) return } + err = func() error { + mts, err := iFs.GetMountTargets() + if err != nil { + return errors.Wrapf(err, "iFs.GetMountTargets") + } + for i := range mts { + err = mts[i].Delete() + if err != nil { + return errors.Wrapf(err, "Delete MountTarget") + } + } + return nil + }() + if err != nil { + self.taskFailed(ctx, fs, errors.Wrapf(err, "Delete MountTarget")) + return + } err = iFs.Delete() if err != nil { self.taskFailed(ctx, fs, errors.Wrapf(err, "iFs.Delete")) diff --git a/pkg/multicloud/aliyun/aliyun.go b/pkg/multicloud/aliyun/aliyun.go index 82767eb998..a07d8eb5e8 100644 --- a/pkg/multicloud/aliyun/aliyun.go +++ b/pkg/multicloud/aliyun/aliyun.go @@ -227,7 +227,7 @@ func _jsonRequest(client *sdk.Client, domain string, version string, apiName str resp, err := processCommonRequest(client, req) if err != nil { - return nil, errors.Wrapf(err, "processCommonRequest") + return nil, errors.Wrapf(err, "processCommonRequest with params %s", params) } body, err := jsonutils.Parse(resp.GetHttpContentBytes()) if err != nil { diff --git a/pkg/multicloud/aliyun/filesystem.go b/pkg/multicloud/aliyun/filesystem.go index 1c0b0d8c41..52b797de9c 100644 --- a/pkg/multicloud/aliyun/filesystem.go +++ b/pkg/multicloud/aliyun/filesystem.go @@ -321,6 +321,15 @@ func (self *SRegion) CreateFileSystem(opts *cloudprovider.FileSystemCraeteOption "ClientToken": utils.GenRequestId(20), "Description": opts.Name, } + + if self.GetCloudEnv() == ALIYUN_FINANCE_CLOUDENV { + if opts.FileSystemType == "standard" { + opts.ZoneId = strings.Replace(opts.ZoneId, "cn-shanghai-finance-1", "jr-cn-shanghai-", 1) + opts.ZoneId = strings.Replace(opts.ZoneId, "cn-shenzhen-finance-1", "jr-cn-shenzhen-", 1) + params["ZoneId"] = opts.ZoneId + } + } + switch opts.FileSystemType { case "standard": params["StorageType"] = utils.Capitalize(opts.StorageType) @@ -341,6 +350,10 @@ func (self *SRegion) CreateFileSystem(opts *cloudprovider.FileSystemCraeteOption if len(opts.NetworkId) > 0 { params["VSwitchId"] = opts.NetworkId } + if opts.BillingCycle != nil { + params["ChargeType"] = "Subscription" + params["Duration"] = fmt.Sprintf("%d", opts.BillingCycle.GetMonths()) + } resp, err := self.nasRequest("CreateFileSystem", params) if err != nil { return nil, errors.Wrapf(err, "CreateFileSystem") diff --git a/pkg/multicloud/aliyun/region.go b/pkg/multicloud/aliyun/region.go index 9eb75afb4c..9c92944f0f 100644 --- a/pkg/multicloud/aliyun/region.go +++ b/pkg/multicloud/aliyun/region.go @@ -158,6 +158,18 @@ func (self *SRegion) nasRequest(action string, params map[string]string) (jsonut if err != nil { return nil, err } + if self.GetCloudEnv() == ALIYUN_FINANCE_CLOUDENV { + if strings.Contains(action, "FileSystem") { + if self.RegionId == "cn-hangzhou" { + params["RegionId"] = "cn-hangzhou-dg-a01" + } + } + if strings.Contains(action, "Access") || strings.Contains(action, "MountTarget") { + if self.RegionId == "cn-hangzhou" { + params["RegionId"] = "cn-hangzhou-finance" + } + } + } return jsonRequest(client, "nas.aliyuncs.com", ALIYUN_NAS_API_VERSION, action, params, self.client.debug) }