diff --git a/pkg/apis/compute/loadbalancer_const.go b/pkg/apis/compute/loadbalancer_const.go index e10535b712..c2592324e4 100644 --- a/pkg/apis/compute/loadbalancer_const.go +++ b/pkg/apis/compute/loadbalancer_const.go @@ -140,11 +140,12 @@ var LB_NETWORK_TYPES = choices.NewChoices( // TODO https_direct sni const ( - LB_LISTENER_TYPE_TCP = "tcp" - LB_LISTENER_TYPE_UDP = "udp" - LB_LISTENER_TYPE_TCP_UDP = "tcp_udp" - LB_LISTENER_TYPE_HTTP = "http" - LB_LISTENER_TYPE_HTTPS = "https" + LB_LISTENER_TYPE_TCP = "tcp" + LB_LISTENER_TYPE_UDP = "udp" + LB_LISTENER_TYPE_TCP_UDP = "tcp_udp" + LB_LISTENER_TYPE_HTTP = "http" + LB_LISTENER_TYPE_HTTPS = "https" + LB_LISTENER_TYPE_TERMINATED_HTTPS = "terminated_https" ) var LB_LISTENER_TYPES = choices.NewChoices( @@ -234,6 +235,7 @@ var LB_STICKY_SESSION_TYPES = choices.NewChoices( // TODO maybe https check when field need comes ;) const ( + LB_HEALTH_CHECK_PING = "ping" LB_HEALTH_CHECK_TCP = "tcp" LB_HEALTH_CHECK_UDP = "udp" LB_HEALTH_CHECK_HTTP = "http" diff --git a/pkg/cloudprovider/loadbalancerlistener.go b/pkg/cloudprovider/loadbalancerlistener.go index 923aeaa4fa..6b4d6e8ff8 100644 --- a/pkg/cloudprovider/loadbalancerlistener.go +++ b/pkg/cloudprovider/loadbalancerlistener.go @@ -84,4 +84,11 @@ type SLoadbalancerListenerRule struct { HealthCheckFail int // for qcloud only StickySessionCookieTimeout int // for qcloud only + + // openstack redirect + Redirect string `width:"16" nullable:"true" list:"user" create:"optional" update:"user" default:"off"` // 跳转类型 + RedirectCode int `nullable:"true" list:"user" create:"optional" update:"user"` // 跳转HTTP code + RedirectScheme string `width:"16" nullable:"true" list:"user" create:"optional" update:"user"` // 跳转uri scheme + RedirectHost string `nullable:"true" list:"user" create:"optional" update:"user"` // 跳转时变更Host + RedirectPath string `nullable:"true" list:"user" create:"optional" update:"user"` // 跳转时变更Path } diff --git a/pkg/compute/models/cloudsyncelb.go b/pkg/compute/models/cloudsyncelb.go index 0649aba645..b4df74da69 100644 --- a/pkg/compute/models/cloudsyncelb.go +++ b/pkg/compute/models/cloudsyncelb.go @@ -339,3 +339,48 @@ func syncQcloudLoadbalancerBackends(ctx context.Context, userCred mcclient.Token return } } + +// openstack elb sync +func SyncOpenstackLoadbalancerBackendgroups(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, provider *SCloudprovider, localLoadbalancer *SLoadbalancer, remoteLoadbalancer cloudprovider.ICloudLoadbalancer, syncRange *SSyncRange) { + remoteBackendgroups, err := remoteLoadbalancer.GetILoadBalancerBackendGroups() + if err != nil { + msg := fmt.Sprintf("GetILoadBalancerBackendGroups for loadbalancer %s failed %s", localLoadbalancer.Name, err) + log.Errorln(msg) + return + } + localLbbgs, remoteLbbgs, result := OpenstackCachedLbbgManager.SyncLoadbalancerBackendgroups(ctx, userCred, provider, localLoadbalancer, remoteBackendgroups, syncRange) + + syncResults.Add(HuaweiCachedLbbgManager, result) + + msg := result.Result() + log.Infof("SyncLoadbalancerBackendgroups for loadbalancer %s result: %s", localLoadbalancer.Name, msg) + if result.IsError() { + return + } + for i := 0; i < len(localLbbgs); i++ { + func() { + lockman.LockObject(ctx, &localLbbgs[i]) + defer lockman.ReleaseObject(ctx, &localLbbgs[i]) + + syncOpenstackLoadbalancerBackends(ctx, userCred, syncResults, provider, &localLbbgs[i], remoteLbbgs[i], syncRange) + }() + } +} + +func syncOpenstackLoadbalancerBackends(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, provider *SCloudprovider, localLbbg *SOpenstackCachedLbbg, remoteLbbg cloudprovider.ICloudLoadbalancerBackendGroup, syncRange *SSyncRange) { + remoteLbbs, err := remoteLbbg.GetILoadbalancerBackends() + if err != nil { + msg := fmt.Sprintf("GetILoadbalancerBackends for lbbg %s failed %s", localLbbg.Name, err) + log.Errorln(msg) + return + } + result := OpenstackCachedLbManager.SyncLoadbalancerBackends(ctx, userCred, provider, localLbbg, remoteLbbs, syncRange) + + syncResults.Add(LoadbalancerBackendManager, result) + + msg := result.Result() + log.Infof("SyncLoadbalancerBackends for LoadbalancerBackendgroup %s result: %s", localLbbg.Name, msg) + if result.IsError() { + return + } +} diff --git a/pkg/compute/models/loadbalancerbackendgroups.go b/pkg/compute/models/loadbalancerbackendgroups.go index be33f04520..9926b97854 100644 --- a/pkg/compute/models/loadbalancerbackendgroups.go +++ b/pkg/compute/models/loadbalancerbackendgroups.go @@ -535,6 +535,15 @@ func (lbbg *SLoadbalancerBackendGroup) StartAwsLoadBalancerBackendGroupCreateTas return nil } +func (lbbg *SLoadbalancerBackendGroup) StartOpenstackLoadBalancerBackendGroupCreateTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "OpenstackLoadbalancerLoadbalancerBackendGroupCreateTask", lbbg, userCred, params, parentTaskId, "", nil) + if err != nil { + return err + } + task.ScheduleRun(nil) + return nil +} + func (lbbg *SLoadbalancerBackendGroup) LBPendingDelete(ctx context.Context, userCred mcclient.TokenCredential) { if lb := lbbg.GetLoadbalancer(); lb != nil && lb.BackendGroupId == lbbg.Id { if _, err := db.UpdateWithLock(ctx, lb, func() error { @@ -760,6 +769,52 @@ func (lbbg *SLoadbalancerBackendGroup) GetQcloudBackendGroupParams(lblis *SLoadb return ret, nil } +func (lbbg *SLoadbalancerBackendGroup) GetOpenstackBackendGroupParams(lblis *SLoadbalancerListener, lbr *SLoadbalancerListenerRule) (*cloudprovider.SLoadbalancerBackendGroup, error) { + ret, err := lbbg.GetBackendGroupParams() + if err != nil { + return nil, errors.Wrap(err, "lbbg.GetBackendGroupParams()") + } + + var stickySession *cloudprovider.SLoadbalancerStickySession + if lblis.StickySession == api.LB_BOOL_ON { + stickySession = &cloudprovider.SLoadbalancerStickySession{ + StickySession: lblis.StickySession, + StickySessionCookie: lblis.StickySessionCookie, + StickySessionType: lblis.StickySessionType, + StickySessionCookieTimeout: lblis.StickySessionCookieTimeout, + } + } + + var healthCheck *cloudprovider.SLoadbalancerHealthCheck + if lblis.HealthCheck == api.LB_BOOL_ON { + healthCheck = &cloudprovider.SLoadbalancerHealthCheck{ + HealthCheckType: lblis.HealthCheckType, + HealthCheckReq: lblis.HealthCheckReq, + HealthCheckExp: lblis.HealthCheckExp, + HealthCheck: lblis.HealthCheck, + HealthCheckTimeout: lblis.HealthCheckTimeout, + HealthCheckDomain: lblis.HealthCheckDomain, + HealthCheckHttpCode: lblis.HealthCheckHttpCode, + HealthCheckURI: lblis.HealthCheckURI, + HealthCheckInterval: lblis.HealthCheckInterval, + HealthCheckRise: lblis.HealthCheckRise, + HealthCheckFail: lblis.HealthCheckFall, + } + } + + if lbr != nil { + ret.ListenerID = lbr.GetExternalId() + } else { + ret.ListenerID = lblis.GetExternalId() + } + ret.ListenType = lblis.ListenerType + ret.Scheduler = lblis.Scheduler + ret.StickySession = stickySession + ret.HealthCheck = healthCheck + + return ret, nil +} + func (lbbg *SLoadbalancerBackendGroup) GetBackendsParams() ([]cloudprovider.SLoadbalancerBackend, error) { backends, err := lbbg.GetBackends() if err != nil { diff --git a/pkg/compute/models/loadbalancercachedacls.go b/pkg/compute/models/loadbalancercachedacls.go index c3ba8d09a9..47de1c847a 100644 --- a/pkg/compute/models/loadbalancercachedacls.go +++ b/pkg/compute/models/loadbalancercachedacls.go @@ -358,6 +358,9 @@ func (man *SCachedLoadbalancerAclManager) GetOrCreateCachedAcl(ctx context.Conte if lblis.GetProviderName() == api.CLOUD_PROVIDER_HUAWEI { listenerId = lblis.Id } + if lblis.GetProviderName() == api.CLOUD_PROVIDER_OPENSTACK { + listenerId = lblis.Id + } region := lblis.GetRegion() if region == nil { diff --git a/pkg/compute/models/loadbalancerlistenerrules.go b/pkg/compute/models/loadbalancerlistenerrules.go index 295078c645..c51ea5ad54 100644 --- a/pkg/compute/models/loadbalancerlistenerrules.go +++ b/pkg/compute/models/loadbalancerlistenerrules.go @@ -499,7 +499,7 @@ func (man *SLoadbalancerListenerRuleManager) ValidateCreateData(ctx context.Cont } backendGroupV := validators.NewModelIdOrNameValidator("backend_group", "loadbalancerbackendgroup", ownerId) - if region.Provider == api.CLOUD_PROVIDER_ONECLOUD { + if region.GetDriver().IsSupportLoadbalancerListenerRuleRedirect() { // backend group can be empty if you support redirect in rule backendGroupV.Optional(true) } @@ -825,6 +825,28 @@ func (lbr *SLoadbalancerListenerRule) updateCachedLoadbalancerBackendGroupAssoci } } } + case api.CLOUD_PROVIDER_OPENSTACK: + _group, err := db.FetchByExternalId(OpenstackCachedLbbgManager, exteralLbbgId) + if err != nil { + if err == sql.ErrNoRows { + lbr.BackendGroupId = "" + } + return fmt.Errorf("Fetch openstack loadbalancer backendgroup by external id %s failed: %s", exteralLbbgId, err) + } + + if _group != nil { + group := _group.(*SOpenstackCachedLbbg) + if group.AssociatedId != lbr.Id { + _, err := db.UpdateWithLock(ctx, group, func() error { + group.AssociatedId = lbr.Id + group.AssociatedType = api.LB_ASSOCIATE_TYPE_RULE + return nil + }) + if err != nil { + return errors.Wrap(err, "LoadbalancerListener.updateCachedLoadbalancerBackendGroupAssociate.openstack") + } + } + } default: return nil } diff --git a/pkg/compute/models/loadbalancerlisteners.go b/pkg/compute/models/loadbalancerlisteners.go index 33daafba45..2dbb3df3c0 100644 --- a/pkg/compute/models/loadbalancerlisteners.go +++ b/pkg/compute/models/loadbalancerlisteners.go @@ -458,7 +458,7 @@ func (lblis *SLoadbalancerListener) ValidateUpdateData(ctx context.Context, user func (lblis *SLoadbalancerListener) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) { lblis.SVirtualResourceBase.PostUpdate(ctx, userCred, query, data) - if account := lblis.GetCloudaccount(); account != nil && account.IsPublicCloud.IsTrue() { + if account := lblis.GetCloudaccount(); account != nil && !account.IsOnPremise { lblis.StartLoadBalancerListenerSyncTask(ctx, userCred, data, "") } } @@ -783,6 +783,29 @@ func (lblis *SLoadbalancerListener) GetQcloudLoadbalancerListenerParams() (*clou return listener, nil } +func (lblis *SLoadbalancerListener) GetOpenstackLoadbalancerListenerParams() (*cloudprovider.SLoadbalancerListener, error) { + listener, err := lblis.GetLoadbalancerListenerParams() + if err != nil { + return nil, err + } + + if backendgroup := lblis.GetLoadbalancerBackendGroup(); backendgroup != nil { + cachedLbbg, err := OpenstackCachedLbbgManager.GetCachedBackendGroupByAssociateId(lblis.GetId()) + if err != nil { + if errors.Cause(err) != sql.ErrNoRows { + return nil, errors.Wrap(err, "loadbalancerListener.GetCachedBackendGroupByAssociateId") + } else { + log.Debugf("loadbalancerListener.GetCachedBackendGroupByAssociateId %s not found", lblis.GetId()) + } + } else { + listener.BackendGroupID = cachedLbbg.ExternalId + listener.BackendGroupType = backendgroup.Type + } + } + + return listener, nil +} + func (lblis *SLoadbalancerListener) GetLoadbalancerCertificate() (*SCachedLoadbalancerCertificate, error) { if len(lblis.CachedCertificateId) == 0 { return nil, nil @@ -1089,6 +1112,20 @@ func (lblis *SLoadbalancerListener) constructFieldsFromCloudListener(userCred mc } } } + case api.CLOUD_PROVIDER_OPENSTACK: + if len(groupId) > 0 { + group, err := db.FetchByExternalIdAndManagerId(OpenstackCachedLbbgManager, groupId, func(q *sqlchemy.SQuery) *sqlchemy.SQuery { + return q.Equals("manager_id", lb.ManagerId) + }) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + lblis.BackendGroupId = "" + } + log.Errorf("Fetch openstack loadbalancer backendgroup by external id %s failed: %s", groupId, err) + } else { + lblis.BackendGroupId = group.(*SOpenstackCachedLbbg).BackendGroupId + } + } default: if len(lblis.BackendGroupId) == 0 && len(groupId) == 0 { lblis.BackendGroupId = lb.BackendGroupId @@ -1161,6 +1198,31 @@ func (lblis *SLoadbalancerListener) updateCachedLoadbalancerBackendGroupAssociat } } } + case api.CLOUD_PROVIDER_OPENSTACK: + _group, err := db.FetchByExternalIdAndManagerId(OpenstackCachedLbbgManager, exteralLbbgId, func(q *sqlchemy.SQuery) *sqlchemy.SQuery { + return q.Equals("manager_id", managerId) + }) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + lblis.BackendGroupId = "" + } else { + return fmt.Errorf("Fetch openstack loadbalancer backendgroup by external id %s failed: %s", exteralLbbgId, err) + } + } + + if _group != nil { + group := _group.(*SOpenstackCachedLbbg) + if group.AssociatedId != lblis.Id { + _, err := db.UpdateWithLock(ctx, group, func() error { + group.AssociatedId = lblis.Id + group.AssociatedType = api.LB_ASSOCIATE_TYPE_LISTENER + return nil + }) + if err != nil { + return errors.Wrap(err, "LoadbalancerListener.updateCachedLoadbalancerBackendGroupAssociate.openstack") + } + } + } default: return nil } diff --git a/pkg/compute/models/loadbalanceropenstackcachedlbb.go b/pkg/compute/models/loadbalanceropenstackcachedlbb.go new file mode 100644 index 0000000000..e063e4ba9c --- /dev/null +++ b/pkg/compute/models/loadbalanceropenstackcachedlbb.go @@ -0,0 +1,284 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/compare" + "yunion.io/x/sqlchemy" + + 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/cloudprovider" + "yunion.io/x/onecloud/pkg/mcclient" +) + +// +onecloud:swagger-gen-ignore +type SOpenstackCachedLbManager struct { + SLoadbalancerLogSkipper + db.SVirtualResourceBaseManager +} + +var OpenstackCachedLbManager *SOpenstackCachedLbManager + +func init() { + OpenstackCachedLbManager = &SOpenstackCachedLbManager{ + SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager( + SOpenstackCachedLb{}, + "openstackcachedlbbs_tbl", + "openstackcachedlbb", + "openstackcachedlbbs", + ), + } + OpenstackCachedLbManager.SetVirtualObject(OpenstackCachedLbManager) +} + +type SOpenstackCachedLb struct { + db.SVirtualResourceBase + db.SExternalizedResourceBase + + SManagedResourceBase + SCloudregionResourceBase + + BackendServerId string `width:"36" charset:"ascii" nullable:"true" list:"user" create:"optional"` // 后端服务器 实例ID + BackendId string `width:"36" charset:"ascii" nullable:"true" list:"user" create:"optional"` // 本地loadbalancebackend id + CachedBackendGroupId string `width:"36" charset:"ascii" nullable:"true" list:"user" create:"optional"` +} + +func (man *SOpenstackCachedLbManager) GetBackendsByLocalBackendId(backendId string) ([]SOpenstackCachedLb, error) { + loadbalancerBackends := []SOpenstackCachedLb{} + q := man.Query().IsFalse("pending_deleted").Equals("backend_id", backendId) + if err := db.FetchModelObjects(man, q, &loadbalancerBackends); err != nil { + return nil, err + } + return loadbalancerBackends, nil +} + +func (man *SOpenstackCachedLbManager) CreateOpenstackCachedLb(ctx context.Context, userCred mcclient.TokenCredential, lbb *SLoadbalancerBackend, cachedLbbg *SOpenstackCachedLbbg, extLoadbalancerBackend cloudprovider.ICloudLoadbalancerBackend, syncOwnerId mcclient.IIdentityProvider) (*SOpenstackCachedLb, error) { + cachedlbb := &SOpenstackCachedLb{} + cachedlbb.SetModelManager(man, cachedlbb) + + cachedlbb.CloudregionId = cachedLbbg.CloudregionId + cachedlbb.ManagerId = cachedLbbg.ManagerId + cachedlbb.CachedBackendGroupId = cachedLbbg.GetId() + cachedlbb.BackendId = lbb.GetId() + cachedlbb.ExternalId = extLoadbalancerBackend.GetGlobalId() + + newName, err := db.GenerateName(man, syncOwnerId, extLoadbalancerBackend.GetName()) + if err != nil { + return nil, err + } + cachedlbb.Name = newName + + if err := cachedlbb.constructFieldsFromCloudLoadbalancerBackend(extLoadbalancerBackend); err != nil { + return nil, err + } + + err = man.TableSpec().Insert(ctx, cachedlbb) + + if err != nil { + return nil, err + } + + SyncCloudProject(userCred, lbb, syncOwnerId, extLoadbalancerBackend, cachedLbbg.ManagerId) + + db.OpsLog.LogEvent(cachedlbb, db.ACT_CREATE, lbb.GetShortDesc(ctx), userCred) + + return cachedlbb, nil +} + +func (lbb *SOpenstackCachedLb) GetCachedBackendGroup() (*SOpenstackCachedLbbg, error) { + lbbg, err := db.FetchById(OpenstackCachedLbbgManager, lbb.CachedBackendGroupId) + if err != nil { + return nil, err + } + + return lbbg.(*SOpenstackCachedLbbg), nil +} + +func (man *SOpenstackCachedLbManager) getLoadbalancerBackendsByLoadbalancerBackendgroup(loadbalancerBackendgroup *SOpenstackCachedLbbg) ([]SOpenstackCachedLb, error) { + loadbalancerBackends := []SOpenstackCachedLb{} + q := man.Query().Equals("cached_backend_group_id", loadbalancerBackendgroup.Id) + if err := db.FetchModelObjects(man, q, &loadbalancerBackends); err != nil { + return nil, err + } + return loadbalancerBackends, nil +} + +func (man *SOpenstackCachedLbManager) SyncLoadbalancerBackends(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, loadbalancerBackendgroup *SOpenstackCachedLbbg, lbbs []cloudprovider.ICloudLoadbalancerBackend, syncRange *SSyncRange) compare.SyncResult { + syncOwnerId := provider.GetOwnerId() + + lockman.LockClass(ctx, man, db.GetLockClassKey(man, syncOwnerId)) + defer lockman.ReleaseClass(ctx, man, db.GetLockClassKey(man, syncOwnerId)) + + syncResult := compare.SyncResult{} + + dbLbbs, err := man.getLoadbalancerBackendsByLoadbalancerBackendgroup(loadbalancerBackendgroup) + if err != nil { + syncResult.Error(err) + return syncResult + } + + removed := []SOpenstackCachedLb{} + commondb := []SOpenstackCachedLb{} + commonext := []cloudprovider.ICloudLoadbalancerBackend{} + added := []cloudprovider.ICloudLoadbalancerBackend{} + + err = compare.CompareSets(dbLbbs, lbbs, &removed, &commondb, &commonext, &added) + if err != nil { + syncResult.Error(err) + return syncResult + } + + for i := 0; i < len(removed); i++ { + err = removed[i].syncRemoveCloudLoadbalancerBackend(ctx, userCred) + if err != nil { + syncResult.DeleteError(err) + } else { + syncResult.Delete() + } + } + for i := 0; i < len(commondb); i++ { + err = commondb[i].SyncWithCloudLoadbalancerBackend(ctx, userCred, commonext[i], syncOwnerId) + if err != nil { + syncResult.UpdateError(err) + } else { + syncMetadata(ctx, userCred, &commondb[i], commonext[i]) + syncResult.Update() + } + } + for i := 0; i < len(added); i++ { + local, err := man.newFromCloudLoadbalancerBackend(ctx, userCred, loadbalancerBackendgroup, added[i], syncOwnerId) + if err != nil { + syncResult.AddError(err) + } else { + syncMetadata(ctx, userCred, local, added[i]) + syncResult.Add() + } + } + return syncResult +} + +func (lbb *SOpenstackCachedLb) syncRemoveCloudLoadbalancerBackend(ctx context.Context, userCred mcclient.TokenCredential) error { + lockman.LockObject(ctx, lbb) + defer lockman.ReleaseObject(ctx, lbb) + + err := lbb.ValidateDeleteCondition(ctx) + if err != nil { // cannot delete + lbb.SetStatus(userCred, api.LB_STATUS_UNKNOWN, "sync to delete") + return errors.Wrap(err, "lbb.ValidateDeleteCondition(ctx)") + } + lbb.SetModelManager(OpenstackCachedLbManager, lbb) + err = db.DeleteModel(ctx, userCred, lbb) + if err != nil { + return err + } + return nil +} + +func (lbb *SOpenstackCachedLb) constructFieldsFromCloudLoadbalancerBackend(extLoadbalancerBackend cloudprovider.ICloudLoadbalancerBackend) error { + lbb.Status = extLoadbalancerBackend.GetStatus() + + instance, err := db.FetchByExternalIdAndManagerId(GuestManager, extLoadbalancerBackend.GetBackendId(), func(q *sqlchemy.SQuery) *sqlchemy.SQuery { + sq := HostManager.Query().SubQuery() + return q.Join(sq, sqlchemy.Equals(sq.Field("id"), q.Field("host_id"))).Filter(sqlchemy.Equals(sq.Field("manager_id"), lbb.ManagerId)) + }) + if err != nil { + return err + } + guest := instance.(*SGuest) + + lbb.BackendServerId = guest.Id + return nil +} + +func (lbb *SOpenstackCachedLb) SyncWithCloudLoadbalancerBackend(ctx context.Context, userCred mcclient.TokenCredential, extLoadbalancerBackend cloudprovider.ICloudLoadbalancerBackend, syncOwnerId mcclient.IIdentityProvider) error { + lbb.SetModelManager(OpenstackCachedLbManager, lbb) + cacheLbbg, err := lbb.GetCachedBackendGroup() + if err != nil { + return errors.Wrap(err, "OpenstackCachedLb.SyncWithCloudLoadbalancerBackend.GetCachedBackendGroup") + } + + localLbbg, err := cacheLbbg.GetLocalBackendGroup(ctx, userCred) + if err != nil { + return errors.Wrap(err, "OpenstackCachedLb.SyncWithCloudLoadbalancerBackend.GetLocalBackendGroup") + } + + locallbb, err := newLocalBackendFromCloudLoadbalancerBackend(ctx, userCred, localLbbg, extLoadbalancerBackend, syncOwnerId) + if err != nil { + return errors.Wrap(err, "OpenstackCachedLb.SyncWithCloudLoadbalancerBackend.newLocalBackendFromCloudLoadbalancerBackend") + } + + diff, err := db.UpdateWithLock(ctx, lbb, func() error { + if locallbb != nil { + lbb.BackendId = locallbb.GetId() + } + + return lbb.constructFieldsFromCloudLoadbalancerBackend(extLoadbalancerBackend) + }) + if err != nil { + return err + } + db.OpsLog.LogSyncUpdate(lbb, diff, userCred) + + SyncCloudProject(userCred, lbb, syncOwnerId, extLoadbalancerBackend, lbb.ManagerId) + + return nil +} + +func (man *SOpenstackCachedLbManager) newFromCloudLoadbalancerBackend(ctx context.Context, userCred mcclient.TokenCredential, loadbalancerBackendgroup *SOpenstackCachedLbbg, extLoadbalancerBackend cloudprovider.ICloudLoadbalancerBackend, syncOwnerId mcclient.IIdentityProvider) (*SOpenstackCachedLb, error) { + localBackendGroup, err := loadbalancerBackendgroup.GetLocalBackendGroup(ctx, userCred) + if err != nil { + return nil, err + } + + locallbb, err := newLocalBackendFromCloudLoadbalancerBackend(ctx, userCred, localBackendGroup, extLoadbalancerBackend, syncOwnerId) + if err != nil { + return nil, err + } + lbb := &SOpenstackCachedLb{} + lbb.SetModelManager(man, lbb) + + lbb.CloudregionId = loadbalancerBackendgroup.CloudregionId + lbb.ManagerId = loadbalancerBackendgroup.ManagerId + lbb.CachedBackendGroupId = loadbalancerBackendgroup.Id + lbb.BackendId = locallbb.GetId() + lbb.ExternalId = extLoadbalancerBackend.GetGlobalId() + + newName, err := db.GenerateName(man, syncOwnerId, extLoadbalancerBackend.GetName()) + if err != nil { + return nil, err + } + lbb.Name = newName + + if err := lbb.constructFieldsFromCloudLoadbalancerBackend(extLoadbalancerBackend); err != nil { + return nil, err + } + + err = man.TableSpec().Insert(ctx, lbb) + + if err != nil { + return nil, err + } + + SyncCloudProject(userCred, lbb, syncOwnerId, extLoadbalancerBackend, loadbalancerBackendgroup.ManagerId) + + db.OpsLog.LogEvent(lbb, db.ACT_CREATE, lbb.GetShortDesc(ctx), userCred) + + return lbb, nil +} diff --git a/pkg/compute/models/loadbalanceropenstackcachedlbbg.go b/pkg/compute/models/loadbalanceropenstackcachedlbbg.go new file mode 100644 index 0000000000..5f2729d9a2 --- /dev/null +++ b/pkg/compute/models/loadbalanceropenstackcachedlbbg.go @@ -0,0 +1,395 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + "fmt" + "strconv" + "strings" + + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/compare" + "yunion.io/x/pkg/utils" + + 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/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" +) + +// +onecloud:swagger-gen-ignore +type SOpenstackCachedLbbgManager struct { + SLoadbalancerLogSkipper + db.SVirtualResourceBaseManager +} + +var OpenstackCachedLbbgManager *SOpenstackCachedLbbgManager + +func init() { + OpenstackCachedLbbgManager = &SOpenstackCachedLbbgManager{ + SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager( + SOpenstackCachedLbbg{}, + "openstackcachedlbbgs_tbl", + "openstackcachedlbbg", + "openstackcachedlbbgs", + ), + } + OpenstackCachedLbbgManager.SetVirtualObject(OpenstackCachedLbbgManager) +} + +type SOpenstackCachedLbbg struct { + db.SVirtualResourceBase + db.SExternalizedResourceBase + + SManagedResourceBase + SCloudregionResourceBase + + LoadbalancerId string `width:"36" charset:"ascii" nullable:"true" list:"user" create:"optional"` + BackendGroupId string `width:"36" charset:"ascii" nullable:"true" list:"user" create:"optional"` + AssociatedId string `width:"36" charset:"ascii" nullable:"true" list:"user" create:"optional"` // 关联ID + AssociatedType string `width:"36" charset:"ascii" nullable:"true" list:"user" create:"optional"` // 关联类型, listener || rule + ProtocolType string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"required"` // 监听协议类型 +} + +func (lbbg *SOpenstackCachedLbbg) GetLocalBackendGroup(ctx context.Context, userCred mcclient.TokenCredential) (*SLoadbalancerBackendGroup, error) { + if len(lbbg.BackendGroupId) == 0 { + return nil, fmt.Errorf("GetLocalBackendGroup no related local backendgroup") + } + + locallbbg, err := db.FetchById(LoadbalancerBackendGroupManager, lbbg.BackendGroupId) + if err != nil { + return nil, err + } + + return locallbbg.(*SLoadbalancerBackendGroup), nil +} + +func (lbbg *SOpenstackCachedLbbg) GetLoadbalancer() (*SLoadbalancer, error) { + lb, err := LoadbalancerManager.FetchById(lbbg.LoadbalancerId) + if err != nil { + log.Errorf("failed to find loadbalancer for backendgroup %s", lbbg.Name) + return nil, errors.Wrap(err, "LoadbalancerManager.FetchById(lbbg.LoadbalancerId)") + } + return lb.(*SLoadbalancer), nil +} + +func (lbbg *SOpenstackCachedLbbg) GetCachedBackends() ([]SOpenstackCachedLb, error) { + ret := []SOpenstackCachedLb{} + err := OpenstackCachedLbManager.Query().Equals("cached_backend_group_id", lbbg.GetId()).IsFalse("pending_deleted").All(&ret) + if err != nil { + log.Errorf("failed to get cached backends for backendgroup %s", lbbg.Name) + return nil, errors.Wrap(err, `OpenstackCachedLbManager.Query().Equals("cached_backend_group_id", lbbg.GetId()).IsFalse("pending_deleted").All(&ret)`) + } + + return ret, nil +} + +func (lbbg *SOpenstackCachedLbbg) GetICloudLoadbalancerBackendGroup() (cloudprovider.ICloudLoadbalancerBackendGroup, error) { + if len(lbbg.ExternalId) == 0 { + return nil, fmt.Errorf("backendgroup %s has no external id", lbbg.GetId()) + } + + lb, err := lbbg.GetLoadbalancer() + if err != nil { + return nil, errors.Wrap(err, "lbbg.GetLoadbalancer()") + } + + iregion, err := lb.GetIRegion() + if err != nil { + return nil, err + } + + ilb, err := iregion.GetILoadBalancerById(lb.GetExternalId()) + if err != nil { + return nil, err + } + + ilbbg, err := ilb.GetILoadBalancerBackendGroupById(lbbg.ExternalId) + if err != nil { + return nil, err + } + + return ilbbg, nil +} + +func (man *SOpenstackCachedLbbgManager) GetUsableCachedBackendGroups(backendGroupId string, protocolType string) ([]SOpenstackCachedLbbg, error) { + ret := []SOpenstackCachedLbbg{} + err := man.Query().IsFalse("pending_deleted").Equals("backend_group_id", backendGroupId).Equals("protocol_type", protocolType).IsNullOrEmpty("associated_id").IsNotEmpty("external_id").All(&ret) + if err != nil { + return ret, err + } + + return ret, nil +} + +func (man *SOpenstackCachedLbbgManager) GetUsableCachedBackendGroup(backendGroupId string, protocolType string) (*SOpenstackCachedLbbg, error) { + ret, err := man.GetUsableCachedBackendGroups(backendGroupId, protocolType) + if err != nil { + return nil, err + } + + if len(ret) > 0 { + return &ret[0], nil + } + + return nil, nil +} + +func (man *SOpenstackCachedLbbgManager) GetCachedBackendGroupByAssociateId(associateId string) (*SOpenstackCachedLbbg, error) { + ret := &SOpenstackCachedLbbg{} + err := man.Query().IsFalse("pending_deleted").Equals("associated_id", associateId).First(ret) + if err != nil { + return nil, err + } + + ret.SetModelManager(man, ret) + return ret, nil +} + +func (man *SOpenstackCachedLbbgManager) GetCachedBackendGroups(backendGroupId string) ([]SOpenstackCachedLbbg, error) { + ret := []SOpenstackCachedLbbg{} + err := man.Query().IsFalse("pending_deleted").Equals("backend_group_id", backendGroupId).All(&ret) + if err != nil { + return nil, err + } + + return ret, nil +} + +func (man *SOpenstackCachedLbbgManager) GetListenerRuleCachedBackendGroups(listenerRuleId string) ([]SOpenstackCachedLbbg, error) { + ret := []SOpenstackCachedLbbg{} + err := man.Query().IsFalse("pending_deleted").Equals("associated_type", api.LB_ASSOCIATE_TYPE_RULE).Equals("associated_id", listenerRuleId).All(&ret) + if err != nil { + return nil, err + } + for i := 0; i < len(ret); i++ { + ret[i].SetModelManager(man, &ret[i]) + } + return ret, nil +} + +func (man *SOpenstackCachedLbbgManager) GetListenerCachedBackendGroups(listenerId string) ([]SOpenstackCachedLbbg, error) { + ret := []SOpenstackCachedLbbg{} + err := man.Query().IsFalse("pending_deleted").Equals("associated_type", api.LB_ASSOCIATE_TYPE_LISTENER).Equals("associated_id", listenerId).All(&ret) + if err != nil { + return nil, err + } + for i := 0; i < len(ret); i++ { + ret[i].SetModelManager(man, &ret[i]) + } + return ret, nil +} + +func (man *SOpenstackCachedLbbgManager) getLoadbalancerBackendgroupsByLoadbalancer(lb *SLoadbalancer) ([]SOpenstackCachedLbbg, error) { + lbbgs := []SOpenstackCachedLbbg{} + q := man.Query().IsFalse("pending_deleted").Equals("loadbalancer_id", lb.Id) + if err := db.FetchModelObjects(man, q, &lbbgs); err != nil { + return nil, errors.Wrap(err, "db.FetchModelObjects(man, q, &lbbgs)") + } + return lbbgs, nil +} + +func (man *SOpenstackCachedLbbgManager) SyncLoadbalancerBackendgroups(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, lb *SLoadbalancer, lbbgs []cloudprovider.ICloudLoadbalancerBackendGroup, syncRange *SSyncRange) ([]SOpenstackCachedLbbg, []cloudprovider.ICloudLoadbalancerBackendGroup, compare.SyncResult) { + syncOwnerId := provider.GetOwnerId() + + lockman.LockClass(ctx, man, db.GetLockClassKey(man, syncOwnerId)) + defer lockman.ReleaseClass(ctx, man, db.GetLockClassKey(man, syncOwnerId)) + + localLbgs := []SOpenstackCachedLbbg{} + remoteLbbgs := []cloudprovider.ICloudLoadbalancerBackendGroup{} + syncResult := compare.SyncResult{} + + dbLbbgs, err := man.getLoadbalancerBackendgroupsByLoadbalancer(lb) + if err != nil { + syncResult.Error(err) + return nil, nil, syncResult + } + + removed := []SOpenstackCachedLbbg{} + commondb := []SOpenstackCachedLbbg{} + commonext := []cloudprovider.ICloudLoadbalancerBackendGroup{} + added := []cloudprovider.ICloudLoadbalancerBackendGroup{} + + err = compare.CompareSets(dbLbbgs, lbbgs, &removed, &commondb, &commonext, &added) + if err != nil { + syncResult.Error(err) + return nil, nil, syncResult + } + + for i := 0; i < len(removed); i++ { + err = removed[i].syncRemoveCloudLoadbalancerBackendgroup(ctx, userCred) + if err != nil { + syncResult.DeleteError(err) + } else { + syncResult.Delete() + } + } + for i := 0; i < len(commondb); i++ { + err = commondb[i].SyncWithCloudLoadbalancerBackendgroup(ctx, userCred, lb, commonext[i], provider.GetOwnerId(), provider) + if err != nil { + syncResult.UpdateError(err) + } else { + syncMetadata(ctx, userCred, &commondb[i], commonext[i]) + localLbgs = append(localLbgs, commondb[i]) + remoteLbbgs = append(remoteLbbgs, commonext[i]) + syncResult.Update() + } + } + for i := 0; i < len(added); i++ { + new, err := man.newFromCloudLoadbalancerBackendgroup(ctx, userCred, lb, added[i], syncOwnerId, provider) + if err != nil { + syncResult.AddError(err) + } else { + syncMetadata(ctx, userCred, new, added[i]) + localLbgs = append(localLbgs, *new) + remoteLbbgs = append(remoteLbbgs, added[i]) + syncResult.Add() + } + } + return localLbgs, remoteLbbgs, syncResult +} + +func (lbbg *SOpenstackCachedLbbg) syncRemoveCloudLoadbalancerBackendgroup(ctx context.Context, userCred mcclient.TokenCredential) error { + lockman.LockObject(ctx, lbbg) + defer lockman.ReleaseObject(ctx, lbbg) + + err := lbbg.ValidateDeleteCondition(ctx) + if err != nil { // cannot delete + lbbg.SetStatus(userCred, api.LB_STATUS_UNKNOWN, "sync to delete") + return errors.Wrap(err, "lbbg.ValidateDeleteCondition(ctx)") + } else { + lbbg.SetModelManager(OpenstackCachedLbbgManager, lbbg) + err := db.DeleteModel(ctx, userCred, lbbg) + if err != nil { + return errors.Wrap(err, "db.DeleteModel(ctx, userCred, lbbg)") + } + } + return nil +} + +func (lbbg *SOpenstackCachedLbbg) isBackendsMatch(backends []SLoadbalancerBackend, ibackends []cloudprovider.ICloudLoadbalancerBackend) bool { + if len(ibackends) != len(backends) { + return false + } + + locals := []string{} + remotes := []string{} + + for i := range backends { + guest := backends[i].GetGuest() + seg := strings.Join([]string{guest.ExternalId, strconv.Itoa(backends[i].Weight), strconv.Itoa(backends[i].Port)}, "/") + locals = append(locals, seg) + } + + for i := range ibackends { + ibackend := ibackends[i] + seg := strings.Join([]string{ibackend.GetBackendId(), strconv.Itoa(ibackend.GetWeight()), strconv.Itoa(ibackend.GetPort())}, "/") + remotes = append(remotes, seg) + } + + for i := range remotes { + if !utils.IsInStringArray(remotes[i], locals) { + return false + } + } + + return true +} + +func (lbbg *SOpenstackCachedLbbg) SyncWithCloudLoadbalancerBackendgroup(ctx context.Context, userCred mcclient.TokenCredential, lb *SLoadbalancer, extLoadbalancerBackendgroup cloudprovider.ICloudLoadbalancerBackendGroup, syncOwnerId mcclient.IIdentityProvider, provider *SCloudprovider) error { + lbbg.SetModelManager(OpenstackCachedLbbgManager, lbbg) + + ibackends, err := extLoadbalancerBackendgroup.GetILoadbalancerBackends() + if err != nil { + return errors.Wrap(err, "OpenstackCachedLbbg.SyncWithCloudLoadbalancerBackendgroup.GetILoadbalancerBackends") + } + + localLbbg, err := lbbg.GetLocalBackendGroup(ctx, userCred) + if err != nil { + return errors.Wrap(err, "OpenstackCachedLbbg.SyncWithCloudLoadbalancerBackendgroup.GetLocalBackendGroup") + } + + backends, err := localLbbg.GetBackends() + if err != nil { + return errors.Wrap(err, "OpenstackCachedLbbg.SyncWithCloudLoadbalancerBackendgroup.GetBackends") + } + + var newLocalLbbg *SLoadbalancerBackendGroup + if !lbbg.isBackendsMatch(backends, ibackends) { + newLocalLbbg, err = newLocalBackendgroupFromCloudLoadbalancerBackendgroup(ctx, userCred, lb, extLoadbalancerBackendgroup, syncOwnerId, provider) + if err != nil { + return errors.Wrap(err, "OpenstackCachedLbbg.SyncWithCloudLoadbalancerBackendgroup.newLocalBackendgroupFromCloudLoadbalancerBackendgroup") + } + } + + diff, err := db.UpdateWithLock(ctx, lbbg, func() error { + lbbg.Status = extLoadbalancerBackendgroup.GetStatus() + if newLocalLbbg != nil { + lbbg.BackendGroupId = newLocalLbbg.GetId() + } + return nil + }) + if err != nil { + return err + } + db.OpsLog.LogSyncUpdate(lbbg, diff, userCred) + + SyncCloudProject(userCred, lbbg, syncOwnerId, extLoadbalancerBackendgroup, provider.Id) + return err +} + +func (man *SOpenstackCachedLbbgManager) newFromCloudLoadbalancerBackendgroup(ctx context.Context, userCred mcclient.TokenCredential, lb *SLoadbalancer, extLoadbalancerBackendgroup cloudprovider.ICloudLoadbalancerBackendGroup, syncOwnerId mcclient.IIdentityProvider, provider *SCloudprovider) (*SOpenstackCachedLbbg, error) { + LocalLbbg, err := newLocalBackendgroupFromCloudLoadbalancerBackendgroup(ctx, userCred, lb, extLoadbalancerBackendgroup, syncOwnerId, provider) + if err != nil { + return nil, err + } + + lbbg := &SOpenstackCachedLbbg{} + lbbg.SetModelManager(man, lbbg) + + region := lb.GetRegion() + if region == nil { + return nil, errors.Wrap(httperrors.ErrInvalidStatus, "loadbalancer is not attached to any region") + } + + lbbg.ManagerId = provider.Id + lbbg.CloudregionId = region.Id + lbbg.LoadbalancerId = lb.Id + lbbg.BackendGroupId = LocalLbbg.GetId() + lbbg.ExternalId = extLoadbalancerBackendgroup.GetGlobalId() + lbbg.ProtocolType = extLoadbalancerBackendgroup.GetProtocolType() + + newName, err := db.GenerateName(man, syncOwnerId, LocalLbbg.GetName()) + if err != nil { + return nil, err + } + + lbbg.Name = newName + lbbg.Status = extLoadbalancerBackendgroup.GetStatus() + + err = man.TableSpec().Insert(ctx, lbbg) + if err != nil { + return nil, err + } + + SyncCloudProject(userCred, lbbg, syncOwnerId, extLoadbalancerBackendgroup, provider.Id) + + db.OpsLog.LogEvent(lbbg, db.ACT_CREATE, lbbg.GetShortDesc(ctx), userCred) + return lbbg, nil +} diff --git a/pkg/compute/models/regiondrivers.go b/pkg/compute/models/regiondrivers.go index c7a785a8b6..fba1d7486e 100644 --- a/pkg/compute/models/regiondrivers.go +++ b/pkg/compute/models/regiondrivers.go @@ -81,6 +81,7 @@ type IRegionDriver interface { RequestSyncstatusLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *SLoadbalancerListener, task taskman.ITask) error RequestSyncLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *SLoadbalancerListener, task taskman.ITask) error + IsSupportLoadbalancerListenerRuleRedirect() bool ValidateCreateLoadbalancerListenerRuleData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, data *jsonutils.JSONDict, backendGroup db.IModel) (*jsonutils.JSONDict, error) ValidateUpdateLoadbalancerListenerRuleData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, backendGroup db.IModel) (*jsonutils.JSONDict, error) RequestCreateLoadbalancerListenerRule(ctx context.Context, userCred mcclient.TokenCredential, lbr *SLoadbalancerListenerRule, task taskman.ITask) error diff --git a/pkg/compute/regiondrivers/kvm.go b/pkg/compute/regiondrivers/kvm.go index ce0e205296..ece0c374f8 100644 --- a/pkg/compute/regiondrivers/kvm.go +++ b/pkg/compute/regiondrivers/kvm.go @@ -274,6 +274,10 @@ func (self *SKVMRegionDriver) ValidateUpdateLoadbalancerBackendData(ctx context. return data, nil } +func (self *SKVMRegionDriver) IsSupportLoadbalancerListenerRuleRedirect() bool { + return true +} + func (self *SKVMRegionDriver) ValidateCreateLoadbalancerListenerRuleData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, data *jsonutils.JSONDict, backendGroup db.IModel) (*jsonutils.JSONDict, error) { var ( listenerV = validators.NewModelIdOrNameValidator("listener", "loadbalancerlistener", ownerId) diff --git a/pkg/compute/regiondrivers/managedvirtual.go b/pkg/compute/regiondrivers/managedvirtual.go index aab701e77c..edb3a1ef73 100644 --- a/pkg/compute/regiondrivers/managedvirtual.go +++ b/pkg/compute/regiondrivers/managedvirtual.go @@ -108,6 +108,10 @@ func (self *SManagedVirtualizationRegionDriver) ValidateCreateLoadbalancerBacken return data, nil } +func (self *SManagedVirtualizationRegionDriver) IsSupportLoadbalancerListenerRuleRedirect() bool { + return false +} + func (self *SManagedVirtualizationRegionDriver) ValidateCreateLoadbalancerListenerRuleData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, data *jsonutils.JSONDict, backendGroup db.IModel) (*jsonutils.JSONDict, error) { return data, nil } diff --git a/pkg/compute/regiondrivers/openstack.go b/pkg/compute/regiondrivers/openstack.go index 0fa4ba277b..8c43037287 100644 --- a/pkg/compute/regiondrivers/openstack.go +++ b/pkg/compute/regiondrivers/openstack.go @@ -17,15 +17,25 @@ package regiondrivers import ( "context" "database/sql" + "fmt" + "regexp" + "time" "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" "yunion.io/x/pkg/util/secrules" 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/models" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/rand" "yunion.io/x/onecloud/pkg/util/rbacutils" ) @@ -75,11 +85,172 @@ func (self *SOpenStackRegionDriver) IsVpcCreateNeedInputCidr() bool { } func (self *SOpenStackRegionDriver) ValidateCreateLoadbalancerData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { - return nil, httperrors.NewNotImplementedError("%s does not currently support creating loadbalancer", self.GetProvider()) + //zoneV := validators.NewModelIdOrNameValidator("zone", "zone", ownerId) + managerIdV := validators.NewModelIdOrNameValidator("manager", "cloudprovider", ownerId) + addressTypeV := validators.NewStringChoicesValidator("address_type", api.LB_ADDR_TYPES) + networkV := validators.NewModelIdOrNameValidator("network", "network", ownerId) + + keyV := map[string]validators.IValidator{ + "status": validators.NewStringChoicesValidator("status", api.LB_STATUS_SPEC).Default(api.LB_STATUS_ENABLED), + "address_type": addressTypeV.Default(api.LB_ADDR_TYPE_INTRANET), + "network": networkV, + //"zone": zoneV, + "manager": managerIdV, + } + + if err := RunValidators(keyV, data, false); err != nil { + return nil, err + } + + // 检查网络可用 + network := networkV.Model.(*models.SNetwork) + _, _, vpc, _, err := network.ValidateElbNetwork(nil) + if err != nil { + return nil, err + } + + if managerIdV.Model.GetId() != vpc.ManagerId { + return nil, httperrors.NewInputParameterError("Loadbalancer's manager (%s(%s)) does not match vpc's(%s(%s)) (%s)", managerIdV.Model.GetName(), managerIdV.Model.GetId(), vpc.GetName(), vpc.GetId(), vpc.ManagerId) + } + + // 公网ELB需要指定EIP + if addressTypeV.Value == api.LB_ADDR_TYPE_INTERNET { + eipV := validators.NewModelIdOrNameValidator("eip", "eip", nil) + if err := eipV.Validate(data); err != nil { + return nil, err + } + + eip := eipV.Model.(*models.SElasticip) + if eip.Status != api.EIP_STATUS_READY { + return nil, fmt.Errorf("eip status not ready") + } + + if len(eip.ExternalId) == 0 { + return nil, fmt.Errorf("eip external id is empty") + } + + data.Set("eip_id", jsonutils.NewString(eip.ExternalId)) + } + + // region := zoneV.Model.(*models.SZone).GetRegion() + region := networkV.Model.(*models.SNetwork).GetRegion() + if region == nil { + return nil, fmt.Errorf("getting region failed") + } + + // data.Set("network_type", jsonutils.NewString(api.LB_NETWORK_TYPE_VPC)) + data.Set("cloudregion_id", jsonutils.NewString(region.GetId())) + data.Set("vpc_id", jsonutils.NewString(vpc.GetId())) + return self.SManagedVirtualizationRegionDriver.ValidateCreateLoadbalancerData(ctx, userCred, ownerId, data) +} + +func (self *SOpenStackRegionDriver) RequestCreateLoadbalancer(ctx context.Context, userCred mcclient.TokenCredential, lb *models.SLoadbalancer, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + iRegion, err := lb.GetIRegion() + if err != nil { + return nil, err + } + + params, err := lb.GetCreateLoadbalancerParams(iRegion) + if err != nil { + return nil, err + } + + Scloudprovider := lb.GetCloudprovider() + params.ProjectId, err = Scloudprovider.SyncProject(ctx, userCred, lb.ProjectId) + if err != nil { + log.Errorf("failed to sync project %s for create %s lb %s error: %v", lb.ProjectId, Scloudprovider.Provider, lb.Name, err) + } + + iLoadbalancer, err := iRegion.CreateILoadBalancer(params) + if err != nil { + return nil, err + } + if err := db.SetExternalId(lb, userCred, iLoadbalancer.GetGlobalId()); err != nil { + return nil, err + } + //wait async create result + err = cloudprovider.WaitMultiStatus(iLoadbalancer, []string{api.LB_STATUS_ENABLED, api.LB_STATUS_UNKNOWN}, 10*time.Second, 8*time.Minute) + if err != nil { + return nil, errors.Wrap(err, "cloudprovider.WaitMultiStatus") + } + if iLoadbalancer.GetStatus() == api.LB_STATUS_UNKNOWN { + return nil, errors.Wrap(fmt.Errorf("status error"), "check status") + } + + if err := lb.SyncWithCloudLoadbalancer(ctx, userCred, iLoadbalancer, nil, lb.GetCloudprovider()); err != nil { + return nil, err + } + + // 公网lb,需要同步public ip + if lb.AddressType == api.LB_ADDR_TYPE_INTERNET { + publicIp, err := iLoadbalancer.GetIEIP() + if err != nil { + return nil, errors.Wrap(err, "iLoadbalancer.GetIEIP()") + } + lb.SyncLoadbalancerEip(ctx, userCred, lb.GetCloudprovider(), publicIp) + } + + lbbgs, err := iLoadbalancer.GetILoadBalancerBackendGroups() + if err != nil { + return nil, err + } + if len(lbbgs) > 0 { + provider := lb.GetCloudprovider() + if provider == nil { + return nil, fmt.Errorf("failed to find cloudprovider for lb %s", lb.Name) + } + models.LoadbalancerBackendGroupManager.SyncLoadbalancerBackendgroups(ctx, userCred, provider, lb, lbbgs, &models.SSyncRange{}) + } + return nil, nil + }) + return nil +} + +func (self *SOpenStackRegionDriver) ValidateDeleteLoadbalancerCondition(ctx context.Context, lb *models.SLoadbalancer) error { + listeners, err := lb.GetLoadbalancerListeners() + if err != nil { + return err + } + + if len(listeners) > 0 { + return httperrors.NewConflictError("loadbalancer is using by %d listener.", len(listeners)) + } + + lbbgs, err := lb.GetLoadbalancerBackendgroups() + if err != nil { + return err + } + + if len(lbbgs) > 0 { + return httperrors.NewConflictError("loadbalancer is using by %d backendgroup.", len(lbbgs)) + } + return nil } func (self *SOpenStackRegionDriver) ValidateCreateLoadbalancerAclData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { - return nil, httperrors.NewNotImplementedError("%s does not currently support creating loadbalancer acl", self.GetProvider()) + // 访问控制: 与listener是1v1的 + // 关系,创建时即需要与具体的listener绑定,不能再变更listner。 + // required: listener_id, acl_type: "white", acl_status: "on", manager,cloudregion,acl_entries + data, err := self.SManagedVirtualizationRegionDriver.ValidateCreateLoadbalancerAclData(ctx, userCred, data) + if err != nil { + return data, err + } + + listenerV := validators.NewModelIdOrNameValidator("listener", "loadbalancerlistener", nil) + err = listenerV.Validate(data) + if err != nil { + return data, err + } + + return data, nil +} + +func (self *SOpenStackRegionDriver) RequestCreateLoadbalancerAcl(ctx context.Context, userCred mcclient.TokenCredential, lbacl *models.SCachedLoadbalancerAcl, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + return self.createLoadbalancerAcl(ctx, userCred, lbacl) + }) + return nil } func (self *SOpenStackRegionDriver) ValidateCreateLoadbalancerCertificateData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { @@ -113,3 +284,1814 @@ func (self *SOpenStackRegionDriver) ValidateCreateEipData(ctx context.Context, u } return nil } + +func (self *SOpenStackRegionDriver) RequestPullLoadbalancerBackendGroup(ctx context.Context, userCred mcclient.TokenCredential, syncResults models.SSyncResultSet, provider *models.SCloudprovider, localLoadbalancer *models.SLoadbalancer, remoteLoadbalancer cloudprovider.ICloudLoadbalancer, syncRange *models.SSyncRange) error { + models.SyncOpenstackLoadbalancerBackendgroups(ctx, userCred, syncResults, provider, localLoadbalancer, remoteLoadbalancer, syncRange) + return nil +} + +func (self *SOpenStackRegionDriver) ValidateCreateLoadbalancerListenerData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, data *jsonutils.JSONDict, lb *models.SLoadbalancer, backendGroup db.IModel) (*jsonutils.JSONDict, error) { + listenerTypeV := validators.NewStringChoicesValidator("listener_type", api.LB_LISTENER_TYPES) + listenerPortV := validators.NewPortValidator("listener_port") + aclStatusV := validators.NewStringChoicesValidator("acl_status", api.LB_BOOL_VALUES) + aclTypeV := validators.NewStringChoicesValidator("acl_type", api.LB_ACL_TYPES) + aclV := validators.NewModelIdOrNameValidator("acl", "loadbalanceracl", ownerId) + keyV := map[string]validators.IValidator{ + "status": validators.NewStringChoicesValidator("status", api.LB_STATUS_SPEC).Default(api.LB_STATUS_ENABLED), + + "listener_type": listenerTypeV, + "listener_port": listenerPortV, + + "send_proxy": validators.NewStringChoicesValidator("send_proxy", api.LB_SENDPROXY_CHOICES).Default(api.LB_SENDPROXY_OFF), + "acl_status": aclStatusV.Default(api.LB_BOOL_OFF), + "acl_type": aclTypeV.Optional(true), + "acl": aclV.Optional(true), + "scheduler": validators.NewStringChoicesValidator("scheduler", api.LB_SCHEDULER_TYPES), + + "sticky_session": validators.NewStringChoicesValidator("sticky_session", api.LB_BOOL_VALUES).Default(api.LB_BOOL_OFF), + "sticky_session_type": validators.NewStringChoicesValidator("sticky_session_type", api.LB_STICKY_SESSION_TYPES).Default(api.LB_STICKY_SESSION_TYPE_INSERT), + "sticky_session_cookie": validators.NewRegexpValidator("sticky_session_cookie", regexp.MustCompile(`\w+`)).Optional(true), + "sticky_session_cookie_timeout": validators.NewNonNegativeValidator("sticky_session_cookie_timeout").Optional(true), + + "x_forwarded_for": validators.NewBoolValidator("x_forwarded_for").Default(true), + // "gzip": validators.NewBoolValidator("gzip").Default(false), + } + + if err := RunValidators(keyV, data, false); err != nil { + return nil, err + } + + // listener uniqueness + listenerType := listenerTypeV.Value + err := models.LoadbalancerListenerManager.CheckListenerUniqueness(ctx, lb, listenerType, listenerPortV.Value) + if err != nil { + return nil, err + } + + // backendgroup check + if lbbg, ok := backendGroup.(*models.SLoadbalancerBackendGroup); ok && lbbg.LoadbalancerId != lb.Id { + return nil, httperrors.NewInputParameterError("backend group %s(%s) belongs to loadbalancer %s instead of %s", + lbbg.Name, lbbg.Id, lbbg.LoadbalancerId, lb.Id) + } + + // https additional certificate check + if listenerType == api.LB_LISTENER_TYPE_TERMINATED_HTTPS { + certV := validators.NewModelIdOrNameValidator("certificate", "loadbalancercertificate", ownerId) + tlsCipherPolicyV := validators.NewStringChoicesValidator("tls_cipher_policy", api.LB_TLS_CIPHER_POLICIES).Default(api.LB_TLS_CIPHER_POLICY_1_2) + httpsV := map[string]validators.IValidator{ + "certificate": certV, + "tls_cipher_policy": tlsCipherPolicyV, + //"enable_http2": validators.NewBoolValidator("enable_http2").Default(true), + } + + if err := RunValidators(httpsV, data, false); err != nil { + return nil, err + } + } + + // health check default depends on input parameters + checkTypeV := models.LoadbalancerListenerManager.CheckTypeV(listenerType) + keyVHealth := map[string]validators.IValidator{ + "health_check": validators.NewStringChoicesValidator("health_check", api.LB_BOOL_VALUES).Default(api.LB_BOOL_ON), + "health_check_type": checkTypeV, + + "health_check_domain": validators.NewDomainNameValidator("health_check_domain").AllowEmpty(true).Default(""), + "health_check_path": validators.NewURLPathValidator("health_check_path").Default(""), + "health_check_uri": validators.NewURLPathValidator("health_check_uri").Default(""), + "health_check_http_code": validators.NewStringMultiChoicesValidator("health_check_http_code", api.LB_HEALTH_CHECK_HTTP_CODES).Sep(",").Default(api.LB_HEALTH_CHECK_HTTP_CODE_DEFAULT), + + "health_check_rise": validators.NewRangeValidator("health_check_rise", 1, 10).Default(3), + "health_check_timeout": validators.NewRangeValidator("health_check_timeout", 1, 50).Default(5), + "health_check_interval": validators.NewRangeValidator("health_check_interval", 1, 50).Default(10), + } + + if err := RunValidators(keyVHealth, data, false); err != nil { + return nil, err + } + + if t, _ := data.Int("health_check_rise"); t > 0 { + data.Set("health_check_fall", jsonutils.NewInt(t)) + } + + interval, _ := data.Int("health_check_interval") + timeout, _ := data.Int("health_check_timeout") + if timeout >= interval { + data.Set("health_check_interval", jsonutils.NewInt(interval+timeout)) + } + + // acl check + if err := models.LoadbalancerListenerManager.ValidateAcl(aclStatusV, aclTypeV, aclV, data, api.CLOUD_PROVIDER_OPENSTACK); err != nil { + return nil, err + } + + return self.SManagedVirtualizationRegionDriver.ValidateCreateLoadbalancerListenerData(ctx, userCred, ownerId, data, lb, backendGroup) +} + +func (self *SOpenStackRegionDriver) RequestCreateLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *models.SLoadbalancerListener, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + { + certId, _ := task.GetParams().GetString("certificate_id") + if len(certId) > 0 { + provider := lblis.GetCloudprovider() + if provider == nil { + return nil, fmt.Errorf("failed to find provider for lblis %s", lblis.Name) + } + + cert, err := models.LoadbalancerCertificateManager.FetchById(certId) + if err != nil { + return nil, errors.Wrapf(err, "LoadbalancerCertificateManager.FetchById(%s)", certId) + } + + lbcert, err := models.CachedLoadbalancerCertificateManager.GetOrCreateCachedCertificate(ctx, userCred, provider, lblis, cert.(*models.SLoadbalancerCertificate)) + if err != nil { + return nil, errors.Wrap(err, "CachedLoadbalancerCertificateManager.GetOrCreateCachedCertificate") + } + + if len(lbcert.ExternalId) == 0 { + _, err = self.createLoadbalancerCertificate(ctx, userCred, lbcert) + if err != nil { + return nil, errors.Wrap(err, "createLoadbalancerCertificate") + } + } + + _, err = db.Update(lblis, func() error { + lblis.CachedCertificateId = lbcert.GetId() + return nil + }) + if err != nil { + return nil, errors.Wrap(err, "regionDriver.RequestCreateLoadbalancerListener.UpdateCachedCertificateId") + } + } + } + + params, err := lblis.GetLoadbalancerListenerParams() + if err != nil { + return nil, errors.Wrapf(err, "lblis.GetLoadbalancerListenerParams") + } + loadbalancer := lblis.GetLoadbalancer() + if loadbalancer == nil { + return nil, fmt.Errorf("failed to find loadbalancer for lblis %s", lblis.Name) + } + iRegion, err := loadbalancer.GetIRegion() + if err != nil { + return nil, errors.Wrap(err, "loadbalancer.GetIRegion") + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(loadbalancer.ExternalId) + if err != nil { + return nil, errors.Wrapf(err, "iRegion.GetILoadBalancerById(%s)", loadbalancer.ExternalId) + } + iListener, err := iLoadbalancer.CreateILoadBalancerListener(ctx, params) + if err != nil { + return nil, errors.Wrap(err, "iLoadbalancer.CreateILoadBalancerListener") + } + if err := db.SetExternalId(lblis, userCred, iListener.GetGlobalId()); err != nil { + return nil, errors.Wrap(err, "db.SetExternalId") + } + // wait async result + err = cloudprovider.WaitMultiStatus(iListener, []string{api.LB_STATUS_ENABLED, api.LB_STATUS_UNKNOWN}, 10*time.Second, 8*time.Minute) + if err != nil { + return nil, errors.Wrap(err, "cloudprovider.WaitMultiStatus") + } + if iListener.GetStatus() == api.LB_STATUS_UNKNOWN { + return nil, errors.Wrap(fmt.Errorf("status error"), "check status") + } + + { + aclId, _ := task.GetParams().GetString("acl_id") + if len(aclId) > 0 { + provider := lblis.GetCloudprovider() + if provider == nil { + return nil, fmt.Errorf("failed to find provider for lblis %s", lblis.Name) + } + + acl, err := models.LoadbalancerAclManager.FetchById(aclId) + if err != nil { + return nil, errors.Wrap(err, "LoadbalancerAclManager.FetchById") + } + + lbacl, err := models.CachedLoadbalancerAclManager.GetOrCreateCachedAcl(ctx, userCred, provider, lblis, acl.(*models.SLoadbalancerAcl)) + if err != nil { + return nil, errors.Wrap(err, "CachedLoadbalancerAclManager.GetOrCreateCachedAcl") + } + + if len(lbacl.ExternalId) == 0 { + _, err = self.createLoadbalancerAcl(ctx, userCred, lbacl) + if err != nil { + return nil, errors.Wrap(err, "createLoadbalancerAcl") + } + } + + _, err = db.Update(lblis, func() error { + lblis.CachedAclId = lbacl.GetId() + return nil + }) + if err != nil { + return nil, errors.Wrap(err, "regionDriver.RequestCreateLoadbalancerListener.UpdateCachedAclId") + } + } + } + + return nil, lblis.SyncWithCloudLoadbalancerListener(ctx, userCred, loadbalancer, iListener, loadbalancer.GetOwnerId(), lblis.GetCloudprovider()) + }) + return nil +} + +func (self *SOpenStackRegionDriver) createLoadbalancerAcl(ctx context.Context, userCred mcclient.TokenCredential, lbacl *models.SCachedLoadbalancerAcl) (jsonutils.JSONObject, error) { + iRegion, err := lbacl.GetIRegion() + if err != nil { + return nil, err + } + + listener, err := lbacl.GetListener() + if err != nil { + return nil, err + } + + acl := &cloudprovider.SLoadbalancerAccessControlList{ + ListenerId: listener.GetExternalId(), + Name: lbacl.Name, + Entrys: []cloudprovider.SLoadbalancerAccessControlListEntry{}, + AccessControlEnable: listener.AclStatus == api.LB_BOOL_ON, + } + + _originAcl, err := db.FetchById(models.LoadbalancerAclManager, lbacl.AclId) + if err != nil { + return nil, errors.Wrap(err, "regionDriver.FetchAcl") + } + + originAcl := _originAcl.(*models.SLoadbalancerAcl) + if originAcl.AclEntries != nil { + for _, entry := range *originAcl.AclEntries { + acl.Entrys = append(acl.Entrys, cloudprovider.SLoadbalancerAccessControlListEntry{CIDR: entry.Cidr, Comment: entry.Comment}) + } + } + iLoadbalancerAcl, err := iRegion.CreateILoadBalancerAcl(acl) + if err != nil { + return nil, err + } + + lbacl.SetModelManager(models.CachedLoadbalancerAclManager, lbacl) + if err := db.SetExternalId(lbacl, userCred, iLoadbalancerAcl.GetGlobalId()); err != nil { + return nil, err + } + return nil, lbacl.SyncWithCloudLoadbalancerAcl(ctx, userCred, iLoadbalancerAcl, lbacl.GetOwnerId()) +} + +func (self *SOpenStackRegionDriver) ValidateUpdateLoadbalancerListenerData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, lblis *models.SLoadbalancerListener, backendGroup db.IModel) (*jsonutils.JSONDict, error) { + if lblis.Status == api.LB_SYNC_CONF { + return nil, httperrors.NewResourceNotFoundError("loadbalancer listener %s is already updating", lblis.Name) + } + ownerId := lblis.GetOwnerId() + // todo: fix me here + aclStatusV := validators.NewStringChoicesValidator("acl_status", api.LB_BOOL_VALUES) + aclStatusV.Default(lblis.AclStatus) + aclTypeV := validators.NewStringChoicesValidator("acl_type", api.LB_ACL_TYPES) + if api.LB_ACL_TYPES.Has(lblis.AclType) { + aclTypeV.Default(lblis.AclType) + } + aclV := validators.NewModelIdOrNameValidator("acl", "loadbalanceracl", ownerId) + if len(lblis.AclId) > 0 { + aclV.Default(lblis.AclId) + } + + certV := validators.NewModelIdOrNameValidator("certificate", "loadbalancercertificate", ownerId) + tlsCipherPolicyV := validators.NewStringChoicesValidator("tls_cipher_policy", api.LB_TLS_CIPHER_POLICIES).Default(api.LB_TLS_CIPHER_POLICY_1_2) + keyV := map[string]validators.IValidator{ + "send_proxy": validators.NewStringChoicesValidator("send_proxy", api.LB_SENDPROXY_CHOICES), + "scheduler": validators.NewStringChoicesValidator("scheduler", api.LB_SCHEDULER_TYPES), + + "acl_status": aclStatusV, + "acl_type": aclTypeV, + "acl": aclV, + + "sticky_session": validators.NewStringChoicesValidator("sticky_session", api.LB_BOOL_VALUES), + "sticky_session_type": validators.NewStringChoicesValidator("sticky_session_type", api.LB_STICKY_SESSION_TYPES), + "sticky_session_cookie": validators.NewRegexpValidator("sticky_session_cookie", regexp.MustCompile(`\w+`)), + "sticky_session_cookie_timeout": validators.NewNonNegativeValidator("sticky_session_cookie_timeout"), + + "health_check": validators.NewStringChoicesValidator("health_check", api.LB_BOOL_VALUES), + "health_check_type": models.LoadbalancerListenerManager.CheckTypeV(lblis.ListenerType), + + "health_check_domain": validators.NewDomainNameValidator("health_check_domain").AllowEmpty(true).Default(""), + "health_check_path": validators.NewURLPathValidator("health_check_path").Default(""), + "health_check_uri": validators.NewURLPathValidator("health_check_uri").Default(""), + "health_check_http_code": validators.NewStringMultiChoicesValidator("health_check_http_code", api.LB_HEALTH_CHECK_HTTP_CODES).Sep(",").Default(api.LB_HEALTH_CHECK_HTTP_CODE_DEFAULT), + + "health_check_rise": validators.NewRangeValidator("health_check_rise", 1, 10).Default(3), + "health_check_timeout": validators.NewRangeValidator("health_check_timeout", 1, 50).Default(10), + "health_check_interval": validators.NewRangeValidator("health_check_interval", 1, 50).Default(5), + + "x_forwarded_for": validators.NewBoolValidator("x_forwarded_for"), + + "certificate": certV, + "tls_cipher_policy": tlsCipherPolicyV, + } + + if err := RunValidators(keyV, data, true); err != nil { + return nil, err + } + + if t, _ := data.Int("health_check_rise"); t > 0 { + data.Set("health_check_fall", jsonutils.NewInt(t)) + } + + { + if lbbg, ok := backendGroup.(*models.SLoadbalancerBackendGroup); ok && lbbg.LoadbalancerId != lblis.LoadbalancerId { + return nil, httperrors.NewInputParameterError("backend group %s(%s) belongs to loadbalancer %s instead of %s", + lbbg.Name, lbbg.Id, lbbg.LoadbalancerId, lblis.LoadbalancerId) + } + } + + return self.SManagedVirtualizationRegionDriver.ValidateUpdateLoadbalancerListenerData(ctx, userCred, data, lblis, backendGroup) +} + +func (self *SOpenStackRegionDriver) RequestSyncLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *models.SLoadbalancerListener, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + { + certId, _ := task.GetParams().GetString("certificate_id") + if len(certId) > 0 { + provider := lblis.GetCloudprovider() + if provider == nil { + return nil, fmt.Errorf("failed to find provider for lblis %s", lblis.Name) + } + + cert, err := models.LoadbalancerCertificateManager.FetchById(certId) + if err != nil { + return nil, err + } + + lbcert, err := models.CachedLoadbalancerCertificateManager.GetOrCreateCachedCertificate(ctx, userCred, provider, lblis, cert.(*models.SLoadbalancerCertificate)) + if err != nil { + return nil, err + } + + if len(lbcert.ExternalId) == 0 { + _, err = self.createLoadbalancerCertificate(ctx, userCred, lbcert) + if err != nil { + return nil, err + } + } + + _, err = db.Update(lblis, func() error { + lblis.CachedCertificateId = lbcert.GetId() + return nil + }) + if err != nil { + return nil, errors.Wrap(err, "openstackRegionDriver.RequestSyncLoadbalancerListener.UpdateCachedCertificateId") + } + } + } + + params, err := lblis.GetOpenstackLoadbalancerListenerParams() + if err != nil { + return nil, err + } + loadbalancer := lblis.GetLoadbalancer() + if loadbalancer == nil { + return nil, fmt.Errorf("failed to find loadbalancer for lblis %s", lblis.Name) + } + iRegion, err := loadbalancer.GetIRegion() + if err != nil { + return nil, err + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(loadbalancer.ExternalId) + if err != nil { + return nil, err + } + iListener, err := iLoadbalancer.GetILoadBalancerListenerById(lblis.ExternalId) + if err != nil { + return nil, err + } + if err := iListener.Sync(ctx, params); err != nil { + return nil, err + } + + { + aclId, _ := task.GetParams().GetString("acl_id") + if len(aclId) > 0 { + provider := lblis.GetCloudprovider() + if provider == nil { + return nil, fmt.Errorf("failed to find provider for lblis %s", lblis.Name) + } + + var lbacl *models.SCachedLoadbalancerAcl + // 先读取缓存,缓存不存在的情况下,从ACL表中取数据创建缓存 + if _lbacl, err := models.CachedLoadbalancerAclManager.FetchById(aclId); err == nil && _lbacl != nil { + lbacl = _lbacl.(*models.SCachedLoadbalancerAcl) + } else { + acl, err := models.LoadbalancerAclManager.FetchById(aclId) + if err != nil { + return nil, err + } + + lbacl, err = models.CachedLoadbalancerAclManager.GetOrCreateCachedAcl(ctx, userCred, provider, lblis, acl.(*models.SLoadbalancerAcl)) + if err != nil { + return nil, err + } + } + + if len(lbacl.ExternalId) == 0 { + _, err = self.createLoadbalancerAcl(ctx, userCred, lbacl) + if err != nil { + return nil, err + } + } else { + _, err = self.syncLoadbalancerAcl(ctx, userCred, lbacl) + if err != nil { + return nil, err + } + } + + _, err = db.Update(lblis, func() error { + lblis.CachedAclId = lbacl.GetId() + return nil + }) + if err != nil { + return nil, errors.Wrap(err, "openstackRegionDriver.RequestSyncLoadbalancerListener.UpdateCachedAclId") + } + } + } + + if err := iListener.Refresh(); err != nil { + return nil, err + } + return nil, lblis.SyncWithCloudLoadbalancerListener(ctx, userCred, loadbalancer, iListener, lblis.GetOwnerId(), lblis.GetCloudprovider()) + }) + return nil +} + +func (self *SOpenStackRegionDriver) RequestDeleteLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *models.SLoadbalancerListener, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + if jsonutils.QueryBoolean(task.GetParams(), "purge", false) { + return nil, nil + } + loadbalancer := lblis.GetLoadbalancer() + if loadbalancer == nil { + return nil, fmt.Errorf("failed to find loadbalancer for lblis %s", lblis.Name) + } + iRegion, err := loadbalancer.GetIRegion() + if err != nil { + return nil, err + } + + if len(loadbalancer.ExternalId) == 0 { + return nil, nil + } + + if len(lblis.ExternalId) == 0 { + return nil, nil + } + + iLoadbalancer, err := iRegion.GetILoadBalancerById(loadbalancer.ExternalId) + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + return nil, nil + } + + return nil, err + } + iListener, err := iLoadbalancer.GetILoadBalancerListenerById(lblis.ExternalId) + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + return nil, nil + } + return nil, err + } + + // 取消服务器组关联 + backendgroupId := iListener.GetBackendGroupId() + if len(backendgroupId) > 0 { + // 删除后端服务器组 + ilbbg, err := iLoadbalancer.GetILoadBalancerBackendGroupById(backendgroupId) + if err != nil { + return nil, errors.Wrap(err, "OpenStackRegionDriver.RequestDeleteLoadbalancerListener.GetILoadBalancerBackendGroup") + } + + err = deleteOpenstackLoadbalancerBackendGroup(ctx, userCred, iLoadbalancer, ilbbg) + if err != nil { + return nil, errors.Wrap(err, "OpenStackRegionDriver.RequestDeleteLoadbalancerListener.DeleteBackendGroup") + } + } + + err = deleteOpenstackCachedLbbg(ctx, userCred, lblis.GetId()) + if err != nil { + return nil, errors.Wrap(err, "OpenstackRegionDriver.RequestDeleteLoadbalancerListener.deleteOpenstackCachedLbbg") + } + + // 删除访问控制 + aclId := iListener.GetAclId() + if len(aclId) > 0 { + iAcl, err := iRegion.GetILoadBalancerAclById(aclId) + if err != nil { + return nil, err + } + + err = iAcl.Delete() + if err != nil { + return nil, err + } + + acl := lblis.GetCachedLoadbalancerAcl() + if acl != nil { + err := db.DeleteModel(ctx, userCred, acl) + if err != nil { + return nil, err + } + } + } + + // remove rules + irules, err := iListener.GetILoadbalancerListenerRules() + if err != nil { + return nil, errors.Wrap(err, "OpenStackRegionDriver.GetILoadbalancerListenerRules") + } + + for i := range irules { + irule := irules[i] + err = deleteOpenstackLoadbalancerListenerRule(ctx, userCred, iLoadbalancer, irule) + if err != nil { + return nil, errors.Wrap(err, "OpenStackRegionDriver.deleteOpenStackLoadbalancerListenerRule") + } + } + + rules, err := lblis.GetLoadbalancerListenerRules() + if err != nil && err != sql.ErrNoRows { + return nil, errors.Wrap(err, "OpenStackRegionDriver.GetLoadbalancerListenerRules") + } + + for i := range rules { + rule := rules[i] + if err := deleteOpenstackLblisRule(ctx, userCred, rule.GetId()); err != nil { + return nil, errors.Wrap(err, "OpenStackRegionDriver.Rule.deleteOpenstackCachedLbbg") + } + } + + return nil, iListener.Delete(ctx) + }) + return nil +} + +func (self *SOpenStackRegionDriver) syncLoadbalancerAcl(ctx context.Context, userCred mcclient.TokenCredential, lbacl *models.SCachedLoadbalancerAcl) (jsonutils.JSONObject, error) { + iRegion, err := lbacl.GetIRegion() + if err != nil { + return nil, err + } + + acl := &cloudprovider.SLoadbalancerAccessControlList{ + Name: lbacl.Name, + Entrys: []cloudprovider.SLoadbalancerAccessControlListEntry{}, + } + + lblis, err := lbacl.GetListener() + if err == nil { + if api.LB_BOOL_ON == lblis.AclStatus { + acl.AccessControlEnable = true + } + } else { + return nil, fmt.Errorf("SOpenStackRegionDriver.syncLoadbalancerAcl %s", err) + } + + _localAcl, err := db.FetchById(models.LoadbalancerAclManager, lbacl.AclId) + if err != nil { + return nil, errors.Wrap(err, "regionDriver.FetchById.LoaclAcl") + } + + localAcl := _localAcl.(*models.SLoadbalancerAcl) + if localAcl.AclEntries != nil { + for _, entry := range *localAcl.AclEntries { + acl.Entrys = append(acl.Entrys, cloudprovider.SLoadbalancerAccessControlListEntry{CIDR: entry.Cidr, Comment: entry.Comment}) + } + } + + lockman.LockRawObject(ctx, "acl", lbacl.Id) + defer lockman.ReleaseRawObject(ctx, "acl", lbacl.Id) + + iLoadbalancerAcl, err := iRegion.GetILoadBalancerAclById(lbacl.ExternalId) + if err != nil { + return nil, err + } + return nil, iLoadbalancerAcl.Sync(acl) +} + +func (self *SOpenStackRegionDriver) IsSupportLoadbalancerListenerRuleRedirect() bool { + return true +} + +func (self *SOpenStackRegionDriver) ValidateCreateLoadbalancerListenerRuleData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, data *jsonutils.JSONDict, backendGroup db.IModel) (*jsonutils.JSONDict, error) { + var ( + listenerV = validators.NewModelIdOrNameValidator("listener", "loadbalancerlistener", ownerId) + domainV = validators.NewHostPortValidator("domain").OptionalPort(true) + pathV = validators.NewURLPathValidator("path") + + redirectV = validators.NewStringChoicesValidator("redirect", api.LB_REDIRECT_TYPES) + redirectCodeV = validators.NewIntChoicesValidator("redirect_code", api.LB_REDIRECT_CODES) + redirectSchemeV = validators.NewStringChoicesValidator("redirect_scheme", api.LB_REDIRECT_SCHEMES) + redirectHostV = validators.NewHostPortValidator("redirect_host").OptionalPort(true) + redirectPathV = validators.NewURLPathValidator("redirect_path") + ) + keyV := map[string]validators.IValidator{ + "status": validators.NewStringChoicesValidator("status", api.LB_STATUS_SPEC).Default(api.LB_STATUS_ENABLED), + + "listener": listenerV, + "domain": domainV.AllowEmpty(true).Default(""), + "path": pathV.Default(""), + + "http_request_rate": validators.NewNonNegativeValidator("http_request_rate").Default(0), + "http_request_rate_per_src": validators.NewNonNegativeValidator("http_request_rate_per_src").Default(0), + + "redirect": redirectV.Default(api.LB_REDIRECT_OFF), + "redirect_code": redirectCodeV.Default(api.LB_REDIRECT_CODE_302), + "redirect_scheme": redirectSchemeV.Optional(true), + "redirect_host": redirectHostV.AllowEmpty(true).Optional(true), + "redirect_path": redirectPathV.AllowEmpty(true).Optional(true), + } + + if err := RunValidators(keyV, data, false); err != nil { + return nil, err + } + + listener := listenerV.Model.(*models.SLoadbalancerListener) + listenerType := listener.ListenerType + if listenerType != api.LB_LISTENER_TYPE_HTTP && listenerType != api.LB_LISTENER_TYPE_HTTPS { + return nil, httperrors.NewInputParameterError("listener type must be http/https, got %s", listenerType) + } + + redirectType := redirectV.Value + if redirectType != api.LB_REDIRECT_OFF { + if redirectType == api.LB_REDIRECT_RAW { + scheme, host, path := redirectSchemeV.Value, redirectHostV.Value, redirectPathV.Value + if (scheme == "" || scheme == listenerType) && host == "" && path == "" { + return nil, httperrors.NewInputParameterError("redirect must have at least one of scheme, host, path changed") + } + if scheme == "" { + data.Set("redirect_scheme", jsonutils.NewString(listenerType)) + } + if host == "" { + data.Set("redirect_host", jsonutils.NewString(domainV.Value)) + } + } + } + + { + if redirectV.Value == api.LB_REDIRECT_OFF { + if backendGroup == nil { + return nil, httperrors.NewInputParameterError("backend_group argument is missing") + } + } + if lbbg, ok := backendGroup.(*models.SLoadbalancerBackendGroup); ok && lbbg.LoadbalancerId != listener.LoadbalancerId { + return nil, httperrors.NewInputParameterError("backend group %s(%s) belongs to loadbalancer %s instead of %s", + lbbg.Name, lbbg.Id, lbbg.LoadbalancerId, listener.LoadbalancerId) + } + } + + err := models.LoadbalancerListenerRuleCheckUniqueness(ctx, listener, domainV.Value, pathV.Value) + if err != nil { + return nil, err + } + + data.Set("cloudregion_id", jsonutils.NewString(listener.GetRegionId())) + data.Set("manager_id", jsonutils.NewString(listener.GetCloudproviderId())) + return data, nil +} + +func (self *SOpenStackRegionDriver) ValidateUpdateLoadbalancerListenerRuleData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, backendGroup db.IModel) (*jsonutils.JSONDict, error) { + var ( + lbr = ctx.Value("lbr").(*models.SLoadbalancerListenerRule) + domainV = validators.NewHostPortValidator("domain").OptionalPort(true) + pathV = validators.NewURLPathValidator("path") + + redirectV = validators.NewStringChoicesValidator("redirect", api.LB_REDIRECT_TYPES) + redirectCodeV = validators.NewIntChoicesValidator("redirect_code", api.LB_REDIRECT_CODES) + redirectSchemeV = validators.NewStringChoicesValidator("redirect_scheme", api.LB_REDIRECT_SCHEMES) + redirectHostV = validators.NewHostPortValidator("redirect_host").OptionalPort(true) + redirectPathV = validators.NewURLPathValidator("redirect_path") + ) + if lbr.Redirect != "" { + redirectV.Default(lbr.Redirect) + } + if lbr.RedirectCode > 0 { + redirectCodeV.Default(int64(lbr.RedirectCode)) + } + if lbr.RedirectScheme != "" { + redirectSchemeV.Default(lbr.RedirectScheme) + } + if lbr.RedirectHost != "" { + redirectHostV.Default(lbr.RedirectHost) + } + if lbr.RedirectPath != "" { + redirectPathV.Default(lbr.RedirectPath) + } + keyV := map[string]validators.IValidator{ + "domain": domainV.AllowEmpty(true).Default(lbr.Domain), + "path": pathV.Default(lbr.Path), + + "http_request_rate": validators.NewNonNegativeValidator("http_request_rate"), + "http_request_rate_per_src": validators.NewNonNegativeValidator("http_request_rate_per_src"), + + "redirect": redirectV, + "redirect_code": redirectCodeV, + "redirect_scheme": redirectSchemeV, + "redirect_host": redirectHostV.AllowEmpty(true), + "redirect_path": redirectPathV.AllowEmpty(true), + } + for _, v := range keyV { + v.Optional(true) + if err := v.Validate(data); err != nil { + return nil, err + } + } + + var ( + redirectType = redirectV.Value + ) + if redirectType != api.LB_REDIRECT_OFF { + if redirectType == api.LB_REDIRECT_RAW { + var ( + lblis = lbr.GetLoadbalancerListener() + listenerType = lblis.ListenerType + ) + scheme, host, path := redirectSchemeV.Value, redirectHostV.Value, redirectPathV.Value + if (scheme == "" || scheme == listenerType) && host == "" && path == "" { + return nil, httperrors.NewInputParameterError("redirect must have at least one of scheme, host, path changed") + } + } + } + + if redirectType == api.LB_REDIRECT_OFF && backendGroup == nil { + return nil, httperrors.NewInputParameterError("non redirect lblistener rule must have backend_group set") + } + if backendGroup, ok := backendGroup.(*models.SLoadbalancerBackendGroup); ok && backendGroup.Id != lbr.BackendGroupId { + listenerM, err := models.LoadbalancerListenerManager.FetchById(lbr.ListenerId) + if err != nil { + return nil, httperrors.NewInputParameterError("loadbalancerlistenerrule %s(%s): fetching listener %s failed", + lbr.Name, lbr.Id, lbr.ListenerId) + } + listener := listenerM.(*models.SLoadbalancerListener) + if backendGroup.LoadbalancerId != listener.LoadbalancerId { + return nil, httperrors.NewInputParameterError("backend group %s(%s) belongs to loadbalancer %s instead of %s", + backendGroup.Name, backendGroup.Id, backendGroup.LoadbalancerId, listener.LoadbalancerId) + } + } + return data, nil +} + +func (self *SOpenStackRegionDriver) RequestCreateLoadbalancerListenerRule(ctx context.Context, userCred mcclient.TokenCredential, lbr *models.SLoadbalancerListenerRule, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + listener := lbr.GetLoadbalancerListener() + if listener == nil { + return nil, fmt.Errorf("failed to find listener for listnener rule %s", lbr.Name) + } + loadbalancer := listener.GetLoadbalancer() + if loadbalancer == nil { + return nil, fmt.Errorf("failed to find loadbalancer for listener %s", listener.Name) + } + iRegion, err := loadbalancer.GetIRegion() + if err != nil { + return nil, err + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(loadbalancer.ExternalId) + if err != nil { + return nil, err + } + iListener, err := iLoadbalancer.GetILoadBalancerListenerById(listener.ExternalId) + if err != nil { + return nil, err + } + rule := &cloudprovider.SLoadbalancerListenerRule{ + Name: lbr.Name, + Domain: lbr.Domain, + Path: lbr.Path, + + Redirect: lbr.Redirect, + RedirectCode: lbr.RedirectCode, + RedirectScheme: lbr.RedirectScheme, + RedirectHost: lbr.RedirectHost, + RedirectPath: lbr.RedirectPath, + } + if len(lbr.BackendGroupId) > 0 { + group := lbr.GetLoadbalancerBackendGroup() + if group == nil { + return nil, fmt.Errorf("failed to find backend group for listener rule %s", lbr.Name) + } + cachedLbbg, err := models.OpenstackCachedLbbgManager.GetCachedBackendGroupByAssociateId(lbr.GetId()) + if err != nil { + return nil, errors.Wrap(err, "openstackRegionDriver.RequestCreateLoadbalancerListenerRule.GetCachedBackendGroupByAssociateId") + } + + if cachedLbbg == nil { + return nil, fmt.Errorf("usable cached backend group not found") + } + + rule.BackendGroupID = cachedLbbg.ExternalId + rule.BackendGroupType = group.Type + } + iListenerRule, err := iListener.CreateILoadBalancerListenerRule(rule) + if err != nil { + return nil, err + } + if err := db.SetExternalId(lbr, userCred, iListenerRule.GetGlobalId()); err != nil { + return nil, err + } + return nil, lbr.SyncWithCloudLoadbalancerListenerRule(ctx, userCred, iListenerRule, listener.GetOwnerId(), loadbalancer.GetCloudprovider()) + }) + return nil +} + +func (self *SOpenStackRegionDriver) RequestDeleteLoadbalancerListenerRule(ctx context.Context, userCred mcclient.TokenCredential, lbr *models.SLoadbalancerListenerRule, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + if jsonutils.QueryBoolean(task.GetParams(), "purge", false) { + return nil, nil + } + listener := lbr.GetLoadbalancerListener() + if listener == nil { + return nil, fmt.Errorf("failed to find listener for listnener rule %s", lbr.Name) + } + loadbalancer := listener.GetLoadbalancer() + if loadbalancer == nil { + return nil, fmt.Errorf("failed to find loadbalancer for listener %s", listener.Name) + } + iRegion, err := loadbalancer.GetIRegion() + if err != nil { + return nil, err + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(loadbalancer.ExternalId) + if err != nil { + return nil, err + } + iListener, err := iLoadbalancer.GetILoadBalancerListenerById(listener.ExternalId) + if err != nil { + return nil, err + } + if len(lbr.ExternalId) == 0 { + return nil, nil + } + iListenerRule, err := iListener.GetILoadBalancerListenerRuleById(lbr.ExternalId) + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + return nil, nil + } + return nil, err + } + err = iListenerRule.Delete(ctx) + if err != nil { + return nil, errors.Wrap(err, "iListenerRule.Delete") + } + //delete associated backendgroup + cachedlbbgs, err := models.OpenstackCachedLbbgManager.GetListenerRuleCachedBackendGroups(lbr.GetId()) + if err != nil { + if err != sql.ErrNoRows { + return nil, errors.Wrap(fmt.Errorf("provider is nil"), "OpenstackRegionDriver.RequestSyncLoadbalancerBackendGroup.GetCachedBackendGroupByAssociateId") + } + } + for i := 0; i < len(cachedlbbgs); i++ { + // delete outdated cache + if err := deleteOpenstackCachedLbbsByLbbg(ctx, userCred, cachedlbbgs[i].GetId()); err != nil { + return nil, errors.Wrap(err, "deleteOpenstackCachedLbbg.deleteOpenstackCachedLbbsByLbbg") + } + err = db.DeleteModel(ctx, userCred, &cachedlbbgs[i]) + if err != nil { + return nil, errors.Wrap(err, "deleteOpenstackCachedLbbg.DeleteModel") + } + // delete ilbbg + ilbbg, err := iLoadbalancer.GetILoadBalancerBackendGroupById(cachedlbbgs[i].ExternalId) + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + ilbbg = nil + } else { + return nil, errors.Wrapf(err, "GetILoadBalancerBackendGroupById(%s)", cachedlbbgs[i].ExternalId) + } + } else { + err := ilbbg.Delete(ctx) + if err != nil { + return nil, errors.Wrap(err, "iLoadbalancerBackendGroup.Delete(ctx)") + } + } + } + return nil, nil + }) + return nil +} + +func (self *SOpenStackRegionDriver) createCachedLbbg(ctx context.Context, lb *models.SLoadbalancer, lblis *models.SLoadbalancerListener, lbr *models.SLoadbalancerListenerRule, lbbg *models.SLoadbalancerBackendGroup) (*models.SOpenstackCachedLbbg, error) { + // create loadbalancer backendgroup cache + cachedLbbg := &models.SOpenstackCachedLbbg{} + cachedLbbg.ManagerId = lb.GetCloudproviderId() + cachedLbbg.CloudregionId = lb.GetRegionId() + cachedLbbg.LoadbalancerId = lb.GetId() + cachedLbbg.BackendGroupId = lbbg.GetId() + if lbr != nil { + cachedLbbg.AssociatedType = api.LB_ASSOCIATE_TYPE_RULE + cachedLbbg.AssociatedId = lbr.GetId() + cachedLbbg.ProtocolType = lblis.ListenerType + } else { + cachedLbbg.AssociatedType = api.LB_ASSOCIATE_TYPE_LISTENER + cachedLbbg.AssociatedId = lblis.GetId() + cachedLbbg.ProtocolType = lblis.ListenerType + } + + err := models.OpenstackCachedLbbgManager.TableSpec().Insert(ctx, cachedLbbg) + if err != nil { + return nil, err + } + + cachedLbbg.SetModelManager(models.OpenstackCachedLbbgManager, cachedLbbg) + return cachedLbbg, nil +} + +func (self *SOpenStackRegionDriver) syncCloudlbbs(ctx context.Context, userCred mcclient.TokenCredential, lb *models.SLoadbalancer, cachedLbbg *models.SOpenstackCachedLbbg, extlbbg cloudprovider.ICloudLoadbalancerBackendGroup, backends []cloudprovider.SLoadbalancerBackend) error { + ibackends, err := extlbbg.GetILoadbalancerBackends() + if err != nil { + return errors.Wrap(err, "OpenstackRegionDriver.syncCloudLoadbalancerBackends.GetILoadbalancerBackends") + } + + m_ibackends := make(map[string]cloudprovider.ICloudLoadbalancerBackend) + m_backends := make(map[string]*cloudprovider.SLoadbalancerBackend) + for _, v := range ibackends { + m_ibackends[v.GetBackendId()] = v + } + + for i, v := range backends { + m_backends[v.ExternalID] = &backends[i] + } + + for _, ibackend := range ibackends { + mbackend, _ := m_backends[ibackend.GetBackendId()] + if mbackend == nil || mbackend.Port != ibackend.GetPort() { + err = extlbbg.RemoveBackendServer(ibackend.GetId(), ibackend.GetWeight(), ibackend.GetPort()) + if err != nil { + return errors.Wrap(err, "OpenstackRegionDriver.syncCloudLoadbalancerBackends.RemoveBackendServer") + } + } else { + if ibackend.GetWeight() != mbackend.Weight { + ibackend.SyncConf(ctx, mbackend.Port, mbackend.Weight) + } + } + } + + for _, backend := range backends { + ibackend, _ := m_ibackends[backend.ExternalID] + if ibackend == nil { + _, err = extlbbg.AddBackendServer(backend.ExternalID, backend.Weight, backend.Port) + if err != nil { + return errors.Wrap(err, "OpenstackRegionDriver.syncCloudLoadbalancerBackends.AddBackendServer") + } + } + } + + return nil +} + +func (self *SOpenStackRegionDriver) syncCachedLbbs(ctx context.Context, userCred mcclient.TokenCredential, lb *models.SLoadbalancer, lbbg *models.SOpenstackCachedLbbg, extlbbg cloudprovider.ICloudLoadbalancerBackendGroup) error { + iBackends, err := extlbbg.GetILoadbalancerBackends() + if err != nil { + return errors.Wrap(err, "OpenstackRegionDriver.syncLoadbalancerBackendCaches.GetILoadbalancerBackends") + } + + if len(iBackends) > 0 { + provider := lb.GetCloudprovider() + if provider == nil { + return fmt.Errorf("failed to find cloudprovider for lb %s", lb.Name) + } + + result := models.OpenstackCachedLbManager.SyncLoadbalancerBackends(ctx, userCred, provider, lbbg, iBackends, &models.SSyncRange{}) + if result.IsError() { + return errors.Wrap(result.AllError(), "OpenstackRegionDriver.syncLoadbalancerBackendCaches.SyncLoadbalancerBackends") + } + } + + return nil +} + +func (self *SOpenStackRegionDriver) updateCachedLbbg(ctx context.Context, cachedLbbg *models.SOpenstackCachedLbbg, backendGroupId string, externalBackendGroupId string, asscoicateId string, asscoicateType string) error { + _, err := db.UpdateWithLock(ctx, cachedLbbg, func() error { + if len(backendGroupId) > 0 { + cachedLbbg.BackendGroupId = backendGroupId + } + + if len(externalBackendGroupId) > 0 { + cachedLbbg.ExternalId = externalBackendGroupId + } + + if len(asscoicateId) > 0 { + cachedLbbg.AssociatedId = asscoicateId + } + + if len(asscoicateType) > 0 { + cachedLbbg.AssociatedType = asscoicateType + } + + return nil + }) + if err != nil { + return err + } + + return nil +} + +func (self *SOpenStackRegionDriver) removeCachedLbbg(ctx context.Context, userCred mcclient.TokenCredential, lbbg *models.SOpenstackCachedLbbg) error { + backends, err := lbbg.GetCachedBackends() + if err != nil && err != sql.ErrNoRows { + return errors.Wrap(err, "openstackRegionDriver.GetCachedBackends") + } + for i := range backends { + err = db.DeleteModel(ctx, userCred, &backends[i]) + if err != nil { + return errors.Wrap(err, "openstackRegionDriver.DeleteModel") + } + } + + err = db.DeleteModel(ctx, userCred, lbbg) + if err != nil { + return errors.Wrap(err, "openstackRegionDriver.DeleteModel") + } + + return nil +} + +func (self *SOpenStackRegionDriver) createLoadbalancerBackendGroup(ctx context.Context, userCred mcclient.TokenCredential, lblis *models.SLoadbalancerListener, lbr *models.SLoadbalancerListenerRule, lbbg *models.SLoadbalancerBackendGroup, backends []cloudprovider.SLoadbalancerBackend) (jsonutils.JSONObject, error) { + if len(lblis.ListenerType) == 0 { + return nil, fmt.Errorf("loadbalancer backendgroup missing protocol type") + } + + iRegion, err := lbbg.GetIRegion() + if err != nil { + return nil, err + } + lb := lbbg.GetLoadbalancer() + if lb == nil { + return nil, fmt.Errorf("failed to find loadbalancer for backendgroup %s", lbbg.Name) + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(lb.ExternalId) + if err != nil { + return nil, err + } + + cachedLbbg, err := self.createCachedLbbg(ctx, lb, lblis, lbr, lbbg) + if err != nil { + return nil, errors.Wrap(err, "OpenstackRegionDriver.createLoadbalancerBackendGroupCache") + } + + group, err := lbbg.GetOpenstackBackendGroupParams(lblis, nil) + if err != nil { + return nil, err + } + // 避免和defaultpool冲突 + if lbr != nil { + group.ListenerID = "" + } + + iLoadbalancerBackendGroup, err := iLoadbalancer.CreateILoadBalancerBackendGroup(group) + if err != nil { + return nil, err + } + + if err := db.SetExternalId(cachedLbbg, userCred, iLoadbalancerBackendGroup.GetGlobalId()); err != nil { + return nil, err + } + + err = self.syncCloudlbbs(ctx, userCred, lb, cachedLbbg, iLoadbalancerBackendGroup, backends) + if err != nil { + return nil, errors.Wrap(err, "OpenstackRegionDriver.createLoadbalancerBackendGroup.syncCloudLoadbalancerBackends") + } + + err = self.syncCachedLbbs(ctx, userCred, lb, cachedLbbg, iLoadbalancerBackendGroup) + if err != nil { + return nil, errors.Wrap(err, "OpenstackRegionDriver.createLoadbalancerBackendGroup.syncLoadbalancerBackendCaches") + } + + return nil, nil + +} + +func deleteOpenstackLoadbalancerListenerRule(ctx context.Context, userCred mcclient.TokenCredential, ilb cloudprovider.ICloudLoadbalancer, irule cloudprovider.ICloudLoadbalancerListenerRule) error { + err := irule.Refresh() + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + return nil + } + + return errors.Wrap(err, "OpenStackRegionDriver.Rule.Refresh") + } + + lbbgId := irule.GetBackendGroupId() + + err = irule.Delete(ctx) + if err != nil && err != cloudprovider.ErrNotFound { + return errors.Wrap(err, "OpenStackRegionDriver.Rule.Delete") + } + + // delete backendgroup + if len(lbbgId) > 0 { + ilbbg, err := ilb.GetILoadBalancerBackendGroupById(lbbgId) + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + return nil + } + + return errors.Wrap(err, "OpenStackRegionDriver.Rule.GetILoadBalancerBackendGroupById") + } + + err = deleteOpenstackLoadbalancerBackendGroup(ctx, userCred, ilb, ilbbg) + if err != nil { + return errors.Wrap(err, "OpenStackRegionDriver.Rule.deleteOpenstackLoadbalancerBackendGroup") + } + } + + return nil +} + +func deleteOpenstackLoadbalancerBackendGroup(ctx context.Context, userCred mcclient.TokenCredential, ilb cloudprovider.ICloudLoadbalancer, ilbbg cloudprovider.ICloudLoadbalancerBackendGroup) error { + err := ilbbg.Refresh() + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + return nil + } + + return errors.Wrap(err, "OpenStackRegionDriver.BackendGroup.Refresh") + } + + ibackends, err := ilbbg.GetILoadbalancerBackends() + if err != nil { + return errors.Wrap(err, "OpenStackRegionDriver.BackendGroup.GetILoadbalancerBackends") + } + + for i := range ibackends { + ilbb := ibackends[i] + err = deleteOpenstackLoadbalancerBackend(ctx, userCred, ilb, ilbbg, ilbb) + if err != nil { + return errors.Wrap(err, "OpenStackRegionDriver.BackendGroup.deleteOpenstackLoadbalancerBackend") + } + } + + err = ilbbg.Delete(ctx) + if err != nil && err != cloudprovider.ErrNotFound { + return errors.Wrap(err, "OpenStackRegionDriver.BackendGroup.Delete") + } + + return nil +} + +func deleteOpenstackLoadbalancerBackend(ctx context.Context, userCred mcclient.TokenCredential, ilb cloudprovider.ICloudLoadbalancer, ilbbg cloudprovider.ICloudLoadbalancerBackendGroup, ilbb cloudprovider.ICloudLoadbalancerBackend) error { + err := ilbbg.RemoveBackendServer(ilbb.GetId(), ilbb.GetWeight(), ilbb.GetPort()) + if err != nil && err != cloudprovider.ErrNotFound { + return errors.Wrap(err, "OpenStackRegionDriver.Backend.Delete") + } + + return nil +} + +func deleteOpenstackLblisRule(ctx context.Context, userCred mcclient.TokenCredential, ruleId string) error { + rule, err := db.FetchById(models.LoadbalancerListenerRuleManager, ruleId) + if err != nil { + if err == sql.ErrNoRows { + return nil + } + + return errors.Wrap(err, "deleteOpenstackLblisRule.FetchById") + } + + err = deleteOpenstackCachedLbbg(ctx, userCred, ruleId) + if err != nil { + return errors.Wrap(err, "deleteOpenstackLblisRule.deleteOpenstackCachedLbbg") + } + + err = db.DeleteModel(ctx, userCred, rule) + if err != nil { + return errors.Wrap(err, "deleteOpenstackLblisRule.DeleteModel") + } + + return nil +} + +func deleteOpenstackCachedLbbg(ctx context.Context, userCred mcclient.TokenCredential, associatedId string) error { + lbbg, err := models.OpenstackCachedLbbgManager.GetCachedBackendGroupByAssociateId(associatedId) + if err != nil { + if err == sql.ErrNoRows { + return nil + } + + return errors.Wrap(err, "deleteOpenstackCachedLbbg.GetCachedBackendGroupByAssociateId") + } + + if err := deleteOpenstackCachedLbbsByLbbg(ctx, userCred, lbbg.GetId()); err != nil { + return errors.Wrap(err, "deleteOpenstackCachedLbbg.deleteOpenstackCachedLbbsByLbbg") + } + + err = db.DeleteModel(ctx, userCred, lbbg) + if err != nil { + return errors.Wrap(err, "deleteOpenstackCachedLbbg.DeleteModel") + } + + return nil +} + +func deleteOpenstackCachedLbbsByLbbg(ctx context.Context, userCred mcclient.TokenCredential, cachedLbbgId string) error { + cachedLbbs := []models.SOpenstackCachedLb{} + q := models.OpenstackCachedLbManager.Query().IsFalse("pending_deleted").Equals("cached_backend_group_id", cachedLbbgId) + err := db.FetchModelObjects(models.OpenstackCachedLbManager, q, &cachedLbbs) + if err != nil { + if err == sql.ErrNoRows { + return nil + } + + return errors.Wrap(err, "deleteOpenstackCachedLbbsByLbbg.FetchModelObjects") + } + + for i := range cachedLbbs { + cachedLbb := cachedLbbs[i] + err = db.DeleteModel(ctx, userCred, &cachedLbb) + if err != nil { + return errors.Wrap(err, "deleteOpenstackCachedLbbsByLbbg.DeleteModel") + } + } + + return nil +} + +func (self *SOpenStackRegionDriver) RequestCreateLoadbalancerBackendGroup(ctx context.Context, userCred mcclient.TokenCredential, lbbg *models.SLoadbalancerBackendGroup, backends []cloudprovider.SLoadbalancerBackend, task taskman.ITask) error { + // 未指定listenerId或ruleId情况下,跳过远端创建步骤 + listenerId, _ := task.GetParams().GetString("listenerId") + ruleId, _ := task.GetParams().GetString("ruleId") + if len(listenerId) == 0 && len(ruleId) == 0 { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + return nil, nil + }) + return nil + } + + var rule *models.SLoadbalancerListenerRule + var listener *models.SLoadbalancerListener + if len(ruleId) > 0 { + _rule, err := db.FetchById(models.LoadbalancerListenerRuleManager, ruleId) + if err != nil { + return err + } + + rule = _rule.(*models.SLoadbalancerListenerRule) + listener = rule.GetLoadbalancerListener() + } else { + _listener, err := db.FetchById(models.LoadbalancerListenerManager, listenerId) + if err != nil { + return err + } + listener = _listener.(*models.SLoadbalancerListener) + } + + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + return self.createLoadbalancerBackendGroup(ctx, userCred, listener, rule, lbbg, backends) + }) + return nil +} + +func (self *SOpenStackRegionDriver) validatorCachedLbbgConflict(ilb cloudprovider.ICloudLoadbalancer, lblis *models.SLoadbalancerListener, olbbg *models.SOpenstackCachedLbbg, nlbbg *models.SOpenstackCachedLbbg, rlbbg *models.SOpenstackCachedLbbg) error { + if rlbbg == nil { + return nil + } + + if len(rlbbg.AssociatedId) > 0 && lblis.GetId() != rlbbg.AssociatedId { + return fmt.Errorf("sync required, backend group aready assoicate with %s %s", rlbbg.AssociatedType, rlbbg.AssociatedId) + } + + if olbbg != nil && len(rlbbg.AssociatedId) > 0 && rlbbg.AssociatedId != olbbg.AssociatedId { + // 与本地关联状态不一致 + return fmt.Errorf("sync required, backend group aready assoicate with %s %s", rlbbg.AssociatedType, rlbbg.AssociatedId) + } + + if nlbbg != nil && len(nlbbg.ExternalId) > 0 { + // 需绑定的服务器组,被人为删除了 + _, err := ilb.GetILoadBalancerBackendGroupById(nlbbg.GetExternalId()) + if err != nil { + return fmt.Errorf("sync required, validatorCachedLbbgConflict.GetILoadBalancerBackendGroupById %s", err) + } + + // 需绑定的服务器组,被人为关联了其他监听或者监听规则 + listeners, err := ilb.GetILoadBalancerListeners() + if err != nil { + return fmt.Errorf("sync required, validatorCachedLbbgConflict.GetILoadBalancerListeners %s", err) + } + + for i := range listeners { + listener := listeners[i] + if bgId := listener.GetBackendGroupId(); len(bgId) > 0 && bgId == nlbbg.ExternalId && listener.GetGlobalId() != lblis.GetExternalId() { + return fmt.Errorf("sync required, backend group aready assoicate with listener %s(external id )", listener.GetGlobalId()) + } + + rules, err := listener.GetILoadbalancerListenerRules() + if err != nil { + return fmt.Errorf("sync required, validatorCachedLbbgConflict.GetILoadbalancerListenerRules %s", err) + } + + for j := range rules { + rule := rules[j] + if bgId := rule.GetBackendGroupId(); len(bgId) > 0 && bgId == nlbbg.ExternalId { + return fmt.Errorf("sync required, backend group aready assoicate with rule %s(external id )", rule.GetGlobalId()) + } + } + } + } + + return nil +} + +func (self *SOpenStackRegionDriver) RequestSyncLoadbalancerBackendGroup(ctx context.Context, userCred mcclient.TokenCredential, lblis *models.SLoadbalancerListener, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + lbbg := lblis.GetLoadbalancerBackendGroup() + if lbbg == nil { + err := fmt.Errorf("failed to find lbbg for lblis %s", lblis.Name) + return nil, errors.Wrap(err, "OpenstackRegionDriver.RequestSyncLoadbalancerbackendGroup.GetLoadbalancerBackendGroup") + } + + iRegion, err := lbbg.GetIRegion() + if err != nil { + return nil, err + } + + lb := lbbg.GetLoadbalancer() + ilb, err := iRegion.GetILoadBalancerById(lb.GetExternalId()) + if err != nil { + return nil, err + } + + provider := lb.GetCloudprovider() + if provider == nil { + return nil, errors.Wrap(fmt.Errorf("provider is nil"), "OpenstackRegionDriver.RequestSyncLoadbalancerBackendGroup.GetCloudprovider") + } + + // new group params + groupInput, err := lbbg.GetOpenstackBackendGroupParams(lblis, nil) + if err != nil { + return nil, errors.Wrap(err, "GetOpenstackBackendGroupParams") + } + + // new backends params + backendsInput, err := lbbg.GetBackendsParams() + if err != nil { + return nil, err + } + + cachedlbbgs, err := models.OpenstackCachedLbbgManager.GetListenerCachedBackendGroups(lblis.GetId()) + if err != nil { + if err != sql.ErrNoRows { + return nil, errors.Wrap(fmt.Errorf("provider is nil"), "OpenstackRegionDriver.RequestSyncLoadbalancerBackendGroup.GetCachedBackendGroupByAssociateId") + } + } + var cachedlbbg *models.SOpenstackCachedLbbg + ibackendGroups := make(map[string]cloudprovider.ICloudLoadbalancerBackendGroup) + for i := 0; i < len(cachedlbbgs); i++ { + // fetch ilbbg + ilbbg, err := ilb.GetILoadBalancerBackendGroupById(cachedlbbgs[i].ExternalId) + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + ilbbg = nil + } else { + return nil, errors.Wrapf(err, "GetILoadBalancerBackendGroupById(%s)", cachedlbbgs[i].ExternalId) + } + } + ibackendGroups[cachedlbbgs[i].Id] = ilbbg + if cachedlbbgs[i].BackendGroupId == lblis.BackendGroupId && ibackendGroups[cachedlbbgs[i].Id] != nil { + cachedlbbg = &cachedlbbgs[i] + } else { + // delete outdated cache,不保留游离的后端组 + if err := deleteOpenstackCachedLbbsByLbbg(ctx, userCred, cachedlbbgs[i].GetId()); err != nil { + return nil, errors.Wrap(err, "deleteOpenstackCachedLbbg.deleteOpenstackCachedLbbsByLbbg") + } + err = db.DeleteModel(ctx, userCred, &cachedlbbgs[i]) + if err != nil { + return nil, errors.Wrap(err, "deleteOpenstackCachedLbbg.DeleteModel") + } + //关联listener的可以直接删除 + if ibackendGroups[cachedlbbgs[i].Id] != nil { + err := ibackendGroups[cachedlbbgs[i].Id].Delete(ctx) + if err != nil { + return nil, errors.Wrap(err, "iLoadbalancerBackendGroup.Delete(ctx)") + } + } + } + } + // create or update + if cachedlbbg == nil { + // 新创建前需要确保listener没有defaultpool(外部操作) + { + iListener, err := ilb.GetILoadBalancerListenerById(lblis.ExternalId) + if err != nil { + return nil, err + } + ilbbgId := iListener.GetBackendGroupId() + if len(ilbbgId) > 0 { + ilbbg, err := ilb.GetILoadBalancerBackendGroupById(ilbbgId) + if err != nil { + if errors.Cause(err) != cloudprovider.ErrNotFound { + return nil, errors.Wrapf(err, "GetILoadBalancerBackendGroupById(%s)", ilbbgId) + } + } else { + err = ilbbg.Delete(ctx) + if err != nil { + return nil, errors.Wrap(err, "iLoadbalancerBackendGroup.Delete(ctx)") + } + } + } + } + + _, err = self.createLoadbalancerBackendGroup(ctx, task.GetUserCred(), lblis, nil, lbbg, backendsInput) + if err != nil { + return nil, errors.Wrap(err, "OpenstackRegionDriver.Sync.Case1.createLoadbalancerBackendGroup") + } + } else { + err = self.syncCloudlbbs(ctx, userCred, lb, cachedlbbg, ibackendGroups[cachedlbbg.Id], backendsInput) + if err != nil { + return nil, errors.Wrap(err, "OpenstackRegionDriver.Sync.Case6.syncCloudlbbs") + } + + err = self.syncCachedLbbs(ctx, userCred, lb, cachedlbbg, ibackendGroups[cachedlbbg.Id]) + if err != nil { + return nil, errors.Wrap(err, "OpenstackRegionDriver.Sync.Case6.syncCloudlbbs") + } + } + // continue here + cachedLbbg, err := models.OpenstackCachedLbbgManager.GetCachedBackendGroupByAssociateId(lblis.GetId()) + if err != nil { + return nil, errors.Wrap(err, "OpenstackRegionDriver.Sync.GetCachedBackendGroupByAssociateId") + } + + ilbbg, err := ilb.GetILoadBalancerBackendGroupById(cachedLbbg.GetExternalId()) + if err != nil { + return nil, errors.Wrap(err, "OpenstackRegionDriver.Sync.GetILoadBalancerBackendGroupById") + } + + err = ilbbg.Sync(ctx, groupInput) + if err != nil { + return nil, errors.Wrap(err, "OpenstackRegionDriver.Sync.LoadbalancerBackendGroup") + } + + if err := cachedLbbg.SyncWithCloudLoadbalancerBackendgroup(ctx, task.GetUserCred(), lb, ilbbg, lb.GetOwnerId(), lb.GetCloudprovider()); err != nil { + return nil, errors.Wrap(err, "OpenstackRegionDriver.Sync.SyncWithCloudLoadbalancerBackendgroup") + } + + return nil, nil + }) + + return nil +} + +func (self *SOpenStackRegionDriver) ValidateDeleteLoadbalancerBackendGroupCondition(ctx context.Context, lbbg *models.SLoadbalancerBackendGroup) error { + // pool不能被l7policy关联,若要解除关联关系,可通过更新转发策略将转测策略的redirect_pool_id更新为null。 + count, err := lbbg.RefCount() + if err != nil { + return err + } + + if count != 0 { + return fmt.Errorf("backendgroup is binding with loadbalancer/listener/listenerrule.") + } + + return nil +} + +func (self *SOpenStackRegionDriver) RequestDeleteLoadbalancerBackendGroup(ctx context.Context, userCred mcclient.TokenCredential, lbbg *models.SLoadbalancerBackendGroup, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + if jsonutils.QueryBoolean(task.GetParams(), "purge", false) { + return nil, nil + } + iRegion, err := lbbg.GetIRegion() + if err != nil { + return nil, errors.Wrap(err, "openstackRegionDriver.RequestDeleteLoadbalancerBackendGroup.") + } + loadbalancer := lbbg.GetLoadbalancer() + if loadbalancer == nil { + return nil, fmt.Errorf("failed to find loadbalancer for backendgroup %s", lbbg.Name) + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(loadbalancer.ExternalId) + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + return nil, nil + } + + return nil, errors.Wrap(err, "openstackRegionDriver.RequestDeleteLoadbalancerBackendGroup.GetILoadBalancerById") + } + + cachedLbbgs, err := models.OpenstackCachedLbbgManager.GetCachedBackendGroups(lbbg.GetId()) + if err != nil { + return nil, errors.Wrap(err, "openstackRegionDriver.RequestDeleteLoadbalancerBackendGroup.GetCachedBackendGroups") + } + + for _, cachedLbbg := range cachedLbbgs { + if len(cachedLbbg.ExternalId) == 0 { + continue + } + + iLoadbalancerBackendGroup, err := iLoadbalancer.GetILoadBalancerBackendGroupById(cachedLbbg.ExternalId) + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + if err := deleteOpenstackCachedLbbg(ctx, userCred, cachedLbbg.AssociatedId); err != nil { + return nil, errors.Wrap(err, "openstackRegionDriver.RequestDeleteLoadbalancerBackendGroup.deleteOpenstackCachedLbbg") + } + + continue + } + return nil, errors.Wrap(err, "openstackRegionDriver.RequestDeleteLoadbalancerBackendGroup.GetILoadBalancerBackendGroupById") + } + + ilbbs, err := iLoadbalancerBackendGroup.GetILoadbalancerBackends() + if err != nil { + return nil, errors.Wrap(err, "openstackRegionDriver.RequestDeleteLoadbalancerBackendGroup.GetILoadbalancerBackends") + } + + for _, ilbb := range ilbbs { + iLoadbalancerBackendGroup.RemoveBackendServer(ilbb.GetId(), ilbb.GetWeight(), ilbb.GetPort()) + + _cachedLbb, err := db.FetchByExternalId(models.OpenstackCachedLbManager, ilbb.GetGlobalId()) + if err != nil { + if err != sql.ErrNoRows { + return nil, errors.Wrap(err, "openstackRegionDriver.RequestDeleteLoadbalancerBackendGroup.FetchByExternalId") + } + continue + } + + cachedLbb := _cachedLbb.(*models.SOpenstackCachedLb) + err = db.DeleteModel(ctx, userCred, cachedLbb) + if err != nil { + return nil, errors.Wrap(err, "openstackRegionDriver.RequestDeleteLoadbalancerBackendGroup.DeleteModel") + } + } + + err = iLoadbalancerBackendGroup.Delete(ctx) + if err != nil { + return nil, errors.Wrap(err, "openstackRegionDriver.RequestDeleteLoadbalancerBackendGroup.Delete") + } + + cachedLbbg.SetModelManager(models.OpenstackCachedLbbgManager, &cachedLbbg) + err = db.DeleteModel(ctx, userCred, &cachedLbbg) + if err != nil { + return nil, errors.Wrap(err, "openstackRegionDriver.RequestDeleteLoadbalancerBackendGroup.DeleteModel") + } + } + + return nil, nil + }) + return nil +} + +func (self *SOpenStackRegionDriver) ValidateCreateLoadbalancerBackendData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, backendType string, lb *models.SLoadbalancer, backendGroup *models.SLoadbalancerBackendGroup, backend db.IModel) (*jsonutils.JSONDict, error) { + ownerId := ctx.Value("ownerId").(mcclient.IIdentityProvider) + man := models.LoadbalancerBackendManager + backendTypeV := validators.NewStringChoicesValidator("backend_type", api.LB_BACKEND_TYPES) + keyV := map[string]validators.IValidator{ + "backend_type": backendTypeV, + "weight": validators.NewRangeValidator("weight", 0, 100).Default(10), + "port": validators.NewPortValidator("port"), + "send_proxy": validators.NewStringChoicesValidator("send_proxy", api.LB_SENDPROXY_CHOICES).Default(api.LB_SENDPROXY_OFF), + } + + if err := RunValidators(keyV, data, false); err != nil { + return nil, err + } + + var basename string + switch backendType { + case api.LB_BACKEND_GUEST: + backendV := validators.NewModelIdOrNameValidator("backend", "server", ownerId) + err := backendV.Validate(data) + if err != nil { + return nil, err + } + guest := backendV.Model.(*models.SGuest) + err = man.ValidateBackendVpc(lb, guest, backendGroup) + if err != nil { + return nil, err + } + + address, err := models.LoadbalancerBackendManager.GetGuestAddress(guest) + if err != nil { + return nil, errors.Wrap(err, "OpenstackRegionDriver.ValidateCreateLoadbalancerBackendData.GetGuestAddress") + } + + data.Set("address", jsonutils.NewString(address)) + basename = guest.Name + backend = backendV.Model + case api.LB_BACKEND_HOST: + if !db.IsAdminAllowCreate(userCred, man) { + return nil, fmt.Errorf("only sysadmin can specify host as backend") + } + backendV := validators.NewModelIdOrNameValidator("backend", "host", userCred) + err := backendV.Validate(data) + if err != nil { + return nil, err + } + host := backendV.Model.(*models.SHost) + { + if len(host.AccessIp) == 0 { + return nil, fmt.Errorf("host %s has no access ip", host.GetId()) + } + data.Set("address", jsonutils.NewString(host.AccessIp)) + } + basename = host.Name + backend = backendV.Model + case api.LB_BACKEND_IP: + if !db.IsAdminAllowCreate(userCred, man) { + return nil, fmt.Errorf("only sysadmin can specify ip address as backend") + } + backendV := validators.NewIPv4AddrValidator("backend") + err := backendV.Validate(data) + if err != nil { + return nil, err + } + ip := backendV.IP.String() + data.Set("address", jsonutils.NewString(ip)) + basename = ip + default: + return nil, fmt.Errorf("internal error: unexpected backend type %s", backendType) + } + + name, _ := data.GetString("name") + if name == "" { + name = fmt.Sprintf("%s-%s-%s-%s", backendGroup.Name, backendType, basename, rand.String(4)) + } + + data.Set("name", jsonutils.NewString(name)) + data.Set("manager_id", jsonutils.NewString(lb.GetCloudproviderId())) + data.Set("cloudregion_id", jsonutils.NewString(lb.GetRegionId())) + return data, nil +} + +func (self *SOpenStackRegionDriver) ValidateUpdateLoadbalancerBackendData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, lbbg *models.SLoadbalancerBackendGroup) (*jsonutils.JSONDict, error) { + keyV := map[string]validators.IValidator{ + "weight": validators.NewRangeValidator("weight", 0, 100).Optional(true), + "port": validators.NewPortValidator("port").Optional(true), + "send_proxy": validators.NewStringChoicesValidator("send_proxy", api.LB_SENDPROXY_CHOICES).Optional(true), + } + + if err := RunValidators(keyV, data, true); err != nil { + return nil, err + } + + // 只能更新权重。不能更新端口 + port, err := data.Int("port") + if err == nil && port != 0 { + return data, fmt.Errorf("can not update backend port.") + } + + return data, nil +} + +func (self *SOpenStackRegionDriver) RequestCreateLoadbalancerBackend(ctx context.Context, userCred mcclient.TokenCredential, lbb *models.SLoadbalancerBackend, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + lbbg := lbb.GetLoadbalancerBackendGroup() + if lbbg == nil { + return nil, fmt.Errorf("failed to find lbbg for backend %s", lbb.Name) + } + lb := lbbg.GetLoadbalancer() + if lb == nil { + return nil, fmt.Errorf("failed to find lb for backendgroup %s", lbbg.Name) + } + + cachedlbbgs, err := models.OpenstackCachedLbbgManager.GetCachedBackendGroups(lbbg.GetId()) + if err != nil { + return nil, errors.Wrap(err, "openstackRegionDriver.RequestCreateLoadbalancerBackend.GetCachedBackendGroups") + } + + guest := lbb.GetGuest() + if guest == nil { + return nil, fmt.Errorf("failed to find guest for lbb %s", lbb.Name) + } + + var ibackend cloudprovider.ICloudLoadbalancerBackend + for _, cachedLbbg := range cachedlbbgs { + iLoadbalancerBackendGroup, err := cachedLbbg.GetICloudLoadbalancerBackendGroup() + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + if err := deleteOpenstackCachedLbbg(ctx, userCred, cachedLbbg.AssociatedId); err != nil { + return nil, errors.Wrap(err, "openstackRegionDriver.RequestCreateLoadbalancerBackend.deleteOpenstackCachedLbbg") + } + + continue + } + + return nil, errors.Wrap(err, "openstackRegionDriver.RequestCreateLoadbalancerBackend.GetICloudLoadbalancerBackendGroup") + } + + ibackend, err = iLoadbalancerBackendGroup.AddBackendServer(guest.ExternalId, lbb.Weight, lbb.Port) + if err != nil { + return nil, errors.Wrap(err, "openstackRegionDriver.RequestCreateLoadbalancerBackend.AddBackendServer") + } + + _, err = models.OpenstackCachedLbManager.CreateOpenstackCachedLb(ctx, userCred, lbb, &cachedLbbg, ibackend, cachedLbbg.GetOwnerId()) + if err != nil { + return nil, errors.Wrap(err, "openstackRegionDriver.RequestCreateLoadbalancerBackend.CreateOpenstackCachedLb") + } + } + + if ibackend != nil { + if err := lbb.SyncWithCloudLoadbalancerBackend(ctx, userCred, ibackend, lbbg.GetOwnerId(), lb.GetCloudprovider()); err != nil { + return nil, errors.Wrap(err, "openstackRegionDriver.RequestCreateLoadbalancerBackend.SyncWithCloudLoadbalancerBackend") + } + } + return nil, nil + }) + return nil +} + +func (self *SOpenStackRegionDriver) RequestSyncLoadbalancerBackend(ctx context.Context, userCred mcclient.TokenCredential, lbb *models.SLoadbalancerBackend, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + cachedlbbs, err := models.OpenstackCachedLbManager.GetBackendsByLocalBackendId(lbb.GetId()) + if err != nil { + return nil, errors.Wrap(err, "openstackRegionDriver.RequestSyncLoadbalancerBackend.GetBackendsByLocalBackendId") + } + + for _, cachedlbb := range cachedlbbs { + cachedlbbg, _ := cachedlbb.GetCachedBackendGroup() + if cachedlbbg == nil { + return nil, fmt.Errorf("failed to find lbbg for backend %s", cachedlbb.Name) + } + lb, err := cachedlbbg.GetLoadbalancer() + if err != nil { + return nil, errors.Wrap(err, "cachedlbbg.GetLoadbalancer()") + } + iRegion, err := lb.GetIRegion() + if err != nil { + return nil, errors.Wrap(err, "openstackRegionDriver.RequestSyncLoadbalancerBackend.GetIRegion") + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(lb.ExternalId) + if err != nil { + return nil, errors.Wrap(err, "openstackRegionDriver.RequestSyncLoadbalancerBackend.GetILoadBalancerById") + } + iLoadbalancerBackendGroup, err := iLoadbalancer.GetILoadBalancerBackendGroupById(cachedlbbg.ExternalId) + if err != nil { + return nil, errors.Wrap(err, "openstackRegionDriver.RequestSyncLoadbalancerBackend.GetILoadBalancerBackendGroupById") + } + + iBackend, err := iLoadbalancerBackendGroup.GetILoadbalancerBackendById(cachedlbb.ExternalId) + if err != nil { + return nil, errors.Wrap(err, "openstackRegionDriver.RequestSyncLoadbalancerBackend.GetILoadbalancerBackendById") + } + + err = iBackend.SyncConf(ctx, lbb.Port, lbb.Weight) + if err != nil { + return nil, errors.Wrap(err, "openstackRegionDriver.RequestSyncLoadbalancerBackend.SyncConf") + } + + iBackend, err = iLoadbalancerBackendGroup.GetILoadbalancerBackendById(cachedlbb.ExternalId) + if err != nil { + return nil, errors.Wrap(err, "openstackRegionDriver.RequestSyncLoadbalancerBackend.GetILoadbalancerBackendById") + } + + err = cachedlbb.SyncWithCloudLoadbalancerBackend(ctx, userCred, iBackend, lbb.GetOwnerId()) + if err != nil { + return nil, errors.Wrap(err, "openstackRegionDriver.RequestSyncLoadbalancerBackend.SyncWithCloudLoadbalancerBackend") + } + } + + return nil, nil + }) + return nil +} + +func (self *SOpenStackRegionDriver) RequestDeleteLoadbalancerBackend(ctx context.Context, userCred mcclient.TokenCredential, lbb *models.SLoadbalancerBackend, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + if jsonutils.QueryBoolean(task.GetParams(), "purge", false) { + return nil, nil + } + + cachedlbbs, err := models.OpenstackCachedLbManager.GetBackendsByLocalBackendId(lbb.GetId()) + if err != nil { + return nil, err + } + + for _, cachedlbb := range cachedlbbs { + cachedlbbg, _ := cachedlbb.GetCachedBackendGroup() + if cachedlbbg == nil { + log.Warningf("failed to find lbbg for backend %s", cachedlbb.Name) + continue + } + lb, err := cachedlbbg.GetLoadbalancer() + if err != nil { + return nil, errors.Wrap(err, "cachedlbbg.GetLoadbalancer()") + } + iRegion, err := lb.GetIRegion() + if err != nil { + return nil, err + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(lb.ExternalId) + if err != nil { + return nil, err + } + iLoadbalancerBackendGroup, err := iLoadbalancer.GetILoadBalancerBackendGroupById(cachedlbbg.ExternalId) + if err != nil { + return nil, err + } + + err = iLoadbalancerBackendGroup.RemoveBackendServer(cachedlbb.ExternalId, lbb.Weight, lbb.Port) + if err != nil { + return nil, err + } + + err = db.DeleteModel(ctx, userCred, &cachedlbb) + if err != nil { + return nil, err + } + } + + return nil, nil + }) + return nil +} diff --git a/pkg/compute/service/handlers.go b/pkg/compute/service/handlers.go index 696c8026c2..629e1b168d 100644 --- a/pkg/compute/service/handlers.go +++ b/pkg/compute/service/handlers.go @@ -150,6 +150,8 @@ func InitHandlers(app *appsrv.Application) { models.AwsCachedLbbgManager, models.QcloudCachedLbManager, models.QcloudCachedLbbgManager, + models.OpenstackCachedLbManager, + models.OpenstackCachedLbbgManager, models.RouteTableManager, models.SchedpolicyManager, diff --git a/pkg/compute/tasks/loadbalancer_backendgroup_create_task.go b/pkg/compute/tasks/loadbalancer_backendgroup_create_task.go index 908d40d224..ed3546f520 100644 --- a/pkg/compute/tasks/loadbalancer_backendgroup_create_task.go +++ b/pkg/compute/tasks/loadbalancer_backendgroup_create_task.go @@ -41,10 +41,15 @@ type AwsLoadbalancerLoadbalancerBackendGroupCreateTask struct { LoadbalancerLoadbalancerBackendGroupCreateTask } +type OpenstackLoadbalancerLoadbalancerBackendGroupCreateTask struct { + LoadbalancerLoadbalancerBackendGroupCreateTask +} + func init() { taskman.RegisterTask(LoadbalancerLoadbalancerBackendGroupCreateTask{}) taskman.RegisterTask(HuaweiLoadbalancerLoadbalancerBackendGroupCreateTask{}) taskman.RegisterTask(AwsLoadbalancerLoadbalancerBackendGroupCreateTask{}) + taskman.RegisterTask(OpenstackLoadbalancerLoadbalancerBackendGroupCreateTask{}) } func (self *LoadbalancerLoadbalancerBackendGroupCreateTask) taskFail(ctx context.Context, lbacl *models.SLoadbalancerBackendGroup, reason jsonutils.JSONObject) { @@ -136,3 +141,31 @@ func (self *AwsLoadbalancerLoadbalancerBackendGroupCreateTask) OnInit(ctx contex self.taskFail(ctx, lbbg, jsonutils.Marshal(err)) } } + +func (self *OpenstackLoadbalancerLoadbalancerBackendGroupCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + lbbg := obj.(*models.SLoadbalancerBackendGroup) + region := lbbg.GetRegion() + if region == nil { + self.taskFail(ctx, lbbg, jsonutils.NewString(fmt.Sprintf("failed to find region for lb backendgroup %s", lbbg.Name))) + return + } + + backends, err := lbbg.GetBackendsParams() + if err != nil { + self.taskFail(ctx, lbbg, jsonutils.Marshal(err)) + return + } + + // 必须指定listenerId或ruleId + listenerId, _ := self.GetParams().GetString("listenerId") + ruleId, _ := self.GetParams().GetString("ruleId") + if len(listenerId) == 0 && len(ruleId) == 0 { + self.taskFail(ctx, lbbg, jsonutils.NewString(fmt.Sprintf("CreateLoadbalancerBackendGroup listener/rule id should not be emtpy"))) + return + } + + self.SetStage("OnLoadbalancerBackendGroupCreateComplete", nil) + if err := region.GetDriver().RequestCreateLoadbalancerBackendGroup(ctx, self.GetUserCred(), lbbg, backends, self); err != nil { + self.taskFail(ctx, lbbg, jsonutils.Marshal(err)) + } +} diff --git a/pkg/compute/tasks/loadbalancer_listener_create_task.go b/pkg/compute/tasks/loadbalancer_listener_create_task.go index 0a35f9ec18..ad9885ba77 100644 --- a/pkg/compute/tasks/loadbalancer_listener_create_task.go +++ b/pkg/compute/tasks/loadbalancer_listener_create_task.go @@ -41,6 +41,8 @@ func getOnLoadbalancerListenerCreateCompleteFunc(provider string) func(ctx conte switch provider { case api.CLOUD_PROVIDER_HUAWEI: return onHuaweiLoadbalancerListenerCreateComplete + case api.CLOUD_PROVIDER_OPENSTACK: + return onOpenstackLoadbalancerListenerCreateComplete default: return onLoadbalancerListenerCreateComplete } @@ -102,6 +104,57 @@ func onHuaweiLoadbalancerListenerCreateComplete(ctx context.Context, lblis *mode } } +func onOpenstackLoadbalancerListenerCreateComplete(ctx context.Context, lblis *models.SLoadbalancerListener, data jsonutils.JSONObject, self *LoadbalancerListenerCreateTask) { + lbbg := lblis.GetLoadbalancerBackendGroup() + if lbbg == nil { + self.taskFail(ctx, lblis, jsonutils.NewString("openstack loadbalancer listener releated backend group not found")) + return + } + + groupParams, err := lbbg.GetOpenstackBackendGroupParams(lblis, nil) + if err != nil { + self.taskFail(ctx, lblis, jsonutils.NewString(err.Error())) + return + } + + params := jsonutils.NewDict() + params.Set("listenerId", jsonutils.NewString(lblis.GetId())) + group, err := models.OpenstackCachedLbbgManager.GetCachedBackendGroupByAssociateId(lblis.GetId()) + if err != nil && err != sql.ErrNoRows { + self.taskFail(ctx, lblis, jsonutils.NewString(err.Error())) + return + } + + if group != nil { + // 服务器组存在 + ilbbg, err := group.GetICloudLoadbalancerBackendGroup() + if err != nil { + self.taskFail(ctx, lblis, jsonutils.NewString(err.Error())) + return + } + // 服务器组已经存在,直接同步即可 + if err := ilbbg.Sync(ctx, groupParams); err != nil { + self.taskFail(ctx, lblis, jsonutils.NewString(err.Error())) + return + } else { + if _, err := db.UpdateWithLock(ctx, group, func() error { + group.AssociatedId = lblis.GetId() + group.AssociatedType = api.LB_ASSOCIATE_TYPE_LISTENER + return nil + }); err != nil { + self.taskFail(ctx, lblis, jsonutils.NewString(err.Error())) + return + } + + self.OnPrepareLoadbalancerBackendgroup(ctx, lblis, data) + } + } else { + // 服务器组不存在 + self.SetStage("OnPrepareLoadbalancerBackendgroup", nil) + lbbg.StartOpenstackLoadBalancerBackendGroupCreateTask(ctx, self.GetUserCred(), params, self.GetTaskId()) + } +} + func (self *LoadbalancerListenerCreateTask) taskFail(ctx context.Context, lblis *models.SLoadbalancerListener, reason jsonutils.JSONObject) { lblis.SetStatus(self.GetUserCred(), api.LB_CREATE_FAILED, reason.String()) db.OpsLog.LogEvent(lblis, db.ACT_ALLOCATE_FAIL, reason, self.UserCred) diff --git a/pkg/compute/tasks/loadbalancer_listener_rule_create_task.go b/pkg/compute/tasks/loadbalancer_listener_rule_create_task.go index dc8cf250db..91b8be837b 100644 --- a/pkg/compute/tasks/loadbalancer_listener_rule_create_task.go +++ b/pkg/compute/tasks/loadbalancer_listener_rule_create_task.go @@ -42,6 +42,8 @@ func getOnPrepareLoadbalancerBackendgroupFunc(provider string) func(ctx context. return onHuaiweiPrepareLoadbalancerBackendgroup case api.CLOUD_PROVIDER_AWS: return onAwsPrepareLoadbalancerBackendgroup + case api.CLOUD_PROVIDER_OPENSTACK: + return onOpenstackPrepareLoadbalancerBackendgroup default: return onPrepareLoadbalancerBackendgroup } @@ -147,6 +149,59 @@ func onAwsPrepareLoadbalancerBackendgroup(ctx context.Context, region *models.SC } } +func onOpenstackPrepareLoadbalancerBackendgroup(ctx context.Context, region *models.SCloudregion, lbr *models.SLoadbalancerListenerRule, data jsonutils.JSONObject, self *LoadbalancerListenerRuleCreateTask) { + lbbg := lbr.GetLoadbalancerBackendGroup() + if lbbg == nil { + self.taskFail(ctx, lbr, jsonutils.NewString("openstack loadbalancer listener rule releated backend group not found")) + return + } + + lblis := lbr.GetLoadbalancerListener() + if lblis == nil { + self.taskFail(ctx, lbr, jsonutils.NewString("openstack loadbalancer listener rule releated listener not found")) + return + } + + params := jsonutils.NewDict() + params.Set("ruleId", jsonutils.NewString(lbr.GetId())) + group, _ := models.OpenstackCachedLbbgManager.GetCachedBackendGroupByAssociateId(lbr.GetId()) + if group != nil { + ilbbg, err := group.GetICloudLoadbalancerBackendGroup() + if err != nil { + self.taskFail(ctx, lbr, jsonutils.NewString(err.Error())) + return + } + + groupParams, err := lbbg.GetOpenstackBackendGroupParams(lblis, lbr) + if err != nil { + self.taskFail(ctx, lbr, jsonutils.NewString(err.Error())) + return + } + groupParams.ListenerID = "" + // 服务器组已经存在,直接同步即可 + if err := ilbbg.Sync(ctx, groupParams); err != nil { + self.taskFail(ctx, lbr, jsonutils.NewString(err.Error())) + return + } else { + group.SetModelManager(models.OpenstackCachedLbbgManager, group) + if _, err := db.UpdateWithLock(ctx, group, func() error { + group.AssociatedId = lbr.GetId() + group.AssociatedType = api.LB_ASSOCIATE_TYPE_RULE + return nil + }); err != nil { + self.taskFail(ctx, lbr, jsonutils.NewString(err.Error())) + return + } + + self.OnCreateLoadbalancerListenerRule(ctx, lbr, data) + } + } else { + // 服务器组不存在 + self.SetStage("OnCreateLoadbalancerListenerRule", nil) + lbbg.StartOpenstackLoadBalancerBackendGroupCreateTask(ctx, self.GetUserCred(), params, self.GetTaskId()) + } +} + func (self *LoadbalancerListenerRuleCreateTask) taskFail(ctx context.Context, lbr *models.SLoadbalancerListenerRule, reason jsonutils.JSONObject) { lbr.SetStatus(self.GetUserCred(), api.LB_CREATE_FAILED, reason.String()) db.OpsLog.LogEvent(lbr, db.ACT_ALLOCATE_FAIL, reason, self.UserCred) diff --git a/pkg/multicloud/openstack/eip.go b/pkg/multicloud/openstack/eip.go index de26166230..1ed7316302 100644 --- a/pkg/multicloud/openstack/eip.go +++ b/pkg/multicloud/openstack/eip.go @@ -304,6 +304,16 @@ func (region *SRegion) AssociateEip(instanceId, eipId string) error { return fmt.Errorf("failed to found instnace %s nics for binding eip", instanceId) } +func (region *SRegion) AssociateEipWithPortId(portid, eipId string) error { + params := map[string]map[string]string{ + "floatingip": { + "port_id": portid, + }, + } + _, err := region.vpcUpdate("/v2.0/floatingips/"+eipId, jsonutils.Marshal(params)) + return err +} + func (region *SRegion) DisassociateEip(eipId string) error { params, _ := jsonutils.Parse([]byte(`{ "floatingip": { diff --git a/pkg/multicloud/openstack/loadbalabcerpool.go b/pkg/multicloud/openstack/loadbalabcerpool.go new file mode 100644 index 0000000000..ff964ad1ff --- /dev/null +++ b/pkg/multicloud/openstack/loadbalabcerpool.go @@ -0,0 +1,578 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package openstack + +import ( + "context" + "fmt" + "net/url" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SLoadbalancerPoolCreateParams struct { + LbAlgorithm string `json:"lb_algorithm,omitempty"` + Protocol string `json:"protocol,omitempty"` + Description string `json:"description,omitempty"` + AdminStateUp bool `json:"admin_state_up,omitempty"` + SessionPersistence *SSessionPersistence `json:"session_persistence"` + LoadbalancerID string `json:"loadbalancer_id,omitempty"` + ListenerID string `json:"listener_id,omitempty"` + Name string `json:"name,omitempty"` + Tags []string `json:"tags,omitempty"` + TLSContainerRef string `json:"tls_container_ref,omitempty"` + CaTLSContainerRef string `json:"ca_tls_container_ref,omitempty"` + CrlContainerRef string `json:"crl_container_ref,omitempty"` + TLSEnabled *bool `json:"tls_enabled,omitempty"` + TLSCiphers string `json:"tls_ciphers,omitempty"` + TLSVersions []string `json:"tls_versions,omitempty"` +} + +type SLoadbalancerPoolUpdateParams struct { + LbAlgorithm string `json:"lb_algorithm,omitempty"` + Description string `json:"description,omitempty"` + AdminStateUp bool `json:"admin_state_up,omitempty"` + SessionPersistence *SSessionPersistence `json:"session_persistence"` + Name string `json:"name,omitempty"` + Tags []string `json:"tags,omitempty"` + TLSContainerRef string `json:"tls_container_ref,omitempty"` + CaTLSContainerRef string `json:"ca_tls_container_ref,omitempty"` + CrlContainerRef string `json:"crl_container_ref,omitempty"` + TLSEnabled *bool `json:"tls_enabled,omitempty"` + TLSCiphers string `json:"tls_ciphers,omitempty"` + TLSVersions []string `json:"tls_versions,omitempty"` +} + +type SSessionPersistence struct { + CookieName string `json:"cookie_name,omitempty"` + Type string `json:"type,omitempty"` +} + +type SLoadbalancerPool struct { + region *SRegion + members []SLoadbalancerMember + healthmonitor *SLoadbalancerHealthmonitor + LbAlgorithm string `json:"lb_algorithm"` + Protocol string `json:"protocol"` + Description string `json:"description"` + AdminStateUp bool `json:"admin_state_up"` + LoadbalancerIds []SLoadbalancerID `json:"loadbalancers"` + CreatedAt string `json:"created_at"` + ProvisioningStatus string `json:"provisioning_status"` + UpdatedAt string `json:"updated_at"` + SessionPersistence SSessionPersistence `json:"session_persistence"` + ListenerIds []SListenerID `json:"listeners"` + MemberIds []SMemberID `json:"members"` + HealthmonitorID string `json:"healthmonitor_id"` + ProjectID string `json:"project_id"` + ID string `json:"id"` + OperatingStatus string `json:"operating_status"` + Name string `json:"name"` + Tags []string `json:"tags"` + TLSContainerRef string `json:"tls_container_ref"` + CaTLSContainerRef string `json:"ca_tls_container_ref"` + CrlContainerRef string `json:"crl_container_ref"` + TLSEnabled bool `json:"tls_enabled"` + TLSCiphers string `json:"tls_ciphers"` + TLSVersions []string `json:"tls_versions"` +} + +func ToOpenstackHealthCheckHttpCode(c string) string { + c = strings.TrimSpace(c) + segs := strings.Split(c, ",") + ret := []string{} + for _, seg := range segs { + seg = strings.TrimLeft(seg, "http_") + seg = strings.TrimSpace(seg) + seg = strings.Replace(seg, "xx", "00", -1) + ret = append(ret, seg) + } + + return strings.Join(ret, ",") +} + +func ToOnecloudHealthCheckHttpCode(c string) string { + c = strings.TrimSpace(c) + segs := strings.Split(c, ",") + ret := []string{} + for _, seg := range segs { + seg = strings.TrimSpace(seg) + seg = strings.Replace(seg, "00", "xx", -1) + seg = "http_" + seg + ret = append(ret, seg) + } + + return strings.Join(ret, ",") +} + +func (pool *SLoadbalancerPool) GetILoadbalancer() cloudprovider.ICloudLoadbalancer { + if len(pool.LoadbalancerIds) != 1 { + return nil + } + loadbalancer, err := pool.region.GetLoadbalancerbyId(pool.LoadbalancerIds[0].ID) + if err != nil { + return nil + } + return loadbalancer +} + +func (pool *SLoadbalancerPool) GetLoadbalancerId() string { + if len(pool.LoadbalancerIds) != 1 { + return "" + } + return pool.LoadbalancerIds[0].ID +} + +func (pool *SLoadbalancerPool) GetProtocolType() string { + switch pool.Protocol { + case "TCP": + return api.LB_LISTENER_TYPE_TCP + case "UDP": + return api.LB_LISTENER_TYPE_UDP + case "HTTP": + return api.LB_LISTENER_TYPE_HTTP + default: + return "" + } +} + +func (pool *SLoadbalancerPool) GetScheduler() string { + switch pool.LbAlgorithm { + case "LEAST_CONNECTIONS": + return api.LB_SCHEDULER_WLC + case "ROUND_ROBIN": + return api.LB_SCHEDULER_WRR + case "SOURCE_IP": + return api.LB_SCHEDULER_SCH + case "SOURCE_IP_PORT": + return api.LB_SCHEDULER_TCH + default: + return "" + } +} + +func (pool *SLoadbalancerPool) GetHealthCheck() (*cloudprovider.SLoadbalancerHealthCheck, error) { + healthCheck := cloudprovider.SLoadbalancerHealthCheck{} + healthCheck.HealthCheckDomain = pool.healthmonitor.DomainName + healthCheck.HealthCheckHttpCode = ToOnecloudHealthCheckHttpCode(pool.healthmonitor.ExpectedCodes) + healthCheck.HealthCheckInterval = pool.healthmonitor.Delay + healthCheck.HealthCheckRise = pool.healthmonitor.MaxRetries + healthCheck.HealthCheckFail = pool.healthmonitor.MaxRetriesDown + healthCheck.HealthCheckTimeout = pool.healthmonitor.Timeout + switch pool.healthmonitor.Type { + case "HTTP": + healthCheck.HealthCheckType = api.LB_HEALTH_CHECK_HTTP + case "HTTPS": + healthCheck.HealthCheckType = api.LB_HEALTH_CHECK_HTTPS + case "TCP": + healthCheck.HealthCheckType = api.LB_HEALTH_CHECK_TCP + case "UDP-CONNECT": + healthCheck.HealthCheckType = api.LB_HEALTH_CHECK_UDP + default: + healthCheck.HealthCheckType = "" + } + healthCheck.HealthCheckURI = pool.healthmonitor.URLPath + return &healthCheck, nil +} + +func (pool *SLoadbalancerPool) GetStickySession() (*cloudprovider.SLoadbalancerStickySession, error) { + if len(pool.SessionPersistence.Type) == 0 { + return nil, nil + } + + var stickySessionType string + switch pool.SessionPersistence.Type { + case "SOURCE_IP": + stickySessionType = api.LB_STICKY_SESSION_TYPE_INSERT + case "HTTP_COOKIE": + stickySessionType = api.LB_STICKY_SESSION_TYPE_INSERT + case "APP_COOKIE": + stickySessionType = api.LB_STICKY_SESSION_TYPE_SERVER + } + + ret := cloudprovider.SLoadbalancerStickySession{ + StickySession: api.LB_BOOL_ON, + StickySessionCookie: pool.SessionPersistence.CookieName, + StickySessionType: stickySessionType, + StickySessionCookieTimeout: 0, + } + + return &ret, nil +} + +func (pool *SLoadbalancerPool) GetName() string { + return pool.Name +} + +func (pool *SLoadbalancerPool) GetId() string { + return pool.ID +} + +func (pool *SLoadbalancerPool) GetGlobalId() string { + return pool.ID +} + +func (pool *SLoadbalancerPool) GetStatus() string { + switch pool.ProvisioningStatus { + case "ACTIVE": + return api.LB_STATUS_ENABLED + case "PENDING_CREATE": + return api.LB_CREATING + case "PENDING_UPDATE": + return api.LB_SYNC_CONF + case "PENDING_DELETE": + return api.LB_STATUS_DELETING + case "DELETED": + return api.LB_STATUS_DELETED + default: + return api.LB_STATUS_UNKNOWN + } +} + +func (pool *SLoadbalancerPool) IsDefault() bool { + return false +} + +func (pool *SLoadbalancerPool) GetType() string { + return api.LB_BACKENDGROUP_TYPE_NORMAL +} + +func (pool *SLoadbalancerPool) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (pool *SLoadbalancerPool) IsEmulated() bool { + return false +} + +func (region *SRegion) GetLoadbalancerPools() ([]SLoadbalancerPool, error) { + pools := []SLoadbalancerPool{} + resource := "/v2/lbaas/pools" + query := url.Values{} + for { + resp, err := region.lbList(resource, query) + if err != nil { + return nil, errors.Wrap(err, "lbList") + } + part := struct { + Pools []SLoadbalancerPool + PoolsLinks SNextLinks + }{} + err = resp.Unmarshal(&part) + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + pools = append(pools, part.Pools...) + marker := part.PoolsLinks.GetNextMark() + if len(marker) == 0 { + break + } + query.Set("marker", marker) + } + + for i := 0; i < len(pools); i++ { + pools[i].region = region + err := pools[i].fetchLoadbalancerHealthmonitor() + if err != nil { + return nil, errors.Wrapf(err, "pools[%d].fetchLoadbalancerHealthmonitor()", i) + } + + } + return pools, nil +} + +func (region *SRegion) GetLoadbalancerPoolById(poolId string) (*SLoadbalancerPool, error) { + body, err := region.lbGet(fmt.Sprintf("/v2/lbaas/pools/%s", poolId)) + if err != nil { + return nil, errors.Wrapf(err, "region.lbGet(fmt.Sprintf(/v2/lbaas/pools/%s)", poolId) + } + pool := SLoadbalancerPool{} + err = body.Unmarshal(&pool, "pool") + if err != nil { + return nil, errors.Wrap(err, "body.Unmarshal") + } + pool.region = region + err = pool.fetchLoadbalancermembers() + if err != nil { + return nil, errors.Wrap(err, "pool.fetchLoadbalancermembers()") + } + err = pool.fetchLoadbalancerHealthmonitor() + if err != nil { + return nil, errors.Wrap(err, "pool.fetchLoadbalancerHealthmonitor()") + } + return &pool, nil +} + +func (region *SRegion) CreateLoadbalancerPool(group *cloudprovider.SLoadbalancerBackendGroup) (*SLoadbalancerPool, error) { + type CreateParams struct { + Pool SLoadbalancerPoolCreateParams `json:"pool"` + } + params := CreateParams{} + params.Pool.AdminStateUp = true + params.Pool.LbAlgorithm = LB_ALGORITHM_MAP[group.Scheduler] + params.Pool.Name = group.Name + params.Pool.LoadbalancerID = group.LoadbalancerID + // 绑定规则时不能指定listener + params.Pool.ListenerID = group.ListenerID + params.Pool.Protocol = LB_PROTOCOL_MAP[group.ListenType] + params.Pool.SessionPersistence = nil + if group.StickySession != nil { + session := SSessionPersistence{} + session.Type = LB_STICKY_SESSION_MAP[group.StickySession.StickySessionType] + if session.Type == "APP_COOKIE" { + session.CookieName = group.StickySession.StickySessionCookie + } + params.Pool.SessionPersistence = &session + } + body, err := region.lbPost("/v2/lbaas/pools", jsonutils.Marshal(params)) + if err != nil { + return nil, errors.Wrap(err, "region.lbPost(/v2/lbaas/pools)") + } + spool := SLoadbalancerPool{} + spool.region = region + err = body.Unmarshal(&spool, "pool") + if err != nil { + return nil, errors.Wrap(err, "body.Unmarshal(&spool, pool)") + } + return &spool, nil +} + +func (pool *SLoadbalancerPool) Refresh() error { + newPool, err := pool.region.GetLoadbalancerPoolById(pool.ID) + if err != nil { + return err + } + return jsonutils.Update(pool, newPool) +} + +func (pool *SLoadbalancerPool) fetchLoadbalancermembers() error { + if len(pool.MemberIds) < 1 { + return nil + } + members, err := pool.region.GetLoadbalancerMenbers(pool.ID) + if err != nil { + return err + } + pool.members = members + return nil +} + +func (pool *SLoadbalancerPool) fetchLoadbalancerHealthmonitor() error { + if len(pool.HealthmonitorID) < 1 { + return nil + } + healthmonitor, err := pool.region.GetLoadbalancerHealthmonitorById(pool.HealthmonitorID) + if err != nil { + return errors.Wrap(err, "pool.region.GetLoadbalancerHealthmonitorById") + } + pool.healthmonitor = healthmonitor + return nil +} + +func (pool *SLoadbalancerPool) GetILoadbalancerBackends() ([]cloudprovider.ICloudLoadbalancerBackend, error) { + ibackends := []cloudprovider.ICloudLoadbalancerBackend{} + for i := 0; i < len(pool.members); i++ { + ibackends = append(ibackends, &pool.members[i]) + } + return ibackends, nil +} + +func (pool *SLoadbalancerPool) GetILoadbalancerBackendById(memberId string) (cloudprovider.ICloudLoadbalancerBackend, error) { + for i := 0; i < len(pool.members); i++ { + if pool.members[i].ID == memberId { + return &pool.members[i], nil + } + } + return nil, errors.Wrapf(cloudprovider.ErrNotFound, "GetILoadbalancerBackendById(%s)", memberId) +} + +func (region *SRegion) UpdateLoadBalancerPool(poolId string, group *cloudprovider.SLoadbalancerBackendGroup) error { + type UpdateParams struct { + Pool SLoadbalancerPoolUpdateParams `json:"pool"` + } + params := UpdateParams{} + params.Pool.AdminStateUp = true + params.Pool.LbAlgorithm = LB_ALGORITHM_MAP[group.Scheduler] + params.Pool.Name = group.Name + if group.StickySession != nil { + session := SSessionPersistence{} + session.Type = LB_STICKY_SESSION_MAP[group.StickySession.StickySessionType] + if session.Type == "APP_COOKIE" { + session.CookieName = group.StickySession.StickySessionCookie + } + params.Pool.SessionPersistence = &session + } + _, err := region.lbUpdate(fmt.Sprintf("/v2/lbaas/pools/%s", poolId), jsonutils.Marshal(params)) + if err != nil { + return errors.Wrapf(err, `region.lbUpdate(fmt.Sprintf("/v2/lbaas/pools/%s", poolId), jsonutils.Marshal(params))`) + } + return nil +} + +func (pool *SLoadbalancerPool) Sync(ctx context.Context, group *cloudprovider.SLoadbalancerBackendGroup) error { + lb, err := pool.region.GetLoadbalancerbyId(pool.GetLoadbalancerId()) + if err != nil { + return errors.Wrap(err, "pool.region.GetLoadbalancerbyId(pool.GetLoadbalancerId())") + } + // ensure loadbalancer status + err = waitLbResStatus(lb, 10*time.Second, 8*time.Minute) + if err != nil { + return errors.Wrap(err, `waitLbResStatus(lb, 10*time.Second, 8*time.Minute)`) + } + // ensure pool status + err = waitLbResStatus(pool, 10*time.Second, 8*time.Minute) + if err != nil { + return errors.Wrap(err, `waitLbResStatus(pool, 10*time.Second, 8*time.Minute)`) + } + // sync healthmonitor + healthmonitor := SLoadbalancerHealthmonitor{} + if len(pool.HealthmonitorID) > 0 { + oldhealthmonitor, err := pool.region.GetLoadbalancerHealthmonitorById(pool.HealthmonitorID) + if err != nil { + return errors.Wrap(err, "pool.region.GetLoadbalancerHealthmonitorById(pool.HealthmonitorID)") + } + // 不能更新健康检查类型,需要删除重建 + var sHealthCheckType string + switch oldhealthmonitor.Type { + case "HTTP": + sHealthCheckType = api.LB_HEALTH_CHECK_HTTP + case "HTTPS": + sHealthCheckType = api.LB_HEALTH_CHECK_HTTPS + case "TCP": + sHealthCheckType = api.LB_HEALTH_CHECK_TCP + case "UDP-CONNECT": + sHealthCheckType = api.LB_HEALTH_CHECK_UDP + default: + sHealthCheckType = "" + } + + if sHealthCheckType != group.HealthCheck.HealthCheckType { + err := pool.region.DeleteLoadbalancerHealthmonitor(pool.HealthmonitorID) + if err != nil { + return errors.Wrapf(err, "pool.region.DeleteLoadbalancerHealthmonitor(%s)", pool.HealthmonitorID) + } + // 等待删除结束 + err = waitLbResStatus(lb, 10*time.Second, 8*time.Minute) + if err != nil { + return errors.Wrap(err, `waitLbResStatus(lb, 10*time.Second, 8*time.Minute)`) + } + + newhealthmonitor, err := pool.region.CreateLoadbalancerHealthmonitor(pool.ID, group.HealthCheck) + if err != nil { + return errors.Wrapf(err, "pool.region.CreateLoadbalancerHealthmonitor(%s,group.HealthCheck)", pool.ID) + } + healthmonitor = *newhealthmonitor + } else { + // ensure healthmonitor status + err = waitLbResStatus(oldhealthmonitor, 10*time.Second, 8*time.Minute) + if err != nil { + return errors.Wrap(err, `waitLbResStatus(oldhealthmonitor, 10*time.Second, 8*time.Minute)`) + } + oldhealthmonitor, err = pool.region.UpdateLoadbalancerHealthmonitor(pool.HealthmonitorID, group.HealthCheck) + if err != nil { + return errors.Wrapf(err, `pool.region.UpdateLoadbalancerHealthmonitor(%s, group.HealthCheck)`, pool.HealthmonitorID) + } + healthmonitor = *oldhealthmonitor + } + } else { + newhealthmonitor, err := pool.region.CreateLoadbalancerHealthmonitor(pool.ID, group.HealthCheck) + if err != nil { + return errors.Wrapf(err, "pool.region.CreateLoadbalancerHealthmonitor(%s, group.HealthCheck)", pool.ID) + } + healthmonitor = *newhealthmonitor + } + + // ensure pool status + err = waitLbResStatus(pool, 10*time.Second, 8*time.Minute) + if err != nil { + return errors.Wrap(err, `waitLbResStatus(pool, 10*time.Second, 8*time.Minute)`) + } + // sync pool + err = pool.region.UpdateLoadBalancerPool(pool.ID, group) + if err != nil { + return errors.Wrapf(err, `pool.region.UpdateLoadBalancerPool(%s, group)`, pool.ID) + } + + // wait healthmonitor status + err = waitLbResStatus(&healthmonitor, 10*time.Second, 8*time.Minute) + if err != nil { + return errors.Wrap(err, `waitLbResStatus(&healthmonitor, 10*time.Second, 8*time.Minute)`) + } + // wait pool status + err = waitLbResStatus(pool, 10*time.Second, 8*time.Minute) + if err != nil { + return errors.Wrap(err, `waitLbResStatus(pool, 10*time.Second, 8*time.Minute)`) + } + return nil +} + +func (region *SRegion) DeleteLoadBalancerPool(poolId string) error { + _, err := region.lbDelete(fmt.Sprintf("/v2/lbaas/pools/%s", poolId)) + if err != nil { + return errors.Wrapf(err, "lbDelete(/v2/lbaas/pools/%s)", poolId) + } + return nil +} + +func (pool *SLoadbalancerPool) Delete(ctx context.Context) error { + lb, err := pool.region.GetLoadbalancerbyId(pool.GetLoadbalancerId()) + if err != nil { + return errors.Wrap(err, "pool.region.GetLoadbalancerbyId(pool.GetLoadbalancerId())") + } + err = waitLbResStatus(lb, 10*time.Second, 8*time.Minute) + if err != nil { + return errors.Wrap(err, `waitLbResStatus(lb, 10*time.Second, 8*time.Minute)`) + } + return pool.region.DeleteLoadBalancerPool(pool.ID) +} + +func (pool *SLoadbalancerPool) AddBackendServer(serverId string, weight, port int) (cloudprovider.ICloudLoadbalancerBackend, error) { + // ensure lb status + lb, err := pool.region.GetLoadbalancerbyId(pool.GetLoadbalancerId()) + if err != nil { + return nil, errors.Wrap(err, "pool.region.GetLoadbalancerbyId(pool.GetLoadbalancerId())") + } + err = waitLbResStatus(lb, 10*time.Second, 8*time.Minute) + if err != nil { + return nil, errors.Wrap(err, `waitLbResStatus(lb, 10*time.Second, 8*time.Minute)`) + } + smemeber, err := pool.region.CreateLoadbalancerMember(pool.ID, serverId, weight, port) + if err != nil { + return nil, errors.Wrapf(err, `CreateLoadbalancerMember(%s,%s,%s,%s)`, pool.ID, serverId, weight, port) + } + err = waitLbResStatus(smemeber, 10*time.Second, 8*time.Minute) + if err != nil { + return nil, errors.Wrap(err, `waitLbResStatus(smemeber, 10*time.Second, 8*time.Minute)`) + } + smemeber.region = pool.region + smemeber.poolID = pool.ID + pool.members = append(pool.members, *smemeber) + return smemeber, nil +} + +// 不是serverId,是memberId +func (pool *SLoadbalancerPool) RemoveBackendServer(id string, weight, port int) error { + return pool.region.DeleteLoadbalancerMember(pool.ID, id) +} + +func (pool *SLoadbalancerPool) GetProjectId() string { + return pool.ProjectID +} diff --git a/pkg/multicloud/openstack/loadbalanceracl.go b/pkg/multicloud/openstack/loadbalanceracl.go new file mode 100644 index 0000000000..1a493d4fdb --- /dev/null +++ b/pkg/multicloud/openstack/loadbalanceracl.go @@ -0,0 +1,188 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package openstack + +import ( + "fmt" + "time" + + "github.com/pkg/errors" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type AclEntrys struct { + AclEntry []AclEntry +} + +type AclEntry struct { + AclEntryComment string + AclEntryIP string +} + +type SLoadbalancerAcl struct { + listener *SLoadbalancerListener +} + +func (acl *SLoadbalancerAcl) GetAclListenerID() string { + return acl.listener.ID +} + +func (acl *SLoadbalancerAcl) GetName() string { + return acl.listener.Name + "AllowedCidrs" +} + +func (acl *SLoadbalancerAcl) GetId() string { + return acl.listener.ID +} + +func (acl *SLoadbalancerAcl) GetGlobalId() string { + return acl.listener.ID +} + +func (acl *SLoadbalancerAcl) GetStatus() string { + return "" +} + +func (acl *SLoadbalancerAcl) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (acl *SLoadbalancerAcl) IsEmulated() bool { + return false +} + +func (acl *SLoadbalancerAcl) Refresh() error { + return acl.listener.Refresh() +} + +func (acl *SLoadbalancerAcl) GetAclEntries() []cloudprovider.SLoadbalancerAccessControlListEntry { + aclEntrys := []cloudprovider.SLoadbalancerAccessControlListEntry{} + for i := 0; i < len(acl.listener.AllowedCidrs); i++ { + aclEntry := cloudprovider.SLoadbalancerAccessControlListEntry{} + aclEntry.CIDR = acl.listener.AllowedCidrs[i] + aclEntry.Comment = "AllowedCidr" + aclEntrys = append(aclEntrys, aclEntry) + } + return aclEntrys +} + +func (region *SRegion) UpdateLoadbalancerListenerAllowedCidrs(listenerId string, cidrs []string) error { + params := jsonutils.NewDict() + listenerParam := jsonutils.NewDict() + listenerParam.Add(jsonutils.NewStringArray(cidrs), "allowed_cidrs") + params.Add(listenerParam, "listener") + _, err := region.lbUpdate(fmt.Sprintf("/v2/lbaas/listeners/%s", listenerId), params) + if err != nil { + return errors.Wrapf(err, `region.lbUpdate(/v2/lbaas/listeners/%s, params)`, listenerId) + } + return nil +} + +func (acl *SLoadbalancerAcl) Delete() error { + // ensure listener status + err := waitLbResStatus(acl.listener, 10*time.Second, 8*time.Minute) + if err != nil { + return errors.Wrap(err, `waitLbResStatus(acl.listener, 10*time.Second, 8*time.Minute)`) + } + err = acl.listener.region.UpdateLoadbalancerListenerAllowedCidrs(acl.listener.ID, []string{}) + if err != nil { + return errors.Wrap(err, `acl.listener.region.UpdateLoadbalancerListenerAllowedCidrs(acl.listener.ID, []string{})`) + } + err = waitLbResStatus(acl.listener, 10*time.Second, 8*time.Minute) + if err != nil { + return errors.Wrap(err, `waitLbResStatus(acl.listener, 10*time.Second, 8*time.Minute)`) + } + return nil +} + +func (region *SRegion) GetLoadbalancerAclDetail(aclId string) (*SLoadbalancerAcl, error) { + listener, err := region.GetLoadbalancerListenerbyId(aclId) + if err != nil { + return nil, errors.Wrapf(err, "region.GetLoadbalancerListenerbyId(s)", aclId) + } + acl := SLoadbalancerAcl{} + acl.listener = listener + return &acl, nil +} + +func (region *SRegion) GetLoadBalancerAcls() ([]SLoadbalancerAcl, error) { + listeners, err := region.GetLoadbalancerListeners() + if err != nil { + return nil, errors.Wrap(err, "region.GetLoadbalancerListeners()") + } + acls := []SLoadbalancerAcl{} + for i := 0; i < len(listeners); i++ { + if len(listeners[i].AllowedCidrs) < 1 { + continue + } + acl := new(SLoadbalancerAcl) + acl.listener = &listeners[i] + acls = append(acls, *acl) + + } + return acls, nil +} + +func (region *SRegion) CreateLoadBalancerAcl(acl *cloudprovider.SLoadbalancerAccessControlList) (*SLoadbalancerAcl, error) { + if !acl.AccessControlEnable { + return nil, errors.Wrap(fmt.Errorf("only support allowed cidrs"), "CreateLoadBalancerAcl") + } + if len(acl.ListenerId) < 1 { + return nil, errors.Wrap(fmt.Errorf("loadbalanceracl must band to a loadbalancerlistener"), "CreateLoadBalancerAcl") + } + cidrs := []string{} + for i := 0; i < len(acl.Entrys); i++ { + cidrs = append(cidrs, acl.Entrys[i].CIDR) + } + sacl, err := region.GetLoadbalancerAclDetail(acl.ListenerId) + if err != nil { + return nil, errors.Wrapf(err, "region.GetLoadbalancerAclDetail(s)", acl.ListenerId) + } + // ensure listener status + err = waitLbResStatus(sacl.listener, 10*time.Second, 8*time.Minute) + if err != nil { + return nil, errors.Wrap(err, `waitLbResStatus(sacl.listener, 10*time.Second, 8*time.Minute)`) + } + return sacl, region.UpdateLoadbalancerListenerAllowedCidrs(acl.ListenerId, cidrs) +} + +func (acl *SLoadbalancerAcl) Sync(_acl *cloudprovider.SLoadbalancerAccessControlList) error { + // ensure listener status + err := waitLbResStatus(acl.listener, 10*time.Second, 8*time.Minute) + if err != nil { + return errors.Wrap(err, "waitLbResStatus(acl.listener, 10*time.Second, 8*time.Minute)") + } + + cidrs := []string{} + for i := 0; i < len(_acl.Entrys); i++ { + cidrs = append(cidrs, _acl.Entrys[i].CIDR) + } + err = acl.listener.region.UpdateLoadbalancerListenerAllowedCidrs(acl.listener.ID, cidrs) + if err != nil { + return errors.Wrapf(err, "UpdateLoadbalancerListenerAllowedCidrs(%s, cidrs)", acl.listener.ID) + } + err = waitLbResStatus(acl.listener, 10*time.Second, 8*time.Minute) + if err != nil { + return errors.Wrap(err, "waitLbResStatus(acl.listener, 10*time.Second, 8*time.Minute)") + } + return nil +} + +func (acl *SLoadbalancerAcl) GetProjectId() string { + return acl.listener.ProjectID +} diff --git a/pkg/multicloud/openstack/loadbalancerl7policy.go b/pkg/multicloud/openstack/loadbalancerl7policy.go new file mode 100644 index 0000000000..df92e59f89 --- /dev/null +++ b/pkg/multicloud/openstack/loadbalancerl7policy.go @@ -0,0 +1,169 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package openstack + +import ( + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SLoadbalancerL7PolicyCreateParams struct { + Description string `json:"description,omitempty"` + AdminStateUp bool `json:"admin_state_up,omitempty"` + ListenerID string `json:"listener_id,omitempty"` + RedirectPrefix string `json:"redirect_prefix,omitempty"` + RedirectURL string `json:"redirect_url,omitempty"` + RedirectPoolID string `json:"redirect_pool_id,omitempty"` + RedirectHTTPCode *int `json:"redirect_http_code,omitempty"` + Name string `json:"name,omitempty"` + Action string `json:"action,omitempty"` + Position *int `json:"position,omitempty"` + Tags []string `json:"tags,omitempty"` +} + +type SLoadbalancerL7Policy struct { + region *SRegion + l7rules []SLoadbalancerL7Rule + ListenerID string `json:"listener_id"` + Description string `json:"description"` + AdminStateUp bool `json:"admin_state_up"` + RuleIds []SL7RuleID `json:"rules"` + CreatedAt string `json:"created_at"` + ProvisioningStatus string `json:"provisioning_status"` + UpdatedAt string `json:"updated_at"` + RedirectHTTPCode int `json:"redirect_http_code"` + RedirectPoolID string `json:"redirect_pool_id"` + RedirectPrefix string `json:"redirect_prefix"` + RedirectURL string `json:"redirect_url"` + Action string `json:"action"` + Position int `json:"position"` + ProjectID string `json:"project_id"` + ID string `json:"id"` + OperatingStatus string `json:"operating_status"` + Name string `json:"name"` + Tags []string `json:"tags"` +} + +func (region *SRegion) GetLoadbalancerL7PolicybyId(policieId string) (*SLoadbalancerL7Policy, error) { + body, err := region.lbGet(fmt.Sprintf("/v2/lbaas/l7policies/%s", policieId)) + if err != nil { + return nil, errors.Wrapf(err, `region.lbGet(/v2/lbaas/l7policies/%s)`, policieId) + } + l7policy := SLoadbalancerL7Policy{} + err = body.Unmarshal(&l7policy, "l7policy") + if err != nil { + return nil, errors.Wrap(err, "body.Unmarshal(&l7policy, policy)") + } + l7policy.region = region + err = l7policy.fetchLoadbalancerL7Rules() + if err != nil { + return nil, errors.Wrap(err, "l7policy.fetchLoadbalancerL7Rules()") + } + return &l7policy, nil +} + +func (region *SRegion) CreateLoadbalancerL7Policy(listenerId string, rule *cloudprovider.SLoadbalancerListenerRule) (*SLoadbalancerL7Policy, error) { + type Params struct { + L7policy SLoadbalancerL7PolicyCreateParams `json:"l7policy"` + } + l7policyParams := Params{} + + l7policyParams.L7policy.AdminStateUp = true + l7policyParams.L7policy.ListenerID = listenerId + l7policyParams.L7policy.Name = rule.Name + + if rule.Redirect == api.LB_REDIRECT_RAW { + l7policyParams.L7policy.RedirectHTTPCode = &rule.RedirectCode + if len(rule.RedirectPath) < 1 { + l7policyParams.L7policy.Action = "REDIRECT_PREFIX" + l7policyParams.L7policy.RedirectPrefix = rule.RedirectScheme + "://" + rule.RedirectHost + } else { + l7policyParams.L7policy.Action = "REDIRECT_TO_URL" + l7policyParams.L7policy.RedirectURL = rule.RedirectScheme + "://" + rule.RedirectHost + rule.RedirectPath + } + } else { + l7policyParams.L7policy.Action = "REDIRECT_TO_POOL" + l7policyParams.L7policy.RedirectPoolID = rule.BackendGroupID + } + + body, err := region.lbPost("/v2/lbaas/l7policies", jsonutils.Marshal(l7policyParams)) + if err != nil { + return nil, errors.Wrap(err, `region.lbPost("/v2/lbaas/l7policies", jsonutils.Marshal(l7policyParams))`) + } + l7policy := SLoadbalancerL7Policy{} + l7policy.region = region + err = body.Unmarshal(&l7policy, "l7policy") + if err != nil { + return nil, errors.Wrap(err, "body.Unmarshal(&l7policy, policy)") + } + return &l7policy, nil +} + +func (region *SRegion) DeleteLoadbalancerListenerL7policy(policyId string) error { + _, err := region.lbDelete(fmt.Sprintf("/v2/lbaas/l7policies/%s", policyId)) + if err != nil { + return errors.Wrapf(err, `region.lbDelete(/v2/lbaas/l7policies/%s)`, policyId) + } + return nil +} + +func (L7policy *SLoadbalancerL7Policy) GetName() string { + return L7policy.Name +} + +func (L7policy *SLoadbalancerL7Policy) GetId() string { + return L7policy.ID +} + +func (L7policy *SLoadbalancerL7Policy) GetGlobalId() string { + return L7policy.ID +} + +func (L7policy *SLoadbalancerL7Policy) GetStatus() string { + switch L7policy.ProvisioningStatus { + case "ACTIVE": + return api.LB_STATUS_ENABLED + case "PENDING_CREATE": + return api.LB_CREATING + case "PENDING_UPDATE": + return api.LB_SYNC_CONF + case "PENDING_DELETE": + return api.LB_STATUS_DELETING + case "DELETED": + return api.LB_STATUS_DELETED + default: + return api.LB_STATUS_UNKNOWN + } +} + +func (L7policy *SLoadbalancerL7Policy) Refresh() error { + newL7policie, err := L7policy.region.GetLoadbalancerL7PolicybyId(L7policy.ID) + if err != nil { + return err + } + return jsonutils.Update(L7policy, newL7policie) +} + +func (L7policy *SLoadbalancerL7Policy) IsEmulated() bool { + return false +} +func (L7policy *SLoadbalancerL7Policy) GetMetadata() *jsonutils.JSONDict { + return nil +} diff --git a/pkg/multicloud/openstack/loadbalancerl7rule.go b/pkg/multicloud/openstack/loadbalancerl7rule.go new file mode 100644 index 0000000000..ecdf8035e0 --- /dev/null +++ b/pkg/multicloud/openstack/loadbalancerl7rule.go @@ -0,0 +1,196 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package openstack + +import ( + "context" + "fmt" + "net/url" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SLoadbalancerL7RuleCreateParams struct { + CompareType string `json:"compare_type"` + Invert *bool `json:"invert"` + Type string `json:"type"` + Value string `json:"value"` + AdminStateUp bool `json:"admin_state_up"` + Tags []string `json:"tags"` +} + +type SLoadbalancerL7Rule struct { + policy *SLoadbalancerL7Policy + CreatedAt string `json:"created_at"` + CompareType string `json:"compare_type"` + ProvisioningStatus string `json:"provisioning_status"` + Invert bool `json:"invert"` + AdminStateUp bool `json:"admin_state_up"` + UpdatedAt string `json:"updated_at"` + Value string `json:"value"` + Key string `json:"key"` + ProjectID string `json:"project_id"` + Type string `json:"type"` + ID string `json:"id"` + OperatingStatus string `json:"operating_status"` + Tags []string `json:"tags"` +} + +func (region *SRegion) CreateLoadbalancerL7Rule(l7policyId string, rule *cloudprovider.SLoadbalancerListenerRule) (*SLoadbalancerL7Rule, error) { + type Params struct { + L7Rule SLoadbalancerL7RuleCreateParams `json:"rule"` + } + l7ruleParams := Params{} + l7ruleParams.L7Rule.AdminStateUp = true + l7ruleParams.L7Rule.Type = "PATH" + l7ruleParams.L7Rule.Value = rule.Path + l7ruleParams.L7Rule.CompareType = "REGEX" + body, err := region.lbPost(fmt.Sprintf("/v2/lbaas/l7policies/%s/rules", l7policyId), jsonutils.Marshal(l7ruleParams)) + if err != nil { + return nil, errors.Wrapf(err, `region.lbPost(/v2/lbaas/l7policies/%s/rules), jsonutils.Marshal(l7ruleParams))`, l7policyId) + } + l7rule := SLoadbalancerL7Rule{} + err = body.Unmarshal(&l7rule, "rule") + if err != nil { + return nil, errors.Wrap(err, `body.Unmarshal(&l7rule, "rule")`) + } + return &l7rule, nil +} + +func (policy *SLoadbalancerL7Policy) fetchLoadbalancerL7Rules() error { + l7rules, err := policy.region.GetLoadbalancerL7Rules(policy.ID) + if err != nil { + return errors.Wrapf(err, "policy.region.GetLoadbalancerL7Rules(%s)", policy.ID) + } + for i := 0; i < len(l7rules); i++ { + l7rules[i].policy = policy + } + policy.l7rules = l7rules + return nil +} + +func (region *SRegion) GetLoadbalancerL7Rules(policyId string) ([]SLoadbalancerL7Rule, error) { + l7rules := []SLoadbalancerL7Rule{} + resource := fmt.Sprintf("/v2/lbaas/l7policies/%s/rules", policyId) + query := url.Values{} + for { + resp, err := region.lbList(resource, query) + if err != nil { + return nil, errors.Wrap(err, "lbList") + } + part := struct { + Rules []SLoadbalancerL7Rule + RulesLinks SNextLinks + }{} + err = resp.Unmarshal(&part) + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + l7rules = append(l7rules, part.Rules...) + marker := part.RulesLinks.GetNextMark() + if len(marker) == 0 { + break + } + query.Set("marker", marker) + } + + return l7rules, nil +} + +func (region *SRegion) GetLoadbalancerL7RulebyId(policyId string, l7ruleId string) (*SLoadbalancerL7Rule, error) { + body, err := region.lbGet(fmt.Sprintf("/v2/lbaas/l7policies/%s/rules/%s", policyId, l7ruleId)) + if err != nil { + return nil, errors.Wrapf(err, `region.lbGet(/v2/lbaas/l7policies/%s/rules/%s )`, policyId, l7ruleId) + } + l7rule := SLoadbalancerL7Rule{} + return &l7rule, body.Unmarshal(&l7rule, "rule") +} + +func (l7r *SLoadbalancerL7Rule) GetName() string { + return l7r.policy.Name + l7r.ID +} + +func (l7r *SLoadbalancerL7Rule) GetId() string { + return l7r.ID +} + +func (l7r *SLoadbalancerL7Rule) GetGlobalId() string { + return l7r.ID +} + +func (l7r *SLoadbalancerL7Rule) GetStatus() string { + switch l7r.ProvisioningStatus { + case "ACTIVE": + return api.LB_STATUS_ENABLED + case "PENDING_CREATE": + return api.LB_CREATING + case "PENDING_UPDATE": + return api.LB_SYNC_CONF + case "PENDING_DELETE": + return api.LB_STATUS_DELETING + case "DELETED": + return api.LB_STATUS_DELETED + default: + return api.LB_STATUS_UNKNOWN + } +} + +func (l7r *SLoadbalancerL7Rule) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SLoadbalancerL7Rule) IsDefault() bool { + return false +} + +func (l7r *SLoadbalancerL7Rule) IsEmulated() bool { + return false +} + +func (l7r *SLoadbalancerL7Rule) Refresh() error { + newL7r, err := l7r.policy.region.GetLoadbalancerL7RulebyId(l7r.policy.ID, l7r.ID) + if err != nil { + return err + } + return jsonutils.Update(l7r, newL7r) +} + +func (l7r *SLoadbalancerL7Rule) GetCondition() string { + return "" +} + +func (l7r *SLoadbalancerL7Rule) GetDomain() string { + return "" +} + +func (l7r *SLoadbalancerL7Rule) GetPath() string { + return l7r.Value +} + +func (l7r *SLoadbalancerL7Rule) GetProjectId() string { + return "" +} + +func (l7r *SLoadbalancerL7Rule) GetBackendGroupId() string { + return l7r.policy.RedirectPoolID +} + +func (l7r *SLoadbalancerL7Rule) Delete(ctx context.Context) error { + return l7r.policy.region.DeleteLoadbalancerListenerL7policy(l7r.policy.ID) +} diff --git a/pkg/multicloud/openstack/loadbalancerlistener.go b/pkg/multicloud/openstack/loadbalancerlistener.go new file mode 100644 index 0000000000..a38564e6b9 --- /dev/null +++ b/pkg/multicloud/openstack/loadbalancerlistener.go @@ -0,0 +1,766 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package openstack + +import ( + "context" + "fmt" + "net/url" + "strconv" + "time" + + "github.com/coredns/coredns/plugin/pkg/log" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SLoadbalancerListenerCreateParams struct { + Protocol string `json:"protocol,omitempty"` + Description string `json:"description,omitempty"` + AdminStateUp bool `json:"admin_state_up,omitempty"` + ConnectionLimit *int `json:"connection_limit,omitempty"` + ProtocolPort string `json:"protocol_port,omitempty"` + LoadbalancerID string `json:"loadbalancer_id,omitempty"` + DefaultPoolId string `json:"default_pool_id,omitempty"` + Name string `json:"name,omitempty"` + InsertHeaders SInsertHeaders `json:"insert_headers,omitempty"` + DefaultTLSContainerRef string `json:"default_tls_container_ref,omitempty"` + SniContainerRefs []string `json:"sni_container_refs,omitempty"` + TimeoutClientData *int `json:"timeout_client_data,omitempty"` + TimeoutMemberConnect *int `json:"timeout_member_connect,omitempty"` + TimeoutMemberData *int `json:"timeout_member_data,omitempty"` + TimeoutTCPInspect *int `json:"timeout_tcp_inspect,omitempty"` + Tags []string `json:"tags,omitempty"` + ClientCaTLSContainerRef string `json:"client_ca_tls_container_ref,omitempty"` + ClientAuthentication string `json:"client_authentication,omitempty"` + ClientCrlContainerRef string `json:"client_crl_container_ref,omitempty"` + AllowedCidrs []string `json:"allowed_cidrs,omitempty"` + TLSCiphers string `json:"tls_ciphers,omitempty"` + TLSVersions []string `json:"tls_versions,omitempty"` +} + +type SLoadbalancerListenerUpdateParams struct { + Description string `json:"description,omitempty"` + AdminStateUp bool `json:"admin_state_up,omitempty"` + ConnectionLimit *int `json:"connection_limit,omitempty"` + DefaultPoolId string `json:"default_pool_id,omitempty"` + Name string `json:"name,omitempty"` + InsertHeaders SInsertHeaders `json:"insert_headers,omitempty"` + DefaultTLSContainerRef string `json:"default_tls_container_ref,omitempty"` + SniContainerRefs []string `json:"sni_container_refs,omitempty"` + TimeoutClientData *int `json:"timeout_client_data,omitempty"` + TimeoutMemberConnect *int `json:"timeout_member_connect,omitempty"` + TimeoutMemberData *int `json:"timeout_member_data,omitempty"` + TimeoutTCPInspect *int `json:"timeout_tcp_inspect,omitempty"` + Tags []string `json:"tags,omitempty"` + ClientCaTLSContainerRef string `json:"client_ca_tls_container_ref,omitempty"` + ClientAuthentication string `json:"client_authentication,omitempty"` + ClientCrlContainerRef string `json:"client_crl_container_ref,omitempty"` + AllowedCidrs []string `json:"allowed_cidrs,omitempty"` + TLSCiphers string `json:"tls_ciphers,omitempty"` + TLSVersions []string `json:"tls_versions,omitempty"` +} + +type SInsertHeaders struct { + XForwardedPort string `json:"X-Forwarded-Port"` + XForwardedFor string `json:"X-Forwarded-For"` +} + +type SLoadbalancerListener struct { + region *SRegion + l7policies []SLoadbalancerL7Policy + pools []SLoadbalancerPool + Description string `json:"description"` + AdminStateUp bool `json:"admin_state_up"` + ProjectID string `json:"project_id"` + Protocol string `json:"protocol"` + ProtocolPort int `json:"protocol_port"` + ProvisioningStatus string `json:"provisioning_status"` + DefaultTLSContainerRef string `json:"default_tls_container_ref"` + LoadbalancerIds []SLoadbalancerID `json:"loadbalancers"` + InsertHeaders SInsertHeaders `json:"insert_headers"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + ID string `json:"id"` + OperatingStatus string `json:"operating_status"` + DefaultPoolID string `json:"default_pool_id"` + SniContainerRefs []string `json:"sni_container_refs"` + L7PolicieIds []SL7PolicieID `json:"l7policies"` + Name string `json:"name"` + TimeoutClientData int `json:"timeout_client_data"` + TimeoutMemberConnect int `json:"timeout_member_connect"` + TimeoutMemberData int `json:"timeout_member_data"` + TimeoutTCPInspect int `json:"timeout_tcp_inspect"` + Tags []string `json:"tags"` + ClientCaTLSContainerRef string `json:"client_ca_tls_container_ref"` + ClientAuthentication string `json:"client_authentication"` + ClientCrlContainerRef string `json:"client_crl_container_ref"` + AllowedCidrs []string `json:"allowed_cidrs"` + TLSCiphers string `json:"tls_ciphers"` + TLSVersions []string `json:"tls_versions"` +} + +func (listener *SLoadbalancerListener) GetName() string { + if len(listener.Name) == 0 { + listener.Refresh() + } + if len(listener.Name) > 0 { + return listener.Name + } + return fmt.Sprintf("HTTP:%d", listener.ProtocolPort) +} + +func (listener *SLoadbalancerListener) GetId() string { + return listener.ID +} + +func (listener *SLoadbalancerListener) GetGlobalId() string { + return listener.GetId() +} + +func (listener *SLoadbalancerListener) GetStatus() string { + switch listener.ProvisioningStatus { + case "ACTIVE": + return api.LB_STATUS_ENABLED + case "PENDING_CREATE": + return api.LB_CREATING + case "PENDING_UPDATE": + return api.LB_SYNC_CONF + case "PENDING_DELETE": + return api.LB_STATUS_DELETING + case "DELETED": + return api.LB_STATUS_DELETED + default: + return api.LB_STATUS_UNKNOWN + } +} + +func (listener *SLoadbalancerListener) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (listener *SLoadbalancerListener) IsEmulated() bool { + return false +} + +func (listener *SLoadbalancerListener) GetEgressMbps() int { + + return 0 +} + +func (region *SRegion) GetLoadbalancerListeners() ([]SLoadbalancerListener, error) { + listeners := []SLoadbalancerListener{} + resource := "/v2/lbaas/listeners" + query := url.Values{} + for { + resp, err := region.lbList(resource, query) + if err != nil { + return nil, errors.Wrap(err, "lbList") + } + part := struct { + Listeners []SLoadbalancerListener + ListenersLinks SNextLinks + }{} + err = resp.Unmarshal(&part) + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + listeners = append(listeners, part.Listeners...) + marker := part.ListenersLinks.GetNextMark() + if len(marker) == 0 { + break + } + query.Set("marker", marker) + } + + for i := 0; i < len(listeners); i++ { + listeners[i].region = region + } + for i := 0; i < len(listeners); i++ { + err := listeners[i].fetchLoadbalancerListenerL7Policies() + if err != nil { + return nil, errors.Wrap(err, "listener.fetchLoadbalancerListenerL7Policies()") + } + } + + for i := 0; i < len(listeners); i++ { + err := listeners[i].fetchLoadbalancerPools() + if err != nil { + return nil, errors.Wrap(err, "listeners[i].fetchLoadbalancerPools()") + } + } + + return listeners, nil +} + +func (region *SRegion) GetLoadbalancerListenerbyId(listenerId string) (*SLoadbalancerListener, error) { + resp, err := region.lbGet(fmt.Sprintf("/v2/lbaas/listeners/%s", listenerId)) + if err != nil { + return nil, errors.Wrapf(err, "region.Get(/v2/lbaas/listeners/%s)", listenerId) + } + listener := SLoadbalancerListener{} + err = resp.Unmarshal(&listener, "listener") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal(&listener, listener)") + } + listener.region = region + err = listener.fetchLoadbalancerListenerL7Policies() + if err != nil { + return nil, errors.Wrap(err, "listener.fetchLoadbalancerListenerL7Policies()") + } + + err = listener.fetchLoadbalancerPools() + if err != nil { + return nil, errors.Wrap(err, "listeners[i].fetchLoadbalancerPools()") + + } + return &listener, nil +} + +func (region *SRegion) CreateLoadbalancerListener(loadbalancerId string, listenerParams *cloudprovider.SLoadbalancerListener) (*SLoadbalancerListener, error) { + type CreateParams struct { + Listener SLoadbalancerListenerCreateParams `json:"listener"` + } + params := CreateParams{} + params.Listener.AdminStateUp = true + params.Listener.LoadbalancerID = loadbalancerId + params.Listener.DefaultPoolId = listenerParams.BackendGroupID + params.Listener.Protocol = LB_PROTOCOL_MAP[listenerParams.ListenerType] + params.Listener.ProtocolPort = strconv.Itoa(listenerParams.ListenerPort) + if listenerParams.ClientIdleTimeout != 0 { + // 毫秒单位 + msClientIdleTimeout := listenerParams.ClientIdleTimeout * 1000 + params.Listener.TimeoutClientData = &msClientIdleTimeout + } + if listenerParams.BackendConnectTimeout != 0 { + msBackendConnectTimeout := listenerParams.BackendConnectTimeout * 1000 + params.Listener.TimeoutMemberConnect = &msBackendConnectTimeout + } + if listenerParams.BackendIdleTimeout != 0 { + msBackendIdleTimeout := listenerParams.BackendIdleTimeout * 1000 + params.Listener.TimeoutMemberData = &msBackendIdleTimeout + } + + params.Listener.Name = listenerParams.Name + if listenerParams.XForwardedFor { + params.Listener.InsertHeaders.XForwardedFor = "true" + } + body, err := region.lbPost("/v2/lbaas/listeners", jsonutils.Marshal(params)) + if err != nil { + return nil, errors.Wrap(err, "region.Post(/v2/lbaas/listeners)") + } + slistener := SLoadbalancerListener{} + slistener.region = region + return &slistener, body.Unmarshal(&slistener, "listener") +} + +func (listener *SLoadbalancerListener) Refresh() error { + newlistener, err := listener.region.GetLoadbalancerListenerbyId(listener.ID) + if err != nil { + return errors.Wrapf(err, "listener.region.GetLoadbalancerListenerbyId(%s)", listener.ID) + } + return jsonutils.Update(listener, newlistener) +} + +func (listener *SLoadbalancerListener) GetListenerType() string { + switch listener.Protocol { + case "HTTP": + return api.LB_LISTENER_TYPE_HTTP + case "HTTPS": + return api.LB_LISTENER_TYPE_HTTPS + case "TERMINATED_HTTPS": + return api.LB_LISTENER_TYPE_TERMINATED_HTTPS + case "TCP": + return api.LB_LISTENER_TYPE_TCP + case "UDP": + return api.LB_LISTENER_TYPE_UDP + default: + return "" + } +} + +func (listener *SLoadbalancerListener) GetListenerPort() int { + return listener.ProtocolPort +} + +func (listener *SLoadbalancerListener) GetBackendGroupId() string { + return listener.DefaultPoolID +} + +func (listener *SLoadbalancerListener) GetBackendServerPort() int { + return listener.ProtocolPort +} + +func (listener *SLoadbalancerListener) GetScheduler() string { + pool, err := listener.fetchFeaturePool() + if err != nil { + log.Errorf("GetScheduler():listener.fetchFeaturePool():%s", err) + return "" + } + switch pool.LbAlgorithm { + case "ROUND_ROBIN": + return api.LB_SCHEDULER_WRR + case "LEAST_CONNECTIONS": + return api.LB_SCHEDULER_WLC + case "SOURCE_IP": + return api.LB_SCHEDULER_SCH + case "SOURCE_IP_PORT": + return api.LB_SCHEDULER_TCH + default: + return "" + } +} + +func (listener *SLoadbalancerListener) GetAclStatus() string { + if len(listener.AllowedCidrs) > 0 { + return api.LB_BOOL_ON + } + return api.LB_BOOL_OFF +} + +func (listener *SLoadbalancerListener) GetAclType() string { + return api.LB_ACL_TYPE_WHITE +} + +func (listener *SLoadbalancerListener) GetAclId() string { + return "" +} + +func (listener *SLoadbalancerListener) GetHealthCheck() string { + pool, err := listener.fetchFeaturePool() + if err != nil { + log.Errorf("GetHealthCheck():listener.fetchFeaturePool():%s", err) + return "" + } + if pool.healthmonitor != nil { + return api.LB_BOOL_ON + } + return api.LB_BOOL_OFF + +} + +func (listener *SLoadbalancerListener) GetHealthCheckType() string { + pool, err := listener.fetchFeaturePool() + if err != nil { + log.Errorf("GetHealthCheckType():listener.fetchFeaturePool():%s", err) + return "" + } + if pool.healthmonitor == nil { + return "" + } + switch pool.healthmonitor.Type { + case "HTTP": + return api.LB_HEALTH_CHECK_HTTP + case "HTTPS": + return api.LB_HEALTH_CHECK_HTTPS + case "TCP": + return api.LB_HEALTH_CHECK_TCP + case "UDP-CONNECT": + return api.LB_HEALTH_CHECK_UDP + default: + return "" + } +} + +func (listener *SLoadbalancerListener) GetHealthCheckDomain() string { + pool, err := listener.fetchFeaturePool() + if err != nil { + log.Errorf("GetHealthCheckDomain():listener.fetchFeaturePool():%s", err) + return "" + } + if pool.healthmonitor == nil { + return "" + } + return pool.healthmonitor.DomainName +} + +func (listener *SLoadbalancerListener) GetHealthCheckURI() string { + pool, err := listener.fetchFeaturePool() + if err != nil { + log.Errorf("GetHealthCheckURI():listener.fetchFeaturePool():%s", err) + return "" + } + if pool.healthmonitor == nil { + return "" + } + return pool.healthmonitor.URLPath +} + +func (listener *SLoadbalancerListener) GetHealthCheckCode() string { + pool, err := listener.fetchFeaturePool() + if err != nil { + log.Errorf("GetHealthCheckCode():listener.fetchFeaturePool():%s", err) + return "" + } + if pool.healthmonitor == nil { + return "" + } + return pool.healthmonitor.ExpectedCodes +} + +func (listener *SLoadbalancerListener) GetHealthCheckRise() int { + pool, err := listener.fetchFeaturePool() + if err != nil { + log.Errorf("GetHealthCheckRise():listener.fetchFeaturePool():%s", err) + return 0 + } + if pool.healthmonitor == nil { + return 0 + } + return pool.healthmonitor.MaxRetries +} + +func (listener *SLoadbalancerListener) GetHealthCheckFail() int { + pool, err := listener.fetchFeaturePool() + if err != nil { + log.Errorf("GetHealthCheckFail():listener.fetchFeaturePool():%s", err) + return 0 + } + if pool.healthmonitor == nil { + return 0 + } + return pool.healthmonitor.MaxRetriesDown +} + +func (listener *SLoadbalancerListener) GetHealthCheckTimeout() int { + pool, err := listener.fetchFeaturePool() + if err != nil { + log.Errorf("GetHealthCheckTimeout():listener.fetchFeaturePool():%s", err) + return 0 + } + if pool.healthmonitor == nil { + return 0 + } + return pool.healthmonitor.Timeout +} + +func (listener *SLoadbalancerListener) GetHealthCheckInterval() int { + pool, err := listener.fetchFeaturePool() + if err != nil { + log.Errorf("GetHealthCheckInterval():listener.fetchFeaturePool():%s", err) + return 0 + } + if pool.healthmonitor == nil { + return 0 + } + return pool.healthmonitor.Delay +} + +func (listener *SLoadbalancerListener) GetHealthCheckReq() string { + return "" +} + +func (listener *SLoadbalancerListener) GetHealthCheckExp() string { + return "" +} + +func (listener *SLoadbalancerListener) GetStickySession() string { + pool, err := listener.fetchFeaturePool() + if err != nil { + log.Errorf("GetStickySession():listener.fetchFeaturePool():%s", err) + return api.LB_BOOL_OFF + } + stickySession, err := pool.GetStickySession() + if err != nil { + log.Errorf("GetStickySession():listener.fetchFeaturePool():%s", err) + return api.LB_BOOL_OFF + } + if stickySession == nil { + return "" + } + return stickySession.StickySession +} + +func (listener *SLoadbalancerListener) GetStickySessionType() string { + pool, err := listener.fetchFeaturePool() + if err != nil { + log.Errorf("GetStickySession():listener.fetchFeaturePool():%s", err) + return "" + } + stickySession, err := pool.GetStickySession() + if err != nil { + log.Errorf("GetStickySession():listener.fetchFeaturePool():%s", err) + return "" + } + if stickySession == nil { + return "" + } + return stickySession.StickySessionType +} + +func (listener *SLoadbalancerListener) GetStickySessionCookie() string { + pool, err := listener.fetchFeaturePool() + if err != nil { + log.Errorf("GetStickySession():listener.fetchFeaturePool():%s", err) + return "" + } + stickySession, err := pool.GetStickySession() + if err != nil { + log.Errorf("GetStickySession():listener.fetchFeaturePool():%s", err) + return "" + } + if stickySession == nil { + return "" + } + return stickySession.StickySessionCookie +} + +func (listener *SLoadbalancerListener) GetStickySessionCookieTimeout() int { + pool, err := listener.fetchFeaturePool() + if err != nil { + log.Errorf("GetStickySession():listener.fetchFeaturePool():%s", err) + return 0 + } + stickySession, err := pool.GetStickySession() + if err != nil { + log.Errorf("GetStickySession():listener.fetchFeaturePool():%s", err) + return 0 + } + if stickySession == nil { + return 0 + } + return stickySession.StickySessionCookieTimeout +} + +func (listener *SLoadbalancerListener) XForwardedForEnabled() bool { + if listener.InsertHeaders.XForwardedFor == "true" { + return true + } + return false +} + +func (listener *SLoadbalancerListener) GzipEnabled() bool { + return false +} + +func (listener *SLoadbalancerListener) GetCertificateId() string { + return "" +} + +func (listener *SLoadbalancerListener) GetTLSCipherPolicy() string { + return listener.TLSCiphers +} + +func (listener *SLoadbalancerListener) HTTP2Enabled() bool { + return false +} + +func (listener *SLoadbalancerListener) fetchLoadbalancerListenerL7Policies() error { + l7policies := []SLoadbalancerL7Policy{} + for i := 0; i < len(listener.L7PolicieIds); i++ { + l7policy, err := listener.region.GetLoadbalancerL7PolicybyId(listener.L7PolicieIds[i].ID) + if err != nil { + return errors.Wrapf(err, "listener.region.GetLoadbalancerL7PolicybyId(%s)", listener.L7PolicieIds[i].ID) + } + l7policies = append(l7policies, *l7policy) + } + listener.l7policies = l7policies + return nil +} + +func (listener *SLoadbalancerListener) GetILoadbalancerListenerRules() ([]cloudprovider.ICloudLoadbalancerListenerRule, error) { + iRules := []cloudprovider.ICloudLoadbalancerListenerRule{} + for i := 0; i < len(listener.l7policies); i++ { + for j := 0; j < len(listener.l7policies[i].l7rules); j++ { + iRules = append(iRules, &listener.l7policies[i].l7rules[j]) + } + } + return iRules, nil +} + +func (region *SRegion) DeleteLoadbalancerListener(listenerId string) error { + _, err := region.lbDelete(fmt.Sprintf("/v2/lbaas/listeners/%s", listenerId)) + if err != nil { + return errors.Wrapf(err, `region.lbDelete(fmt.Sprintf("/v2/lbaas/listeners/%s", listenerId))`) + } + return nil +} + +func (listener *SLoadbalancerListener) Delete(ctx context.Context) error { + waitLbResStatus(listener, 10*time.Second, 1*time.Minute) + return listener.region.DeleteLoadbalancerListener(listener.ID) +} + +func (listener *SLoadbalancerListener) CreateILoadBalancerListenerRule(rule *cloudprovider.SLoadbalancerListenerRule) (cloudprovider.ICloudLoadbalancerListenerRule, error) { + l7policy, err := listener.region.CreateLoadbalancerL7Policy(listener.ID, rule) + if err != nil { + return nil, errors.Wrapf(err, `listener.region.CreateLoadbalancerL7Policy(%s, rule)`, listener.ID) + } + // async wait + err = waitLbResStatus(l7policy, 10*time.Second, 8*time.Minute) + if err != nil { + return nil, errors.Wrap(err, `waitLbResStatus(l7policy, 10*time.Second, 8*time.Minute)`) + } + + l7rule, err := listener.region.CreateLoadbalancerL7Rule(l7policy.ID, rule) + if err != nil { + return nil, errors.Wrapf(err, `listener.region.CreateLoadbalancerL7Rule(%s, rule)`, l7policy.ID) + } + l7rule.policy = l7policy + // async wait + err = waitLbResStatus(l7rule, 10*time.Second, 8*time.Minute) + if err != nil { + return nil, errors.Wrap(err, `waitLbResStatus(l7rule, 10*time.Second, 8*time.Minute)`) + } + return l7rule, nil +} + +func (listener *SLoadbalancerListener) GetILoadBalancerListenerRuleById(ruleId string) (cloudprovider.ICloudLoadbalancerListenerRule, error) { + for i := 0; i < len(listener.l7policies); i++ { + for j := 0; j < len(listener.l7policies[i].l7rules); j++ { + if listener.l7policies[i].l7rules[j].GetId() == ruleId { + return &listener.l7policies[i].l7rules[j], nil + } + } + } + return nil, nil +} + +func (listener *SLoadbalancerListener) fetchLoadbalancerPools() error { + pools := []SLoadbalancerPool{} + if len(listener.DefaultPoolID) > 0 { + defaultPool, err := listener.region.GetLoadbalancerPoolById(listener.DefaultPoolID) + if err != nil { + return errors.Wrapf(err, "listener.region.GetLoadbalancerPoolById(%s)", listener.DefaultPoolID) + } + pools = append(pools, *defaultPool) + } + for i := 0; i < len(listener.l7policies); i++ { + if len(listener.l7policies[i].RedirectPoolID) > 0 { + policyPool, err := listener.region.GetLoadbalancerPoolById(listener.l7policies[i].RedirectPoolID) + if err != nil { + return errors.Wrapf(err, "listener.region.GetLoadbalancerPoolById(%s)", listener.l7policies[i].RedirectPoolID) + } + pools = append(pools, *policyPool) + } + } + listener.pools = pools + return nil +} + +func (listener *SLoadbalancerListener) fetchFeaturePool() (*SLoadbalancerPool, error) { + if len(listener.pools) < 1 { + return nil, fmt.Errorf("can't find pool with healthmonitor") + } + for i := 0; i < len(listener.pools); i++ { + if listener.pools[i].healthmonitor != nil { + return &listener.pools[i], nil + } + } + return &listener.pools[0], nil +} + +func (region *SRegion) UpdateLoadBalancerListenerAdminStateUp(AdminStateUp bool, loadbalancerListenerId string) error { + params := jsonutils.NewDict() + poolParam := jsonutils.NewDict() + poolParam.Add(jsonutils.NewBool(AdminStateUp), "admin_state_up") + params.Add(poolParam, "listener") + _, err := region.lbUpdate(fmt.Sprintf("/v2/lbaas/listeners/%s", loadbalancerListenerId), params) + if err != nil { + return errors.Wrapf(err, `region.lbUpdate(/v2/lbaas/listeners/%s, params)`, loadbalancerListenerId) + } + return nil +} + +func (region *SRegion) UpdateLoadBalancerListener(loadbalancerListenerId string, lblis *cloudprovider.SLoadbalancerListener) error { + type UpdateParams struct { + Listener SLoadbalancerListenerUpdateParams `json:"listener"` + } + params := UpdateParams{} + params.Listener.AdminStateUp = true + params.Listener.DefaultPoolId = lblis.BackendGroupID + + if lblis.ClientIdleTimeout != 0 { + // 毫秒单位 + msClientIdleTimeout := lblis.ClientIdleTimeout * 1000 + params.Listener.TimeoutClientData = &msClientIdleTimeout + } + if lblis.BackendConnectTimeout != 0 { + msBackendConnectTimeout := lblis.BackendConnectTimeout * 1000 + params.Listener.TimeoutMemberConnect = &msBackendConnectTimeout + } + if lblis.BackendIdleTimeout != 0 { + msBackendIdleTimeout := lblis.BackendIdleTimeout * 1000 + params.Listener.TimeoutMemberData = &msBackendIdleTimeout + } + params.Listener.Name = lblis.Name + if lblis.XForwardedFor { + params.Listener.InsertHeaders.XForwardedFor = "true" + } + _, err := region.lbUpdate(fmt.Sprintf("/v2/lbaas/listeners/%s", loadbalancerListenerId), jsonutils.Marshal(params)) + if err != nil { + return errors.Wrapf(err, `region.lbUpdate(/v2/lbaas/listeners/%s, jsonutils.Marshal(params))`, loadbalancerListenerId) + } + return nil +} + +func (listener *SLoadbalancerListener) Start() error { + // ensure listener status + err := waitLbResStatus(listener, 10*time.Second, 8*time.Minute) + if err != nil { + return errors.Wrap(err, ` waitLbResStatus(listener, 10*time.Second, 8*time.Minute)`) + } + err = listener.region.UpdateLoadBalancerListenerAdminStateUp(true, listener.ID) + if err != nil { + return errors.Wrapf(err, `listener.region.UpdateLoadBalancerListenerAdminStateUp(true, %s)`, listener.ID) + } + err = waitLbResStatus(listener, 10*time.Second, 8*time.Minute) + if err != nil { + return errors.Wrap(err, `waitLbResStatus(listener, 10*time.Second, 8*time.Minute)`) + } + return nil +} + +func (listener *SLoadbalancerListener) Stop() error { + // ensure listener status + err := waitLbResStatus(listener, 10*time.Second, 8*time.Minute) + if err != nil { + return errors.Wrap(err, ` waitLbResStatus(listener, 10*time.Second, 8*time.Minute)`) + } + err = listener.region.UpdateLoadBalancerListenerAdminStateUp(false, listener.ID) + if err != nil { + return errors.Wrapf(err, `listener.region.UpdateLoadBalancerListenerAdminStateUp(false,%s)`, listener.ID) + } + err = waitLbResStatus(listener, 10*time.Second, 8*time.Minute) + if err != nil { + return errors.Wrap(err, `waitLbResStatus(listener, 10*time.Second, 8*time.Minute)`) + } + return nil +} + +func (listener *SLoadbalancerListener) Sync(ctx context.Context, lblis *cloudprovider.SLoadbalancerListener) error { + // ensure listener status + err := waitLbResStatus(listener, 10*time.Second, 8*time.Minute) + if err != nil { + return errors.Wrap(err, ` waitLbResStatus(listener, 10*time.Second, 8*time.Minute)`) + } + err = listener.region.UpdateLoadBalancerListener(listener.ID, lblis) + if err != nil { + return errors.Wrapf(err, `listener.region.UpdateLoadBalancerListener(%s, lblis)`, listener.ID) + } + err = waitLbResStatus(listener, 10*time.Second, 8*time.Minute) + if err != nil { + return errors.Wrap(err, `waitLbResStatus(listener, 10*time.Second, 8*time.Minute)`) + } + return nil +} + +func (listener *SLoadbalancerListener) GetProjectId() string { + return listener.ProjectID +} diff --git a/pkg/multicloud/openstack/loadbalancermember.go b/pkg/multicloud/openstack/loadbalancermember.go new file mode 100644 index 0000000000..3cda5b1508 --- /dev/null +++ b/pkg/multicloud/openstack/loadbalancermember.go @@ -0,0 +1,262 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package openstack + +import ( + "context" + "fmt" + "net/url" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" +) + +type SLoadbalancerMemberCreateParams struct { + Name string `json:"name,omitempty"` + Weight *int `json:"weight,omitempty"` + AdminStateUp bool `json:"admin_state_up,omitempty"` + SubnetID string `json:"subnet_id,omitempty"` + Address string `json:"address,omitempty"` + ProtocolPort *int `json:"protocol_port,omitempty"` + MonitorPort *int `json:"monitor_port,omitempty"` + Backup *bool `json:"backup,omitempty"` + Tags []string `json:"tags,omitempty"` +} + +type SLoadbalancerMember struct { + poolID string + region *SRegion + MonitorPort int `json:"monitor_port"` + ProjectID string `json:"project_id"` + Name string `json:"name"` + Weight int `json:"weight"` + Backup bool `json:"backup"` + AdminStateUp bool `json:"admin_state_up"` + SubnetID string `json:"subnet_id"` + CreatedAt string `json:"created_at"` + ProvisioningStatus string `json:"provisioning_status"` + MonitorAddress string `json:"monitor_address"` + UpdatedAt string `json:"updated_at"` + Address string `json:"address"` + ProtocolPort int `json:"protocol_port"` + ID string `json:"id"` + OperatingStatus string `json:"operating_status"` + Tags []string `json:"tags"` +} + +func (member *SLoadbalancerMember) GetName() string { + return member.Name +} + +func (member *SLoadbalancerMember) GetId() string { + return member.ID +} + +func (member *SLoadbalancerMember) GetGlobalId() string { + return member.GetId() +} + +func (member *SLoadbalancerMember) GetStatus() string { + switch member.ProvisioningStatus { + case "ACTIVE": + return api.LB_STATUS_ENABLED + case "PENDING_CREATE": + return api.LB_CREATING + case "PENDING_UPDATE": + return api.LB_SYNC_CONF + case "PENDING_DELETE": + return api.LB_STATUS_DELETING + case "DELETED": + return api.LB_STATUS_DELETED + default: + return api.LB_STATUS_UNKNOWN + } +} + +func (member *SLoadbalancerMember) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (member *SLoadbalancerMember) IsEmulated() bool { + return false +} + +func (region *SRegion) GetLoadbalancerMenberById(poolId string, MenberId string) (*SLoadbalancerMember, error) { + body, err := region.lbGet(fmt.Sprintf("/v2/lbaas/pools/%s/members/%s", poolId, MenberId)) + if err != nil { + return nil, errors.Wrapf(err, "region.Get(/v2/lbaas/pools/%s/members/%s)", poolId, MenberId) + } + member := SLoadbalancerMember{} + member.region = region + member.poolID = poolId + return &member, body.Unmarshal(&member, "member") +} + +func (region *SRegion) GetLoadbalancerMenbers(poolId string) ([]SLoadbalancerMember, error) { + members := []SLoadbalancerMember{} + resource := fmt.Sprintf("/v2/lbaas/pools/%s/members", poolId) + query := url.Values{} + for { + resp, err := region.lbList(resource, query) + if err != nil { + return nil, errors.Wrap(err, "lbList") + } + part := struct { + Members []SLoadbalancerMember + MembersLinks SNextLinks + }{} + err = resp.Unmarshal(&part) + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + members = append(members, part.Members...) + marker := part.MembersLinks.GetNextMark() + if len(marker) == 0 { + break + } + query.Set("marker", marker) + } + + for i := 0; i < len(members); i++ { + members[i].poolID = poolId + members[i].region = region + } + return members, nil +} + +// serverId 转ip,对接不准确 +func (region *SRegion) CreateLoadbalancerMember(poolId, serverId string, weight, port int) (*SLoadbalancerMember, error) { + ports, err := region.GetPorts("", serverId) + if len(ports) < 1 { + return nil, errors.Wrap(err, "server have no port") + } + fixedip := SFixedIP{} + for i := 0; i < len(ports); i++ { + if len(ports[i].FixedIps) > 0 { + fixedip = ports[i].FixedIps[0] + break + } + } + if len(fixedip.IpAddress) < 1 || len(fixedip.SubnetID) < 1 { + return nil, errors.Wrap(err, "server have no fixedip") + } + type CreateParams struct { + Member SLoadbalancerMemberCreateParams `json:"member"` + } + memberParams := CreateParams{} + memberParams.Member.AdminStateUp = true + memberParams.Member.Address = fixedip.IpAddress + memberParams.Member.SubnetID = fixedip.SubnetID + memberParams.Member.ProtocolPort = &port + memberParams.Member.Weight = &weight + + body, err := region.lbPost(fmt.Sprintf("/v2/lbaas/pools/%s/members", poolId), jsonutils.Marshal(memberParams)) + if err != nil { + return nil, errors.Wrapf(err, `region.lbPost(/v2/lbaas/pools/%s/members, jsonutils.Marshal(memberParams))`, poolId) + } + member := SLoadbalancerMember{} + member.region = region + member.poolID = poolId + return &member, body.Unmarshal(&member, "member") +} + +func (region *SRegion) DeleteLoadbalancerMember(poolId, memberId string) error { + _, err := region.lbDelete(fmt.Sprintf("/v2/lbaas/pools/%s/members/%s", poolId, memberId)) + if err != nil { + return errors.Wrapf(err, "region.lbDelete(fmt.Sprintf(/v2/lbaas/pools/%s/members/%s)", poolId, memberId) + } + return nil +} + +func (member *SLoadbalancerMember) Refresh() error { + newMember, err := member.region.GetLoadbalancerMenberById(member.poolID, member.ID) + if err != nil { + return err + } + return jsonutils.Update(member, newMember) +} + +func (member *SLoadbalancerMember) GetWeight() int { + return member.Weight +} + +func (member *SLoadbalancerMember) GetPort() int { + return member.ProtocolPort +} + +func (member *SLoadbalancerMember) GetBackendType() string { + return api.LB_BACKEND_GUEST +} + +func (member *SLoadbalancerMember) GetBackendRole() string { + return api.LB_BACKEND_ROLE_DEFAULT +} + +// 网络地址映射设备 +func (member *SLoadbalancerMember) GetBackendId() string { + ports, err := member.region.GetPorts("", "") + if err != nil { + log.Errorln(errors.Wrap(err, "member.region.GetPorts()")) + } + for i := 0; i < len(ports); i++ { + for j := 0; j < len(ports[i].FixedIps); j++ { + fixedIP := ports[i].FixedIps[j] + if fixedIP.SubnetID == member.SubnetID && fixedIP.IpAddress == member.Address { + return ports[i].DeviceID + } + } + } + return "" +} + +func (member *SLoadbalancerMember) GetProjectId() string { + return member.ProjectID +} + +func (region *SRegion) UpdateLoadBalancerMemberWtight(poolId, memberId string, weight int) error { + params := jsonutils.NewDict() + poolParam := jsonutils.NewDict() + poolParam.Add(jsonutils.NewInt(int64(weight)), "weight") + params.Add(poolParam, "member") + _, err := region.lbUpdate(fmt.Sprintf("/v2/lbaas/pools/%s/members/%s", poolId, memberId), params) + if err != nil { + return errors.Wrapf(err, "region.lbUpdate(fmt.Sprintf(/v2/lbaas/pools/%s/members/%s", poolId, memberId) + } + return nil +} + +func (member *SLoadbalancerMember) SyncConf(ctx context.Context, port, weight int) error { + if port > 0 { + log.Warningf("Elb backend SyncConf unsupport modify port") + } + // ensure member status + err := waitLbResStatus(member, 10*time.Second, 8*time.Minute) + if err != nil { + return errors.Wrap(err, "waitLbResStatus(member, 10*time.Second, 8*time.Minute)") + } + err = member.region.UpdateLoadBalancerMemberWtight(member.poolID, member.ID, weight) + if err != nil { + return errors.Wrapf(err, "member.region.UpdateLoadBalancerMemberWtight(%s,%s,%s)", member.poolID, member.ID, weight) + } + err = waitLbResStatus(member, 10*time.Second, 8*time.Minute) + if err != nil { + return errors.Wrap(err, "waitLbResStatus(member, 10*time.Second, 8*time.Minute)") + } + return nil +} diff --git a/pkg/multicloud/openstack/loadbalbacer.go b/pkg/multicloud/openstack/loadbalbacer.go new file mode 100644 index 0000000000..cc9b5b7738 --- /dev/null +++ b/pkg/multicloud/openstack/loadbalbacer.go @@ -0,0 +1,489 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http:// www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package openstack + +import ( + "context" + "fmt" + "net/url" + "time" + + "github.com/coredns/coredns/plugin/pkg/log" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +var LB_ALGORITHM_MAP = map[string]string{ + api.LB_SCHEDULER_RR: "ROUND_ROBIN", + api.LB_SCHEDULER_WRR: "ROUND_ROBIN", + api.LB_SCHEDULER_WLC: "LEAST_CONNECTIONS", + api.LB_SCHEDULER_SCH: "SOURCE_IP", + api.LB_SCHEDULER_TCH: "SOURCE_IP_PORT", +} + +var LB_PROTOCOL_MAP = map[string]string{ + api.LB_LISTENER_TYPE_HTTP: "HTTP", + api.LB_LISTENER_TYPE_HTTPS: "HTTPS", + api.LB_LISTENER_TYPE_TERMINATED_HTTPS: "TERMINATED_HTTPS", + api.LB_LISTENER_TYPE_UDP: "UDP", + api.LB_LISTENER_TYPE_TCP: "TCP", +} + +var LB_STICKY_SESSION_MAP = map[string]string{ + api.LB_STICKY_SESSION_TYPE_INSERT: "HTTP_COOKIE", + api.LB_STICKY_SESSION_TYPE_SERVER: "APP_COOKIE", +} + +var LB_HEALTHCHECK_TYPE_MAP = map[string]string{ + api.LB_HEALTH_CHECK_HTTP: "HTTP", + api.LB_HEALTH_CHECK_HTTPS: "HTTPS", + api.LB_HEALTH_CHECK_TCP: "TCP", + api.LB_HEALTH_CHECK_UDP: "UDP_CONNECT", +} + +type SLoadbalancerCreateParams struct { + Description string `json:"description,omitempty"` + AdminStateUp bool `json:"admin_state_up,omitempty"` + ProjectID string `json:"project_id,omitempty"` + VipNetworkId string `json:"vip_network_id,omitempty"` + VipSubnetID string `json:"vip_subnet_id,omitempty"` + VipAddress string `json:"vip_address,omitempty"` + Provider string `json:"provider,omitempty"` + Name string `json:"name,omitempty"` + VipQosPolicyID string `json:"vip_qos_policy_id,omitempty"` + AvailabilityZone string `json:"availability_zone,omitempty"` + Tags []string `json:"tags,omitempty"` +} + +type SLoadbalancerID struct { + ID string `json:"id"` +} + +type SPoolID struct { + ID string `json:"id"` +} + +type SMemberID struct { + ID string `json:"id"` +} + +type SListenerID struct { + ID string `json:"id"` +} + +type SL7PolicieID struct { + ID string `json:"id"` +} + +type SL7RuleID struct { + ID string `json:"id"` +} + +type SLoadbalancer struct { + multicloud.SLoadbalancerBase + region *SRegion + + Description string `json:"description"` + AdminStateUp bool `json:"admin_state_up"` + ProjectID string `json:"project_id"` + ProvisioningStatus string `json:"provisioning_status"` + FlavorID string `json:"flavor_id"` + VipSubnetID string `json:"vip_subnet_id"` + ListenerIds []SListenerID `json:"listeners"` + VipAddress string `json:"vip_address"` + VipNetworkID string `json:"vip_network_id"` + VipPortID string `json:"vip_port_id"` + Provider string `json:"provider"` + PoolIds []SPoolID `json:"pools"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + ID string `json:"id"` + OperatingStatus string `json:"operating_status"` + Name string `json:"name"` + VipQosPolicyID string `json:"vip_qos_policy_id"` + AvailabilityZone string `json:"availability_zone"` + Tags []string `json:"tags"` +} + +func waitLbResStatus(res cloudprovider.ICloudResource, interval time.Duration, timeout time.Duration) error { + err := cloudprovider.WaitMultiStatus(res, []string{api.LB_STATUS_ENABLED, api.LB_STATUS_UNKNOWN}, interval, timeout) + if err != nil { + return errors.Wrap(err, "waitLbResStatus(res, interval, timeout)") + } + if res.GetStatus() == api.LB_STATUS_UNKNOWN { + return errors.Wrap(fmt.Errorf("status error"), "check status") + } + return nil +} + +func (lb *SLoadbalancer) GetName() string { + return lb.Name +} + +func (lb *SLoadbalancer) GetId() string { + return lb.ID +} + +func (lb *SLoadbalancer) GetGlobalId() string { + return lb.ID +} + +func (lb *SLoadbalancer) GetStatus() string { + switch lb.ProvisioningStatus { + case "ACTIVE": + return api.LB_STATUS_ENABLED + case "PENDING_CREATE": + return api.LB_CREATING + case "PENDING_UPDATE": + return api.LB_SYNC_CONF + case "PENDING_DELETE": + return api.LB_STATUS_DELETING + case "DELETED": + return api.LB_STATUS_DELETED + default: + return api.LB_STATUS_UNKNOWN + } +} + +func (lb *SLoadbalancer) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (lb *SLoadbalancer) GetAddress() string { + return lb.VipAddress +} + +func (lb *SLoadbalancer) GetAddressType() string { + eip, err := lb.GetIEIP() + if err != nil { + return api.LB_ADDR_TYPE_INTRANET + } + if eip == nil { + return api.LB_ADDR_TYPE_INTRANET + } + return api.LB_ADDR_TYPE_INTERNET +} + +func (lb *SLoadbalancer) GetNetworkType() string { + network, err := lb.region.GetVpc(lb.VipNetworkID) + if err != nil { + log.Error(errors.Wrapf(err, "lb.region.GetNetwork(%s)", lb.VipNetworkID)) + } + if network.NetworkType == "flat" || network.NetworkType == "vlan" { + return api.LB_NETWORK_TYPE_CLASSIC + } + return api.LB_NETWORK_TYPE_VPC +} + +func (lb *SLoadbalancer) GetNetworkIds() []string { + return []string{lb.VipSubnetID} +} + +func (lb *SLoadbalancer) GetZoneId() string { + return lb.AvailabilityZone +} + +func (lb *SLoadbalancer) IsEmulated() bool { + return false +} + +func (lb *SLoadbalancer) GetVpcId() string { + return lb.VipNetworkID +} + +func (lb *SLoadbalancer) Refresh() error { + loadbalancer, err := lb.region.GetLoadbalancerbyId(lb.ID) + if err != nil { + return err + } + return jsonutils.Update(lb, loadbalancer) +} + +func (region *SRegion) GetLoadbalancers() ([]SLoadbalancer, error) { + loadbalancers := []SLoadbalancer{} + resource := "/v2/lbaas/loadbalancers" + query := url.Values{} + for { + resp, err := region.lbList(resource, query) + if err != nil { + return nil, errors.Wrap(err, "lbList") + } + part := struct { + Loadbalancers []SLoadbalancer + LoadbalancersLinks SNextLinks + }{} + err = resp.Unmarshal(&part) + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + loadbalancers = append(loadbalancers, part.Loadbalancers...) + marker := part.LoadbalancersLinks.GetNextMark() + if len(marker) == 0 { + break + } + query.Set("marker", marker) + } + for i := 0; i < len(loadbalancers); i++ { + loadbalancers[i].region = region + } + return loadbalancers, nil +} + +func (region *SRegion) GetLoadbalancerbyId(loadbalancerId string) (*SLoadbalancer, error) { + // region.client.Debug(true) + body, err := region.lbGet(fmt.Sprintf("/v2/lbaas/loadbalancers/%s", loadbalancerId)) + if err != nil { + return nil, errors.Wrapf(err, `region.lbGet(/v2/lbaas/loadbalancers/%s)`, loadbalancerId) + } + loadbalancer := SLoadbalancer{} + err = body.Unmarshal(&loadbalancer, "loadbalancer") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal(loadbalancer)") + } + loadbalancer.region = region + return &loadbalancer, nil +} + +func (region *SRegion) CreateLoadBalancer(loadbalancer *cloudprovider.SLoadbalancer) (*SLoadbalancer, error) { + type CreateParams struct { + Loadbalancer SLoadbalancerCreateParams `json:"loadbalancer"` + } + params := CreateParams{} + params.Loadbalancer.AdminStateUp = true + params.Loadbalancer.AvailabilityZone = loadbalancer.ZoneID + params.Loadbalancer.Name = loadbalancer.Name + params.Loadbalancer.ProjectID = loadbalancer.ProjectId + params.Loadbalancer.VipSubnetID = loadbalancer.NetworkIDs[0] + params.Loadbalancer.VipAddress = loadbalancer.Address + + body, err := region.lbPost("/v2/lbaas/loadbalancers", jsonutils.Marshal(params)) + if err != nil { + return nil, errors.Wrap(err, `region.lbPost("/v2/lbaas/loadbalancers", jsonutils.Marshal(params))`) + } + sloadbalancer := SLoadbalancer{} + err = body.Unmarshal(&sloadbalancer, "loadbalancer") + if err != nil { + return nil, errors.Wrap(err, "body.Unmarshal(sloadbalancer, loadbalancer)") + } + sloadbalancer.region = region + if len(loadbalancer.EipID) > 0 { + err = region.AssociateEipWithPortId(sloadbalancer.VipPortID, loadbalancer.EipID) + if err != nil { + return nil, errors.Wrapf(err, "region.AssociateEipWithPortId(%s, %s)", sloadbalancer.VipPortID, loadbalancer.EipID) + } + } + return &sloadbalancer, nil +} + +func (region *SRegion) DeleteLoadbalancer(loadbalancerId string) error { + _, err := region.lbDelete(fmt.Sprintf("/v2/lbaas/loadbalancers/%s?cascade=True", loadbalancerId)) + if err != nil { + return errors.Wrapf(err, `region.lbDelete(/v2/lbaas/loadbalancers/%s?cascade=True)`, loadbalancerId) + } + return nil +} + +func (lb *SLoadbalancer) Delete(ctx context.Context) error { + return lb.region.DeleteLoadbalancer(lb.ID) +} + +func (lb *SLoadbalancer) GetILoadBalancerBackendGroups() ([]cloudprovider.ICloudLoadbalancerBackendGroup, error) { + ibackendgroups := []cloudprovider.ICloudLoadbalancerBackendGroup{} + for i := 0; i < len(lb.PoolIds); i++ { + pool, err := lb.region.GetLoadbalancerPoolById(lb.PoolIds[i].ID) + if err != nil { + return nil, errors.Wrapf(err, "lb.region.GetLoadbalancerPoolById(%s)", lb.PoolIds[i].ID) + } + ibackendgroups = append(ibackendgroups, pool) + } + return ibackendgroups, nil +} + +func (lb *SLoadbalancer) CreateILoadBalancerBackendGroup(group *cloudprovider.SLoadbalancerBackendGroup) (cloudprovider.ICloudLoadbalancerBackendGroup, error) { + // ensure lb status + err := waitLbResStatus(lb, 10*time.Second, 8*time.Minute) + if err != nil { + return nil, errors.Wrap(err, "waitLbResStatus(lb, api.LB_STATUS_ENABLED, 10*time.Second, 8*time.Minute)") + } + // create pool + spool, err := lb.region.CreateLoadbalancerPool(group) + if err != nil { + return nil, errors.Wrap(err, "lb.region.CreateLoadbalancerPool") + } + // wait spool + err = waitLbResStatus(spool, 10*time.Second, 8*time.Minute) + if err != nil { + return nil, errors.Wrap(err, "waitLbResStatus(spool, 10*time.Second, 8*time.Minute)") + } + // create healthmonitor + if group.HealthCheck != nil { + + healthmonitor, err := lb.region.CreateLoadbalancerHealthmonitor(spool.ID, group.HealthCheck) + if err != nil { + return nil, errors.Wrapf(err, "region.CreateLoadbalancerHealthmonitor(%s, group.HealthCheck)", spool.ID) + } + spool.healthmonitor = healthmonitor + } + // wait health monitor + if spool.healthmonitor != nil { + err = waitLbResStatus(spool.healthmonitor, 10*time.Second, 8*time.Minute) + if err != nil { + return nil, errors.Wrap(err, "waitLbResStatus(spool.healthmonitor, 10*time.Second, 8*time.Minute)") + } + } + return spool, nil +} + +func (lb *SLoadbalancer) CreateILoadBalancerListener(ctx context.Context, listener *cloudprovider.SLoadbalancerListener) (cloudprovider.ICloudLoadbalancerListener, error) { + // ensure lb status + err := waitLbResStatus(lb, 10*time.Second, 8*time.Minute) + if err != nil { + return nil, errors.Wrap(err, "waitLbResStatus(lb, api.LB_STATUS_ENABLED, 10*time.Second, 8*time.Minute)") + } + slistener, err := lb.region.CreateLoadbalancerListener(lb.ID, listener) + if err != nil { + return nil, errors.Wrapf(err, "lb.region.CreateLoadbalancerListener(%s, listener)", lb.ID) + } + return slistener, nil +} + +func (lb *SLoadbalancer) GetLoadbalancerSpec() string { + return lb.Description +} + +func (lb *SLoadbalancer) GetChargeType() string { + eip, err := lb.GetIEIP() + if err != nil { + log.Errorf("lb.GetIEIP():", err) + } + if err != nil { + return eip.GetInternetChargeType() + } + + return api.EIP_CHARGE_TYPE_BY_TRAFFIC +} + +func (lb *SLoadbalancer) GetEgressMbps() int { + return 0 +} + +func (lb *SLoadbalancer) GetILoadBalancerBackendGroupById(poolId string) (cloudprovider.ICloudLoadbalancerBackendGroup, error) { + err := lb.Refresh() + if err != nil { + return nil, errors.Wrap(err, "lb.Refresh()") + } + index := -1 + for i := 0; i < len(lb.PoolIds); i++ { + if poolId == lb.PoolIds[i].ID { + index = i + } + } + if index < 0 { + return nil, cloudprovider.ErrNotFound + } + spool, err := lb.region.GetLoadbalancerPoolById(poolId) + if err != nil { + return nil, errors.Wrapf(err, "lb.region.GetLoadbalancerPoolById(%s)", poolId) + } + if spool.GetStatus() == api.LB_STATUS_DELETING { + return nil, cloudprovider.ErrNotFound + } + return spool, nil +} + +func (lb *SLoadbalancer) GetIEIP() (cloudprovider.ICloudEIP, error) { + eips, err := lb.region.GetEips("") + if err != nil { + return nil, errors.Wrapf(err, "lb.region.GetEips()") + } + for _, eip := range eips { + if eip.PortId == lb.VipPortID { + return &eip, nil + } + } + return nil, nil +} + +func (region *SRegion) UpdateLoadBalancerAdminStateUp(AdminStateUp bool, loadbalancerId string) error { + params := jsonutils.NewDict() + poolParam := jsonutils.NewDict() + poolParam.Add(jsonutils.NewBool(AdminStateUp), "admin_state_up") + params.Add(poolParam, "loadbalancer") + _, err := region.lbUpdate(fmt.Sprintf("/v2/lbaas/loadbalancers/%s", loadbalancerId), params) + if err != nil { + return errors.Wrapf(err, `region.lbUpdate(/v2/lbaas/loadbalancers/%s), params)`, loadbalancerId) + } + return nil +} + +func (lb *SLoadbalancer) Start() error { + // ensure lb status + err := waitLbResStatus(lb, 10*time.Second, 8*time.Minute) + if err != nil { + return errors.Wrap(err, "waitLbResStatus(lb, api.LB_STATUS_ENABLED, 10*time.Second, 8*time.Minute)") + } + err = lb.region.UpdateLoadBalancerAdminStateUp(true, lb.ID) + if err != nil { + return errors.Wrapf(err, "lb.region.UpdateLoadBalancerAdminStateUp(true, %s)", lb.ID) + } + err = waitLbResStatus(lb, 10*time.Second, 8*time.Minute) + if err != nil { + return errors.Wrap(err, "waitLbResStatus(lb, 10*time.Second, 8*time.Minute)") + } + return nil +} + +func (lb *SLoadbalancer) Stop() error { + // ensure lb status + err := waitLbResStatus(lb, 10*time.Second, 8*time.Minute) + if err != nil { + return errors.Wrap(err, "waitLbResStatus(lb, api.LB_STATUS_ENABLED, 10*time.Second, 8*time.Minute)") + } + err = lb.region.UpdateLoadBalancerAdminStateUp(false, lb.ID) + if err != nil { + return errors.Wrapf(err, "lb.region.UpdateLoadBalancerAdminStateUp(false,%s)", lb.ID) + } + err = waitLbResStatus(lb, 10*time.Second, 8*time.Minute) + if err != nil { + return errors.Wrap(err, "waitLbResStatus(lb, 10*time.Second, 8*time.Minute)") + } + return nil +} + +func (lb *SLoadbalancer) GetILoadBalancerListenerById(listenerId string) (cloudprovider.ICloudLoadbalancerListener, error) { + + return lb.region.GetLoadbalancerListenerbyId(listenerId) +} + +func (lb *SLoadbalancer) GetILoadBalancerListeners() ([]cloudprovider.ICloudLoadbalancerListener, error) { + ilisteners := []cloudprovider.ICloudLoadbalancerListener{} + for i := 0; i < len(lb.ListenerIds); i++ { + listener, err := lb.region.GetLoadbalancerListenerbyId(lb.ListenerIds[i].ID) + if err != nil { + return nil, errors.Wrapf(err, "lb.region.GetLoadbalancerListenerbyId(%s)", lb.ListenerIds[i].ID) + } + ilisteners = append(ilisteners, listener) + } + return ilisteners, nil +} + +func (lb *SLoadbalancer) GetProjectId() string { + return lb.ProjectID +} diff --git a/pkg/multicloud/openstack/loadbalbancerhealthmonitor.go b/pkg/multicloud/openstack/loadbalbancerhealthmonitor.go new file mode 100644 index 0000000000..dfffc00f20 --- /dev/null +++ b/pkg/multicloud/openstack/loadbalbancerhealthmonitor.go @@ -0,0 +1,226 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package openstack + +import ( + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SLoadbalancerHealthmonitorCreateParams struct { + Name string `json:"name,omitempty"` + AdminStateUp bool `json:"admin_state_up"` + PoolID string `json:"pool_id,omitempty"` + Delay *int `json:"delay,omitempty"` + ExpectedCodes string `json:"expected_codes,omitempty"` + MaxRetries *int `json:"max_retries,omitempty"` + HTTPMethod string `json:"http_method,omitempty"` + Timeout *int `json:"timeout,omitempty"` + URLPath string `json:"url_path,omitempty"` + Type string `json:"type,omitempty"` + MaxRetriesDown *int `json:"max_retries_down,omitempty"` + Tags []string `json:"tags,omitempty"` + HTTPVersion *float64 `json:"http_version,omitempty"` + DomainName string `json:"domain_name,omitempty"` +} + +type SLoadbalancerHealthmonitorUpdateParams struct { + Name string `json:"name,omitempty"` + AdminStateUp bool `json:"admin_state_up"` + Delay *int `json:"delay,omitempty"` + ExpectedCodes string `json:"expected_codes,omitempty"` + MaxRetries *int `json:"max_retries,omitempty"` + HTTPMethod string `json:"http_method,omitempty"` + Timeout *int `json:"timeout,omitempty"` + URLPath string `json:"url_path,omitempty"` + MaxRetriesDown *int `json:"max_retries_down,omitempty"` + Tags []string `json:"tags,omitempty"` + HTTPVersion *float64 `json:"http_version,omitempty"` + DomainName string `json:"domain_name,omitempty"` +} + +type SLoadbalancerHealthmonitor struct { + region *SRegion + ProjectID string `json:"project_id"` + Name string `json:"name"` + AdminStateUp bool `json:"admin_state_up"` + PoolIds []SPoolID `json:"pools"` + CreatedAt string `json:"created_at"` + ProvisioningStatus string `json:"provisioning_status"` + UpdatedAt string `json:"updated_at"` + Delay int `json:"delay"` + ExpectedCodes string `json:"expected_codes"` + MaxRetries int `json:"max_retries"` + HTTPMethod string `json:"http_method"` + Timeout int `json:"timeout"` + MaxRetriesDown int `json:"max_retries_down"` + URLPath string `json:"url_path"` + Type string `json:"type"` + ID string `json:"id"` + OperatingStatus string `json:"operating_status"` + Tags []string `json:"tags"` + HTTPVersion float64 `json:"http_version"` + DomainName string `json:"domain_name"` +} + +func (region *SRegion) GetLoadbalancerHealthmonitorById(healthmonitorId string) (*SLoadbalancerHealthmonitor, error) { + body, err := region.lbGet(fmt.Sprintf("/v2/lbaas/healthmonitors/%s", healthmonitorId)) + if err != nil { + return nil, errors.Wrapf(err, `region.lbGet(/v2/lbaas/healthmonitors/%s)`, healthmonitorId) + } + healthmonitor := SLoadbalancerHealthmonitor{} + healthmonitor.region = region + return &healthmonitor, body.Unmarshal(&healthmonitor, "healthmonitor") +} +func (region *SRegion) CreateLoadbalancerHealthmonitor(poolId string, healthcheck *cloudprovider.SLoadbalancerHealthCheck) (*SLoadbalancerHealthmonitor, error) { + type CreateParams struct { + Healthmonitor SLoadbalancerHealthmonitorCreateParams `json:"healthmonitor"` + } + params := CreateParams{} + params.Healthmonitor.AdminStateUp = true + params.Healthmonitor.Delay = &healthcheck.HealthCheckInterval + params.Healthmonitor.Timeout = &healthcheck.HealthCheckTimeout + + params.Healthmonitor.MaxRetries = &healthcheck.HealthCheckRise + params.Healthmonitor.MaxRetriesDown = &healthcheck.HealthCheckFail + params.Healthmonitor.PoolID = poolId + + switch healthcheck.HealthCheckType { + case api.LB_HEALTH_CHECK_TCP: + params.Healthmonitor.Type = "TCP" + case api.LB_HEALTH_CHECK_UDP: + params.Healthmonitor.Type = "UDP-CONNECT" + case api.LB_HEALTH_CHECK_HTTP: + params.Healthmonitor.Type = "HTTP" + case api.LB_HEALTH_CHECK_HTTPS: + params.Healthmonitor.Type = "HTTPS" + case api.LB_HEALTH_CHECK_PING: + params.Healthmonitor.Type = "PING" + default: + params.Healthmonitor.Type = "PING" + } + + if params.Healthmonitor.Type == "HTTP" || params.Healthmonitor.Type == "HTTPS" { + params.Healthmonitor.HTTPMethod = "GET" + httpVersion := 1.1 + params.Healthmonitor.HTTPVersion = &httpVersion + params.Healthmonitor.DomainName = healthcheck.HealthCheckDomain + params.Healthmonitor.URLPath = healthcheck.HealthCheckURI + params.Healthmonitor.ExpectedCodes = ToOpenstackHealthCheckHttpCode(healthcheck.HealthCheckHttpCode) + } + + body, err := region.lbPost("/v2/lbaas/healthmonitors", jsonutils.Marshal(params)) + if err != nil { + return nil, errors.Wrap(err, `region.lbPost("/v2/lbaas/healthmonitors", jsonutils.Marshal(params))`) + } + shealthmonitor := SLoadbalancerHealthmonitor{} + shealthmonitor.region = region + err = body.Unmarshal(&shealthmonitor, "healthmonitor") + if err != nil { + return nil, errors.Wrap(err, "body.Unmarshal(&shealthmonitor, healthmonitor)") + } + return &shealthmonitor, nil +} + +func (region *SRegion) UpdateLoadbalancerHealthmonitor(healthmonitorId string, healthcheck *cloudprovider.SLoadbalancerHealthCheck) (*SLoadbalancerHealthmonitor, error) { + type UpdateParams struct { + Healthmonitor SLoadbalancerHealthmonitorUpdateParams `json:"healthmonitor"` + } + params := UpdateParams{} + params.Healthmonitor.AdminStateUp = true + params.Healthmonitor.Delay = &healthcheck.HealthCheckInterval + params.Healthmonitor.Timeout = &healthcheck.HealthCheckTimeout + + params.Healthmonitor.MaxRetries = &healthcheck.HealthCheckRise + params.Healthmonitor.MaxRetriesDown = &healthcheck.HealthCheckFail + + if healthcheck.HealthCheckType == api.LB_HEALTH_CHECK_HTTP || healthcheck.HealthCheckType == api.LB_HEALTH_CHECK_HTTPS { + params.Healthmonitor.HTTPMethod = "GET" + httpVersion := 1.1 + params.Healthmonitor.HTTPVersion = &httpVersion + params.Healthmonitor.DomainName = healthcheck.HealthCheckDomain + params.Healthmonitor.URLPath = healthcheck.HealthCheckURI + params.Healthmonitor.ExpectedCodes = ToOpenstackHealthCheckHttpCode(healthcheck.HealthCheckHttpCode) + } + + body, err := region.lbUpdate(fmt.Sprintf("/v2/lbaas/healthmonitors/%s", healthmonitorId), jsonutils.Marshal(params)) + if err != nil { + return nil, errors.Wrapf(err, `region.lbUpdate(/v2/lbaas/healthmonitors/%s), jsonutils.Marshal(params))`, healthmonitorId) + } + shealthmonitor := SLoadbalancerHealthmonitor{} + err = body.Unmarshal(&shealthmonitor, "healthmonitor") + shealthmonitor.region = region + if err != nil { + return nil, errors.Wrap(err, "body.Unmarshal(&shealthmonitor, healthmonitor)") + } + return &shealthmonitor, nil +} + +func (region *SRegion) DeleteLoadbalancerHealthmonitor(healthmonitorId string) error { + _, err := region.lbDelete(fmt.Sprintf("/v2/lbaas/healthmonitors/%s", healthmonitorId)) + if err != nil { + return errors.Wrapf(err, `region.lbDelete(/v2/lbaas/healthmonitors/%s )`, healthmonitorId) + } + return nil +} + +func (healthmonitor *SLoadbalancerHealthmonitor) GetName() string { + return healthmonitor.Name +} + +func (healthmonitor *SLoadbalancerHealthmonitor) GetId() string { + return healthmonitor.ID +} + +func (healthmonitor *SLoadbalancerHealthmonitor) GetGlobalId() string { + return healthmonitor.ID +} + +func (healthmonitor *SLoadbalancerHealthmonitor) GetStatus() string { + switch healthmonitor.ProvisioningStatus { + case "ACTIVE": + return api.LB_STATUS_ENABLED + case "PENDING_CREATE": + return api.LB_CREATING + case "PENDING_UPDATE": + return api.LB_SYNC_CONF + case "PENDING_DELETE": + return api.LB_STATUS_DELETING + case "DELETED": + return api.LB_STATUS_DELETED + default: + return api.LB_STATUS_UNKNOWN + } +} + +func (healthmonitor *SLoadbalancerHealthmonitor) Refresh() error { + newhealthmonitor, err := healthmonitor.region.GetLoadbalancerHealthmonitorById(healthmonitor.ID) + if err != nil { + return err + } + return jsonutils.Update(healthmonitor, newhealthmonitor) +} + +func (healthmonitor *SLoadbalancerHealthmonitor) IsEmulated() bool { + return false +} +func (healthmonitor *SLoadbalancerHealthmonitor) GetMetadata() *jsonutils.JSONDict { + return nil +} diff --git a/pkg/multicloud/openstack/openstack.go b/pkg/multicloud/openstack/openstack.go index 773d1de8bf..2fdf1ba825 100644 --- a/pkg/multicloud/openstack/openstack.go +++ b/pkg/multicloud/openstack/openstack.go @@ -38,13 +38,14 @@ const ( CLOUD_PROVIDER_OPENSTACK = api.CLOUD_PROVIDER_OPENSTACK OPENSTACK_DEFAULT_REGION = "RegionOne" - OPENSTACK_SERVICE_COMPUTE = "compute" - OPENSTACK_SERVICE_NETWORK = "network" - OPENSTACK_SERVICE_IDENTITY = "identity" - OPENSTACK_SERVICE_VOLUMEV3 = "volumev3" - OPENSTACK_SERVICE_VOLUMEV2 = "volumev2" - OPENSTACK_SERVICE_VOLUME = "volume" - OPENSTACK_SERVICE_IMAGE = "image" + OPENSTACK_SERVICE_COMPUTE = "compute" + OPENSTACK_SERVICE_NETWORK = "network" + OPENSTACK_SERVICE_IDENTITY = "identity" + OPENSTACK_SERVICE_VOLUMEV3 = "volumev3" + OPENSTACK_SERVICE_VOLUMEV2 = "volumev2" + OPENSTACK_SERVICE_VOLUME = "volume" + OPENSTACK_SERVICE_IMAGE = "image" + OPENSTACK_SERVICE_LOADBALANCER = "load-balancer" ErrNoEndpoint = errors.Error("no valid endpoint") ) @@ -373,6 +374,10 @@ func (cli *SOpenStackClient) imageUpload(region, url string, body io.Reader) (*h return session.RawRequest(OPENSTACK_SERVICE_IMAGE, "", httputils.PUT, url, header, body) } +func (cli *SOpenStackClient) lbRequest(region string, method httputils.THttpMethod, resource string, query url.Values, body interface{}) (jsonutils.JSONObject, error) { + return jsonReuest(cli.tokenCredential, OPENSTACK_SERVICE_LOADBALANCER, region, cli.endpointType, method, resource, query, body, cli.debug) +} + func (cli *SOpenStackClient) fetchToken() error { if cli.tokenCredential != nil { return nil @@ -533,7 +538,7 @@ func (self *SOpenStackClient) GetCapabilities() []string { cloudprovider.CLOUD_CAPABILITY_PROJECT, cloudprovider.CLOUD_CAPABILITY_COMPUTE, cloudprovider.CLOUD_CAPABILITY_NETWORK, - // cloudprovider.CLOUD_CAPABILITY_LOADBALANCER, + cloudprovider.CLOUD_CAPABILITY_LOADBALANCER, // cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE, // cloudprovider.CLOUD_CAPABILITY_RDS, // cloudprovider.CLOUD_CAPABILITY_CACHE, diff --git a/pkg/multicloud/openstack/region.go b/pkg/multicloud/openstack/region.go index 495547d27a..195f3dc385 100644 --- a/pkg/multicloud/openstack/region.go +++ b/pkg/multicloud/openstack/region.go @@ -18,6 +18,7 @@ import ( "fmt" "io" "net/url" + "time" "yunion.io/x/jsonutils" "yunion.io/x/pkg/errors" @@ -43,7 +44,15 @@ type SRegion struct { } func (region *SRegion) GetILoadBalancerBackendGroups() ([]cloudprovider.ICloudLoadbalancerBackendGroup, error) { - return nil, cloudprovider.ErrNotImplemented + backendGroups := []cloudprovider.ICloudLoadbalancerBackendGroup{} + pools, err := region.GetLoadbalancerPools() + if err != nil { + return backendGroups, errors.Wrap(err, "region.GetLoadbalancerPools()") + } + for i := 0; i < len(pools); i++ { + backendGroups = append(backendGroups, &pools[i]) + } + return backendGroups, nil } func (region *SRegion) GetClient() *SOpenStackClient { @@ -354,6 +363,28 @@ func (region *SRegion) bsCreate(projectId, resource string, params interface{}) return region.client.bsCreate(projectId, region.Name, resource, params) } +//loadbalancer + +func (region *SRegion) lbList(resource string, query url.Values) (jsonutils.JSONObject, error) { + return region.client.lbRequest(region.Name, httputils.GET, resource, query, nil) +} + +func (region *SRegion) lbGet(resource string) (jsonutils.JSONObject, error) { + return region.client.lbRequest(region.Name, httputils.GET, resource, nil, nil) +} + +func (region *SRegion) lbUpdate(resource string, params interface{}) (jsonutils.JSONObject, error) { + return region.client.lbRequest(region.Name, httputils.PUT, resource, nil, params) +} + +func (region *SRegion) lbPost(resource string, params interface{}) (jsonutils.JSONObject, error) { + return region.client.lbRequest(region.Name, httputils.POST, resource, nil, params) +} + +func (region *SRegion) lbDelete(resource string) (jsonutils.JSONObject, error) { + return region.client.lbRequest(region.Name, httputils.DELETE, resource, nil, nil) +} + func (region *SRegion) ProjectId() string { return region.client.tokenCredential.GetProjectId() } @@ -413,15 +444,27 @@ func (region *SRegion) GetIEipById(eipId string) (cloudprovider.ICloudEIP, error } func (region *SRegion) GetILoadBalancers() ([]cloudprovider.ICloudLoadbalancer, error) { - return nil, cloudprovider.ErrNotImplemented + loadbalancers := []cloudprovider.ICloudLoadbalancer{} + sloadbalancers, err := region.GetLoadbalancers() + if err != nil { + return nil, errors.Wrap(err, "region.GetLoadbalancers()") + } + for i := 0; i < len(sloadbalancers); i++ { + loadbalancers = append(loadbalancers, &sloadbalancers[i]) + } + return loadbalancers, nil } func (region *SRegion) GetILoadBalancerById(loadbalancerId string) (cloudprovider.ICloudLoadbalancer, error) { - return nil, cloudprovider.ErrNotImplemented + sloadbalancer, err := region.GetLoadbalancerbyId(loadbalancerId) + if err != nil { + return nil, errors.Wrapf(err, "region.GetLoadbalancerbyId(%s)", loadbalancerId) + } + return sloadbalancer, nil } func (region *SRegion) GetILoadBalancerAclById(aclId string) (cloudprovider.ICloudLoadbalancerAcl, error) { - return nil, cloudprovider.ErrNotImplemented + return region.GetLoadbalancerAclDetail(aclId) } func (region *SRegion) GetILoadBalancerCertificateById(certId string) (cloudprovider.ICloudLoadbalancerCertificate, error) { @@ -433,7 +476,15 @@ func (region *SRegion) CreateILoadBalancerCertificate(cert *cloudprovider.SLoadb } func (region *SRegion) GetILoadBalancerAcls() ([]cloudprovider.ICloudLoadbalancerAcl, error) { - return nil, cloudprovider.ErrNotImplemented + iloadbalancerAcls := []cloudprovider.ICloudLoadbalancerAcl{} + acls, err := region.GetLoadBalancerAcls() + if err != nil { + return nil, errors.Wrap(err, "region.GetLoadBalancerAcls") + } + for i := 0; i < len(acls); i++ { + iloadbalancerAcls = append(iloadbalancerAcls, &acls[i]) + } + return iloadbalancerAcls, nil } func (region *SRegion) GetILoadBalancerCertificates() ([]cloudprovider.ICloudLoadbalancerCertificate, error) { @@ -441,11 +492,26 @@ func (region *SRegion) GetILoadBalancerCertificates() ([]cloudprovider.ICloudLoa } func (region *SRegion) CreateILoadBalancer(loadbalancer *cloudprovider.SLoadbalancer) (cloudprovider.ICloudLoadbalancer, error) { - return nil, cloudprovider.ErrNotImplemented + sloadbalancer, err := region.CreateLoadBalancer(loadbalancer) + if err != nil { + return nil, errors.Wrap(err, "region.CreateLoadBalancer") + } + return sloadbalancer, nil } func (region *SRegion) CreateILoadBalancerAcl(acl *cloudprovider.SLoadbalancerAccessControlList) (cloudprovider.ICloudLoadbalancerAcl, error) { - return nil, cloudprovider.ErrNotImplemented + sacl, err := region.CreateLoadBalancerAcl(acl) + if err != nil { + return nil, errors.Wrap(err, "region.CreateLoadBalancerAcl(acl)") + } + err = cloudprovider.WaitMultiStatus(sacl.listener, []string{api.LB_STATUS_ENABLED, api.LB_STATUS_UNKNOWN}, 10*time.Second, 8*time.Minute) + if err != nil { + return nil, errors.Wrap(err, "cloudprovider.WaitMultiStatus") + } + if sacl.listener.GetStatus() == api.LB_STATUS_UNKNOWN { + return nil, errors.Wrap(fmt.Errorf("status error"), "check status") + } + return sacl, nil } func (region *SRegion) GetISkus() ([]cloudprovider.ICloudSku, error) { diff --git a/pkg/multicloud/openstack/shell/loadbalancer.go b/pkg/multicloud/openstack/shell/loadbalancer.go new file mode 100644 index 0000000000..8a30e3477a --- /dev/null +++ b/pkg/multicloud/openstack/shell/loadbalancer.go @@ -0,0 +1,83 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/openstack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type LoadbalancerListOptions struct { + } + type LoadbalancerPoolListOptions struct { + } + type LoadbalancerListenerListOptions struct { + } + type LoadbalancerOptions struct { + ID string `help:"LoadbalancerId"` + } + type LoadbalancerListenerOptions struct { + ID string `help:"LoadbalancerListenerId"` + } + + shellutils.R(&LoadbalancerListOptions{}, "lb-list", "List loadbalancers", func(cli *openstack.SRegion, args *LoadbalancerListOptions) error { + loadbalancers, err := cli.GetLoadbalancers() + if err != nil { + return err + } + printObject(loadbalancers) + return nil + }) + shellutils.R(&LoadbalancerOptions{}, "lb-show", "Show loadbalancer", func(cli *openstack.SRegion, args *LoadbalancerOptions) error { + loadbalancer, err := cli.GetLoadbalancerbyId(args.ID) + if err != nil { + return err + } + printObject(loadbalancer) + return nil + }) + shellutils.R(&LoadbalancerOptions{}, "lb-delete", "delete loadbalancer", func(cli *openstack.SRegion, args *LoadbalancerOptions) error { + err := cli.DeleteLoadbalancer(args.ID) + if err != nil { + return err + } + return nil + }) + shellutils.R(&LoadbalancerPoolListOptions{}, "lbpool-list", "List loadbalancers pool", func(cli *openstack.SRegion, args *LoadbalancerPoolListOptions) error { + loadbalancers, err := cli.GetLoadbalancerPools() + if err != nil { + return err + } + printObject(loadbalancers) + return nil + }) + + shellutils.R(&LoadbalancerListenerListOptions{}, "lblistener-list", "List loadbalancers listener", func(cli *openstack.SRegion, args *LoadbalancerListenerListOptions) error { + loadbalancers, err := cli.GetLoadbalancerListeners() + if err != nil { + return err + } + printObject(loadbalancers) + return nil + }) + shellutils.R(&LoadbalancerOptions{}, "lblistener-delete", "Delete loadbalancer listener", func(cli *openstack.SRegion, args *LoadbalancerOptions) error { + err := cli.DeleteLoadbalancerListener(args.ID) + if err != nil { + return err + } + return nil + }) +} diff --git a/pkg/multicloud/openstack/vpc.go b/pkg/multicloud/openstack/vpc.go index 442fb302c1..66490ee57d 100644 --- a/pkg/multicloud/openstack/vpc.go +++ b/pkg/multicloud/openstack/vpc.go @@ -62,7 +62,7 @@ type SVpc struct { VlanTransparent bool Fescription string IsDefault bool - NetworkType string `json:"provider:network_type"` + NetworkType string `json:"provider:network_type"` // flat, vlan, vxlan, or gre ... PhysicalNetwork string `json:"provider:physical_network"` SegmentationId string `json:"provider:segmentation_id"` }