1
0
mirror of https://github.com/rancher/steve.git synced 2025-04-27 19:05:09 +00:00
steve/pkg/server/resources/counts/counts.go

266 lines
5.4 KiB
Go
Raw Normal View History

2019-08-12 22:15:19 +00:00
package counts
import (
"net/http"
2019-09-09 21:28:55 +00:00
"strconv"
2019-08-12 22:15:19 +00:00
"sync"
2019-09-11 21:05:00 +00:00
"github.com/rancher/steve/pkg/accesscontrol"
"github.com/rancher/steve/pkg/attributes"
"github.com/rancher/steve/pkg/clustercache"
2020-01-31 05:37:59 +00:00
"github.com/rancher/steve/pkg/schemaserver/store/empty"
"github.com/rancher/steve/pkg/schemaserver/types"
2019-09-09 21:28:55 +00:00
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
2020-01-31 05:37:59 +00:00
schema2 "k8s.io/apimachinery/pkg/runtime/schema"
2019-08-12 22:15:19 +00:00
)
var (
ignore = map[string]bool{
2019-08-13 04:32:57 +00:00
"count": true,
"schema": true,
"apiRoot": true,
2019-08-12 22:15:19 +00:00
}
)
2020-01-31 05:37:59 +00:00
func Register(schemas *types.APISchemas, ccache clustercache.ClusterCache) {
schemas.MustImportAndCustomize(Count{}, func(schema *types.APISchema) {
2019-08-12 22:15:19 +00:00
schema.CollectionMethods = []string{http.MethodGet}
2019-08-13 04:32:57 +00:00
schema.ResourceMethods = []string{http.MethodGet}
2020-01-31 05:37:59 +00:00
schema.Attributes["access"] = accesscontrol.AccessListByVerb{
2019-08-12 23:47:23 +00:00
"watch": accesscontrol.AccessList{
{
Namespace: "*",
ResourceName: "*",
},
},
}
2019-09-09 21:28:55 +00:00
schema.Store = &Store{
ccache: ccache,
}
2019-08-12 22:15:19 +00:00
})
}
type Count struct {
2019-08-12 23:47:23 +00:00
ID string `json:"id,omitempty"`
2019-09-09 21:28:55 +00:00
Counts map[string]ItemCount `json:"counts"`
2019-08-12 22:15:19 +00:00
}
type ItemCount struct {
2019-08-13 04:32:57 +00:00
Count int `json:"count,omitempty"`
Namespaces map[string]int `json:"namespaces,omitempty"`
2019-09-09 21:28:55 +00:00
Revision int `json:"revision,omitempty"`
2019-08-12 22:15:19 +00:00
}
type Store struct {
empty.Store
2019-09-09 21:28:55 +00:00
ccache clustercache.ClusterCache
2019-08-12 22:15:19 +00:00
}
2020-01-31 05:37:59 +00:00
func toAPIObject(c Count) types.APIObject {
return types.APIObject{
Type: "count",
ID: c.ID,
Object: c,
}
}
func (s *Store) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (types.APIObject, error) {
2019-09-09 21:28:55 +00:00
c := s.getCount(apiOp)
2020-01-31 05:37:59 +00:00
return toAPIObject(c), nil
2019-08-12 22:15:19 +00:00
}
2020-01-31 05:37:59 +00:00
func (s *Store) List(apiOp *types.APIRequest, schema *types.APISchema) (types.APIObjectList, error) {
2019-09-09 21:28:55 +00:00
c := s.getCount(apiOp)
2020-01-31 05:37:59 +00:00
return types.APIObjectList{
Objects: []types.APIObject{
toAPIObject(c),
},
}, nil
2019-08-12 22:15:19 +00:00
}
2020-01-31 05:37:59 +00:00
func (s *Store) Watch(apiOp *types.APIRequest, schema *types.APISchema, w types.WatchRequest) (chan types.APIEvent, error) {
2019-09-09 21:28:55 +00:00
var (
result = make(chan types.APIEvent, 100)
counts map[string]ItemCount
2020-01-31 05:37:59 +00:00
gvrToSchema = map[schema2.GroupVersionResource]*types.APISchema{}
2019-09-09 21:28:55 +00:00
countLock sync.Mutex
)
counts = s.getCount(apiOp).Counts
for id := range counts {
2020-01-31 05:37:59 +00:00
schema := apiOp.Schemas.LookupSchema(id)
2019-09-09 21:28:55 +00:00
if schema == nil {
continue
}
2019-08-12 23:47:23 +00:00
2019-09-09 21:28:55 +00:00
gvrToSchema[attributes.GVR(schema)] = schema
2019-08-12 23:47:23 +00:00
}
go func() {
2019-09-09 21:28:55 +00:00
<-apiOp.Context().Done()
2020-01-31 05:01:21 +00:00
countLock.Lock()
2019-09-09 21:28:55 +00:00
close(result)
2020-01-31 05:01:21 +00:00
result = nil
countLock.Unlock()
2019-08-12 23:47:23 +00:00
}()
2019-09-09 21:28:55 +00:00
onChange := func(add bool, gvr schema2.GroupVersionResource, _ string, obj runtime.Object) error {
countLock.Lock()
defer countLock.Unlock()
2019-08-12 23:47:23 +00:00
2020-01-31 05:01:21 +00:00
if result == nil {
return nil
}
2019-09-09 21:28:55 +00:00
schema := gvrToSchema[gvr]
if schema == nil {
return nil
2019-08-12 23:47:23 +00:00
}
2019-09-09 21:28:55 +00:00
_, namespace, revision, ok := getInfo(obj)
if !ok {
return nil
}
2019-08-12 23:47:23 +00:00
2019-09-09 21:28:55 +00:00
itemCount := counts[schema.ID]
if revision <= itemCount.Revision {
return nil
2019-08-12 23:47:23 +00:00
}
2019-09-09 21:28:55 +00:00
if add {
itemCount.Count++
if namespace != "" {
itemCount.Namespaces[namespace]++
2019-08-13 04:32:57 +00:00
}
2019-09-09 21:28:55 +00:00
} else {
itemCount.Count--
if namespace != "" {
itemCount.Namespaces[namespace]--
2019-08-13 04:32:57 +00:00
}
2019-08-12 23:47:23 +00:00
}
2019-09-09 21:28:55 +00:00
counts[schema.ID] = itemCount
countsCopy := map[string]ItemCount{}
for k, v := range counts {
2020-01-31 05:01:21 +00:00
ns := map[string]int{}
for i, j := range v.Namespaces {
ns[i] = j
}
countsCopy[k] = ItemCount{
Count: v.Count,
Revision: v.Revision,
Namespaces: ns,
}
2019-09-09 21:28:55 +00:00
}
result <- types.APIEvent{
Name: "resource.change",
ResourceType: "counts",
2020-01-31 05:37:59 +00:00
Object: toAPIObject(Count{
2019-09-09 21:28:55 +00:00
ID: "count",
Counts: countsCopy,
}),
2019-08-12 23:47:23 +00:00
}
2019-09-09 21:28:55 +00:00
return nil
2019-08-12 23:47:23 +00:00
}
2019-08-12 22:15:19 +00:00
2019-09-09 21:28:55 +00:00
s.ccache.OnAdd(apiOp.Context(), func(gvr schema2.GroupVersionResource, key string, obj runtime.Object) error {
return onChange(true, gvr, key, obj)
})
s.ccache.OnRemove(apiOp.Context(), func(gvr schema2.GroupVersionResource, key string, obj runtime.Object) error {
2019-09-11 21:27:44 +00:00
return onChange(false, gvr, key, obj)
2019-09-09 21:28:55 +00:00
})
2019-08-12 22:15:19 +00:00
2019-09-09 21:28:55 +00:00
return result, nil
}
2019-08-12 22:15:19 +00:00
2020-01-31 05:37:59 +00:00
func (s *Store) schemasToWatch(apiOp *types.APIRequest) (result []*types.APISchema) {
for _, schema := range apiOp.Schemas.Schemas {
2019-08-12 22:15:19 +00:00
if ignore[schema.ID] {
continue
}
2019-08-16 18:40:42 +00:00
if attributes.PreferredVersion(schema) != "" {
continue
}
if attributes.PreferredGroup(schema) != "" {
continue
}
2019-08-12 22:15:19 +00:00
if schema.Store == nil {
continue
}
if apiOp.AccessControl.CanList(apiOp, schema) != nil {
continue
}
2019-09-09 21:28:55 +00:00
if apiOp.AccessControl.CanWatch(apiOp, schema) != nil {
continue
}
2019-08-12 22:15:19 +00:00
2019-09-09 21:28:55 +00:00
result = append(result, schema)
2019-08-12 22:15:19 +00:00
}
2019-09-09 21:28:55 +00:00
return
}
2019-08-12 23:47:23 +00:00
2019-09-09 21:28:55 +00:00
func getInfo(obj interface{}) (name string, namespace string, revision int, ok bool) {
r, ok := obj.(runtime.Object)
if !ok {
return "", "", 0, false
2019-08-12 23:47:23 +00:00
}
2019-09-09 21:28:55 +00:00
meta, err := meta.Accessor(r)
if err != nil {
return "", "", 0, false
2019-08-12 22:15:19 +00:00
}
2019-09-09 21:28:55 +00:00
revision, err = strconv.Atoi(meta.GetResourceVersion())
if err != nil {
return "", "", 0, false
2019-08-12 22:15:19 +00:00
}
2019-09-09 21:28:55 +00:00
return meta.GetName(), meta.GetNamespace(), revision, true
2019-08-12 22:15:19 +00:00
}
2019-08-12 23:47:23 +00:00
2019-09-09 21:28:55 +00:00
func (s *Store) getCount(apiOp *types.APIRequest) Count {
counts := map[string]ItemCount{}
for _, schema := range s.schemasToWatch(apiOp) {
gvr := attributes.GVR(schema)
rev := 0
itemCount := ItemCount{
Namespaces: map[string]int{},
2019-08-12 23:47:23 +00:00
}
2019-09-09 21:28:55 +00:00
for _, obj := range s.ccache.List(gvr) {
_, ns, revision, ok := getInfo(obj)
if !ok {
continue
}
if revision > rev {
rev = revision
}
itemCount.Count++
if ns != "" {
itemCount.Namespaces[ns]++
}
}
itemCount.Revision = rev
counts[schema.ID] = itemCount
}
return Count{
ID: "count",
Counts: counts,
}
2019-08-12 23:47:23 +00:00
}