From 4523700054f804e8f977f5c25444a7ac654a9ea3 Mon Sep 17 00:00:00 2001 From: wanyaoqi Date: Sat, 15 Jun 2019 20:16:43 +0800 Subject: [PATCH] shared project resource --- pkg/cloudcommon/db/interface.go | 1 + pkg/cloudcommon/db/resourcebase.go | 2 +- pkg/cloudcommon/db/sharablevirtual.go | 138 +++++++++++++++--- pkg/cloudcommon/db/sharedresource.go | 30 ++++ pkg/compute/models/networks.go | 3 +- pkg/compute/service/handlers.go | 1 + .../predicates/network_schedtag_predicate.go | 2 +- pkg/util/openstack/network.go | 4 +- pkg/util/zstack/network.go | 4 +- 9 files changed, 160 insertions(+), 25 deletions(-) create mode 100644 pkg/cloudcommon/db/sharedresource.go diff --git a/pkg/cloudcommon/db/interface.go b/pkg/cloudcommon/db/interface.go index 69ca6b5039..b6225bb1d9 100644 --- a/pkg/cloudcommon/db/interface.go +++ b/pkg/cloudcommon/db/interface.go @@ -264,6 +264,7 @@ type ISharableVirtualModel interface { IVirtualModel GetISharableVirtualModel() ISharableVirtualModel + GetSharedProjects() []string } type IAdminSharableVirtualModelManager interface { diff --git a/pkg/cloudcommon/db/resourcebase.go b/pkg/cloudcommon/db/resourcebase.go index 6931beea0e..068ab71889 100644 --- a/pkg/cloudcommon/db/resourcebase.go +++ b/pkg/cloudcommon/db/resourcebase.go @@ -100,5 +100,5 @@ func (model *SResourceBase) MarkDelete() error { } func (model *SResourceBase) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { - return DeleteModel(ctx, userCred, model) + return DeleteModel(ctx, userCred, model.GetIResourceModel()) } diff --git a/pkg/cloudcommon/db/sharablevirtual.go b/pkg/cloudcommon/db/sharablevirtual.go index 02a678ab12..458f73e8cc 100644 --- a/pkg/cloudcommon/db/sharablevirtual.go +++ b/pkg/cloudcommon/db/sharablevirtual.go @@ -16,8 +16,10 @@ package db import ( "context" + "database/sql" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/sqlchemy" "yunion.io/x/onecloud/pkg/cloudcommon/consts" @@ -50,9 +52,12 @@ func (manager *SSharableVirtualResourceBaseManager) FilterByOwner(q *sqlchemy.SQ if owner != nil { switch scope { case rbacutils.ScopeProject: - if len(owner.GetProjectId()) > 0 { + ownerProjectid := owner.GetProjectId() + if len(ownerProjectid) > 0 { + rq := SharedResourceManager.Query().SubQuery() + q.LeftJoin(rq, sqlchemy.Equals(q.Field("id"), rq.Field("resource_id"))) q = q.Filter(sqlchemy.OR( - sqlchemy.Equals(q.Field("tenant_id"), owner.GetProjectId()), + sqlchemy.Equals(q.Field("tenant_id"), ownerProjectid), sqlchemy.AND( sqlchemy.IsTrue(q.Field("is_public")), sqlchemy.Equals(q.Field("public_scope"), rbacutils.ScopeSystem), @@ -62,6 +67,11 @@ func (manager *SSharableVirtualResourceBaseManager) FilterByOwner(q *sqlchemy.SQ sqlchemy.Equals(q.Field("public_scope"), rbacutils.ScopeDomain), sqlchemy.Equals(q.Field("domain_id"), owner.GetProjectDomainId()), ), + sqlchemy.AND( + sqlchemy.Equals(rq.Field("resource_type"), manager.Keyword()), + sqlchemy.Equals(rq.Field("target_project_id"), ownerProjectid), + sqlchemy.Equals(q.Field("tenant_id"), rq.Field("owner_project_id")), + ), )) } case rbacutils.ScopeDomain: @@ -106,22 +116,64 @@ func (model *SSharableVirtualResourceBase) PerformPublic(ctx context.Context, us if !model.IsPublic { targetScopeStr, _ := data.GetString("scope") targetScope := rbacutils.String2ScopeDefault(targetScopeStr, rbacutils.ScopeSystem) - allowScope := policy.PolicyManager.AllowScope(userCred, consts.GetServiceType(), model.GetModelManager().KeywordPlural(), policy.PolicyActionPerform, "public") - if targetScope.HigherThan(allowScope) { - return nil, httperrors.NewForbiddenError("not enough privilege") + if targetScope == rbacutils.ScopeProject { + if sharedWithProject, err := data.GetString("shared_with"); err == nil { + tenant, err := TenantCacheManager.FetchTenantByIdOrName(ctx, sharedWithProject) + if err != nil { + return nil, httperrors.NewInternalServerError("fetch tenant error %s", err) + } + if tenant.DomainId != model.DomainId { + return nil, httperrors.NewBadRequestError("can't shared project to other domain") + } + sharedResource := new(SSharedResource) + err = SharedResourceManager.Query(). + Equals("resource_type", model.GetModelManager().Keyword()). + Equals("resource_id", model.Id).Equals("owner_project_id", model.ProjectId). + Equals("target_project_id", tenant.GetId()).First(sharedResource) + if err != nil { + if err != sql.ErrNoRows { + return nil, httperrors.NewInternalServerError("query resource failed %s", err) + } else { + sharedResource.ResourceType = model.GetModelManager().Keyword() + sharedResource.ResourceId = model.Id + sharedResource.OwnerProjectId = model.ProjectId + sharedResource.TargetProjectId = tenant.GetId() + if insetErr := SharedResourceManager.TableSpec().Insert(sharedResource); insetErr != nil { + return nil, httperrors.NewInternalServerError("Insert shared resource failed %s", insetErr) + } + diff, err := Update(model, func() error { + model.PublicScope = string(targetScope) + return nil + }) + if err == nil { + OpsLog.LogEvent(model, ACT_UPDATE, diff, userCred) + } + return nil, err + } + } else { + return nil, httperrors.NewBadRequestError("Resource has been shared to %s", tenant.GetName()) + } + } else { + return nil, httperrors.NewMissingParameterError("shared_with") + } + } else { + allowScope := policy.PolicyManager.AllowScope(userCred, consts.GetServiceType(), model.GetModelManager().KeywordPlural(), policy.PolicyActionPerform, "public") + if targetScope.HigherThan(allowScope) { + return nil, httperrors.NewForbiddenError("not enough privilege") + } + if targetScope != rbacutils.ScopeSystem && targetScope != rbacutils.ScopeDomain { + return nil, httperrors.NewInputParameterError("invalid scope %s", targetScope) + } + diff, err := Update(model, func() error { + model.IsPublic = true + model.PublicScope = string(targetScope) + return nil + }) + if err == nil { + OpsLog.LogEvent(model, ACT_UPDATE, diff, userCred) + } + return nil, err } - if targetScope != rbacutils.ScopeSystem && targetScope != rbacutils.ScopeDomain { - return nil, httperrors.NewInputParameterError("invalid scope %s", targetScope) - } - diff, err := Update(model, func() error { - model.IsPublic = true - model.PublicScope = string(targetScope) - return nil - }) - if err == nil { - OpsLog.LogEvent(model, ACT_UPDATE, diff, userCred) - } - return nil, err } return nil, nil } @@ -131,6 +183,7 @@ func (model *SSharableVirtualResourceBase) AllowPerformPrivate(ctx context.Conte } func (model *SSharableVirtualResourceBase) PerformPrivate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if model.IsPublic { allowScope := policy.PolicyManager.AllowScope(userCred, consts.GetServiceType(), model.GetModelManager().KeywordPlural(), policy.PolicyActionPerform, "private") requireScope := rbacutils.String2ScopeDefault(model.PublicScope, rbacutils.ScopeSystem) @@ -138,13 +191,31 @@ func (model *SSharableVirtualResourceBase) PerformPrivate(ctx context.Context, u return nil, httperrors.NewForbiddenError("not enough privileges: allow %s require %s", allowScope, requireScope) } diff, err := Update(model, func() error { + model.PublicScope = string(rbacutils.ScopeProject) model.IsPublic = false return nil }) if err == nil { OpsLog.LogEvent(model, ACT_UPDATE, diff, userCred) + } else { + return nil, httperrors.NewInternalServerError("Update shared resource error: %s", err) } - return nil, err + } + + var errStr string + srs := make([]SSharedResource, 0) + err := SharedResourceManager.Query().Equals("owner_project_id", model.ProjectId).All(&srs) + if err != nil { + log.Errorln(err) + } + for i := 0; i < len(srs); i++ { + srs[i].SetModelManager(SharedResourceManager, &srs[i]) + if err := srs[i].Delete(ctx, userCred); err != nil { + errStr += err.Error() + } + } + if len(errStr) > 0 { + return nil, httperrors.NewInternalServerError("Update shared resource error: %s", errStr) } return nil, nil } @@ -152,3 +223,34 @@ func (model *SSharableVirtualResourceBase) PerformPrivate(ctx context.Context, u func (model *SSharableVirtualResourceBase) GetISharableVirtualModel() ISharableVirtualModel { return model.GetVirtualObject().(ISharableVirtualModel) } + +func (model *SSharableVirtualResourceBase) GetSharedProjects() []string { + sharedResources := make([]SSharedResource, 0) + res := make([]string, 0) + SharedResourceManager.Query().Equals("resource_type", model.GetModelManager().Keyword()).Equals("resource_id", model.GetId()).All(&sharedResources) + for i := 0; i < len(sharedResources); i++ { + res = append(res, sharedResources[i].TargetProjectId) + } + return res +} + +func (model *SSharableVirtualResourceBase) getMoreDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, extra *jsonutils.JSONDict) *jsonutils.JSONDict { + projects := model.GetSharedProjects() + if len(projects) > 0 { + extra.Set("shared_projects", jsonutils.NewStringArray(projects)) + } + return extra +} + +func (model *SSharableVirtualResourceBase) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { + extra := model.SVirtualResourceBase.GetCustomizeColumns(ctx, userCred, query) + return model.getMoreDetails(ctx, userCred, query, extra) +} + +func (model *SSharableVirtualResourceBase) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*jsonutils.JSONDict, error) { + extra, err := model.SVirtualResourceBase.GetExtraDetails(ctx, userCred, query) + if err != nil { + return nil, err + } + return model.getMoreDetails(ctx, userCred, query, extra), nil +} diff --git a/pkg/cloudcommon/db/sharedresource.go b/pkg/cloudcommon/db/sharedresource.go new file mode 100644 index 0000000000..292f636867 --- /dev/null +++ b/pkg/cloudcommon/db/sharedresource.go @@ -0,0 +1,30 @@ +package db + +// sharing resoure between project +type SSharedResource struct { + SResourceBase + + Id int64 `primary:"true" auto_increment:"true" list:"user"` + + ResourceType string `width:"32" charset:"ascii" nullable:"false" list:"user"` + ResourceId string `width:"128" charset:"ascii" nullable:"false" index:"true" list:"user"` + OwnerProjectId string `width:"128" charset:"ascii" nullable:"false" index:"true" list:"user"` + TargetProjectId string `width:"128" charset:"ascii" nullable:"false" index:"true" list:"user"` +} + +type SSharedResourceManager struct { + SResourceBaseManager +} + +var SharedResourceManager *SSharedResourceManager + +func init() { + SharedResourceManager = &SSharedResourceManager{ + SResourceBaseManager: NewResourceBaseManager( + SSharedResource{}, + "shared_resources_tbl", + "shared_resource", + "shared_resources", + ), + } +} diff --git a/pkg/compute/models/networks.go b/pkg/compute/models/networks.go index 29721e3ebc..7e136a562f 100644 --- a/pkg/compute/models/networks.go +++ b/pkg/compute/models/networks.go @@ -755,7 +755,8 @@ func parseNetworkInfo(userCred mcclient.TokenCredential, info *api.NetworkConfig } } net := netObj.(*SNetwork) - if net.IsOwner(userCred) || net.IsPublic || db.IsAdminAllowGet(userCred, net) { + if net.IsOwner(userCred) || net.IsPublic || db.IsAdminAllowGet(userCred, net) || + utils.IsInStringArray(userCred.GetProjectId(), net.GetSharedProjects()) { info.Network = netObj.GetId() } else { return nil, httperrors.NewForbiddenError("no allow to access network %s", info.Network) diff --git a/pkg/compute/service/handlers.go b/pkg/compute/service/handlers.go index bf34005d14..490220e927 100644 --- a/pkg/compute/service/handlers.go +++ b/pkg/compute/service/handlers.go @@ -47,6 +47,7 @@ func InitHandlers(app *appsrv.Application) { taskman.TaskObjectManager, db.UserCacheManager, db.TenantCacheManager, + db.SharedResourceManager, models.GuestcdromManager, models.NetInterfaceManager, models.VCenterManager, diff --git a/pkg/scheduler/algorithm/predicates/network_schedtag_predicate.go b/pkg/scheduler/algorithm/predicates/network_schedtag_predicate.go index 1e42f3ef25..2a1b79c872 100644 --- a/pkg/scheduler/algorithm/predicates/network_schedtag_predicate.go +++ b/pkg/scheduler/algorithm/predicates/network_schedtag_predicate.go @@ -99,7 +99,7 @@ func (p *NetworkSchedtagPredicate) IsResourceFitInput(u *core.Unit, _ core.Candi if network.IsPublic { return fmt.Errorf("Network %s is public", network.Name) } - if network.ProjectId != schedData.Project { + if network.ProjectId != schedData.Project && utils.IsInStringArray(schedData.Project, network.GetSharedProjects()) { return fmt.Errorf("Network project %s not owner by %s", network.ProjectId, schedData.Project) } } else { diff --git a/pkg/util/openstack/network.go b/pkg/util/openstack/network.go index fd096121ed..21bc57790f 100644 --- a/pkg/util/openstack/network.go +++ b/pkg/util/openstack/network.go @@ -140,11 +140,11 @@ func (network *SNetwork) GetIpMask() int8 { } func (network *SNetwork) GetIsPublic() bool { - return true + return false } func (network *SNetwork) GetPublicScope() rbacutils.TRbacScope { - return rbacutils.ScopeDomain + return rbacutils.ScopeProject } func (network *SNetwork) GetServerType() string { diff --git a/pkg/util/zstack/network.go b/pkg/util/zstack/network.go index d6a218cdd2..7e82270406 100644 --- a/pkg/util/zstack/network.go +++ b/pkg/util/zstack/network.go @@ -190,11 +190,11 @@ func (network *SNetwork) GetIpMask() int8 { func (network *SNetwork) GetIsPublic() bool { // return network.IsDefault - return true + return false } func (self *SNetwork) GetPublicScope() rbacutils.TRbacScope { - return rbacutils.ScopeDomain + return rbacutils.ScopeProject } func (network *SNetwork) GetServerType() string {