Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce code and fix webhook cache latency #361

Merged
33 changes: 33 additions & 0 deletions k8s/apis/containers/v1/generic_container_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package v1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +kubebuilder:object:generate=true
type ContainerStatus struct {
Properties map[string]string `json:"properties"`
}

// +kubebuilder:object:generate=true
type ContainerSpec struct {
}

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// Container is the Schema for the Containers API
type CommonContainer struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec ContainerSpec `json:"spec,omitempty"`
Status ContainerStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true
// ContainerList contains a list of Container
type CommonContainerList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []CommonContainer `json:"items"`
}
106 changes: 106 additions & 0 deletions k8s/apis/containers/v1/generic_webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package v1

import (
"context"
"errors"
"fmt"
"time"

apierrors "k8s.io/apimachinery/pkg/api/errors"

"github.com/eclipse-symphony/symphony/k8s/apis/metrics/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

type GetSubResourceNums func() (n int, err error)

var commoncontainerlog = logf.Log.WithName("commoncontainer-resource")
var cacheClient client.Client
var readerClient client.Reader
var commoncontainermetrics *metrics.Metrics

func InitCommonContainerWebHook(mgr ctrl.Manager) error {
if commoncontainermetrics == nil {
initmetrics, err := metrics.New()
if err != nil {
return err
}
commoncontainermetrics = initmetrics
}
cacheClient = mgr.GetClient()
readerClient = mgr.GetAPIReader()
return nil
}

func SetupWebhookWithManager(mgr ctrl.Manager, resource client.Object) error {
mgr.GetFieldIndexer().IndexField(context.Background(), resource, ".metadata.name", func(rawObj client.Object) []string {
return []string{rawObj.GetName()}
})

return ctrl.NewWebhookManagedBy(mgr).
For(resource).
Complete()
}

// Default implements webhook.Defaulter so a webhook will be registered for the type
func (r *CommonContainer) Default() {
commoncontainerlog.Info("default", "name", r.Name, "kind", r.Kind)
}

func (r *CommonContainer) ValidateCreate() (admission.Warnings, error) {
commoncontainerlog.Info("validate create", "name", r.Name, "kind", r.Kind)
return nil, nil
}
func (r *CommonContainer) ValidateUpdate(old runtime.Object) (admission.Warnings, error) {
commoncontainerlog.Info("validate update", "name", r.Name, "kind", r.Kind)
return nil, nil
}

func (r *CommonContainer) ValidateDelete() (admission.Warnings, error) {
return nil, errors.New("Not implemented")
}

func (r *CommonContainer) validateDeleteContainer() error {
return errors.New("Not implemented")
}

func (r *CommonContainer) ValidateDeleteImpl(getSubResourceNums GetSubResourceNums) (admission.Warnings, error) {

commoncontainerlog.Info("validate delete", "name", r.Name, "kind", r.Kind)

validateDeleteTime := time.Now()
validationError := r.validateDeleteContainerImpl(getSubResourceNums)
if validationError != nil {
commoncontainermetrics.ControllerValidationLatency(
validateDeleteTime,
metrics.CreateOperationType,
metrics.InvalidResource,
metrics.ContainerResourceType)
} else {
commoncontainermetrics.ControllerValidationLatency(
validateDeleteTime,
metrics.CreateOperationType,
metrics.ValidResource,
metrics.ContainerResourceType)
}

return nil, validationError
}

func (r *CommonContainer) validateDeleteContainerImpl(getSubResourceNums GetSubResourceNums) error {
itemsNum, err := getSubResourceNums()
if err != nil {
commoncontainerlog.Error(err, "could not list nested resources ", "name", r.Name, "kind", r.Kind)
return apierrors.NewBadRequest(fmt.Sprintf("%s could not list nested resources for %s.", r.Kind, r.Name))
}
if itemsNum > 0 {
commoncontainerlog.Error(err, "nested resources are not empty", "name", r.Name, "kind", r.Kind)
return apierrors.NewBadRequest(fmt.Sprintf("%s nested resources with root resource '%s' are not empty", r.Kind, r.Name))
}

return nil
}
111 changes: 111 additions & 0 deletions k8s/apis/containers/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions k8s/apis/federation/v1/catalog_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
package v1

import (
commoncontainers "gopls-workspace/apis/containers/v1"
k8smodel "gopls-workspace/apis/model/v1"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/webhook"
)

type CatalogStatus struct {
Expand All @@ -35,6 +37,26 @@ type CatalogList struct {
Items []Catalog `json:"items"`
}

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
FireDefend marked this conversation as resolved.
Show resolved Hide resolved
// CatalogContainer is the Schema for the CatalogContainer API
type CatalogContainer struct {
commoncontainers.CommonContainer
}

// +kubebuilder:object:root=true
// CatalogContainerList contains a list of CatalogContainer
type CatalogContainerList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []CatalogContainer `json:"items"`
}

var _ webhook.Validator = &CatalogContainer{}

var _ webhook.Defaulter = &CatalogContainer{}

func init() {
SchemeBuilder.Register(&Catalog{}, &CatalogList{})
SchemeBuilder.Register(&CatalogContainer{}, &CatalogContainerList{})
}
37 changes: 26 additions & 11 deletions k8s/apis/federation/v1/catalog_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,15 @@ import (

// log is for logging in this package.
var cataloglog = logf.Log.WithName("catalog-resource")
var myCatalogClient client.Client
var myCatalogReaderClient client.Reader
var catalogWebhookValidationMetrics *metrics.Metrics

func (r *Catalog) SetupWebhookWithManager(mgr ctrl.Manager) error {
myCatalogClient = mgr.GetClient()
myCatalogReaderClient = mgr.GetAPIReader()
mgr.GetFieldIndexer().IndexField(context.Background(), &Catalog{}, ".metadata.name", func(rawObj client.Object) []string {
catalog := rawObj.(*Catalog)
return []string{catalog.Name}
})
mgr.GetFieldIndexer().IndexField(context.Background(), &Catalog{}, ".spec.rootResource", func(rawObj client.Object) []string {
catalog := rawObj.(*Catalog)
return []string{catalog.Spec.RootResource}
})

// initialize the controller operation metrics
if catalogWebhookValidationMetrics == nil {
Expand Down Expand Up @@ -68,20 +64,25 @@ func (r *Catalog) Default() {

if r.Spec.RootResource != "" {
var catalogContainer CatalogContainer
err := myCatalogClient.Get(context.Background(), client.ObjectKey{Name: r.Spec.RootResource, Namespace: r.Namespace}, &catalogContainer)
err := myCatalogReaderClient.Get(context.Background(), client.ObjectKey{Name: r.Spec.RootResource, Namespace: r.Namespace}, &catalogContainer)
if err != nil {
cataloglog.Error(err, "failed to get catalog container", "name", r.Spec.RootResource)
} else {
ownerReference := metav1.OwnerReference{
APIVersion: catalogContainer.APIVersion,
Kind: catalogContainer.Kind,
APIVersion: GroupVersion.String(), //catalogContainer.APIVersion
Kind: "CatalogContainer", //catalogContainer.Kind
Name: catalogContainer.Name,
UID: catalogContainer.UID,
}

if !configutils.CheckOwnerReferenceAlreadySet(r.OwnerReferences, ownerReference) {
r.OwnerReferences = append(r.OwnerReferences, ownerReference)
}

if r.Labels == nil {
r.Labels = make(map[string]string)
}
r.Labels["rootResource"] = r.Spec.RootResource
}
}
}
Expand Down Expand Up @@ -170,7 +171,7 @@ func (r *Catalog) checkSchema() *field.Error {
if schemaName, ok := r.Spec.Metadata["schema"]; ok {
cataloglog.Info("Find schema name", "name", schemaName)
var catalogs CatalogList
err := myCatalogClient.List(context.Background(), &catalogs, client.InNamespace(r.ObjectMeta.Namespace), client.MatchingFields{".metadata.name": schemaName})
err := myCatalogReaderClient.List(context.Background(), &catalogs, client.InNamespace(r.ObjectMeta.Namespace), client.MatchingFields{"metadata.name": schemaName}, client.Limit(1))
if err != nil || len(catalogs.Items) == 0 {
cataloglog.Error(err, "Could not find the required schema.", "name", schemaName)
return field.Invalid(field.NewPath("spec").Child("Metadata"), schemaName, "could not find the required schema")
Expand Down Expand Up @@ -236,7 +237,7 @@ func (r *Catalog) validateNameOnCreate() *field.Error {

func (r *Catalog) validateRootResource() *field.Error {
var catalogContainer CatalogContainer
err := myCatalogClient.Get(context.Background(), client.ObjectKey{Name: r.Spec.RootResource, Namespace: r.Namespace}, &catalogContainer)
err := myCatalogReaderClient.Get(context.Background(), client.ObjectKey{Name: r.Spec.RootResource, Namespace: r.Namespace}, &catalogContainer)
if err != nil {
return field.Invalid(field.NewPath("spec").Child("rootResource"), r.Spec.RootResource, "rootResource must be a valid catalog container")
}
Expand All @@ -247,3 +248,17 @@ func (r *Catalog) validateRootResource() *field.Error {

return nil
}

func (r *CatalogContainer) ValidateDelete() (admission.Warnings, error) {
cataloglog.Info("validate delete solution container", "name", r.Name)
FireDefend marked this conversation as resolved.
Show resolved Hide resolved
getSubResourceNums := func() (int, error) {
var catalogList CatalogList
err := myCatalogReaderClient.List(context.Background(), &catalogList, client.InNamespace(r.Namespace), client.MatchingLabels{"rootResource": r.Name}, client.Limit(1))
if err != nil {
return 0, err
} else {
return len(catalogList.Items), nil
}
}
return r.ValidateDeleteImpl(getSubResourceNums)
}
Loading
Loading