diff --git a/pkg/cloudcommon/db/interface.go b/pkg/cloudcommon/db/interface.go index 91d33d0690..c50b088607 100644 --- a/pkg/cloudcommon/db/interface.go +++ b/pkg/cloudcommon/db/interface.go @@ -17,11 +17,11 @@ package db import ( "context" "net/http" + "time" "yunion.io/x/jsonutils" "yunion.io/x/sqlchemy" - "time" "yunion.io/x/onecloud/pkg/appsrv" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" "yunion.io/x/onecloud/pkg/cloudcommon/object" @@ -108,6 +108,8 @@ type IModelManager interface { /* name uniqueness scope, system/domain/project, default is system */ NamespaceScope() rbacutils.TRbacScope ResourceScope() rbacutils.TRbacScope + + QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) } type IModel interface { diff --git a/pkg/cloudcommon/db/modelbase.go b/pkg/cloudcommon/db/modelbase.go index d0169ef4d9..4995a81a8c 100644 --- a/pkg/cloudcommon/db/modelbase.go +++ b/pkg/cloudcommon/db/modelbase.go @@ -26,6 +26,7 @@ import ( "yunion.io/x/onecloud/pkg/appsrv" "yunion.io/x/onecloud/pkg/cloudcommon/object" + "yunion.io/x/onecloud/pkg/cloudcommon/policy" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/rbacutils" @@ -263,6 +264,91 @@ func (manager *SModelBaseManager) ResourceScope() rbacutils.TRbacScope { return rbacutils.ScopeSystem } +func (manager *SModelBaseManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + return q, nil +} + +func (manager *SModelBaseManager) AllowGetPropertyDistinctField(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return true +} + +func (manager *SModelBaseManager) GetPropertyDistinctField(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) { + im, ok := manager.GetVirtualObject().(IModelManager) + if !ok { + im = manager + } + fn, err := query.GetArray("field") + efs, _ := query.GetArray("extra_field") + fields := make([]string, len(fn)) + + // validate field + for i, f := range fn { + fields[i], err = f.GetString() + if err != nil { + return nil, httperrors.NewInputParameterError("can't get string field") + } + var hasField = false + for _, field := range manager.getTable().Fields() { + if field.Name() == fields[i] { + hasField = true + break + } + } + if !hasField { + return nil, httperrors.NewBadRequestError("model has no field %s", fields[i]) + } + } + + var res = jsonutils.NewDict() + q := im.Query() + q, err = ListItemQueryFilters(im, ctx, q, userCred, query, policy.PolicyActionList) + if err != nil { + return nil, err + } + var backupQuery = *q + + // query field + for i := 0; i < len(fields); i++ { + var nq = backupQuery + nq.AppendField(nq.Field(fields[i])) + of, err := nq.Distinct().AllStringMap() + if err == sql.ErrNoRows { + continue + } + if err != nil && err != sql.ErrNoRows { + return nil, httperrors.NewInternalServerError("Query database error %s", err) + } + ofa := make([]string, len(of)) + for j := 0; j < len(of); j++ { + ofa[j] = of[j][fields[i]] + } + res.Set(fields[i], jsonutils.Marshal(ofa)) + } + + // query extra field + for i := 0; i < len(efs); i++ { + nq := backupQuery + fe, _ := efs[i].GetString() + nqp, err := im.QueryDistinctExtraField(&nq, fe) + if err != nil { + return nil, err + } + ef, err := nqp.AllStringMap() + if err == sql.ErrNoRows { + continue + } + efa := make([]string, len(ef)) + for i := 0; i < len(ef); i++ { + efa[i] = ef[i][fe] + } + if err != nil && err != sql.ErrNoRows { + return nil, httperrors.NewInternalServerError("Query database error %s", err) + } + res.Set(fe, jsonutils.Marshal(efa)) + } + return res, nil +} + func (model *SModelBase) GetId() string { return "" } diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index 958d4f0bed..24461270be 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -475,6 +475,23 @@ func (manager *SGuestManager) OrderByExtraFields(ctx context.Context, q *sqlchem return q, nil } +func (manager *SGuestManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + switch field { + case "account": + hosts := HostManager.Query().SubQuery() + cloudproviders := CloudproviderManager.Query().SubQuery() + cloudaccounts := CloudaccountManager.Query().SubQuery() + q = q.Join(hosts, sqlchemy.Equals(q.Field("host_id"), hosts.Field("id"))) + q = q.Join(cloudproviders, sqlchemy.Equals(hosts.Field("manager_id"), cloudproviders.Field("id"))) + q = q.Join(cloudaccounts, sqlchemy.Equals(cloudproviders.Field("cloudaccount_id"), cloudaccounts.Field("id"))) + q.GroupBy(cloudaccounts.Field("name")) + q.AppendField(cloudaccounts.Field("name", "account")) + default: + return nil, httperrors.NewBadRequestError("unsupport field %s", field) + } + return q, nil +} + func (guest *SGuest) GetHypervisor() string { if len(guest.Hypervisor) == 0 { return api.HYPERVISOR_DEFAULT diff --git a/pkg/compute/models/networks.go b/pkg/compute/models/networks.go index 2c73c91572..279a6abff0 100644 --- a/pkg/compute/models/networks.go +++ b/pkg/compute/models/networks.go @@ -1666,6 +1666,26 @@ func (manager *SNetworkManager) ListItemFilter(ctx context.Context, q *sqlchemy. return q, nil } +func (manager *SNetworkManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + switch field { + case "account": + vpcs := VpcManager.Query().SubQuery() + wires := WireManager.Query().SubQuery() + cloudproviders := CloudproviderManager.Query().SubQuery() + cloudaccounts := CloudaccountManager.Query().Distinct().SubQuery() + q = q.Join(wires, sqlchemy.Equals(q.Field("wire_id"), wires.Field("id"))) + q = q.Join(vpcs, sqlchemy.Equals(wires.Field("vpc_id"), vpcs.Field("id"))) + q = q.Join(cloudproviders, sqlchemy.Equals(vpcs.Field("manager_id"), cloudproviders.Field("id"))) + q = q.Join(cloudaccounts, sqlchemy.Equals(cloudproviders.Field("cloudaccount_id"), cloudaccounts.Field("id"))) + q.GroupBy(cloudaccounts.Field("name")) + q.AppendField(cloudaccounts.Field("name", "account")) + default: + return nil, httperrors.NewBadRequestError("unsupport field %s", field) + } + + return q, nil +} + func (manager *SNetworkManager) InitializeData() error { // set network status networks := make([]SNetwork, 0) diff --git a/pkg/compute/models/routetables.go b/pkg/compute/models/routetables.go index 14d7b2bc30..0447d3d818 100644 --- a/pkg/compute/models/routetables.go +++ b/pkg/compute/models/routetables.go @@ -224,6 +224,21 @@ func (man *SRouteTableManager) ValidateCreateData(ctx context.Context, userCred return man.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, data) } +func (manager *SRouteTableManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + switch field { + case "account": + cloudproviders := CloudproviderManager.Query().SubQuery() + cloudaccounts := CloudaccountManager.Query().Distinct().SubQuery() + q = q.Join(cloudproviders, sqlchemy.Equals(q.Field("manager_id"), cloudproviders.Field("id"))) + q = q.Join(cloudaccounts, sqlchemy.Equals(cloudproviders.Field("cloudaccount_id"), cloudaccounts.Field("id"))) + q.GroupBy(cloudaccounts.Field("name")) + q.AppendField(cloudaccounts.Field("name", "account")) + default: + return nil, httperrors.NewBadRequestError("unsupport field %s", field) + } + return q, nil +} + func (rt *SRouteTable) AllowPerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { return db.IsAdminAllowPerform(userCred, rt, "purge") } diff --git a/pkg/compute/models/vpcs.go b/pkg/compute/models/vpcs.go index 532b54e847..c9dffe136f 100644 --- a/pkg/compute/models/vpcs.go +++ b/pkg/compute/models/vpcs.go @@ -674,6 +674,21 @@ func (manager *SVpcManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQue return q, nil } +func (manager *SVpcManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + switch field { + case "account": + cloudproviders := CloudproviderManager.Query().SubQuery() + cloudaccounts := CloudaccountManager.Query().Distinct().SubQuery() + q = q.Join(cloudproviders, sqlchemy.Equals(q.Field("manager_id"), cloudproviders.Field("id"))) + q = q.Join(cloudaccounts, sqlchemy.Equals(cloudproviders.Field("cloudaccount_id"), cloudaccounts.Field("id"))) + q.GroupBy(cloudaccounts.Field("name")) + q.AppendField(cloudaccounts.Field("name", "account")) + default: + return nil, httperrors.NewBadRequestError("unsupport field %s", field) + } + return q, nil +} + func (self *SVpc) SyncRemoteWires(ctx context.Context, userCred mcclient.TokenCredential) error { ivpc, err := self.GetIVpc() if err != nil {