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"
|
|
|
|
|
2020-06-12 04:50:59 +00:00
|
|
|
"github.com/rancher/apiserver/pkg/store/empty"
|
|
|
|
"github.com/rancher/apiserver/pkg/types"
|
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"
|
2024-06-04 18:52:48 +00:00
|
|
|
"github.com/rancher/wrangler/v3/pkg/summary"
|
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
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2022-10-03 21:12:11 +00:00
|
|
|
// Register registers a new count schema. This schema isn't a true resource but instead returns counts for other resources
|
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
|
|
|
}
|
|
|
|
|
2020-03-13 02:14:40 +00:00
|
|
|
type Summary struct {
|
|
|
|
Count int `json:"count,omitempty"`
|
|
|
|
States map[string]int `json:"states,omitempty"`
|
|
|
|
Error int `json:"errors,omitempty"`
|
|
|
|
Transitioning int `json:"transitioning,omitempty"`
|
|
|
|
}
|
|
|
|
|
2020-03-26 01:15:21 +00:00
|
|
|
func (s *Summary) DeepCopy() *Summary {
|
|
|
|
r := *s
|
|
|
|
if r.States != nil {
|
|
|
|
r.States = map[string]int{}
|
|
|
|
for k := range s.States {
|
|
|
|
r.States[k] = s.States[k]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return &r
|
|
|
|
}
|
|
|
|
|
2019-08-12 22:15:19 +00:00
|
|
|
type ItemCount struct {
|
2020-03-13 02:14:40 +00:00
|
|
|
Summary Summary `json:"summary,omitempty"`
|
|
|
|
Namespaces map[string]Summary `json:"namespaces,omitempty"`
|
2020-08-28 04:28:37 +00:00
|
|
|
Revision int `json:"-"`
|
2019-08-12 22:15:19 +00:00
|
|
|
}
|
|
|
|
|
2020-03-26 01:15:21 +00:00
|
|
|
func (i *ItemCount) DeepCopy() *ItemCount {
|
|
|
|
r := *i
|
|
|
|
r.Summary = *r.Summary.DeepCopy()
|
|
|
|
if r.Namespaces != nil {
|
|
|
|
r.Namespaces = map[string]Summary{}
|
|
|
|
for k, v := range i.Namespaces {
|
|
|
|
r.Namespaces[k] = *v.DeepCopy()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return &r
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2022-10-03 21:12:11 +00:00
|
|
|
// Watch creates a watch for the Counts schema. This returns only the counts which have changed since the watch was established
|
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 (
|
2022-10-03 21:12:11 +00:00
|
|
|
result = make(chan Count, 100)
|
2019-09-09 21:28:55 +00:00
|
|
|
counts map[string]ItemCount
|
2020-10-23 20:57:57 +00:00
|
|
|
gvkToSchema = map[schema2.GroupVersionKind]*types.APISchema{}
|
2019-09-09 21:28:55 +00:00
|
|
|
countLock sync.Mutex
|
|
|
|
)
|
|
|
|
|
2020-07-20 16:21:03 +00:00
|
|
|
go func() {
|
|
|
|
<-apiOp.Context().Done()
|
|
|
|
countLock.Lock()
|
|
|
|
close(result)
|
|
|
|
result = nil
|
|
|
|
countLock.Unlock()
|
|
|
|
}()
|
|
|
|
|
2019-09-09 21:28:55 +00:00
|
|
|
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
|
|
|
|
2020-10-23 20:57:57 +00:00
|
|
|
gvkToSchema[attributes.GVK(schema)] = schema
|
2019-08-12 23:47:23 +00:00
|
|
|
}
|
|
|
|
|
2020-10-23 20:57:57 +00:00
|
|
|
onChange := func(add bool, gvk schema2.GroupVersionKind, _ string, obj, oldObj runtime.Object) error {
|
2019-09-09 21:28:55 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-10-23 20:57:57 +00:00
|
|
|
schema := gvkToSchema[gvk]
|
2019-09-09 21:28:55 +00:00
|
|
|
if schema == nil {
|
|
|
|
return nil
|
2019-08-12 23:47:23 +00:00
|
|
|
}
|
|
|
|
|
2020-03-13 02:14:40 +00:00
|
|
|
_, namespace, revision, summary, ok := getInfo(obj)
|
2019-09-09 21:28:55 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2020-03-13 02:14:40 +00:00
|
|
|
if oldObj != nil {
|
|
|
|
if _, _, _, oldSummary, ok := getInfo(oldObj); ok {
|
|
|
|
if oldSummary.Transitioning == summary.Transitioning &&
|
|
|
|
oldSummary.Error == summary.Error &&
|
2020-08-28 04:28:37 +00:00
|
|
|
simpleState(oldSummary) == simpleState(summary) {
|
2020-03-13 02:14:40 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
itemCount = removeCounts(itemCount, namespace, oldSummary)
|
2020-03-14 18:46:20 +00:00
|
|
|
itemCount = addCounts(itemCount, namespace, summary)
|
2020-03-13 02:14:40 +00:00
|
|
|
} else {
|
|
|
|
return nil
|
2019-08-13 04:32:57 +00:00
|
|
|
}
|
2020-03-13 02:14:40 +00:00
|
|
|
} else if add {
|
|
|
|
itemCount = addCounts(itemCount, namespace, summary)
|
2019-09-09 21:28:55 +00:00
|
|
|
} else {
|
2020-03-13 02:14:40 +00:00
|
|
|
itemCount = removeCounts(itemCount, namespace, summary)
|
2019-08-12 23:47:23 +00:00
|
|
|
}
|
2019-09-09 21:28:55 +00:00
|
|
|
|
|
|
|
counts[schema.ID] = itemCount
|
2022-10-03 21:12:11 +00:00
|
|
|
changedCount := map[string]ItemCount{
|
2024-03-13 23:41:21 +00:00
|
|
|
schema.ID: *itemCount.DeepCopy(),
|
2019-09-09 21:28:55 +00:00
|
|
|
}
|
|
|
|
|
2022-10-03 21:12:11 +00:00
|
|
|
result <- Count{
|
|
|
|
ID: "count",
|
|
|
|
Counts: changedCount,
|
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
|
|
|
|
2020-10-23 20:57:57 +00:00
|
|
|
s.ccache.OnAdd(apiOp.Context(), func(gvk schema2.GroupVersionKind, key string, obj runtime.Object) error {
|
|
|
|
return onChange(true, gvk, key, obj, nil)
|
2020-03-13 02:14:40 +00:00
|
|
|
})
|
2020-10-23 20:57:57 +00:00
|
|
|
s.ccache.OnChange(apiOp.Context(), func(gvk schema2.GroupVersionKind, key string, obj, oldObj runtime.Object) error {
|
|
|
|
return onChange(true, gvk, key, obj, oldObj)
|
2019-09-09 21:28:55 +00:00
|
|
|
})
|
2020-10-23 20:57:57 +00:00
|
|
|
s.ccache.OnRemove(apiOp.Context(), func(gvk schema2.GroupVersionKind, key string, obj runtime.Object) error {
|
|
|
|
return onChange(false, gvk, key, obj, nil)
|
2019-09-09 21:28:55 +00:00
|
|
|
})
|
2019-08-12 22:15:19 +00:00
|
|
|
|
2022-10-03 21:12:11 +00:00
|
|
|
// buffer the counts so that we don't spam the consumer with constant updates
|
|
|
|
return countsBuffer(result), nil
|
2019-09-09 21:28:55 +00:00
|
|
|
}
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
2020-03-13 02:14:40 +00:00
|
|
|
func getInfo(obj interface{}) (name string, namespace string, revision int, summaryResult summary.Summary, ok bool) {
|
2019-09-09 21:28:55 +00:00
|
|
|
r, ok := obj.(runtime.Object)
|
|
|
|
if !ok {
|
2020-03-13 02:14:40 +00:00
|
|
|
return "", "", 0, summaryResult, 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 {
|
2020-03-13 02:14:40 +00:00
|
|
|
return "", "", 0, summaryResult, 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 {
|
2020-03-13 02:14:40 +00:00
|
|
|
return "", "", 0, summaryResult, false
|
2019-08-12 22:15:19 +00:00
|
|
|
}
|
|
|
|
|
2020-03-21 14:51:13 +00:00
|
|
|
summaryResult = summary.Summarize(r)
|
2020-03-13 02:14:40 +00:00
|
|
|
return meta.GetName(), meta.GetNamespace(), revision, summaryResult, true
|
|
|
|
}
|
|
|
|
|
|
|
|
func removeCounts(itemCount ItemCount, ns string, summary summary.Summary) ItemCount {
|
|
|
|
itemCount.Summary = removeSummary(itemCount.Summary, summary)
|
2020-03-14 18:46:20 +00:00
|
|
|
if ns != "" {
|
2020-03-13 02:14:40 +00:00
|
|
|
itemCount.Namespaces[ns] = removeSummary(itemCount.Namespaces[ns], summary)
|
|
|
|
}
|
|
|
|
return itemCount
|
|
|
|
}
|
|
|
|
|
|
|
|
func addCounts(itemCount ItemCount, ns string, summary summary.Summary) ItemCount {
|
|
|
|
itemCount.Summary = addSummary(itemCount.Summary, summary)
|
2020-03-14 18:46:20 +00:00
|
|
|
if ns != "" {
|
2020-03-13 02:14:40 +00:00
|
|
|
itemCount.Namespaces[ns] = addSummary(itemCount.Namespaces[ns], summary)
|
|
|
|
}
|
|
|
|
return itemCount
|
|
|
|
}
|
|
|
|
|
|
|
|
func removeSummary(counts Summary, summary summary.Summary) Summary {
|
|
|
|
counts.Count--
|
|
|
|
if summary.Transitioning {
|
|
|
|
counts.Transitioning--
|
|
|
|
}
|
|
|
|
if summary.Error {
|
|
|
|
counts.Error--
|
|
|
|
}
|
2020-08-28 04:28:37 +00:00
|
|
|
if simpleState(summary) != "" {
|
2020-03-13 02:14:40 +00:00
|
|
|
if counts.States == nil {
|
|
|
|
counts.States = map[string]int{}
|
|
|
|
}
|
2022-10-14 20:21:17 +00:00
|
|
|
counts.States[simpleState(summary)]--
|
2020-03-13 02:14:40 +00:00
|
|
|
}
|
|
|
|
return counts
|
|
|
|
}
|
|
|
|
|
|
|
|
func addSummary(counts Summary, summary summary.Summary) Summary {
|
|
|
|
counts.Count++
|
|
|
|
if summary.Transitioning {
|
|
|
|
counts.Transitioning++
|
|
|
|
}
|
|
|
|
if summary.Error {
|
|
|
|
counts.Error++
|
|
|
|
}
|
2020-08-28 04:28:37 +00:00
|
|
|
if simpleState(summary) != "" {
|
2020-03-13 02:14:40 +00:00
|
|
|
if counts.States == nil {
|
|
|
|
counts.States = map[string]int{}
|
|
|
|
}
|
2022-10-14 20:21:17 +00:00
|
|
|
counts.States[simpleState(summary)]++
|
2020-03-13 02:14:40 +00:00
|
|
|
}
|
|
|
|
return counts
|
2019-08-12 22:15:19 +00:00
|
|
|
}
|
2019-08-12 23:47:23 +00:00
|
|
|
|
2020-08-28 04:28:37 +00:00
|
|
|
func simpleState(summary summary.Summary) string {
|
|
|
|
if summary.Error {
|
|
|
|
return "error"
|
|
|
|
} else if summary.Transitioning {
|
|
|
|
return "in-progress"
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
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) {
|
2020-10-23 20:57:57 +00:00
|
|
|
gvk := attributes.GVK(schema)
|
2020-02-10 17:18:20 +00:00
|
|
|
access, _ := attributes.Access(schema).(accesscontrol.AccessListByVerb)
|
2019-09-09 21:28:55 +00:00
|
|
|
|
|
|
|
rev := 0
|
|
|
|
itemCount := ItemCount{
|
2020-03-13 02:14:40 +00:00
|
|
|
Namespaces: map[string]Summary{},
|
2019-08-12 23:47:23 +00:00
|
|
|
}
|
2019-09-09 21:28:55 +00:00
|
|
|
|
2020-02-10 17:18:20 +00:00
|
|
|
all := access.Grants("list", "*", "*")
|
|
|
|
|
2020-10-23 20:57:57 +00:00
|
|
|
for _, obj := range s.ccache.List(gvk) {
|
2020-03-13 02:14:40 +00:00
|
|
|
name, ns, revision, summary, ok := getInfo(obj)
|
2019-09-09 21:28:55 +00:00
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-02-10 17:18:20 +00:00
|
|
|
if !all && !access.Grants("list", ns, name) && !access.Grants("get", ns, name) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2019-09-09 21:28:55 +00:00
|
|
|
if revision > rev {
|
|
|
|
rev = revision
|
|
|
|
}
|
|
|
|
|
2020-03-13 02:14:40 +00:00
|
|
|
itemCount = addCounts(itemCount, ns, summary)
|
2019-09-09 21:28:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
itemCount.Revision = rev
|
|
|
|
counts[schema.ID] = itemCount
|
|
|
|
}
|
|
|
|
|
|
|
|
return Count{
|
|
|
|
ID: "count",
|
|
|
|
Counts: counts,
|
|
|
|
}
|
2019-08-12 23:47:23 +00:00
|
|
|
}
|