diff --git a/pkg/apis/compute/cloudaccount_const.go b/pkg/apis/compute/cloudaccount_const.go index 30c644fe9a..638b63898f 100644 --- a/pkg/apis/compute/cloudaccount_const.go +++ b/pkg/apis/compute/cloudaccount_const.go @@ -15,6 +15,7 @@ package compute import ( + "yunion.io/x/onecloud/pkg/apis" "yunion.io/x/onecloud/pkg/cloudprovider" ) @@ -103,9 +104,9 @@ const ( ) const ( - CLOUD_ACCOUNT_SHARE_MODE_ACCOUNT_DOMAIN = "account_domain" - CLOUD_ACCOUNT_SHARE_MODE_SYSTEM = "system" - CLOUD_ACCOUNT_SHARE_MODE_PROVIDER_DOMAIN = "provider_domain" + CLOUD_ACCOUNT_SHARE_MODE_ACCOUNT_DOMAIN = apis.CLOUD_ACCOUNT_SHARE_MODE_ACCOUNT_DOMAIN + CLOUD_ACCOUNT_SHARE_MODE_SYSTEM = apis.CLOUD_ACCOUNT_SHARE_MODE_SYSTEM + CLOUD_ACCOUNT_SHARE_MODE_PROVIDER_DOMAIN = apis.CLOUD_ACCOUNT_SHARE_MODE_PROVIDER_DOMAIN ) var ( diff --git a/pkg/apis/share.go b/pkg/apis/share.go index 90a3e028a0..31f0bfda5f 100644 --- a/pkg/apis/share.go +++ b/pkg/apis/share.go @@ -14,7 +14,16 @@ package apis -import "yunion.io/x/onecloud/pkg/util/rbacutils" +import ( + "yunion.io/x/onecloud/pkg/util/rbacutils" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +const ( + CLOUD_ACCOUNT_SHARE_MODE_ACCOUNT_DOMAIN = "account_domain" + CLOUD_ACCOUNT_SHARE_MODE_SYSTEM = "system" + CLOUD_ACCOUNT_SHARE_MODE_PROVIDER_DOMAIN = "provider_domain" +) type SAccountShareInfo struct { IsPublic bool @@ -22,3 +31,139 @@ type SAccountShareInfo struct { ShareMode string SharedDomains []string } + +type SShareInfo struct { + IsPublic bool + PublicScope rbacutils.TRbacScope + SharedDomains []string + SharedProjects []string +} + +func (i SAccountShareInfo) GetProjectShareInfo() SShareInfo { + ret := SShareInfo{} + switch i.ShareMode { + case CLOUD_ACCOUNT_SHARE_MODE_ACCOUNT_DOMAIN: + ret.IsPublic = true + ret.PublicScope = rbacutils.ScopeDomain + case CLOUD_ACCOUNT_SHARE_MODE_PROVIDER_DOMAIN: + ret.IsPublic = true + ret.PublicScope = rbacutils.ScopeDomain + case CLOUD_ACCOUNT_SHARE_MODE_SYSTEM: + ret.IsPublic = true + if i.IsPublic && i.PublicScope == rbacutils.ScopeSystem { + ret.PublicScope = rbacutils.ScopeSystem + } else { + ret.PublicScope = rbacutils.ScopeDomain + ret.SharedDomains = i.SharedDomains + } + } + return ret +} + +func (i SAccountShareInfo) GetDomainShareInfo() SShareInfo { + ret := SShareInfo{} + switch i.ShareMode { + case CLOUD_ACCOUNT_SHARE_MODE_ACCOUNT_DOMAIN: + ret.IsPublic = false + ret.PublicScope = rbacutils.ScopeNone + case CLOUD_ACCOUNT_SHARE_MODE_PROVIDER_DOMAIN: + ret.IsPublic = false + ret.PublicScope = rbacutils.ScopeNone + case CLOUD_ACCOUNT_SHARE_MODE_SYSTEM: + if i.IsPublic && i.PublicScope == rbacutils.ScopeSystem { + ret.IsPublic = true + ret.PublicScope = rbacutils.ScopeSystem + } else if len(i.SharedDomains) > 0 { + ret.IsPublic = true + ret.PublicScope = rbacutils.ScopeDomain + ret.SharedDomains = i.SharedDomains + } else { + ret.IsPublic = false + ret.PublicScope = rbacutils.ScopeNone + } + } + return ret +} + +func (i SShareInfo) IsViolate(i2 SShareInfo) bool { + if i.IsPublic && !i2.IsPublic { + return true + } else if !i.IsPublic && i2.IsPublic { + return false + } + // is_public equals + if i.PublicScope.HigherThan(i2.PublicScope) { + return true + } else if i2.PublicScope.HigherThan(i.PublicScope) { + return false + } + // public_scope equals + aNoB, _, bNoA := stringutils2.Split(stringutils2.NewSortedStrings(i.SharedDomains), stringutils2.NewSortedStrings(i2.SharedDomains)) + if len(aNoB) > 0 { + return true + } else if len(bNoA) > 0 { + return false + } + // shared_domains equals + aNoB, _, bNoA = stringutils2.Split(stringutils2.NewSortedStrings(i.SharedProjects), stringutils2.NewSortedStrings(i2.SharedProjects)) + if len(aNoB) > 0 { + return true + } else if len(bNoA) > 0 { + return false + } + return false +} + +func (i SShareInfo) Intersect(i2 SShareInfo) SShareInfo { + if i.IsPublic && !i2.IsPublic { + return i2 + } else if !i.IsPublic && i2.IsPublic { + return i + } + // is_public equals + if i.PublicScope.HigherThan(i2.PublicScope) { + return i2 + } else if i2.PublicScope.HigherThan(i.PublicScope) { + return i + } + // public_scope equals + _, domains, _ := stringutils2.Split(stringutils2.NewSortedStrings(i.SharedDomains), stringutils2.NewSortedStrings(i2.SharedDomains)) + _, projs, _ := stringutils2.Split(stringutils2.NewSortedStrings(i.SharedProjects), stringutils2.NewSortedStrings(i2.SharedProjects)) + ret := SShareInfo{ + IsPublic: i.IsPublic, + PublicScope: i.PublicScope, + SharedDomains: domains, + SharedProjects: projs, + } + if ret.PublicScope == rbacutils.ScopeProject && len(ret.SharedProjects) == 0 { + ret.IsPublic = false + ret.PublicScope = rbacutils.ScopeNone + } + return ret +} + +func (i SShareInfo) Equals(i2 SShareInfo) bool { + if !i.IsViolate(i2) && !i2.IsViolate(i) { + return true + } else { + return false + } +} + +func (i *SShareInfo) FixProjectShare() { + if i.PublicScope == rbacutils.ScopeProject && len(i.SharedProjects) == 0 { + i.IsPublic = false + i.PublicScope = rbacutils.ScopeNone + } +} + +func (i *SShareInfo) FixDomainShare() { + if i.PublicScope == rbacutils.ScopeProject { + i.IsPublic = false + i.PublicScope = rbacutils.ScopeNone + i.SharedProjects = nil + } else if i.PublicScope == rbacutils.ScopeDomain && len(i.SharedDomains) == 0 { + i.IsPublic = false + i.PublicScope = rbacutils.ScopeNone + } +} diff --git a/pkg/apis/share_test.go b/pkg/apis/share_test.go new file mode 100644 index 0000000000..4deeb17c5f --- /dev/null +++ b/pkg/apis/share_test.go @@ -0,0 +1,199 @@ +// 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 apis + +import ( + "testing" + + "yunion.io/x/onecloud/pkg/util/rbacutils" +) + +func TestSShareInfo_IsViolate(t *testing.T) { + cases := []struct { + name string + s1 SShareInfo + s2 SShareInfo + violate1 bool + violate2 bool + }{ + { + name: "case1", + s1: SShareInfo{ + IsPublic: false, + PublicScope: rbacutils.ScopeNone, + }, + s2: SShareInfo{ + IsPublic: true, + PublicScope: rbacutils.ScopeSystem, + }, + violate1: false, + violate2: true, + }, + { + name: "case2", + s1: SShareInfo{ + IsPublic: true, + PublicScope: rbacutils.ScopeProject, + SharedProjects: []string{"p1"}, + }, + s2: SShareInfo{ + IsPublic: true, + PublicScope: rbacutils.ScopeProject, + SharedProjects: []string{"p2"}, + }, + violate1: true, + violate2: true, + }, + { + name: "case3", + s1: SShareInfo{ + IsPublic: true, + PublicScope: rbacutils.ScopeDomain, + SharedDomains: []string{"p1"}, + }, + s2: SShareInfo{ + IsPublic: true, + PublicScope: rbacutils.ScopeProject, + SharedProjects: []string{"p2"}, + }, + violate1: true, + violate2: false, + }, + { + name: "case4", + s1: SShareInfo{ + IsPublic: true, + PublicScope: rbacutils.ScopeDomain, + SharedDomains: []string{"p1", "p2"}, + }, + s2: SShareInfo{ + IsPublic: true, + PublicScope: rbacutils.ScopeDomain, + SharedDomains: []string{"p1", "p2"}, + }, + violate1: false, + violate2: false, + }, + } + for _, c := range cases { + if c.s1.IsViolate(c.s2) != c.violate1 { + t.Errorf("[%s] s1.violate(s2) want %v", c.name, c.violate1) + } else if c.s2.IsViolate(c.s1) != c.violate2 { + t.Errorf("[%s] s2.violate(s1) want: %v", c.name, c.violate2) + } + } +} + +func TestSShareInfo_Intersect(t *testing.T) { + cases := []struct { + name string + s1 SShareInfo + s2 SShareInfo + want SShareInfo + }{ + { + name: "case1", + s1: SShareInfo{ + IsPublic: false, + PublicScope: rbacutils.ScopeNone, + }, + s2: SShareInfo{ + IsPublic: true, + PublicScope: rbacutils.ScopeSystem, + }, + want: SShareInfo{ + IsPublic: false, + PublicScope: rbacutils.ScopeNone, + }, + }, + { + name: "case2", + s1: SShareInfo{ + IsPublic: true, + PublicScope: rbacutils.ScopeProject, + SharedProjects: []string{"p1"}, + }, + s2: SShareInfo{ + IsPublic: true, + PublicScope: rbacutils.ScopeProject, + SharedProjects: []string{"p2"}, + }, + want: SShareInfo{ + IsPublic: false, + PublicScope: rbacutils.ScopeNone, + }, + }, + { + name: "case3", + s1: SShareInfo{ + IsPublic: true, + PublicScope: rbacutils.ScopeDomain, + SharedDomains: []string{"p1"}, + }, + s2: SShareInfo{ + IsPublic: true, + PublicScope: rbacutils.ScopeProject, + SharedProjects: []string{"p2"}, + }, + want: SShareInfo{ + IsPublic: true, + PublicScope: rbacutils.ScopeProject, + SharedProjects: []string{"p2"}, + }, + }, + { + name: "case4", + s1: SShareInfo{ + IsPublic: true, + PublicScope: rbacutils.ScopeDomain, + SharedDomains: []string{"p1", "p2"}, + }, + s2: SShareInfo{ + IsPublic: true, + PublicScope: rbacutils.ScopeDomain, + SharedDomains: []string{"p1", "p2"}, + }, + want: SShareInfo{ + IsPublic: true, + PublicScope: rbacutils.ScopeDomain, + SharedDomains: []string{"p1", "p2"}, + }, + }, + { + name: "case5", + s1: SShareInfo{ + IsPublic: true, + PublicScope: rbacutils.ScopeDomain, + SharedDomains: []string{"p1", "p2", "p4"}, + }, + s2: SShareInfo{ + IsPublic: true, + PublicScope: rbacutils.ScopeDomain, + SharedDomains: []string{"p1", "p2", "p3"}, + }, + want: SShareInfo{ + IsPublic: true, + PublicScope: rbacutils.ScopeDomain, + SharedDomains: []string{"p1", "p2"}, + }, + }, + } + for _, c := range cases { + inter := c.s1.Intersect(c.s2) + if !inter.Equals(c.want) { + t.Errorf("[%s] intersect got %#v != want %#v", c.name, inter, c.want) + } + } +} diff --git a/pkg/cloudcommon/db/infraresource.go b/pkg/cloudcommon/db/infraresource.go index f9fad62bb7..56744b1589 100644 --- a/pkg/cloudcommon/db/infraresource.go +++ b/pkg/cloudcommon/db/infraresource.go @@ -22,7 +22,6 @@ import ( "yunion.io/x/sqlchemy" "yunion.io/x/onecloud/pkg/apis" - "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/rbacutils" @@ -217,43 +216,48 @@ func (model *SInfrasResourceBase) Delete(ctx context.Context, userCred mcclient. return model.SDomainLevelResourceBase.Delete(ctx, userCred) } +func (model *SInfrasResourceBase) GetSharedInfo() apis.SShareInfo { + ret := apis.SShareInfo{} + ret.IsPublic = model.IsPublic + ret.PublicScope = rbacutils.String2ScopeDefault(model.PublicScope, rbacutils.ScopeNone) + ret.SharedDomains = model.GetSharedDomains() + ret.SharedProjects = nil + // fix + if len(ret.SharedDomains) > 0 { + ret.PublicScope = rbacutils.ScopeDomain + ret.SharedProjects = nil + ret.IsPublic = true + } else if !ret.IsPublic { + ret.PublicScope = rbacutils.ScopeNone + } + return ret +} + +func (model *SInfrasResourceBase) SaveSharedInfo(src apis.TOwnerSource, ctx context.Context, userCred mcclient.TokenCredential, si apis.SShareInfo) { + diff, _ := Update(model, func() error { + model.PublicSrc = string(src) + model.IsPublic = si.IsPublic + model.PublicScope = string(si.PublicScope) + return nil + }) + if len(diff) > 0 { + OpsLog.LogEvent(model, ACT_SYNC_SHARE, diff, userCred) + } + SharedResourceManager.shareToTarget(ctx, userCred, model.GetIInfrasModel(), SharedTargetProject, nil, nil, nil) + SharedResourceManager.shareToTarget(ctx, userCred, model.GetIInfrasModel(), SharedTargetDomain, si.SharedDomains, nil, nil) +} + func (model *SInfrasResourceBase) SyncShareState(ctx context.Context, userCred mcclient.TokenCredential, shareInfo apis.SAccountShareInfo) { + si := shareInfo.GetDomainShareInfo() if model.PublicSrc != string(apis.OWNER_SOURCE_LOCAL) { - diff, _ := Update(model, func() error { - model.PublicSrc = string(apis.OWNER_SOURCE_CLOUD) - switch shareInfo.ShareMode { - case compute.CLOUD_ACCOUNT_SHARE_MODE_ACCOUNT_DOMAIN: - model.IsPublic = false - model.PublicScope = string(rbacutils.ScopeNone) - SharedResourceManager.shareToTarget(ctx, userCred, model.GetIInfrasModel(), SharedTargetProject, nil, nil, nil) - SharedResourceManager.shareToTarget(ctx, userCred, model.GetIInfrasModel(), SharedTargetDomain, nil, nil, nil) - case compute.CLOUD_ACCOUNT_SHARE_MODE_PROVIDER_DOMAIN: - model.IsPublic = false - model.PublicScope = string(rbacutils.ScopeNone) - SharedResourceManager.shareToTarget(ctx, userCred, model.GetIInfrasModel(), SharedTargetProject, nil, nil, nil) - SharedResourceManager.shareToTarget(ctx, userCred, model.GetIInfrasModel(), SharedTargetDomain, nil, nil, nil) - case compute.CLOUD_ACCOUNT_SHARE_MODE_SYSTEM: - if shareInfo.IsPublic && shareInfo.PublicScope == rbacutils.ScopeSystem { - model.IsPublic = true - model.PublicScope = string(rbacutils.ScopeSystem) - SharedResourceManager.shareToTarget(ctx, userCred, model.GetIInfrasModel(), SharedTargetProject, nil, nil, nil) - SharedResourceManager.shareToTarget(ctx, userCred, model.GetIInfrasModel(), SharedTargetDomain, nil, nil, nil) - } else if len(shareInfo.SharedDomains) > 0 { - model.IsPublic = true - model.PublicScope = string(rbacutils.ScopeDomain) - SharedResourceManager.shareToTarget(ctx, userCred, model.GetIInfrasModel(), SharedTargetProject, nil, nil, nil) - SharedResourceManager.shareToTarget(ctx, userCred, model.GetIInfrasModel(), SharedTargetDomain, shareInfo.SharedDomains, nil, nil) - } else { - model.IsPublic = false - model.PublicScope = string(rbacutils.ScopeNone) - SharedResourceManager.shareToTarget(ctx, userCred, model.GetIInfrasModel(), SharedTargetProject, nil, nil, nil) - SharedResourceManager.shareToTarget(ctx, userCred, model.GetIInfrasModel(), SharedTargetDomain, nil, nil, nil) - } - } - return nil - }) - if len(diff) > 0 { - OpsLog.LogEvent(model, ACT_SYNC_SHARE, diff, userCred) + model.SaveSharedInfo(apis.OWNER_SOURCE_CLOUD, ctx, userCred, si) + } else { + localSi := model.GetSharedInfo() + if localSi.IsViolate(si) { + newSi := localSi.Intersect(si) + newSi.FixDomainShare() + // reset to cloud base public_src + model.SaveSharedInfo(apis.OWNER_SOURCE_CLOUD, ctx, userCred, newSi) } } } diff --git a/pkg/cloudcommon/db/sharablevirtual.go b/pkg/cloudcommon/db/sharablevirtual.go index be4e6e2429..0e0a2862be 100644 --- a/pkg/cloudcommon/db/sharablevirtual.go +++ b/pkg/cloudcommon/db/sharablevirtual.go @@ -22,7 +22,6 @@ import ( "yunion.io/x/sqlchemy" "yunion.io/x/onecloud/pkg/apis" - "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/rbacutils" @@ -210,37 +209,52 @@ func (model *SSharableVirtualResourceBase) Delete(ctx context.Context, userCred return model.SVirtualResourceBase.Delete(ctx, userCred) } +func (model *SSharableVirtualResourceBase) GetSharedInfo() apis.SShareInfo { + ret := apis.SShareInfo{} + ret.IsPublic = model.IsPublic + ret.PublicScope = rbacutils.String2ScopeDefault(model.PublicScope, rbacutils.ScopeNone) + ret.SharedDomains = model.GetSharedDomains() + ret.SharedProjects = model.GetSharedProjects() + // fix + if len(ret.SharedDomains) > 0 { + ret.PublicScope = rbacutils.ScopeDomain + ret.SharedProjects = nil + ret.IsPublic = true + } else if len(ret.SharedProjects) > 0 { + ret.PublicScope = rbacutils.ScopeProject + ret.SharedDomains = nil + ret.IsPublic = true + } else if !ret.IsPublic { + ret.PublicScope = rbacutils.ScopeNone + } + return ret +} + +func (model *SSharableVirtualResourceBase) SaveSharedInfo(src apis.TOwnerSource, ctx context.Context, userCred mcclient.TokenCredential, si apis.SShareInfo) { + diff, _ := Update(model, func() error { + model.PublicSrc = string(src) + model.IsPublic = si.IsPublic + model.PublicScope = string(si.PublicScope) + return nil + }) + if len(diff) > 0 { + OpsLog.LogEvent(model, ACT_SYNC_SHARE, diff, userCred) + } + SharedResourceManager.shareToTarget(ctx, userCred, model.GetISharableVirtualModel(), SharedTargetProject, si.SharedProjects, nil, nil) + SharedResourceManager.shareToTarget(ctx, userCred, model.GetISharableVirtualModel(), SharedTargetDomain, si.SharedDomains, nil, nil) +} + func (model *SSharableVirtualResourceBase) SyncShareState(ctx context.Context, userCred mcclient.TokenCredential, shareInfo apis.SAccountShareInfo) { + si := shareInfo.GetProjectShareInfo() if model.PublicSrc != string(apis.OWNER_SOURCE_LOCAL) { - diff, _ := Update(model, func() error { - model.PublicSrc = string(apis.OWNER_SOURCE_CLOUD) - switch shareInfo.ShareMode { - case compute.CLOUD_ACCOUNT_SHARE_MODE_ACCOUNT_DOMAIN: - model.IsPublic = true - model.PublicScope = string(rbacutils.ScopeDomain) - SharedResourceManager.shareToTarget(ctx, userCred, model.GetISharableVirtualModel(), SharedTargetProject, nil, nil, nil) - SharedResourceManager.shareToTarget(ctx, userCred, model.GetISharableVirtualModel(), SharedTargetDomain, nil, nil, nil) - case compute.CLOUD_ACCOUNT_SHARE_MODE_PROVIDER_DOMAIN: - model.IsPublic = true - model.PublicScope = string(rbacutils.ScopeDomain) - SharedResourceManager.shareToTarget(ctx, userCred, model.GetISharableVirtualModel(), SharedTargetProject, nil, nil, nil) - SharedResourceManager.shareToTarget(ctx, userCred, model.GetISharableVirtualModel(), SharedTargetDomain, nil, nil, nil) - case compute.CLOUD_ACCOUNT_SHARE_MODE_SYSTEM: - model.IsPublic = true - if shareInfo.IsPublic && shareInfo.PublicScope == rbacutils.ScopeSystem { - model.PublicScope = string(rbacutils.ScopeSystem) - SharedResourceManager.shareToTarget(ctx, userCred, model.GetISharableVirtualModel(), SharedTargetProject, nil, nil, nil) - SharedResourceManager.shareToTarget(ctx, userCred, model.GetISharableVirtualModel(), SharedTargetDomain, nil, nil, nil) - } else { - model.PublicScope = string(rbacutils.ScopeDomain) - SharedResourceManager.shareToTarget(ctx, userCred, model.GetISharableVirtualModel(), SharedTargetProject, nil, nil, nil) - SharedResourceManager.shareToTarget(ctx, userCred, model.GetISharableVirtualModel(), SharedTargetDomain, shareInfo.SharedDomains, nil, nil) - } - } - return nil - }) - if len(diff) > 0 { - OpsLog.LogEvent(model, ACT_SYNC_SHARE, diff, userCred) + model.SaveSharedInfo(apis.OWNER_SOURCE_CLOUD, ctx, userCred, si) + } else { + localSi := model.GetSharedInfo() + if localSi.IsViolate(si) { + newSi := localSi.Intersect(si) + newSi.FixProjectShare() + // reset to cloud base public_src + model.SaveSharedInfo(apis.OWNER_SOURCE_CLOUD, ctx, userCred, newSi) } } } diff --git a/pkg/compute/models/buckets.go b/pkg/compute/models/buckets.go index 09de12717b..3f5ac79cc7 100644 --- a/pkg/compute/models/buckets.go +++ b/pkg/compute/models/buckets.go @@ -219,6 +219,7 @@ func (manager *SBucketManager) newFromCloudBucket( } SyncCloudProject(userCred, &bucket, provider.GetOwnerId(), extBucket, provider.Id) + bucket.SyncShareState(ctx, userCred, provider.getAccountShareInfo()) db.OpsLog.LogEvent(&bucket, db.ACT_CREATE, bucket.GetShortDesc(ctx), userCred) @@ -292,6 +293,7 @@ func (bucket *SBucket) syncWithCloudBucket( if provider != nil { SyncCloudProject(userCred, bucket, provider.GetOwnerId(), extBucket, provider.Id) + bucket.SyncShareState(ctx, userCred, provider.getAccountShareInfo()) } return nil