Skip to content

Commit

Permalink
internalstorage: add Any CollectionResource
Browse files Browse the repository at this point in the history
Signed-off-by: Iceber Gu <wei.cai-nat@daocloud.io>
  • Loading branch information
Iceber committed Jul 14, 2022
1 parent 652bea2 commit b457761
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 16 deletions.
14 changes: 10 additions & 4 deletions pkg/apiserver/registry/clusterpedia/collectionresources/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,18 +163,24 @@ func (s *REST) ConvertToTable(ctx context.Context, object runtime.Object, tableO
case *internal.CollectionResourceList:
var rows []metav1.TableRow
for _, item := range obj.Items {
name := item.Name
if len(item.ResourceTypes) == 0 {
rows = append(rows, metav1.TableRow{
Object: runtime.RawExtension{Object: item.DeepCopy()},
Cells: []interface{}{item.Name, "*"},
})
continue
}

var grs []string
for _, rt := range item.ResourceTypes {
if rt.Resource == "" {
rt.Resource = "*"
}
grs = append(grs, rt.GroupResource().String())
grs = append(grs, rt.Group+"."+rt.Resource)
}

rows = append(rows, metav1.TableRow{
Object: runtime.RawExtension{Object: item.DeepCopy()},
Cells: []interface{}{name, strings.Join(grs, ",")},
Cells: []interface{}{item.Name, strings.Join(grs, ",")},
})
}

Expand Down
7 changes: 7 additions & 0 deletions pkg/storage/internalstorage/collectionresource.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,18 @@ import (
)

const (
CollectionResourceAny = "any"
CollectionResourceWorkloads = "workloads"
CollectionResourceKubeResources = "kuberesources"
)

var collectionResources = []internal.CollectionResource{
{
ObjectMeta: metav1.ObjectMeta{
Name: CollectionResourceAny,
},
ResourceTypes: []internal.CollectionResourceType{},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: CollectionResourceWorkloads,
Expand Down
154 changes: 142 additions & 12 deletions pkg/storage/internalstorage/collectionresource_storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,25 @@ package internalstorage

import (
"context"
"fmt"
"net/url"
"sort"
"strings"

"gorm.io/gorm"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"

internal "github.com/clusterpedia-io/api/clusterpedia"
"github.com/clusterpedia-io/clusterpedia/pkg/storage"
)

const (
URLQueryGroups = "groups"
URLQueryResources = "resources"
)

type CollectionResourceStorage struct {
db *gorm.DB
typesQuery *gorm.DB
Expand All @@ -20,6 +29,11 @@ type CollectionResourceStorage struct {
}

func NewCollectionResourceStorage(db *gorm.DB, cr *internal.CollectionResource) storage.CollectionResourceStorage {
storage := &CollectionResourceStorage{db: db, collectionResource: cr.DeepCopy()}
if len(cr.ResourceTypes) == 0 {
return storage
}

typesQuery := db
groups := make([]string, 0)
for _, rt := range cr.ResourceTypes {
Expand All @@ -40,27 +54,51 @@ func NewCollectionResourceStorage(db *gorm.DB, cr *internal.CollectionResource)
if len(groups) != 0 {
typesQuery = typesQuery.Or(map[string]interface{}{"group": groups})
}

return &CollectionResourceStorage{
db: db,
typesQuery: typesQuery,
collectionResource: cr.DeepCopy(),
}
storage.typesQuery = typesQuery
return storage
}

func (s *CollectionResourceStorage) query(ctx context.Context, metadata bool) (*gorm.DB, ObjectList) {
func (s *CollectionResourceStorage) query(ctx context.Context, opts *internal.ListOptions) (*gorm.DB, ObjectList, error) {
var result ObjectList = &ResourceList{}
if metadata {
if opts.OnlyMetadata {
result = &ResourceMetadataList{}
}

query := s.db.WithContext(ctx).Model(&Resource{})
return result.Select(query).Where(s.typesQuery), result
if s.typesQuery != nil {
return result.Select(query).Where(s.typesQuery), result, nil
}

// The `URLQueryGroups` and `URLQueryResources` only works on *Any Collection Resource*,
// does it nned to work on other collection resources?
gvrs, err := resolveGVRsFromURLQuery(opts.URLQuery)
if err != nil {
return nil, nil, apierrors.NewBadRequest(err.Error())
}
if len(gvrs) == 0 {
return nil, nil, apierrors.NewBadRequest("url query - `groups` or `resources` is required")
}

typesQuery := s.db
for _, gvr := range gvrs {
where := map[string]interface{}{"group": gvr.Group}
if gvr.Version != "" {
where["version"] = gvr.Version
}
if gvr.Resource != "" {
where["resource"] = gvr.Resource
}
typesQuery = typesQuery.Or(where)
}
return result.Select(query).Where(typesQuery), result, nil
}

func (s *CollectionResourceStorage) Get(ctx context.Context, opts *internal.ListOptions) (*internal.CollectionResource, error) {
query, list := s.query(ctx, opts.OnlyMetadata)
_, query, err := applyListOptionsToCollectionResourceQuery(query, opts)
query, list, err := s.query(ctx, opts)
if err != nil {
return nil, err
}
_, query, err = applyListOptionsToCollectionResourceQuery(query, opts)
if err != nil {
return nil, err
}
Expand All @@ -80,7 +118,9 @@ func (s *CollectionResourceStorage) Get(ctx context.Context, opts *internal.List
objs = append(objs, obj)

if resourceType := resource.GetResourceType(); !resourceType.Empty() {
if _, ok := gvrs[resourceType.GroupVersionResource()]; !ok {
gvr := resourceType.GroupVersionResource()
if _, ok := gvrs[gvr]; !ok {
gvrs[gvr] = struct{}{}
types = append(types, internal.CollectionResourceType{
Group: resourceType.Group,
Resource: resourceType.Resource,
Expand All @@ -100,6 +140,96 @@ func (s *CollectionResourceStorage) Get(ctx context.Context, opts *internal.List
}, nil
}

func resolveGVRsFromURLQuery(query url.Values) (gvrs []schema.GroupVersionResource, err error) {
if query.Has(URLQueryGroups) {
for _, group := range strings.Split(query.Get(URLQueryGroups), ",") {
gv, err := parseGroupVersion(group)
if err != nil {
return nil, fmt.Errorf("%s query: %w", URLQueryGroups, err)
}

gvrs = append(gvrs, gv.WithResource(""))
}
}
if query.Has(URLQueryResources) {
for _, resource := range strings.Split(query.Get(URLQueryResources), ",") {
gvr, err := parseGroupVersionResource(resource)
if err != nil {
return nil, fmt.Errorf("%s query: %w", URLQueryResources, err)
}

gvrs = append(gvrs, gvr)
}
}
return
}

func parseGroupVersion(gv string) (schema.GroupVersion, error) {
gv = strings.ReplaceAll(gv, " ", "")
if (len(gv) == 0) || (gv == "/") {
// match legacy group
return schema.GroupVersion{}, nil
}

strs := strings.Split(gv, "/")
switch len(strs) {
case 1:
/*
match:
* "group"
*/
return schema.GroupVersion{Group: strs[0]}, nil
case 2:
/*
match:
* "/"
* "group/version"
* "/version"
* "group/"
*/
return schema.GroupVersion{Group: strs[0], Version: strs[1]}, nil
}
return schema.GroupVersion{}, fmt.Errorf("unexpected GroupVersion string: %v, expect <group> or <group>/<version>", gv)
}

func parseGroupVersionResource(gvr string) (schema.GroupVersionResource, error) {
gvr = strings.ReplaceAll(gvr, " ", "")
if gvr == "" {
return schema.GroupVersionResource{}, fmt.Errorf("unexpected GroupVersionResource string: %v, expect <group>/<resource> or <group>/<version>/<resource>", gvr)
}

strs := strings.Split(gvr, "/")
switch len(strs) {
case 2:
/*
match:
* "group/resource"
* "/resource" in legacy group /api
not match:
* "/"
* "group/"
*/
if strs[1] != "" {
return schema.GroupVersionResource{Group: strs[0], Resource: strs[1]}, nil
}
case 3:
/*
match:
* "group/version/resource"
* "/version/resource"
* "//resource"
* "group//resource"
not match:
* "group/version/"
* "group//"
*/
if strs[2] != "" {
return schema.GroupVersionResource{Group: strs[0], Version: strs[1], Resource: strs[2]}, nil
}
}
return schema.GroupVersionResource{}, fmt.Errorf("unexpected GroupVersionResource string: %v, expect <group>/<resource> or <group>/<version>/<resource>", gvr)
}

// TODO(iceber): support with remaining count and continue
func applyListOptionsToCollectionResourceQuery(query *gorm.DB, opts *internal.ListOptions) (int64, *gorm.DB, error) {
return applyListOptionsToQuery(query, opts, nil)
Expand Down

0 comments on commit b457761

Please sign in to comment.