diff --git a/cmd/climc/climc.go b/cmd/climc/climc.go index a8cc818dd9..f6beed408b 100644 --- a/cmd/climc/climc.go +++ b/cmd/climc/climc.go @@ -9,7 +9,7 @@ import ( "strings" "time" - prompt "github.com/c-bata/go-prompt" + "github.com/c-bata/go-prompt" "yunion.io/x/log" "yunion.io/x/pkg/util/version" @@ -28,7 +28,7 @@ type BaseOptions struct { Version bool `help:"Show version"` Timeout int `default:"600" help:"Number of seconds to wait for a response"` Insecure bool `default:"false" help:"Allow skip server cert verification if URL is https" short-token:"k"` - NoCachedToken bool `default:"false" help:"Force not use cached token"` + NoCachedToken bool `default:"$NO_CACHED_TOKEN|false" help:"Force not use cached token"` OsUsername string `default:"$OS_USERNAME" help:"Username, defaults to env[OS_USERNAME]"` OsPassword string `default:"$OS_PASSWORD" help:"Password, defaults to env[OS_PASSWORD]"` // OsProjectId string `default:"$OS_PROJECT_ID" help:"Proejct ID, defaults to env[OS_PROJECT_ID]"` @@ -126,8 +126,11 @@ func newClientSession(options *BaseOptions) (*mcclient.ClientSession, error) { options.Insecure) var cacheToken mcclient.TokenCredential - tokenCachePath := filepath.Join(os.TempDir(), "OS_AUTH_CACHE_TOKEN") + authUrlAlter := strings.Replace(options.OsAuthURL, "/", "", -1) + authUrlAlter = strings.Replace(authUrlAlter, ":", "", -1) + tokenCachePath := filepath.Join(os.TempDir(), fmt.Sprintf("OS_AUTH_CACHE_TOKEN-%s-%s-%s-%s", authUrlAlter, options.OsUsername, options.OsDomainName, options.OsProjectName)) cacheFile, err := os.Open(tokenCachePath) + if err == nil && cacheFile != nil && !options.NoCachedToken { fileInfo, _ := cacheFile.Stat() dur, err := time.ParseDuration("-24h") @@ -138,13 +141,14 @@ func newClientSession(options *BaseOptions) (*mcclient.ClientSession, error) { err := json.Unmarshal(bytesToken, token) if err != nil { fmt.Printf("Unmarshal token error:%s", err) - } else { + } else if token.IsValid() { cacheToken = token } } cacheFile.Close() } } + if cacheToken == nil { token, err := client.Authenticate(options.OsUsername, options.OsPassword, @@ -158,12 +162,16 @@ func newClientSession(options *BaseOptions) (*mcclient.ClientSession, error) { if err != nil { fmt.Printf("Marshal token error:%s", err) } else { - fo, _ := os.Create(tokenCachePath) - fo.Write(bytesCacheToken) - fo.Close() + fo, err := os.Create(tokenCachePath) + if err != nil { + fmt.Printf("Save token cache fail: %s", err) + } else { + fo.Write(bytesCacheToken) + fo.Close() + } } } else { - fmt.Printf("******** Use Token Cache At %s ********\n", tokenCachePath) + // fmt.Printf("******** Use Token Cache At %s ********\n", tokenCachePath) } session := client.NewSession(options.OsRegionName, diff --git a/cmd/climc/shell/elasticips.go b/cmd/climc/shell/elasticips.go new file mode 100644 index 0000000000..e7a891fadd --- /dev/null +++ b/cmd/climc/shell/elasticips.go @@ -0,0 +1,186 @@ +package shell + +import ( + "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() { + type ElasticipListOptions struct { + Manager string `help:"Show servers imported from manager"` + Region string `help:"Show servers in cloudregion"` + + options.BaseListOptions + } + R(&ElasticipListOptions{}, "eip-list", "List elastic IPs", func(s *mcclient.ClientSession, args *ElasticipListOptions) error { + var params *jsonutils.JSONDict + { + var err error + params, err = args.BaseListOptions.Params() + if err != nil { + return err + } + } + if len(args.Manager) > 0 { + params.Add(jsonutils.NewString(args.Manager), "manager") + } + if len(args.Region) > 0 { + params.Add(jsonutils.NewString(args.Region), "region") + } + results, err := modules.Elasticips.List(s, params) + if err != nil { + return err + } + printList(results, modules.Elasticips.GetColumns(s)) + return nil + }) + + type EipCreateOptions struct { + MANAGER string `help:"cloud provider"` + REGION string `help:"cloud region in which EIP is allocated"` + NAME string `help:"name of the EIP"` + BW int `help:"Bandwidth in Mbps"` + ChargeType string `help:"bandwidth charge type, either traffic or bandwidth" choices:"traffic|bandwidth"` + } + R(&EipCreateOptions{}, "eip-create", "Create an EIP", func(s *mcclient.ClientSession, args *EipCreateOptions) error { + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(args.MANAGER), "manager") + params.Add(jsonutils.NewString(args.REGION), "region") + params.Add(jsonutils.NewString(args.NAME), "name") + params.Add(jsonutils.NewInt(int64(args.BW)), "bandwidth") + + if len(args.ChargeType) > 0 { + params.Add(jsonutils.NewString(args.ChargeType), "charge_type") + } + + result, err := modules.Elasticips.Create(s, params) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type EipDeleteOptions struct { + ID string `help:"ID or name of EIP"` + } + R(&EipDeleteOptions{}, "eip-delete", "Delete an EIP", func(s *mcclient.ClientSession, args *EipDeleteOptions) error { + result, err := modules.Elasticips.Delete(s, args.ID, nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type EipUpdateOptions struct { + ID string `help:"ID or name of EIP"` + Name string `help:"New name of EIP"` + Desc string `help:"New description of EIP"` + } + R(&EipUpdateOptions{}, "eip-update", "Update EIP properties", func(s *mcclient.ClientSession, args *EipUpdateOptions) error { + params := jsonutils.NewDict() + if len(args.Name) > 0 { + params.Add(jsonutils.NewString(args.Name), "name") + } + if len(args.Desc) > 0 { + params.Add(jsonutils.NewString(args.Desc), "description") + } + result, err := modules.Elasticips.Update(s, args.ID, params) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type EipAssociateOptions struct { + ID string `help:"ID or name of EIP"` + INSTANCEID string `help:"ID of instance the eip associated with"` + InstanceType string `default:"server" help:"Instance type that the eip associated with, default is server" choices:"server"` + } + R(&EipAssociateOptions{}, "eip-associate", "Associate an EIP to an instance", func(s *mcclient.ClientSession, args *EipAssociateOptions) error { + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(args.InstanceType), "instance_type") + params.Add(jsonutils.NewString(args.INSTANCEID), "instance_id") + result, err := modules.Elasticips.PerformAction(s, args.ID, "associate", params) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type EipSingleOptions struct { + ID string `help:"ID or name of EIP"` + } + R(&EipSingleOptions{}, "eip-dissociate", "Dissociate an EIP from an instance", func(s *mcclient.ClientSession, args *EipSingleOptions) error { + result, err := modules.Elasticips.PerformAction(s, args.ID, "dissociate", nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + + R(&EipSingleOptions{}, "eip-sync", "Synchronize status of an EIP", func(s *mcclient.ClientSession, args *EipSingleOptions) error { + result, err := modules.Elasticips.PerformAction(s, args.ID, "sync", nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type ServerCreateEipOptions struct { + ID string `help:"server ID or name"` + BW int `help:"EIP bandwidth in Mbps"` + ChargeType string `help:"bandwidth charge type, either traffic or bandwidth" choices:"traffic|bandwidth"` + } + R(&ServerCreateEipOptions{}, "server-create-eip", "allocate an EIP and associate EIP to server", func(s *mcclient.ClientSession, args *ServerCreateEipOptions) error { + params := jsonutils.NewDict() + params.Add(jsonutils.NewInt(int64(args.BW)), "bandwidth") + + if len(args.ChargeType) > 0 { + params.Add(jsonutils.NewString(args.ChargeType), "charge_type") + } + + result, err := modules.Servers.PerformAction(s, args.ID, "create-eip", params) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type EipShowOptions struct { + ID string `help:"ID or name of EIP"` + } + R(&EipShowOptions{}, "eip-show", "show details of an EIP", func(s *mcclient.ClientSession, args *EipShowOptions) error { + result, err := modules.Servers.Get(s, args.ID, nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type EipChangeBandwidthOptions struct { + ID string `help:"ID or name of the EIP"` + BW int `help:"new bandwidth of EIP"` + } + R(&EipChangeBandwidthOptions{}, "eip-change-bandwidth", "Change maximal bandwidth of EIP", func(s *mcclient.ClientSession, args *EipChangeBandwidthOptions) error { + params := jsonutils.NewDict() + params.Add(jsonutils.NewInt(int64(args.BW)), "bandwidth") + result, err := modules.Elasticips.PerformAction(s, args.ID, "change-bandwidth", params) + if err != nil { + return err + } + printObject(result) + return nil + }) + +} diff --git a/pkg/cloudcommon/db/opslog.go b/pkg/cloudcommon/db/opslog.go index f201f4e9c4..457908e27f 100644 --- a/pkg/cloudcommon/db/opslog.go +++ b/pkg/cloudcommon/db/opslog.go @@ -133,6 +133,8 @@ const ( ACT_GUEST_ATTACH_ISOLATED_DEVICE_FAIL = "guest_attach_isolated_deivce_fail" ACT_GUEST_DETACH_ISOLATED_DEVICE = "guest_detach_isolated_deivce" ACT_GUEST_DETACH_ISOLATED_DEVICE_FAIL = "guest_detach_isolated_deivce_fail" + + ACT_CHANGE_BANDWIDTH = "eip_change_bandwidth" ) type SOpsLogManager struct { diff --git a/pkg/cloudprovider/resources.go b/pkg/cloudprovider/resources.go index 54cf295b8d..55ff5d022c 100644 --- a/pkg/cloudprovider/resources.go +++ b/pkg/cloudprovider/resources.go @@ -29,6 +29,7 @@ type ICloudRegion interface { GetIZones() ([]ICloudZone, error) GetIVpcs() ([]ICloudVpc, error) + GetIEips() ([]ICloudEIP, error) GetIZoneById(id string) (ICloudZone, error) GetIVpcById(id string) (ICloudVpc, error) @@ -38,6 +39,10 @@ type ICloudRegion interface { CreateIVpc(name string, desc string, cidr string) (ICloudVpc, error) + CreateEIP(bwMbps int) (ICloudEIP, error) + + GetIEipById(id string) (ICloudEIP, error) + GetProvider() string } @@ -134,7 +139,7 @@ type ICloudVM interface { GetIDisks() ([]ICloudDisk, error) GetINics() ([]ICloudNic, error) - GetEIP() ICloudEIP + GetIEIP() (ICloudEIP, error) // GetStatus() string // GetRemoteStatus() string @@ -175,9 +180,25 @@ type ICloudNic interface { } type ICloudEIP interface { - GetIP() string - GetAllocationId() string - GetChargeType() string + ICloudResource + + GetIpAddr() string + GetMode() string + GetAssociationType() string + GetAssociationExternalId() string + + GetBandwidth() int + + GetInternetChargeType() string + + GetManagerId() string + + Delete() error + + Associate(instanceId string) error + Dissociate() error + + ChangeBandwidth(bw int) error } type ICloudSecurityGroup interface { diff --git a/pkg/cloudprovider/waitstatus.go b/pkg/cloudprovider/waitstatus.go index aaccb5a489..10a8393cc3 100644 --- a/pkg/cloudprovider/waitstatus.go +++ b/pkg/cloudprovider/waitstatus.go @@ -1,6 +1,10 @@ package cloudprovider -import "time" +import ( + "time" + + "yunion.io/x/log" +) func WaitStatus(res ICloudResource, expect string, interval time.Duration, timeout time.Duration) error { startTime := time.Now() @@ -9,6 +13,7 @@ func WaitStatus(res ICloudResource, expect string, interval time.Duration, timeo if err != nil { return err } + log.Debugf("status %s expect %s", res.GetStatus(), expect) if res.GetStatus() == expect { return nil } diff --git a/pkg/compute/handlers.go b/pkg/compute/handlers.go index 439e415b09..c0aedb1ba1 100644 --- a/pkg/compute/handlers.go +++ b/pkg/compute/handlers.go @@ -56,6 +56,7 @@ func InitHandlers(app *appsrv.Application) { models.SecurityGroupRuleManager, models.VCenterManager, models.DnsRecordManager, + models.ElasticipManager, } { db.RegisterModelManager(manager) handler := db.NewModelHandler(manager) diff --git a/pkg/compute/models/cloudproviders.go b/pkg/compute/models/cloudproviders.go index c659160fc4..45da73b25a 100644 --- a/pkg/compute/models/cloudproviders.go +++ b/pkg/compute/models/cloudproviders.go @@ -86,6 +86,10 @@ func (self *SCloudprovider) getStoragecacheCount() int { return StoragecacheManager.Query().Equals("manager_id", self.Id).Count() } +func (self *SCloudprovider) getEipCount() int { + return ElasticipManager.Query().Equals("manager_id", self.Id).Count() +} + func (self *SCloudprovider) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { return self.SEnabledStatusStandaloneResourceBase.ValidateUpdateData(ctx, userCred, query, data) } @@ -383,7 +387,9 @@ func (manager *SCloudproviderManager) FetchCloudproviderById(providerId string) func (manager *SCloudproviderManager) FetchCloudproviderByIdOrName(providerId string) *SCloudprovider { providerObj, err := manager.FetchByIdOrName("", providerId) if err != nil { - log.Errorf("%s", err) + if err != sql.ErrNoRows { + log.Errorf("%s", err) + } return nil } return providerObj.(*SCloudprovider) @@ -394,6 +400,7 @@ type SCloudproviderUsage struct { VpcCount int StorageCount int StorageCacheCount int + EipCount int } func (usage *SCloudproviderUsage) isEmpty() bool { @@ -409,6 +416,9 @@ func (usage *SCloudproviderUsage) isEmpty() bool { if usage.StorageCacheCount > 0 { return false } + if usage.EipCount > 0 { + return false + } return true } @@ -418,6 +428,7 @@ func (self *SCloudprovider) getUsage() *SCloudproviderUsage { usage.VpcCount = self.getVpcCount() usage.StorageCount = self.getStorageCount() usage.StorageCacheCount = self.getStoragecacheCount() + usage.EipCount = self.getEipCount() return &usage } diff --git a/pkg/compute/models/elasticips.go b/pkg/compute/models/elasticips.go new file mode 100644 index 0000000000..cb60e4ca08 --- /dev/null +++ b/pkg/compute/models/elasticips.go @@ -0,0 +1,750 @@ +package models + +import ( + "context" + "database/sql" + "fmt" + + "yunion.io/x/pkg/util/compare" + "yunion.io/x/pkg/utils" + "yunion.io/x/pkg/tristate" + "yunion.io/x/log" + "yunion.io/x/jsonutils" + "yunion.io/x/sqlchemy" + + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" +) + +const ( + EIP_MODE_INSTANCE_PUBLICIP = "public_ip" + EIP_MODE_STANDALONE_EIP = "elastic_ip" + + EIP_ASSOCIATE_TYPE_SERVER = "server" + + EIP_STATUS_READY = "ready" + EIP_STATUS_UNKNOWN = "unknown" + EIP_STATUS_ALLOCATE = "allocate" + EIP_STATUS_ALLOCATE_FAIL = "allocate_fail" + EIP_STATUS_DEALLOCATE = "deallocate" + EIP_STATUS_DEALLOCATE_FAIL = "deallocate_fail" + EIP_STATUS_ASSOCIATE = "associate" + EIP_STATUS_ASSOCIATE_FAIL = "associate_fail" + EIP_STATUS_DISSOCIATE = "dissociate" + EIP_STATUS_DISSOCIATE_FAIL = "dissociate_fail" + + EIP_CHARGE_TYPE_BY_TRAFFIC = "traffic" + EIP_CHARGE_TYPE_BY_BANDWIDTH = "bandwidth" + EIP_CHARGE_TYPE_DEFAULT = EIP_CHARGE_TYPE_BY_TRAFFIC +) + +type SElasticipManager struct { + db.SVirtualResourceBaseManager +} + +var ElasticipManager *SElasticipManager + +func init() { + ElasticipManager = &SElasticipManager{SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager(SElasticip{}, "elasticips_tbl", "eip", "eips")} +} + +type SElasticip struct { + db.SVirtualResourceBase + + SManagedResourceBase + + Mode string `width:"32" charset:"ascii" list:"user"` + + IpAddr string `width:"17" charset:"ascii" list:"user"` + + AssociateType string `width:"32" charset:"ascii" list:"user"` + AssociateId string `width:"128" charset:"ascii" list:"user"` + + Bandwidth int `list:"user" create:"required"` + + ChargeType string `list:"user" create:"required" default:"traffic"` + + AutoDellocate tristate.TriState `default:"false" get:"user" create:"optional"` + + CloudregionId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required"` +} + + +func (manager *SElasticipManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) { + q, err := manager.SVirtualResourceBaseManager.ListItemFilter(ctx, q, userCred, query) + if err != nil { + return nil, err + } + + managerFilter, _ := query.GetString("manager") + if len(managerFilter) > 0 { + managerI, err := CloudproviderManager.FetchByIdOrName(userCred.GetProjectId(), managerFilter) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError("cloud provider %s not found", managerFilter) + } else { + return nil, httperrors.NewGeneralError(err) + } + } + q = q.Equals("manager_id", managerI.GetId()) + } + + regionFilter, _ := query.GetString("region") + if len(regionFilter) > 0 { + regionObj, err := CloudregionManager.FetchByIdOrName(userCred.GetProjectId(), regionFilter) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError("cloud region %s not found", regionFilter) + } else { + return nil, httperrors.NewGeneralError(err) + } + } + q = q.Equals("cloudregion_id", regionObj.GetId()) + } + + return q, nil +} + +func (manager *SElasticipManager) getEipsByRegion(region *SCloudregion, provider *SCloudprovider) ([]SElasticip, error) { + eips := make([]SElasticip, 0) + q := manager.Query().Equals("cloudregion_id", region.Id) + if provider != nil { + q = q.Equals("manager_id", provider.Id) + } + err := db.FetchModelObjects(manager, q, &eips) + if err != nil { + return nil, err + } + return eips, nil +} + +func (self *SElasticip) GetRegion() *SCloudregion { + return CloudregionManager.FetchRegionById(self.CloudregionId) +} + +func (manager *SElasticipManager) SyncEips(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, region *SCloudregion, eips []cloudprovider.ICloudEIP) (compare.SyncResult) { + // localEips := make([]SElasticip, 0) + // remoteEips := make([]cloudprovider.ICloudEIP, 0) + syncResult := compare.SyncResult{} + + dbEips, err := manager.getEipsByRegion(region, provider) + if err != nil { + syncResult.Error(err) + return syncResult + } + + removed := make([]SElasticip, 0) + commondb := make([]SElasticip, 0) + commonext := make([]cloudprovider.ICloudEIP, 0) + added := make([]cloudprovider.ICloudEIP, 0) + + err = compare.CompareSets(dbEips, eips, &removed, &commondb, &commonext, &added) + if err != nil { + syncResult.Error(err) + return syncResult + } + + for i := 0; i < len(removed); i += 1 { + err = removed[i].SetStatus(userCred, EIP_STATUS_UNKNOWN, "sync to delete") + if err != nil { + syncResult.DeleteError(err) + } else { + syncResult.Delete() + } + } + for i := 0; i < len(commondb); i += 1 { + err = commondb[i].SyncWithCloudEip(userCred, commonext[i]) + if err != nil { + syncResult.UpdateError(err) + } else { + syncResult.Update() + } + } + for i := 0; i < len(added); i += 1 { + _, err := manager.newFromCloudEip(userCred, added[i], region) + if err != nil { + syncResult.AddError(err) + } else { + syncResult.Add() + } + } + + return syncResult +} + +func (self *SElasticip) SyncInstanceWithCloudEip(ctx context.Context, userCred mcclient.TokenCredential, ext cloudprovider.ICloudEIP) error { + vm := self.getVM() + vmExtId := ext.GetAssociationExternalId() + + if vm == nil && len(vmExtId) == 0 { + return nil + } + if vm != nil && vm.ExternalId == vmExtId { + return nil + } + + if vm != nil { // dissociate + err := self.Dissociate(ctx, userCred) + if err != nil { + log.Errorf("fail to dissociate vm: %s", err) + return err + } + } + + if len(vmExtId) > 0 { + newVM, err := GuestManager.FetchByExternalId(vmExtId) + if err != nil { + log.Errorf("fail to find vm by external ID %s", vmExtId) + return err + } + err = self.AssociateVM(userCred, newVM.(*SGuest)) + if err != nil { + log.Errorf("fail to associate with new vm %s", err) + return err + } + } + + return nil +} + +func (self *SElasticip) SyncWithCloudEip(userCred mcclient.TokenCredential, ext cloudprovider.ICloudEIP) error { + _, err := self.GetModelManager().TableSpec().Update(self, func() error { + + // self.Name = ext.GetName() + self.Bandwidth = ext.GetBandwidth() + self.IpAddr = ext.GetIpAddr() + self.Mode = ext.GetMode() + self.Status = ext.GetStatus() + self.ExternalId = ext.GetGlobalId() + // self.ManagerId = ext.GetManagerId() + self.IsEmulated = ext.IsEmulated() + // self.ProjectId = userCred.GetProjectId() + self.ChargeType = ext.GetInternetChargeType() + + return nil + }) + if err != nil { + log.Errorf("SyncWithCloudEip fail %s", err) + } + return err +} + +func (manager *SElasticipManager) newFromCloudEip(userCred mcclient.TokenCredential, extEip cloudprovider.ICloudEIP, region *SCloudregion) (*SElasticip, error) { + eip := SElasticip{} + eip.SetModelManager(manager) + + eip.Name = extEip.GetName() + eip.Status = extEip.GetStatus() + eip.ExternalId = extEip.GetGlobalId() + eip.IpAddr = extEip.GetIpAddr() + eip.Mode = extEip.GetMode() + eip.IsEmulated = extEip.IsEmulated() + eip.ManagerId = extEip.GetManagerId() + eip.CloudregionId = region.Id + eip.ChargeType = extEip.GetInternetChargeType() + + eip.ProjectId = userCred.GetProjectId() + + err := manager.TableSpec().Insert(&eip) + if err != nil { + log.Errorf("newFromCloudEip fail %s", err) + return nil, err + } + return &eip, nil +} + +func (manager *SElasticipManager) getEipForInstance(instanceType string, instanceId string) (*SElasticip, error) { + eip := SElasticip{} + + q := manager.Query() + q = q.Equals("associate_type", instanceType) + q = q.Equals("associate_id", instanceId) + + err := q.First(&eip) + + if err != nil { + if err != sql.ErrNoRows { + log.Errorf("getEipForInstance query fail %s", err) + return nil, err + } else { + return nil, nil + } + } + + eip.SetModelManager(manager) + + return &eip, nil +} + +func (self *SElasticip) getVM() *SGuest { + if self.AssociateType == "server" && len(self.AssociateId) > 0 { + return GuestManager.FetchGuestById(self.AssociateId) + } + return nil +} + +func (self *SElasticip) Dissociate(ctx context.Context, userCred mcclient.TokenCredential) error { + if len(self.AssociateType) == 0 { + return nil + } + vm := self.getVM() + if vm == nil { + log.Errorf("dissociate VM not exists???") + } + _, err := self.GetModelManager().TableSpec().Update(self, func() error { + self.AssociateId = "" + self.AssociateType = "" + return nil + }) + if err != nil { + return err + } + if vm != nil { + db.OpsLog.LogEvent(self, db.ACT_EIP_DETACH, vm.GetShortDesc(), userCred) + } + if self.Mode == EIP_MODE_INSTANCE_PUBLICIP { + self.Delete(ctx, userCred) + } + return nil +} + +func (self *SElasticip) AssociateVM(userCred mcclient.TokenCredential, vm *SGuest) error { + if len(self.AssociateType) > 0 { + return fmt.Errorf("EIP has been associated!!") + } + _, err := self.GetModelManager().TableSpec().Update(self, func() error { + self.AssociateType = "server" + self.AssociateId = vm.Id + return nil + }) + if err != nil { + return err + } + db.OpsLog.LogEvent(self, db.ACT_EIP_ATTACH, vm.GetShortDesc(), userCred) + return nil +} + +func (manager *SElasticipManager) getEipByExtEip(userCred mcclient.TokenCredential, extEip cloudprovider.ICloudEIP, region *SCloudregion) (*SElasticip, error) { + eipObj, err := manager.FetchByExternalId(extEip.GetGlobalId()) + if err == nil { + return eipObj.(*SElasticip), nil + } + if err != sql.ErrNoRows { + log.Errorf("FetchByExternalId fail %s", err) + return nil, err + } + + return manager.newFromCloudEip(userCred, extEip, region) +} + +func (manager *SElasticipManager) 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"}) + if len(regionStr) == 0 { + return nil, httperrors.NewInputParameterError("Missing region/region_id") + } + region, err := CloudregionManager.FetchByIdOrName("", regionStr) + if err != nil { + if err != sql.ErrNoRows { + return nil, httperrors.NewGeneralError(err) + } else { + return nil, httperrors.NewResourceNotFoundError("Region %s not found", regionStr) + } + } + data.Add(jsonutils.NewString(region.GetId()), "cloudregion_id") + + managerStr := jsonutils.GetAnyString(data, []string{"manager", "manager_id"}) + if len(managerStr) == 0 { + return nil, httperrors.NewInputParameterError("Missing manager/manager_id") + } + + provider, err := CloudproviderManager.FetchByIdOrName("", managerStr) + if err != nil { + if err != sql.ErrNoRows { + return nil, httperrors.NewGeneralError(err) + } else { + return nil, httperrors.NewResourceNotFoundError("Cloud provider %s not found", managerStr) + } + } + data.Add(jsonutils.NewString(provider.GetId()), "manager_id") + + chargeType := jsonutils.GetAnyString(data, []string{"charge_type"}) + if len(chargeType) == 0 { + chargeType = EIP_CHARGE_TYPE_DEFAULT + } + + if ! utils.IsInStringArray(chargeType, []string{EIP_CHARGE_TYPE_BY_BANDWIDTH, EIP_CHARGE_TYPE_BY_TRAFFIC}) { + return nil, httperrors.NewInputParameterError("charge type %s not supported", chargeType) + } + + data.Add(jsonutils.NewString(chargeType), "charge_type") + + eipPendingUsage := &SQuota{Eip: 1} + err = QuotaManager.CheckSetPendingQuota(ctx, userCred, userCred.GetProjectId(), eipPendingUsage) + if err != nil { + return nil, httperrors.NewOutOfQuotaError("Out of eip quota: %s", err) + } + + data, err = manager.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data) + if err != nil { + return nil, err + } + + return data, nil +} + +func (self *SElasticip) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data jsonutils.JSONObject) { + self.SVirtualResourceBase.PostCreate(ctx, userCred, ownerProjId, query, data) + eipPendingUsage := &SQuota{Eip: 1} + self.startEipAllocateTask(ctx, userCred, nil, eipPendingUsage) +} + +func (self *SElasticip) startEipAllocateTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict, pendingUsage quotas.IQuota) error { + /*params := jsonutils.NewDict() + params.Add(jsonutils.NewString(instanceExtId), "instance_external_id") + params.Add(jsonutils.NewString(instanceId), "instance_id") + params.Add(jsonutils.NewString(instanceType), "instance_type") + */ + + task, err := taskman.TaskManager.NewTask(ctx, "EipAllocateTask", self, userCred, params, "", "", pendingUsage) + if err != nil { + log.Errorf("newtask EipAllocateTask fail %s", err) + return err + } + self.SetStatus(userCred, EIP_STATUS_ALLOCATE, "start allocate") + task.ScheduleRun(nil) + return nil +} + +func (self *SElasticip) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + log.Infof("Elasticip delete do nothing") + return nil +} + +func (self *SElasticip) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + return self.SVirtualResourceBase.Delete(ctx, userCred) +} + +func (self *SElasticip) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + return self.StartEipDeallocateTask(ctx, userCred, "") +} + +func (self *SElasticip) ValidateDeleteCondition(ctx context.Context) error { + if len(self.AssociateId) > 0 { + return fmt.Errorf("eip is associated with instance") + } + return self.SVirtualResourceBase.ValidateDeleteCondition(ctx) +} + +func (self *SElasticip) StartEipDeallocateTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "EipDeallocateTask", self, userCred, nil, parentTaskId, "", nil) + if err != nil { + log.Errorf("newTask EipDeallocateTask fail %s", err) + return err + } + self.SetStatus(userCred, EIP_STATUS_DEALLOCATE, "start to delete") + task.ScheduleRun(nil) + return nil +} + +func (self *SElasticip) AllowPerformAssociate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SElasticip) PerformAssociate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if len(self.AssociateId) > 0 { + return nil, httperrors.NewConflictError("eip has been associated with instance") + } + + if self.Status != EIP_STATUS_READY { + return nil, httperrors.NewInvalidStatusError("eip cannot associate in status %s", self.Status) + } + + if self.Mode == EIP_MODE_INSTANCE_PUBLICIP { + return nil, httperrors.NewUnsupportOperationError("fixed eip cannot be associated") + } + + instanceId := jsonutils.GetAnyString(data, []string{"instance", "instance_id"}) + if len(instanceId) == 0 { + return nil, httperrors.NewInputParameterError("Missing instance_id") + } + instanceType := jsonutils.GetAnyString(data, []string{"instance_type"}) + if len(instanceType) == 0 { + instanceType = EIP_ASSOCIATE_TYPE_SERVER + } + + if instanceType != EIP_ASSOCIATE_TYPE_SERVER { + return nil, httperrors.NewInputParameterError("Unsupported %s", instanceType) + } + + vmObj, err := GuestManager.FetchByIdOrName(userCred.GetProjectId(), instanceId) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError("server %s not found", instanceId) + } else { + return nil, httperrors.NewGeneralError(err) + } + } + + server := vmObj.(*SGuest) + + if ok, _ := utils.InStringArray(server.Status, []string{VM_READY, VM_RUNNING}); !ok { + return nil, httperrors.NewInvalidStatusError("cannot associate server in status %s", server.Status) + } + + serverRegion := server.getRegion() + if serverRegion == nil { + return nil, httperrors.NewInputParameterError("server region is not found???") + } + + eipRegion := self.GetRegion() + if eipRegion == nil { + return nil, httperrors.NewInputParameterError("eip region is not found???") + } + + if serverRegion.Id != eipRegion.Id { + return nil, httperrors.NewInputParameterError("eip and server are not in the same region") + } + + srvHost := server.GetHost() + if srvHost == nil { + return nil, httperrors.NewInputParameterError("server host is not found???") + } + + if srvHost.ManagerId != self.ManagerId { + return nil, httperrors.NewInputParameterError("server and eip are not managed by the same provider") + } + + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(server.ExternalId), "instance_external_id") + params.Add(jsonutils.NewString(server.Id), "instance_id") + params.Add(jsonutils.NewString(EIP_ASSOCIATE_TYPE_SERVER), "instance_type") + + err = self.StartEipAssociateTask(ctx, userCred, params) + return nil, err +} + +func (self *SElasticip) StartEipAssociateTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict) error { + task, err := taskman.TaskManager.NewTask(ctx, "EipAssociateTask", self, userCred, params, "", "", nil) + if err != nil { + log.Errorf("create EipAssociateTask task fail %s", err) + return err + } + self.SetStatus(userCred, EIP_STATUS_ASSOCIATE, "start to associate") + task.ScheduleRun(nil) + return nil +} + +func (self *SElasticip) AllowPerformDissociate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SElasticip) PerformDissociate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if len(self.AssociateId) == 0 { + return nil, httperrors.NewConflictError("eip is not associated with instance") + } + + if self.Status != EIP_STATUS_READY { + return nil, httperrors.NewInvalidStatusError("eip cannot dissociate in status %s", self.Status) + } + + if self.Mode == EIP_MODE_INSTANCE_PUBLICIP { + return nil, httperrors.NewUnsupportOperationError("fixed public eip cannot be dissociated") + } + + err := self.StartEipDissociateTask(ctx, userCred, "") + return nil, err +} + +func (self *SElasticip) StartEipDissociateTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "EipDissociateTask", self, userCred, nil, parentTaskId, "", nil) + if err != nil { + log.Errorf("create EipDissociateTask fail %s", err) + return nil + } + self.SetStatus(userCred, EIP_STATUS_DISSOCIATE, "start to dissociate") + task.ScheduleRun(nil) + return nil +} + +func (self *SElasticip) GetIRegion() (cloudprovider.ICloudRegion, error) { + provider, err := self.GetDriver() + if err != nil { + return nil, err + } + + region := self.GetRegion() + if region == nil { + return nil, fmt.Errorf("fail to find region for eip") + } + + return provider.GetIRegionById(region.GetExternalId()) +} + +func (self *SElasticip) GetIEip() (cloudprovider.ICloudEIP, error) { + iregion, err := self.GetIRegion() + if err != nil { + return nil, err + } + return iregion.GetIEipById(self.GetExternalId()) +} + +func (self *SElasticip) AllowPerformSync(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SElasticip) PerformSync(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + /*if self.Status != EIP_STATUS_READY && ! strings.HasSuffix(self.Status, "_fail") { + return nil, httperrors.NewInvalidStatusError("eip cannot syncstatus in status %s", self.Status) + }*/ + + if self.Mode == EIP_MODE_INSTANCE_PUBLICIP { + return nil, httperrors.NewUnsupportOperationError("fixed eip cannot be dissociated") + } + + err := self.StartEipSyncstatusTask(ctx, userCred, "") + return nil, err +} + +func (self *SElasticip) StartEipSyncstatusTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "EipSyncstatusTask", self, userCred, nil, parentTaskId, "", nil) + if err != nil { + log.Errorf("create EipSyncstatusTask fail %s", err) + return err + } + self.SetStatus(userCred, "sync", "synchronize") + task.ScheduleRun(nil) + return nil +} + +func (self *SElasticip) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { + extra := self.SVirtualResourceBase.GetExtraDetails(ctx, userCred, query) + return self.getMoreDetails(extra) +} + +func (self *SElasticip) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { + extra := self.SVirtualResourceBase.GetCustomizeColumns(ctx, userCred, query) + return self.getMoreDetails(extra) +} + +func (self *SElasticip) getMoreDetails(extra *jsonutils.JSONDict) *jsonutils.JSONDict { + vm := self.getVM() + if vm != nil { + extra.Add(jsonutils.NewString(vm.GetName()), "associate_name") + } + return extra +} + +func (manager *SElasticipManager) allocateEipAndAssociateVM(ctx context.Context, userCred mcclient.TokenCredential, vm *SGuest, bw int, chargeType string, managerId string, regionId string) error { + eipPendingUsage := &SQuota{Eip: 1} + err := QuotaManager.CheckSetPendingQuota(ctx, userCred, userCred.GetProjectId(), eipPendingUsage) + if err != nil { + return httperrors.NewOutOfQuotaError("Out of eip quota: %s", err) + } + + eip := SElasticip{} + eip.SetModelManager(manager) + + eip.Mode = EIP_MODE_STANDALONE_EIP + eip.AutoDellocate = tristate.True + eip.Bandwidth = bw + eip.ChargeType = chargeType + eip.ProjectId = vm.ProjectId + eip.ManagerId = managerId + eip.CloudregionId = regionId + eip.Name = fmt.Sprintf("eip-for-%s", vm.GetName()) + + err = manager.TableSpec().Insert(&eip) + if err != nil { + log.Errorf("create EIP record fail %s", err) + return err + } + + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(vm.ExternalId), "instance_external_id") + params.Add(jsonutils.NewString(vm.Id), "instance_id") + params.Add(jsonutils.NewString(EIP_ASSOCIATE_TYPE_SERVER), "instance_type") + + return eip.startEipAllocateTask(ctx, userCred, params, eipPendingUsage) +} + +func (self *SElasticip) AllowPerformChangeBandwidth(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SElasticip) PerformChangeBandwidth(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if self.Status != EIP_STATUS_READY { + return nil, httperrors.NewInvalidStatusError("cannot change bandwidth in status %s", self.Status) + } + + bandwidth, err := data.Int("bandwidth") + if err != nil || bandwidth <= 0 { + return nil, httperrors.NewInputParameterError("Invalid bandwidth") + } + err = self.StartEipChangeBandwidthTask(ctx, userCred, bandwidth) + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + return nil, nil +} + +func (self *SElasticip) StartEipChangeBandwidthTask(ctx context.Context, userCred mcclient.TokenCredential, bandwidth int64) error { + params := jsonutils.NewDict() + params.Add(jsonutils.NewInt(bandwidth), "bandwidth") + + task, err := taskman.TaskManager.NewTask(ctx, "EipChangeBandwidthTask", self, userCred, params, "", "", nil) + if err != nil { + log.Errorf("create EipChangeBandwidthTask fail %s", err) + return err + } + task.ScheduleRun(nil) + return nil +} + +func (self *SElasticip) DoChangeBandwidth(userCred mcclient.TokenCredential, bandwidth int) error { + _, err := self.GetModelManager().TableSpec().Update(self, func() error { + self.Bandwidth = bandwidth + return nil + }) + + if err != nil { + log.Errorf("DoChangeBandwidth update fail %s", err) + return err + } + + changes := jsonutils.NewDict() + changes.Add(jsonutils.NewInt(int64(self.Bandwidth)), "obw") + changes.Add(jsonutils.NewInt(int64(bandwidth)), "nbw") + db.OpsLog.LogEvent(self, db.ACT_CHANGE_BANDWIDTH, changes, userCred) + + return nil +} + +type EipUsage struct { + PublicIPCount int + EIPCount int + EIPUsedCount int +} + +func (u EipUsage) Total() int { + return u.PublicIPCount + u.EIPCount +} + +func (manager *SElasticipManager) TotalCount(projectId string) EipUsage { + usage := EipUsage{} + q1 := manager.Query().Equals("mode", EIP_MODE_INSTANCE_PUBLICIP) + q2 := manager.Query().Equals("mode", EIP_MODE_STANDALONE_EIP) + q3 := manager.Query().Equals("mode", EIP_MODE_STANDALONE_EIP).IsNotEmpty("associate_id") + if len(projectId) > 0 { + q1 = q1.Equals("tenant_id", projectId) + q2 = q2.Equals("tenant_id", projectId) + q3 = q3.Equals("tenant_id", projectId) + } + usage.PublicIPCount = q1.Count() + usage.EIPCount = q2.Count() + usage.EIPUsedCount = q3.Count() + return usage +} \ No newline at end of file diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index fb8f5c2eb1..c9e40e87b4 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -298,6 +298,24 @@ func (manager *SGuestManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQ q = q.In("host_id", sq) } + regionFilter, _ := queryDict.GetString("region") + if len(regionFilter) > 0 { + regionObj, err := CloudregionManager.FetchByIdOrName(userCred.GetProjectId(), regionFilter) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError("cloud region %s not found", regionFilter) + } else { + return nil, httperrors.NewGeneralError(err) + } + } + hosts := HostManager.Query().SubQuery() + zones := ZoneManager.Query().SubQuery() + sq := hosts.Query(hosts.Field("id")) + sq = sq.Join(zones, sqlchemy.Equals(hosts.Field("zone_id"), zones.Field("id"))) + sq = sq.Filter(sqlchemy.Equals(zones.Field("cloudregion_id"), regionObj.GetId())) + q = q.In("host_id", sq) + } + gpu, _ := queryDict.GetString("gpu") if len(gpu) != 0 { isodev := IsolatedDeviceManager.Query().SubQuery() @@ -924,6 +942,10 @@ func (self *SGuest) GetCustomizeColumns(ctx context.Context, userCred mcclient.T } } extra.Add(jsonutils.NewString(strings.Join(self.getRealIPs(), ",")), "ips") + eip, _ := self.GetEip() + if eip != nil { + extra.Add(jsonutils.NewString(eip.IpAddr), "eip") + } extra.Add(jsonutils.NewInt(int64(self.getDiskSize())), "disk") // flavor?? // extra.Add(jsonutils.NewString(self.getFlavorName()), "flavor") @@ -989,6 +1011,10 @@ func (self *SGuest) GetExtraDetails(ctx context.Context, userCred mcclient.Token } extra.Add(jsonutils.NewString(self.getAdminSecurityRules()), "admin_security_rules") } + eip, _ := self.GetEip() + if eip != nil { + extra.Add(jsonutils.NewString(eip.IpAddr), "eip") + } return self.moreExtraInfo(extra) } @@ -1120,6 +1146,10 @@ func (self *SGuest) getIPs() []string { ips := self.getRealIPs() vips := self.getVirtualIPs() ips = append(ips, vips...) + eip, _ := self.GetEip() + if eip != nil { + ips = append(ips, eip.IpAddr) + } return ips } @@ -1131,6 +1161,14 @@ func (self *SGuest) getZone() *SZone { return nil } +func (self *SGuest) getRegion() *SCloudregion { + zone := self.getZone() + if zone != nil { + return zone.GetRegion() + } + return nil +} + func (self *SGuest) GetOS() string { if len(self.OsType) > 0 { return self.OsType @@ -3543,6 +3581,156 @@ func (manager *SGuestManager) CleanPendingDeleteServers(ctx context.Context, use } } +func (self *SGuest) GetEip() (*SElasticip, error) { + return ElasticipManager.getEipForInstance("server", self.Id) +} + +func (self *SGuest) SyncVMEip(ctx context.Context, userCred mcclient.TokenCredential, extEip cloudprovider.ICloudEIP) compare.SyncResult { + result := compare.SyncResult{} + + eip, err := self.GetEip() + if err != nil { + result.Error(fmt.Errorf("getEip error %s", err)) + return result + } + + if eip == nil && extEip == nil { + // do nothing + } else if eip == nil && extEip != nil { + // add + neip, err := ElasticipManager.getEipByExtEip(userCred, extEip, self.getRegion()) + if err != nil { + result.AddError(err) + } else { + err = neip.AssociateVM(userCred, self) + if err != nil { + result.AddError(err) + } else { + result.Add() + } + } + } else if eip != nil && extEip == nil { + // remove + err = eip.Dissociate(ctx, userCred) + if err != nil { + result.DeleteError(err) + } else { + result.Delete() + } + } else { + // sync + if eip.IpAddr != extEip.GetIpAddr() { + // remove then add + err = eip.Dissociate(ctx, userCred) + if err != nil { + // fail to remove + result.DeleteError(err) + } else { + result.Delete() + neip, err := ElasticipManager.getEipByExtEip(userCred, extEip, self.getRegion()) + if err != nil { + result.AddError(err) + } else { + err = neip.AssociateVM(userCred, self) + if err != nil { + result.AddError(err) + } else { + result.Add() + } + } + } + } else { + // do nothing + err := eip.SyncWithCloudEip(userCred, extEip) + if err != nil { + result.UpdateError(err) + } else { + result.Update() + } + } + } + + return result +} + +func (self *SGuest) GetIVM() (cloudprovider.ICloudVM, error) { + if len(self.ExternalId) == 0 { + msg := fmt.Sprintf("GetIVM: not managed by a provider") + log.Errorf(msg) + return nil, fmt.Errorf(msg) + } + host := self.GetHost() + if host == nil { + msg := fmt.Sprintf("GetIVM: No valid host") + log.Errorf(msg) + return nil, fmt.Errorf(msg) + } + ihost, err := host.GetIHost() + if err != nil { + msg := fmt.Sprintf("GetIVM: getihost fail %s", err) + log.Errorf(msg) + return nil, fmt.Errorf(msg) + } + return ihost.GetIVMById(self.ExternalId) +} + +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) + } + return nil, nil +} + +func (self *SGuest) DeleteEip(ctx context.Context, userCred mcclient.TokenCredential) error { + eip, err := self.GetEip() + if err != nil { + log.Errorf("Delete eip fail for get Eip %s", err) + return err + } + if eip == nil { + return nil + } + err = eip.Delete(ctx, userCred) + if err != nil { + log.Errorf("Delete eip fail %s", err) + return err + } + return nil +} + func (self *SGuest) SetDisableDelete(val bool) error { _, err := self.GetModelManager().TableSpec().Update(self, func() error { if val { diff --git a/pkg/compute/models/hosts.go b/pkg/compute/models/hosts.go index 0874133ba1..00f653b80a 100644 --- a/pkg/compute/models/hosts.go +++ b/pkg/compute/models/hosts.go @@ -1435,13 +1435,6 @@ func (self *SHost) GetIHost() (cloudprovider.ICloudHost, error) { return nil, fmt.Errorf("No cloudprovide for host: %s", err) } ihost, err := provider.GetIHostById(self.ExternalId) - - /* izone, err := self.GetIZone() - if err != nil { - return nil, fmt.Errorf("fail to find izone by id %s", err) - } - ihost, err := izone.GetIHostById(self.ExternalId)*/ - if err != nil { log.Errorf("fail to find ihost by id %s", err) return nil, fmt.Errorf("fail to find ihost by id %s", err) diff --git a/pkg/compute/models/quotas.go b/pkg/compute/models/quotas.go index 93bbe28832..a648831646 100644 --- a/pkg/compute/models/quotas.go +++ b/pkg/compute/models/quotas.go @@ -71,7 +71,7 @@ func (self *SQuota) FetchUsage(projectId string) error { diskSize := totalDiskSize(projectId, tristate.None, tristate.None, false) net := totalGuestNicCount(projectId, nil, false) guest := totalGuestResourceCount(projectId, nil, nil, "", false, false, "") - + eipUsage := ElasticipManager.TotalCount(projectId) // XXX // keypair belongs to user // keypair := totalKeypairCount(projectId) @@ -80,7 +80,7 @@ func (self *SQuota) FetchUsage(projectId string) error { self.Memory = guest.TotalMemSize self.Storage = diskSize self.Port = net.InternalNicCount + net.InternalVirtualNicCount - self.Eip = 0 + self.Eip = eipUsage.Total() self.Eport = net.ExternalNicCount + net.ExternalVirtualNicCount self.Bw = net.InternalBandwidth self.Ebw = net.ExternalBandwidth diff --git a/pkg/compute/models/vpcs.go b/pkg/compute/models/vpcs.go index 368a4a963b..e2d11f3b72 100644 --- a/pkg/compute/models/vpcs.go +++ b/pkg/compute/models/vpcs.go @@ -7,14 +7,15 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/util/compare" + "yunion.io/x/pkg/util/netutils" + "yunion.io/x/sqlchemy" + "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" - "yunion.io/x/pkg/util/compare" - "yunion.io/x/pkg/util/netutils" - "yunion.io/x/sqlchemy" ) const ( diff --git a/pkg/compute/options/options.go b/pkg/compute/options/options.go index a018a3e2f1..df6f2894f7 100644 --- a/pkg/compute/options/options.go +++ b/pkg/compute/options/options.go @@ -35,7 +35,7 @@ type ComputeOptions struct { DefaultMemoryQuota int `default:"51200" help:"Common memory quota per tenant in MB, default 50G"` DefaultStorageQuota int `default:"3072000" help:"Common storage quota per tenant in MB, default 3000G"` DefaultPortQuota int `default:"50" help:"Common network port quota per tenant, default 50"` - DefaultEipQuota int `default:"0" help:"Common floating IP quota per tenant, default 0"` + DefaultEipQuota int `default:"5" help:"Common floating IP quota per tenant, default 0"` DefaultEportQuota int `default:"50" help:"Common exit network port quota per tenant, default 50"` DefaultBwQuota int `default:"500000" help:"Common network port bandwidth in mbps quota per tenant, default 50*10Gbps"` DefaultEbwQuota int `default:"1000" help:"Common exit network port bandwidth quota per tenant, default 1Gbps"` diff --git a/pkg/compute/tasks/cloud_provider_sync_info_task.go b/pkg/compute/tasks/cloud_provider_sync_info_task.go index 51700d543f..db8f39cb40 100644 --- a/pkg/compute/tasks/cloud_provider_sync_info_task.go +++ b/pkg/compute/tasks/cloud_provider_sync_info_task.go @@ -109,6 +109,8 @@ func syncCloudProviderInfo(ctx context.Context, provider *models.SCloudprovider, continue } + syncRegionEips(ctx, provider, task, &localRegions[i], remoteRegions[i]) + localZones, remoteZones := syncRegionZones(ctx, provider, task, &localRegions[i], remoteRegions[i]) syncRegionVPCs(ctx, provider, task, &localRegions[i], remoteRegions[i]) @@ -126,6 +128,25 @@ func syncCloudProviderInfo(ctx context.Context, provider *models.SCloudprovider, } } +func syncRegionEips(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, localRegion *models.SCloudregion, remoteRegion cloudprovider.ICloudRegion) { + eips, err := remoteRegion.GetIEips() + if err != nil { + msg := fmt.Sprintf("GetIEips for region %s failed %s", remoteRegion.GetName(), err) + log.Errorf(msg) + logSyncFailed(provider, task, msg) + return + } + + result := models.ElasticipManager.SyncEips(ctx, task.UserCred, provider, localRegion, eips) + msg := result.Result() + log.Infof("SyncEips for region %s result: %s", localRegion.Name, msg) + if result.IsError() { + logSyncFailed(provider, task, msg) + return + } + db.OpsLog.LogEvent(provider, db.ACT_SYNC_HOST_COMPLETE, msg, task.UserCred) +} + func syncRegionZones(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, localRegion *models.SCloudregion, remoteRegion cloudprovider.ICloudRegion) ([]models.SZone, []cloudprovider.ICloudZone) { zones, err := remoteRegion.GetIZones() if err != nil { @@ -384,6 +405,7 @@ func syncHostVMs(ctx context.Context, provider *models.SCloudprovider, task *Clo for i := 0; i < len(localVMs); i += 1 { syncVMNics(ctx, provider, task, localHost, &localVMs[i], remoteVMs[i]) syncVMDisks(ctx, provider, task, localHost, &localVMs[i], remoteVMs[i]) + syncVMEip(ctx, provider, task, &localVMs[i], remoteVMs[i]) } } @@ -426,3 +448,21 @@ func syncVMDisks(ctx context.Context, provider *models.SCloudprovider, task *Clo db.OpsLog.LogEvent(provider, db.ACT_SYNC_HOST_COMPLETE, msg, task.UserCred) logclient.AddActionLog(provider, getAction(task.Params), notes, task.UserCred, true) } + +func syncVMEip(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, localVM *models.SGuest, remoteVM cloudprovider.ICloudVM) { + eip, err := remoteVM.GetIEIP() + if err != nil { + msg := fmt.Sprintf("GetIEIP for VM %s failed %s", remoteVM.GetName(), err) + log.Errorf(msg) + logSyncFailed(provider, task, msg) + return + } + result := localVM.SyncVMEip(ctx, task.UserCred, eip) + msg := result.Result() + log.Infof("syncVMEip for VM %s result: %s", localVM.Name, msg) + if result.IsError() { + logSyncFailed(provider, task, msg) + return + } + db.OpsLog.LogEvent(provider, db.ACT_SYNC_HOST_COMPLETE, msg, task.UserCred) +} \ No newline at end of file diff --git a/pkg/compute/tasks/eip_allocate_task.go b/pkg/compute/tasks/eip_allocate_task.go new file mode 100644 index 0000000000..2183de8f76 --- /dev/null +++ b/pkg/compute/tasks/eip_allocate_task.go @@ -0,0 +1,73 @@ +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/compute/models" +) + + +type EipAllocateTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(EipAllocateTask{}) +} + +func (self *EipAllocateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + eip := obj.(*models.SElasticip) + + iregion, err := eip.GetIRegion() + if err != nil { + msg := fmt.Sprintf("fail to find iregion for eip %s", err) + eip.SetStatus(self.UserCred, models.EIP_STATUS_ALLOCATE_FAIL, msg) + self.SetStageFailed(ctx, msg) + return + } + + extEip, err := iregion.CreateEIP(eip.Bandwidth) + if err != nil { + msg := fmt.Sprintf("create eip fail %s", err) + eip.SetStatus(self.UserCred, models.EIP_STATUS_ALLOCATE_FAIL, msg) + self.SetStageFailed(ctx, msg) + return + } + + err = eip.SyncWithCloudEip(self.UserCred, extEip) + + if err != nil { + msg := fmt.Sprintf("sync eip fail %s", err) + eip.SetStatus(self.UserCred, models.EIP_STATUS_ALLOCATE_FAIL, msg) + self.SetStageFailed(ctx, msg) + return + } + + eipPendingUsage := &models.SQuota{Eip: 1} + err = models.QuotaManager.CancelPendingUsage(ctx, self.UserCred, self.UserCred.GetProjectId(), eipPendingUsage, eipPendingUsage) + + if err != nil { + log.Errorf("CancelPendingUsage fail %s", err) + } + + if self.Params != nil && self.Params.Contains("instance_id") { + self.SetStage("on_eip_associate_complete", nil) + err = eip.StartEipAssociateTask(ctx, self.UserCred, self.Params) + if err != nil { + msg := fmt.Sprintf("start associate task fail %s", err) + self.SetStageFailed(ctx, msg) + } + } else { + self.SetStageComplete(ctx, nil) + } +} + +func (self *EipAllocateTask) OnEipAssociateComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + self.SetStageComplete(ctx, nil) +} \ No newline at end of file diff --git a/pkg/compute/tasks/eip_associate_task.go b/pkg/compute/tasks/eip_associate_task.go new file mode 100644 index 0000000000..da81236056 --- /dev/null +++ b/pkg/compute/tasks/eip_associate_task.go @@ -0,0 +1,61 @@ +package tasks + +import ( + "fmt" + "context" + + "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" +) + +type EipAssociateTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(EipAssociateTask{}) +} + +func (self *EipAssociateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + eip := obj.(*models.SElasticip) + + extEip, err := eip.GetIEip() + if err != nil { + msg := fmt.Sprintf("fail to find iEIP for eip %s", err) + eip.SetStatus(self.UserCred, models.EIP_STATUS_ASSOCIATE_FAIL, msg) + self.SetStageFailed(ctx, msg) + return + } + + instanceId, _ := self.Params.GetString("instance_id") + server := models.GuestManager.FetchGuestById(instanceId) + if server == nil { + msg := fmt.Sprintf("fail to find server for instanceId %s", instanceId) + eip.SetStatus(self.UserCred, models.EIP_STATUS_ASSOCIATE_FAIL, msg) + self.SetStageFailed(ctx, msg) + return + } + + err = extEip.Associate(server.ExternalId) + if err != nil { + msg := fmt.Sprintf("fail to remote associate EIP %s", err) + eip.SetStatus(self.UserCred, models.EIP_STATUS_ASSOCIATE_FAIL, msg) + self.SetStageFailed(ctx, msg) + return + } + + err = eip.AssociateVM(self.UserCred, server) + if err != nil { + msg := fmt.Sprintf("fail to local associate EIP %s", err) + eip.SetStatus(self.UserCred, models.EIP_STATUS_ASSOCIATE_FAIL, msg) + self.SetStageFailed(ctx, msg) + return + } + + eip.SetStatus(self.UserCred, models.EIP_STATUS_READY, "associate") + + self.SetStageComplete(ctx, nil) +} \ No newline at end of file diff --git a/pkg/compute/tasks/eip_change_bandwidth_task.go b/pkg/compute/tasks/eip_change_bandwidth_task.go new file mode 100644 index 0000000000..2900a7cdf8 --- /dev/null +++ b/pkg/compute/tasks/eip_change_bandwidth_task.go @@ -0,0 +1,57 @@ +package tasks + +import ( + "fmt" + "context" + + "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" + +) + +type EipChangeBandwidthTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(EipChangeBandwidthTask{}) +} + +func (self *EipChangeBandwidthTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + eip := obj.(*models.SElasticip) + + extEip, err := eip.GetIEip() + if err != nil { + msg := fmt.Sprintf("fail to find iEip %s", err) + self.SetStageFailed(ctx, msg) + return + } + + bandwidth, _ := self.Params.Int("bandwidth") + if bandwidth <= 0 { + msg := fmt.Sprintf("invalid bandwidth %d", bandwidth) + self.SetStageFailed(ctx, msg) + return + } + + err = extEip.ChangeBandwidth(int(bandwidth)) + + if err != nil { + msg := fmt.Sprintf("fail to find iEip %s", err) + self.SetStageFailed(ctx, msg) + return + } + + err = eip.DoChangeBandwidth(self.UserCred, int(bandwidth)) + + if err != nil { + msg := fmt.Sprintf("fail to synchronize iEip bandwidth %s", err) + self.SetStageFailed(ctx, msg) + return + } + + self.SetStageComplete(ctx, nil) +} \ No newline at end of file diff --git a/pkg/compute/tasks/eip_deallocate_task.go b/pkg/compute/tasks/eip_deallocate_task.go new file mode 100644 index 0000000000..6efb5311e9 --- /dev/null +++ b/pkg/compute/tasks/eip_deallocate_task.go @@ -0,0 +1,53 @@ +package tasks + +import ( + "fmt" + "context" + + "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" + +) + +type EipDeallocateTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(EipDeallocateTask{}) +} + +func (self *EipDeallocateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + eip := obj.(*models.SElasticip) + + if len(eip.ExternalId) > 0 { + expEip, err := eip.GetIEip() + if err != nil { + msg := fmt.Sprintf("fail to find iEIP for eip %s", err) + eip.SetStatus(self.UserCred, models.EIP_STATUS_DEALLOCATE_FAIL, msg) + self.SetStageFailed(ctx, msg) + return + } + + err = expEip.Delete() + if err != nil { + msg := fmt.Sprintf("fail to delete iEIP %s", err) + eip.SetStatus(self.UserCred, models.EIP_STATUS_DEALLOCATE_FAIL, msg) + self.SetStageFailed(ctx, msg) + return + } + } + + err := eip.RealDelete(ctx, self.UserCred) + if err != nil { + msg := fmt.Sprintf("fail to delete EIP %s", err) + eip.SetStatus(self.UserCred, models.EIP_STATUS_DEALLOCATE_FAIL, msg) + self.SetStageFailed(ctx, msg) + return + } + + self.SetStageComplete(ctx, nil) +} \ No newline at end of file diff --git a/pkg/compute/tasks/eip_dissociate_task.go b/pkg/compute/tasks/eip_dissociate_task.go new file mode 100644 index 0000000000..cad5a98cf2 --- /dev/null +++ b/pkg/compute/tasks/eip_dissociate_task.go @@ -0,0 +1,65 @@ +package tasks + + +import ( + "fmt" + "context" + + "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" +) + +type EipDissociateTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(EipDissociateTask{}) +} + +func (self *EipDissociateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + eip := obj.(*models.SElasticip) + + extEip, err := eip.GetIEip() + if err != nil { + msg := fmt.Sprintf("fail to find iEIP for eip %s", err) + eip.SetStatus(self.UserCred, models.EIP_STATUS_DISSOCIATE_FAIL, msg) + self.SetStageFailed(ctx, msg) + return + } + + if len(extEip.GetAssociationExternalId()) > 0 { + err = extEip.Dissociate() + if err != nil { + msg := fmt.Sprintf("fail to remote dissociate eip %s", err) + eip.SetStatus(self.UserCred, models.EIP_STATUS_DISSOCIATE_FAIL, msg) + self.SetStageFailed(ctx, msg) + return + } + } + + err = eip.Dissociate(ctx, self.UserCred) + if err != nil { + msg := fmt.Sprintf("fail to local dissociate eip %s", err) + eip.SetStatus(self.UserCred, models.EIP_STATUS_DISSOCIATE_FAIL, msg) + self.SetStageFailed(ctx, msg) + return + } + + eip.SetStatus(self.UserCred, models.EIP_STATUS_READY, "dissociate") + + if eip.AutoDellocate.IsTrue() { + self.SetStage("on_auto_dellocate_complete", nil) + eip.StartEipDeallocateTask(ctx, self.UserCred, self.Id) + } else { + self.SetStageComplete(ctx, nil) + } +} + +func (self *EipDissociateTask) OnAutoDellocateComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + self.SetStageComplete(ctx, nil) +} + diff --git a/pkg/compute/tasks/eip_syncstatus_task.go b/pkg/compute/tasks/eip_syncstatus_task.go new file mode 100644 index 0000000000..e44bbdbc84 --- /dev/null +++ b/pkg/compute/tasks/eip_syncstatus_task.go @@ -0,0 +1,59 @@ +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" +) + + +type EipSyncstatusTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(EipSyncstatusTask{}) +} + +func (self *EipSyncstatusTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + eip := obj.(*models.SElasticip) + + extEip, err := eip.GetIEip() + if err != nil { + msg := fmt.Sprintf("fail to find ieip for eip %s", err) + eip.SetStatus(self.UserCred, models.EIP_STATUS_UNKNOWN, msg) + self.SetStageFailed(ctx, msg) + return + } + + err = extEip.Refresh() + if err != nil { + msg := fmt.Sprintf("fail to refresh eip status %s", err) + eip.SetStatus(self.UserCred, models.EIP_STATUS_UNKNOWN, msg) + self.SetStageFailed(ctx, msg) + return + } + + err = eip.SyncWithCloudEip(self.UserCred, extEip) + if err != nil { + msg := fmt.Sprintf("fail to sync eip status %s", err) + eip.SetStatus(self.UserCred, models.EIP_STATUS_UNKNOWN, msg) + self.SetStageFailed(ctx, msg) + return + } + + err = eip.SyncInstanceWithCloudEip(ctx, self.UserCred, extEip) + if err != nil { + msg := fmt.Sprintf("fail to sync eip status %s", err) + eip.SetStatus(self.UserCred, models.EIP_STATUS_UNKNOWN, msg) + self.SetStageFailed(ctx, msg) + return + } + + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/guest_delete_task.go b/pkg/compute/tasks/guest_delete_task.go index 8d9a2ce11a..51a4e42ef0 100644 --- a/pkg/compute/tasks/guest_delete_task.go +++ b/pkg/compute/tasks/guest_delete_task.go @@ -31,6 +31,20 @@ func (self *GuestDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel func (self *GuestDeleteTask) OnGuestStopComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { guest := obj.(*models.SGuest) + + eip, _ := guest.GetEip() + if eip != nil && eip.Mode != models.EIP_MODE_INSTANCE_PUBLICIP { + // detach floating EIP only + self.SetStage("on_eip_dissociate_complete", nil) + eip.StartEipDissociateTask(ctx, self.UserCred, self.GetTaskId()) + } else { + self.OnEipDissociateComplete(ctx, obj, nil) + } +} + +func (self *GuestDeleteTask) OnEipDissociateComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + guest := obj.(*models.SGuest) + if options.Options.EnablePendingDelete && !guest.PendingDeleted && !jsonutils.QueryBoolean(self.Params, "purge", false) && !jsonutils.QueryBoolean(self.Params, "override_pending_delete", false) { @@ -102,6 +116,7 @@ func (self *GuestDeleteTask) OnGuestDeleteComplete(ctx context.Context, obj db.I guest.LeaveAllGroups(self.UserCred) guest.DetachAllNetworks(ctx, self.UserCred) guest.EjectIso(self.UserCred) + guest.DeleteEip(ctx, self.UserCred) guest.GetDriver().OnDeleteGuestFinalCleanup(ctx, guest, self.UserCred) self.DeleteGuest(ctx, guest) } diff --git a/pkg/compute/usages/handler.go b/pkg/compute/usages/handler.go index de4013c749..2da5203bc3 100644 --- a/pkg/compute/usages/handler.go +++ b/pkg/compute/usages/handler.go @@ -212,6 +212,7 @@ func ReportGeneralUsage(userCred mcclient.TokenCredential, rangeObj db.IStandalo IsolatedDeviceUsage(rangeObj, hostTypes), WireUsage(rangeObj, hostTypes), NetworkUsage(userCred, rangeObj), + EipUsage(), ) return @@ -336,3 +337,13 @@ func IsolatedDeviceUsage(rangeObj db.IStandaloneModel, hostType []string) Usage count[prefix] = ret.Devices return count } + +func EipUsage() Usage { + eipUsage := models.ElasticipManager.TotalCount("") + count := make(map[string]interface{}) + count["eip.all"] = eipUsage.Total() + count["eip.public_ip"] = eipUsage.PublicIPCount + count["eip.floating_ip"] = eipUsage.EIPCount + count["eip.floating_ip.used"] = eipUsage.EIPUsedCount + return count +} \ No newline at end of file diff --git a/pkg/httperrors/errors.go b/pkg/httperrors/errors.go index 0daa2cc0b4..4df2957513 100644 --- a/pkg/httperrors/errors.go +++ b/pkg/httperrors/errors.go @@ -11,7 +11,7 @@ func NewJsonClientError(code int, title string, msg string, error httputils.Erro return &err } -func errorMessage(msg string, params ...interface{}) (string, httputils.Error) { +func errorMessage(msg string, params []interface{}) (string, httputils.Error) { fileds := make([]string, len(params)) for i, v := range params { fileds[i] = fmt.Sprint(v) diff --git a/pkg/mcclient/modules/mod_elasticips.go b/pkg/mcclient/modules/mod_elasticips.go new file mode 100644 index 0000000000..cfbe654842 --- /dev/null +++ b/pkg/mcclient/modules/mod_elasticips.go @@ -0,0 +1,17 @@ +package modules + +var ( + Elasticips ResourceManager +) + +func init() { + Elasticips = NewComputeManager("eip", "eips", + []string{"ID", "Name", "IP_Addr", "Status", + "Associate_Type", "Associate_ID", + "Associate_Name", + "Bandwidth", "Charge_Type", + }, + []string{}) + + registerCompute(&Elasticips) +} diff --git a/pkg/mcclient/modules/mod_servers.go b/pkg/mcclient/modules/mod_servers.go index 317dc6194a..7b5c175f5b 100644 --- a/pkg/mcclient/modules/mod_servers.go +++ b/pkg/mcclient/modules/mod_servers.go @@ -50,7 +50,7 @@ var ( func init() { Servers = ServerManager{NewComputeManager("server", "servers", []string{"ID", "Name", "Billing_type", - "IPs", "Disk", "Status", + "IPs", "EIP", "Disk", "Status", "vcpu_count", "vmem_size", "ext_bw", "Zone_name", "Secgroup", "Secgrp_id", diff --git a/pkg/mcclient/options/base.go b/pkg/mcclient/options/base.go index 5aa6fa8e81..1062e83346 100644 --- a/pkg/mcclient/options/base.go +++ b/pkg/mcclient/options/base.go @@ -146,7 +146,7 @@ type BaseListOptions struct { Offset *int `default:"0" help:"Page offset"` OrderBy []string `help:"Name of the field to be ordered by"` Order string `help:"List order" choices:"desc|asc"` - Details *bool `help:"Show more details"` + Details *bool `help:"Show more details" default:"false"` Search string `help:"Filter results by a simple keyword search"` Meta *bool `help:"Piggyback metadata information"` Filter []string `help:"Filters"` diff --git a/pkg/mcclient/options/servers.go b/pkg/mcclient/options/servers.go index 6acc7b2dd1..3ded557f64 100644 --- a/pkg/mcclient/options/servers.go +++ b/pkg/mcclient/options/servers.go @@ -20,6 +20,8 @@ type ServerListOptions struct { AdminSecgroup string `help:"AdminSecgroup ID or Name"` Hypervisor string `help:"Show server of hypervisor" choices:"kvm|esxi|container|baremetal|aliyun"` Manager string `help:"Show servers imported from manager"` + Region string `help:"Show servers in cloudregion"` + BaseListOptions } diff --git a/pkg/util/aliyun/eip.go b/pkg/util/aliyun/eip.go new file mode 100644 index 0000000000..a57b636335 --- /dev/null +++ b/pkg/util/aliyun/eip.go @@ -0,0 +1,296 @@ +package aliyun + +import ( + "time" + "yunion.io/x/log" + "fmt" + + "yunion.io/x/pkg/utils" + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" +) + +type TInternetChargeType string + +const ( + InternetChargeByTraffic = TInternetChargeType("PayByTraffic") + InternetChargeByBandwidth = TInternetChargeType("PayByBandwidth") +) + +const ( + EIP_STATUS_ASSOCIATING = "Associating" + EIP_STATUS_UNASSOCIATING = "Unassociating" + EIP_STATUS_INUSE = "InUse" + EIP_STATUS_AVAILABLE = "Available" + + EIP_OPERATION_LOCK_FINANCIAL = "financial" + EIP_OPERATION_LOCK_SECURITY = "security" + + + EIP_INSTANCE_TYPE_ECS = "EcsInstance" // (默认值):VPC类型的ECS实例 + EIP_INTANNCE_TYPE_SLB = "SlbInstance" // :VPC类型的SLB实例 + EIP_INSTANCE_TYPE_NAT = "Nat" // :NAT网关 + EIP_INSTANCE_TYPE_HAVIP = "HaVip" // :HAVIP +) + +type SEipAddress struct { + region *SRegion + + AllocationId string + + InternetChargeType string + + IpAddress string + Status string + + InstanceType string + InstanceId string + Bandwidth int /* Mbps */ + + AllocationTime time.Time + + OperationLocks string +} + +func (self *SEipAddress) GetId() string { + return self.AllocationId +} + +func (self *SEipAddress) GetName() string { + return self.IpAddress +} + +func (self *SEipAddress) GetGlobalId() string { + return self.AllocationId +} + +func (self *SEipAddress) GetStatus() string { + switch self.Status { + case EIP_STATUS_AVAILABLE, EIP_STATUS_INUSE: + return models.EIP_STATUS_READY + case EIP_STATUS_ASSOCIATING: + return models.EIP_STATUS_ASSOCIATE + case EIP_STATUS_UNASSOCIATING: + return models.EIP_STATUS_DISSOCIATE + default: + return models.EIP_STATUS_UNKNOWN + } +} + +func (self *SEipAddress) Refresh() error { + if self.IsEmulated() { + return nil + } + new, err := self.region.GetEip(self.AllocationId) + if err != nil { + return err + } + return jsonutils.Update(self, new) +} + +func (self *SEipAddress) IsEmulated() bool { + if self.AllocationId == self.InstanceId { + // fixed Public IP + return true + } else { + return false + } +} + +func (self *SEipAddress) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SEipAddress) GetIpAddr() string { + return self.IpAddress +} + +func (self *SEipAddress) GetMode() string { + if self.InstanceId == self.AllocationId { + return models.EIP_MODE_INSTANCE_PUBLICIP + } else { + return models.EIP_MODE_STANDALONE_EIP + } +} + +func (self *SEipAddress) GetAssociationType() string { + switch self.InstanceType { + case EIP_INSTANCE_TYPE_ECS: + return "server" + default: + log.Fatalf("unsupported type: %s", self.InstanceType) + return "unsupported" + } +} + +func (self *SEipAddress) GetAssociationExternalId() string { + return self.InstanceId +} + +func (self *SEipAddress) GetManagerId() string { + return self.region.client.providerId +} + +func (self *SEipAddress) Delete() error { + return self.region.DeallocateEIP(self.AllocationId) +} + +func (self *SEipAddress) GetBandwidth() int { + return self.Bandwidth +} + +func (self *SEipAddress) GetInternetChargeType() string { + switch self.InternetChargeType { + case string(InternetChargeByTraffic): + return models.EIP_CHARGE_TYPE_BY_TRAFFIC + case string(InternetChargeByBandwidth): + return models.EIP_CHARGE_TYPE_BY_BANDWIDTH + default: + return models.EIP_CHARGE_TYPE_BY_TRAFFIC + } +} + +func (self *SEipAddress) Associate(instanceId string) error { + err := self.region.AssociateEip(self.AllocationId, instanceId) + if err != nil { + return err + } + err = cloudprovider.WaitStatus(self, models.EIP_STATUS_READY, 10*time.Second, 180*time.Second) + return err +} + +func (self *SEipAddress) Dissociate() error { + err := self.region.DissociateEip(self.AllocationId, self.InstanceId) + if err != nil { + return err + } + err = cloudprovider.WaitStatus(self, models.EIP_STATUS_READY, 10*time.Second, 180*time.Second) + return err +} + +func (self *SEipAddress) ChangeBandwidth(bw int) error { + return self.region.UpdateEipBandwidth(self.AllocationId, bw) +} + +func (region *SRegion) GetEips(eipId string, offset int, limit int) ([]SEipAddress, int, error) { + if limit > 50 || limit <= 0 { + limit = 50 + } + + params := make(map[string]string) + params["RegionId"] = region.RegionId + params["PageSize"] = fmt.Sprintf("%d", limit) + params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1) + + if len(eipId) > 0 { + params["AllocationId"] = eipId + } + + body, err := region.ecsRequest("DescribeEipAddresses", params) + if err != nil { + log.Errorf("DescribeEipAddresses fail %s", err) + return nil, 0, err + } + + // log.Errorf("%s", body) + + eips := make([]SEipAddress, 0) + err = body.Unmarshal(&eips, "EipAddresses", "EipAddress") + if err != nil { + log.Errorf("Unmarshal EipAddress details fail %s", err) + return nil, 0, err + } + total, _ := body.Int("TotalCount") + for i := 0; i < len(eips); i += 1 { + eips[i].region = region + } + return eips, int(total), nil +} + +func (region *SRegion) GetEip(eipId string) (*SEipAddress, error) { + eips, total, err := region.GetEips(eipId, 0, 1) + if err != nil { + return nil, err + } + if total != 1 { + return nil, cloudprovider.ErrNotFound + } + return &eips[0], nil +} + +func (region *SRegion) AllocateEIP(bwMbps int, chargeType TInternetChargeType) (*SEipAddress, error) { + params := make(map[string]string) + + params["Bandwidth"] = fmt.Sprintf("%d", bwMbps) + params["InternetChargeType"] = string(chargeType) + params["InstanceChargeType"] = "PostPaid" + params["ClientToken"] = utils.GenRequestId(20) + + body, err := region.ecsRequest("AllocateEipAddress", params) + if err != nil { + log.Errorf("AllocateEipAddress fail %s", err) + return nil, err + } + + eipId, err := body.GetString("AllocationId") + if err != nil { + log.Errorf("fail to get AllocationId after EIP allocation??? %s", err) + return nil, err + } + + return region.GetEip(eipId) +} + +func (region *SRegion) CreateEIP(bwMbps int) (cloudprovider.ICloudEIP, error) { + eip, err := region.AllocateEIP(bwMbps, InternetChargeByTraffic) + return eip, err +} + +func (region *SRegion) DeallocateEIP(eipId string) (error) { + params := make(map[string]string) + params["AllocationId"] = eipId + + _, err := region.ecsRequest("ReleaseEipAddress", params) + if err != nil { + log.Errorf("ReleaseEipAddress fail %s", err) + } + return err +} + +func (region *SRegion) AssociateEip(eipId string, instanceId string) (error) { + params := make(map[string]string) + params["AllocationId"] = eipId + params["InstanceId"] = instanceId + + _, err := region.ecsRequest("AssociateEipAddress", params) + if err != nil { + log.Errorf("AssociateEipAddress fail %s", err) + } + return err +} + +func (region *SRegion) DissociateEip(eipId string, instanceId string) (error) { + params := make(map[string]string) + params["AllocationId"] = eipId + params["InstanceId"] = instanceId + + _, err := region.ecsRequest("UnassociateEipAddress", params) + if err != nil { + log.Errorf("UnassociateEipAddress fail %s", err) + } + return err +} + +func (region *SRegion) UpdateEipBandwidth(eipId string, bw int) error { + params := make(map[string]string) + params["AllocationId"] = eipId + params["Bandwidth"] = fmt.Sprintf("%d", bw) + + _, err := region.ecsRequest("ModifyEipAddressAttribute", params) + if err != nil { + log.Errorf("ModifyEipAddressAttribute fail %s", err) + } + return err +} \ No newline at end of file diff --git a/pkg/util/aliyun/instance.go b/pkg/util/aliyun/instance.go index 2d7e95e49b..934889e7d5 100644 --- a/pkg/util/aliyun/instance.go +++ b/pkg/util/aliyun/instance.go @@ -34,24 +34,6 @@ type SDedicatedHostAttribute struct { DedicatedHostName string } -type SEipAddress struct { - AllocationId string - InternetChargeType string - IpAddress string -} - -func (self *SEipAddress) GetIP() string { - return self.IpAddress -} - -func (self *SEipAddress) GetAllocationId() string { - return self.AllocationId -} - -func (self *SEipAddress) GetChargeType() string { - return self.GetChargeType() -} - type SIpAddress struct { IpAddress []string } @@ -247,10 +229,6 @@ func (self *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) { return nics, nil } -func (self *SInstance) GetEIP() cloudprovider.ICloudEIP { - return &self.EipAddress -} - func (self *SInstance) GetVcpuCount() int8 { return self.Cpu } @@ -723,3 +701,23 @@ func (self *SInstance) SyncSecurityGroup(secgroupId string, name string, rules [ } return nil } + +func (self *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) { + if len(self.PublicIpAddress.IpAddress) > 0 { + eip := SEipAddress{} + eip.region = self.host.zone.region + eip.IpAddress = self.PublicIpAddress.IpAddress[0] + eip.InstanceId = self.InstanceId + eip.InstanceType = EIP_INSTANCE_TYPE_ECS + eip.Status = EIP_STATUS_INUSE + eip.AllocationId = self.InstanceId // fixed + eip.AllocationTime = self.CreationTime + eip.Bandwidth = self.InternetMaxBandwidthOut + eip.InternetChargeType = self.InternetChargeType + return &eip, nil + } else if len(self.EipAddress.IpAddress) > 0 { + return self.host.zone.region.GetEip(self.EipAddress.AllocationId) + } else { + return nil, nil + } +} \ No newline at end of file diff --git a/pkg/util/aliyun/region.go b/pkg/util/aliyun/region.go index 311f7bf195..4fc9d104b9 100644 --- a/pkg/util/aliyun/region.go +++ b/pkg/util/aliyun/region.go @@ -570,3 +570,37 @@ func (self *SRegion) updateInstance(instId string, name, desc, passwd, hostname func (self *SRegion) UpdateInstancePassword(instId string, passwd string) error { return self.updateInstance(instId, "", "", passwd, "") } + +func (self *SRegion) GetIEips() ([]cloudprovider.ICloudEIP, error) { + eips, total, err := self.GetEips("", 0, 50) + if err != nil { + return nil, err + } + for len(eips) < total { + var parts []SEipAddress + parts, total, err = self.GetEips("", len(eips), 50) + if err != nil { + return nil, err + } + eips = append(eips, parts...) + } + ret := make([]cloudprovider.ICloudEIP, len(eips)) + for i := 0; i < len(eips); i += 1 { + ret[i] = &eips[i] + } + return ret, nil +} + +func (self *SRegion) GetIEipById(eipId string) (cloudprovider.ICloudEIP, error) { + eips, total, err := self.GetEips(eipId, 0, 1) + if err != nil { + return nil, err + } + if total == 0 { + return nil, cloudprovider.ErrNotFound + } + if total > 1 { + return nil, cloudprovider.ErrDuplicateId + } + return &eips[0], nil +} \ No newline at end of file diff --git a/pkg/util/aliyun/shell/eip.go b/pkg/util/aliyun/shell/eip.go new file mode 100644 index 0000000000..4ea2534fbd --- /dev/null +++ b/pkg/util/aliyun/shell/eip.go @@ -0,0 +1,54 @@ +package shell + +import ( + "yunion.io/x/onecloud/pkg/util/aliyun" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type EipListOptions struct { + Offset int `help:"List offset"` + Limit int `help:"List limit"` + } + shellutils.R(&EipListOptions{}, "eip-list", "List eips", func(cli *aliyun.SRegion, args *EipListOptions) error { + eips, total, e := cli.GetEips("", args.Offset, args.Limit) + if e != nil { + return e + } + printList(eips, total, args.Offset, args.Limit, []string{}) + return nil + }) + + type EipAllocateOptions struct { + BW int `help:"Bandwidth limit in Mbps"` + } + shellutils.R(&EipAllocateOptions{}, "eip-create", "Allocate an EIP", func(cli *aliyun.SRegion, args *EipAllocateOptions) error { + eip, err := cli.AllocateEIP(args.BW, aliyun.InternetChargeByTraffic) + if err != nil { + return err + } + printObject(eip) + return nil + }) + + type EipReleaseOptions struct { + ID string `help:"EIP allocation ID"` + } + shellutils.R(&EipReleaseOptions{}, "eip-delete", "Release an EIP", func(cli *aliyun.SRegion, args *EipReleaseOptions) error { + err := cli.DeallocateEIP(args.ID) + return err + }) + + type EipAssociateOptions struct { + ID string `help:"EIP allocation ID"` + INSTANCE string `help:"Instance ID"` + } + shellutils.R(&EipAssociateOptions{}, "eip-associate", "Associate an EIP", func(cli *aliyun.SRegion, args *EipAssociateOptions) error { + err := cli.AssociateEip(args.ID, args.INSTANCE) + return err + }) + shellutils.R(&EipAssociateOptions{}, "eip-dissociate", "Dissociate an EIP", func(cli *aliyun.SRegion, args *EipAssociateOptions) error { + err := cli.DissociateEip(args.ID, args.INSTANCE) + return err + }) +} diff --git a/pkg/util/esxi/virtualmachine.go b/pkg/util/esxi/virtualmachine.go index 5cf0149ee9..1ba9b35965 100644 --- a/pkg/util/esxi/virtualmachine.go +++ b/pkg/util/esxi/virtualmachine.go @@ -109,8 +109,8 @@ func (self *SVirtualMachine) GetINics() ([]cloudprovider.ICloudNic, error) { return nil, cloudprovider.ErrNotImplemented } -func (self *SVirtualMachine) GetEIP() cloudprovider.ICloudEIP { - return nil +func (self *SVirtualMachine) GetIEIP() (cloudprovider.ICloudEIP, error) { + return nil, nil } func (self *SVirtualMachine) GetVcpuCount() int8 {