From 3dbd5a2ea26b18ff906e51dba3b2221937c987f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E6=B3=BD=E8=BD=A9?= Date: Fri, 6 Sep 2024 10:03:05 +0800 Subject: [PATCH] dynamicconfig: implement control plane (1/n) (#707) Signed-off-by: spacewander --- .github/workflows/labeler.yml | 2 +- .../internal/controller/component/k8s.go | 18 +- .../controller/dynamicconfig_controller.go | 159 ++++++++++++++ controller/internal/istio/envoyfilter.go | 123 ++++++++++- controller/internal/istio/envoyfilter_test.go | 38 ++++ .../istio/testdata/dynamic_configs.yml | 75 +++++++ controller/internal/metrics/metrics.go | 7 + controller/internal/metrics/metrics_test.go | 2 +- controller/pkg/component/component.go | 1 + .../dynamicconfig_controller_test.go | 112 ++++++++++ .../integration/controller/suite_test.go | 6 + .../testdata/dynamicconfig/dynamicconfig.yml | 9 + controller/tests/pkg/utils.go | 2 + .../crds/htnn.mosn.io_dynamicconfigs.yaml | 132 ++++++++++++ .../htnn-controller/templates/rbac/role.yaml | 26 +++ patch/README.md | 1 + .../istio/1.21/20240903-dynamic-configs.patch | 16 ++ types/apis/v1/dynamicconfig_types.go | 100 +++++++++ types/apis/v1/validation.go | 5 + types/apis/v1/zz_generated.deepcopy.go | 98 +++++++++ .../versioned/typed/apis/v1/apis_client.go | 5 + .../versioned/typed/apis/v1/dynamicconfig.go | 196 ++++++++++++++++++ .../typed/apis/v1/fake/fake_apis_client.go | 4 + .../typed/apis/v1/fake/fake_dynamicconfig.go | 142 +++++++++++++ .../typed/apis/v1/generated_expansion.go | 2 + 25 files changed, 1274 insertions(+), 7 deletions(-) create mode 100644 controller/internal/controller/dynamicconfig_controller.go create mode 100644 controller/internal/istio/testdata/dynamic_configs.yml create mode 100644 controller/tests/integration/controller/dynamicconfig_controller_test.go create mode 100644 controller/tests/integration/controller/testdata/dynamicconfig/dynamicconfig.yml create mode 100644 manifests/charts/htnn-controller/templates/crds/htnn.mosn.io_dynamicconfigs.yaml create mode 100644 patch/istio/1.21/20240903-dynamic-configs.patch create mode 100644 types/apis/v1/dynamicconfig_types.go create mode 100644 types/pkg/client/clientset/versioned/typed/apis/v1/dynamicconfig.go create mode 100644 types/pkg/client/clientset/versioned/typed/apis/v1/fake/fake_dynamicconfig.go diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 3dd04b42..b7ddbc0a 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -14,5 +14,5 @@ jobs: uses: "pascalgn/size-label-action@v0.5.4" env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - IGNORED: "**/*.pb.*\n*.sum\ntypes/pkg/client\n**/zz_generated.deepcopy.go" + IGNORED: "**/*.pb.*\n*.sum\ntypes/pkg/client/**\n**/zz_generated.deepcopy.go" # use same size labels as kubernetes: https://github.com/kubernetes/kubernetes/labels?q=size diff --git a/controller/internal/controller/component/k8s.go b/controller/internal/controller/component/k8s.go index f5fd0abb..d9d7e872 100644 --- a/controller/internal/controller/component/k8s.go +++ b/controller/internal/controller/component/k8s.go @@ -47,11 +47,23 @@ func NewK8sOutput(c client.Client) component.Output { } func (o *k8sOutput) FromFilterPolicy(ctx context.Context, generatedEnvoyFilters map[component.EnvoyFilterKey]*istiov1a3.EnvoyFilter) error { + return o.diffGeneratedEnvoyFilters(ctx, "FilterPolicy", generatedEnvoyFilters) +} + +func (o *k8sOutput) FromConsumer(ctx context.Context, ef *istiov1a3.EnvoyFilter) error { + return o.diffGeneratedEnvoyFilter(ctx, "Consumer", ef) +} + +func (o *k8sOutput) FromDynamicConfig(ctx context.Context, efs map[component.EnvoyFilterKey]*istiov1a3.EnvoyFilter) error { + return o.diffGeneratedEnvoyFilters(ctx, "DynamicConfig", efs) +} + +func (o *k8sOutput) diffGeneratedEnvoyFilters(ctx context.Context, creator string, generatedEnvoyFilters map[component.EnvoyFilterKey]*istiov1a3.EnvoyFilter) error { logger := o.logger var envoyfilters istiov1a3.EnvoyFilterList if err := o.List(ctx, &envoyfilters, - client.MatchingLabels{constant.LabelCreatedBy: "FilterPolicy"}, + client.MatchingLabels{constant.LabelCreatedBy: creator}, ); err != nil { return fmt.Errorf("failed to list EnvoyFilter: %w", err) } @@ -101,12 +113,12 @@ func (o *k8sOutput) FromFilterPolicy(ctx context.Context, generatedEnvoyFilters return nil } -func (o *k8sOutput) FromConsumer(ctx context.Context, ef *istiov1a3.EnvoyFilter) error { +func (o *k8sOutput) diffGeneratedEnvoyFilter(ctx context.Context, creator string, ef *istiov1a3.EnvoyFilter) error { logger := o.logger nsName := types.NamespacedName{Name: ef.Name, Namespace: ef.Namespace} var envoyfilters istiov1a3.EnvoyFilterList - if err := o.List(ctx, &envoyfilters, client.MatchingLabels{constant.LabelCreatedBy: "Consumer"}); err != nil { + if err := o.List(ctx, &envoyfilters, client.MatchingLabels{constant.LabelCreatedBy: creator}); err != nil { return fmt.Errorf("failed to list EnvoyFilter: %w", err) } diff --git a/controller/internal/controller/dynamicconfig_controller.go b/controller/internal/controller/dynamicconfig_controller.go new file mode 100644 index 00000000..15cf1c1a --- /dev/null +++ b/controller/internal/controller/dynamicconfig_controller.go @@ -0,0 +1,159 @@ +/* +Copyright The HTNN Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + "fmt" + "time" + + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "mosn.io/htnn/controller/internal/istio" + "mosn.io/htnn/controller/internal/log" + "mosn.io/htnn/controller/internal/metrics" + "mosn.io/htnn/controller/pkg/component" + mosniov1 "mosn.io/htnn/types/apis/v1" +) + +// DynamicConfigReconciler reconciles a DynamicConfig object +type DynamicConfigReconciler struct { + component.ResourceManager + Output component.Output +} + +//+kubebuilder:rbac:groups=htnn.mosn.io,resources=dynamicconfigs,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=htnn.mosn.io,resources=dynamicconfigs/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=htnn.mosn.io,resources=dynamicconfigs/finalizers,verbs=update + +func (r *DynamicConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + reconcilationStart := time.Now() + defer func() { + reconcilationDuration := time.Since(reconcilationStart).Seconds() + metrics.DynamicConfigReconcileDurationDistribution.Record(reconcilationDuration) + }() + + log.Info("Reconcile DynamicConfig") + + var dynamicConfigs mosniov1.DynamicConfigList + state, err := r.dynamicconfigsToState(ctx, &dynamicConfigs) + if err != nil { + return ctrl.Result{}, err + } + + err = r.generateCustomResource(ctx, state) + if err != nil { + return ctrl.Result{}, err + } + + err = r.updateDynamicConfigs(ctx, &dynamicConfigs) + return ctrl.Result{}, err +} + +type dynamicConfigReconcileState struct { + namespaceToDynamicConfigs map[string]map[string]*mosniov1.DynamicConfig +} + +func (r *DynamicConfigReconciler) dynamicconfigsToState(ctx context.Context, + dynamicConfigs *mosniov1.DynamicConfigList) (*dynamicConfigReconcileState, error) { + + if err := r.List(ctx, dynamicConfigs); err != nil { + return nil, fmt.Errorf("failed to list DynamicConfig: %w", err) + } + + namespaceToDynamicConfigs := make(map[string]map[string]*mosniov1.DynamicConfig) + for i := range dynamicConfigs.Items { + dynamicConfig := &dynamicConfigs.Items[i] + + // defensive code in case the webhook doesn't work + if dynamicConfig.IsSpecChanged() { + err := mosniov1.ValidateDynamicConfig(dynamicConfig) + if err != nil { + log.Errorf("invalid DynamicConfig, err: %v, name: %s, namespace: %s", dynamicConfig.Name, dynamicConfig.Namespace) + dynamicConfig.SetAccepted(mosniov1.ReasonInvalid, err.Error()) + continue + } + } + if !dynamicConfig.IsValid() { + continue + } + + namespace := dynamicConfig.Namespace + if namespaceToDynamicConfigs[namespace] == nil { + namespaceToDynamicConfigs[namespace] = make(map[string]*mosniov1.DynamicConfig) + } + + name := dynamicConfig.Spec.Type + if namespaceToDynamicConfigs[namespace][name] != nil { + log.Errorf("duplicate DynamicConfig %s/%s, k8s name %s takes effect, k8s name %s ignored", namespace, name, + namespaceToDynamicConfigs[namespace][name].Name, dynamicConfig.Name) + dynamicConfig.SetAccepted(mosniov1.ReasonInvalid, + fmt.Sprintf("duplicate with another DynamicConfig %s/%s, k8s name %s", namespace, name, dynamicConfig.Name)) + } else { + namespaceToDynamicConfigs[namespace][name] = dynamicConfig + dynamicConfig.SetAccepted(mosniov1.ReasonAccepted) + } + } + + state := &dynamicConfigReconcileState{ + namespaceToDynamicConfigs: namespaceToDynamicConfigs, + } + return state, nil +} + +func (r *DynamicConfigReconciler) generateCustomResource(ctx context.Context, state *dynamicConfigReconcileState) error { + efs := istio.GenerateDynamicConfigs(state.namespaceToDynamicConfigs) + return r.Output.FromDynamicConfig(ctx, efs) +} + +func (r *DynamicConfigReconciler) updateDynamicConfigs(ctx context.Context, dynamicConfigs *mosniov1.DynamicConfigList) error { + for i := range dynamicConfigs.Items { + dynamicConfig := &dynamicConfigs.Items[i] + if !dynamicConfig.Status.IsChanged() { + continue + } + dynamicConfig.Status.Reset() + if err := r.UpdateStatus(ctx, dynamicConfig, &dynamicConfig.Status); err != nil { + return fmt.Errorf("failed to update DynamicConfig status: %w, namespacedName: %v", + err, + types.NamespacedName{Name: dynamicConfig.Name, Namespace: dynamicConfig.Namespace}) + } + } + return nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *DynamicConfigReconciler) SetupWithManager(mgr ctrl.Manager) error { + controller := ctrl.NewControllerManagedBy(mgr). + Named("dynamicConfig"). + Watches( + &mosniov1.DynamicConfig{}, + handler.EnqueueRequestsFromMapFunc(func(_ context.Context, _ client.Object) []reconcile.Request { + return triggerReconciliation() + }), + builder.WithPredicates( + predicate.GenerationChangedPredicate{}, + ), + ) + return controller.Complete(r) +} diff --git a/controller/internal/istio/envoyfilter.go b/controller/internal/istio/envoyfilter.go index b19c9636..a9fd57ab 100644 --- a/controller/internal/istio/envoyfilter.go +++ b/controller/internal/istio/envoyfilter.go @@ -15,6 +15,7 @@ package istio import ( + "encoding/json" "fmt" "sort" @@ -29,6 +30,7 @@ import ( "mosn.io/htnn/controller/internal/model" "mosn.io/htnn/controller/pkg/component" "mosn.io/htnn/controller/pkg/constant" + mosniov1 "mosn.io/htnn/types/apis/v1" ) func MustNewStruct(fields map[string]interface{}) *structpb.Struct { @@ -41,8 +43,9 @@ func MustNewStruct(fields map[string]interface{}) *structpb.Struct { } const ( - DefaultHTTPFilter = "htnn-http-filter" - ECDSConsumerName = "htnn-consumer" + DefaultHTTPFilter = "htnn-http-filter" + ECDSConsumerName = "htnn-consumer" + DynamicConfigEnvoyFilterName = "htnn-dynamic-config" ) type configWrapper struct { @@ -465,3 +468,119 @@ func GenerateConsumers(consumers map[string]interface{}) *istiov1a3.EnvoyFilter }, } } + +func GenerateDynamicConfigs(namespacedDynamicConfigs map[string]map[string]*mosniov1.DynamicConfig) map[component.EnvoyFilterKey]*istiov1a3.EnvoyFilter { + efs := map[component.EnvoyFilterKey]*istiov1a3.EnvoyFilter{} + for ns, dynamicConfigs := range namespacedDynamicConfigs { + ef := &istiov1a3.EnvoyFilter{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns, + Name: DynamicConfigEnvoyFilterName, + Labels: map[string]string{ + constant.LabelCreatedBy: "DynamicConfig", + }, + }, + Spec: istioapi.EnvoyFilter{ + ConfigPatches: []*istioapi.EnvoyFilter_EnvoyConfigObjectPatch{}, + }, + } + // Each DynamicConfig is smaller than 1.5MB, which is the limit applied by the k8s API server (the value may be different by configured). + // In prod, we generate the EnvoyFilter inside the istio, so the size of EnvoyFilter doesn't matter. + + configs := make([]*mosniov1.DynamicConfig, 0, len(dynamicConfigs)) + for _, dynamicConfig := range dynamicConfigs { + configs = append(configs, dynamicConfig) + } + sort.Slice(configs, func(i, j int) bool { + return configs[i].Spec.Type < configs[j].Spec.Type + }) + + httpFilters := []interface{}{} + for _, cfg := range configs { + var dispatchedConfig interface{} + _ = json.Unmarshal(cfg.Spec.Config.Raw, &dispatchedConfig) + + ef.Spec.ConfigPatches = append(ef.Spec.ConfigPatches, &istioapi.EnvoyFilter_EnvoyConfigObjectPatch{ + ApplyTo: istioapi.EnvoyFilter_EXTENSION_CONFIG, + Patch: &istioapi.EnvoyFilter_Patch{ + Operation: istioapi.EnvoyFilter_Patch_ADD, + Value: MustNewStruct(map[string]interface{}{ + "name": fmt.Sprintf("htnn-DynamicConfig-%s", cfg.Spec.Type), + "typed_config": map[string]interface{}{ + "@type": "type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config", + "library_id": "dc", + "library_path": ctrlcfg.GoSoPath(), + "plugin_name": "dc", + "plugin_config": map[string]interface{}{ + "@type": "type.googleapis.com/xds.type.v3.TypedStruct", + "value": map[string]interface{}{ + "name": cfg.Spec.Type, + "config": dispatchedConfig, + }, + }, + }, + }), + }, + }) + httpFilters = append(httpFilters, map[string]interface{}{ + "name": fmt.Sprintf("htnn-DynamicConfig-%s", cfg.Spec.Type), + "config_discovery": map[string]interface{}{ + "config_source": map[string]interface{}{ + "ads": map[string]interface{}{}, + }, + "type_urls": []interface{}{ + "type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config", + }, + }, + }) + } + + httpFilters = append(httpFilters, map[string]interface{}{ + "name": "envoy.filters.http.router", + "typed_config": map[string]interface{}{ + "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router", + }, + }) + listener := &istioapi.EnvoyFilter_EnvoyConfigObjectPatch{ + ApplyTo: istioapi.EnvoyFilter_LISTENER, + Patch: &istioapi.EnvoyFilter_Patch{ + Operation: istioapi.EnvoyFilter_Patch_ADD, + Value: MustNewStruct(map[string]interface{}{ + "name": "htnn_dynamic_config", + "internal_listener": map[string]interface{}{}, + "filter_chains": []interface{}{ + map[string]interface{}{ + "filters": []interface{}{ + map[string]interface{}{ + "name": "envoy.filters.network.http_connection_manager", + "typed_config": map[string]interface{}{ + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "stat_prefix": "htnn_dynamic_config", + "http_filters": httpFilters, + "route_config": map[string]interface{}{ + "name": "htnn_dynamic_config", + "virtual_hosts": []interface{}{ + map[string]interface{}{ + "name": "htnn_dynamic_config", + "domains": []interface{}{"*"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + ), + }, + } + ef.Spec.ConfigPatches = append(ef.Spec.ConfigPatches, listener) + efs[component.EnvoyFilterKey{ + Namespace: ns, + Name: ef.Name, + }] = ef + } + + return efs +} diff --git a/controller/internal/istio/envoyfilter_test.go b/controller/internal/istio/envoyfilter_test.go index cf6d1213..49cd059d 100644 --- a/controller/internal/istio/envoyfilter_test.go +++ b/controller/internal/istio/envoyfilter_test.go @@ -23,11 +23,14 @@ import ( local_ratelimit "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/local_ratelimit/v3" "github.com/stretchr/testify/require" istiov1a3 "istio.io/client-go/pkg/apis/networking/v1alpha3" + "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/yaml" "mosn.io/htnn/api/pkg/filtermanager/api" "mosn.io/htnn/api/pkg/plugins" ctrlcfg "mosn.io/htnn/controller/internal/config" + "mosn.io/htnn/controller/pkg/component" + mosniov1 "mosn.io/htnn/types/apis/v1" ) type basePlugin struct { @@ -161,3 +164,38 @@ func TestGenerateConsumers(t *testing.T) { want := string(d) require.Equal(t, want, actual) } + +func TestGenerateDynamicConfigs(t *testing.T) { + patch := gomonkey.ApplyFuncReturn(ctrlcfg.GoSoPath, "/etc/libgolang.so") + defer patch.Reset() + + out := GenerateDynamicConfigs(map[string]map[string]*mosniov1.DynamicConfig{ + "ns": { + "cb_name": { + Spec: mosniov1.DynamicConfigSpec{ + Type: "cb_name", + Config: runtime.RawExtension{ + Raw: []byte(`{"key": "value"}`), + }, + }, + }, + "cb_name2": { + Spec: mosniov1.DynamicConfigSpec{ + Type: "cb_name2", + Config: runtime.RawExtension{ + Raw: []byte(`{"key2": "value"}`), + }, + }, + }, + }, + }) + d, _ := yaml.Marshal(out[component.EnvoyFilterKey{ + Namespace: "ns", + Name: DynamicConfigEnvoyFilterName, + }]) + actual := string(d) + expFile := filepath.Join("testdata", "dynamic_configs.yml") + d, _ = os.ReadFile(expFile) + want := string(d) + require.Equal(t, want, actual) +} diff --git a/controller/internal/istio/testdata/dynamic_configs.yml b/controller/internal/istio/testdata/dynamic_configs.yml new file mode 100644 index 00000000..02412a6c --- /dev/null +++ b/controller/internal/istio/testdata/dynamic_configs.yml @@ -0,0 +1,75 @@ +metadata: + creationTimestamp: null + labels: + htnn.mosn.io/created-by: DynamicConfig + name: htnn-dynamic-config + namespace: ns +spec: + configPatches: + - applyTo: EXTENSION_CONFIG + patch: + operation: ADD + value: + name: htnn-DynamicConfig-cb_name + typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config + library_id: dc + library_path: /etc/libgolang.so + plugin_config: + '@type': type.googleapis.com/xds.type.v3.TypedStruct + value: + config: + key: value + name: cb_name + plugin_name: dc + - applyTo: EXTENSION_CONFIG + patch: + operation: ADD + value: + name: htnn-DynamicConfig-cb_name2 + typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config + library_id: dc + library_path: /etc/libgolang.so + plugin_config: + '@type': type.googleapis.com/xds.type.v3.TypedStruct + value: + config: + key2: value + name: cb_name2 + plugin_name: dc + - applyTo: LISTENER + patch: + operation: ADD + value: + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + http_filters: + - config_discovery: + config_source: + ads: {} + type_urls: + - type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config + name: htnn-DynamicConfig-cb_name + - config_discovery: + config_source: + ads: {} + type_urls: + - type.googleapis.com/envoy.extensions.filters.http.golang.v3alpha.Config + name: htnn-DynamicConfig-cb_name2 + - name: envoy.filters.http.router + typed_config: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + route_config: + name: htnn_dynamic_config + virtual_hosts: + - domains: + - '*' + name: htnn_dynamic_config + stat_prefix: htnn_dynamic_config + internal_listener: {} + name: htnn_dynamic_config +status: {} diff --git a/controller/internal/metrics/metrics.go b/controller/internal/metrics/metrics.go index bae3f787..5c1dfbf4 100644 --- a/controller/internal/metrics/metrics.go +++ b/controller/internal/metrics/metrics.go @@ -24,6 +24,7 @@ const ( FP = "htnn_filterpolicy" Consumer = "htnn_consumer" SR = "htnn_service_registry" + DC = "htnn_dynamic_config" TranslateDurationSuffix = "translate_duration_seconds" ReconcileDurationSuffix = "reconcile_duration_seconds" ) @@ -38,6 +39,7 @@ var ( FPReconcileDurationDistribution component.Distribution = &voidMetric{} ConsumerReconcileDurationDistribution component.Distribution = &voidMetric{} ServiceRegistryReconcileDurationDistribution component.Distribution = &voidMetric{} + DynamicConfigReconcileDurationDistribution component.Distribution = &voidMetric{} ) func InitMetrics(provider component.MetricProvider) { @@ -62,4 +64,9 @@ func InitMetrics(provider component.MetricProvider) { // minimal: 100 microseconds []float64{1e-4, 1e-3, 0.01, 0.1, 1, 10}, ) + DynamicConfigReconcileDurationDistribution = provider.NewDistribution(fmt.Sprintf("%s_%s", DC, ReconcileDurationSuffix), + "How long in seconds HTNN reconciles DynamicConfig.", + // minimal: 100 microseconds + []float64{1e-4, 1e-3, 0.01, 0.1, 1, 10}, + ) } diff --git a/controller/internal/metrics/metrics_test.go b/controller/internal/metrics/metrics_test.go index 7c97d843..3fd69968 100644 --- a/controller/internal/metrics/metrics_test.go +++ b/controller/internal/metrics/metrics_test.go @@ -34,5 +34,5 @@ func (m *metricProvider) NewDistribution(name string, description string, bucket func TestInitMetrics(t *testing.T) { p := &metricProvider{} InitMetrics(p) - assert.Equal(t, 4, p.distributions) + assert.Equal(t, 5, p.distributions) } diff --git a/controller/pkg/component/component.go b/controller/pkg/component/component.go index 90dcdbb3..a5be3fd1 100644 --- a/controller/pkg/component/component.go +++ b/controller/pkg/component/component.go @@ -35,6 +35,7 @@ type Output interface { // it assumes the write already succeed, and don't retry on error, // so the output should handle the retry by themselves. That's why the error is not returned here. FromServiceRegistry(ctx context.Context, serviceEntries map[string]*istioapi.ServiceEntry) + FromDynamicConfig(ctx context.Context, envoyFilters map[EnvoyFilterKey]*istiov1a3.EnvoyFilter) error } type ResourceManager interface { diff --git a/controller/tests/integration/controller/dynamicconfig_controller_test.go b/controller/tests/integration/controller/dynamicconfig_controller_test.go new file mode 100644 index 00000000..340423d8 --- /dev/null +++ b/controller/tests/integration/controller/dynamicconfig_controller_test.go @@ -0,0 +1,112 @@ +// Copyright The HTNN Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controller + +import ( + "context" + "path/filepath" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + istiov1a3 "istio.io/client-go/pkg/apis/networking/v1alpha3" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "mosn.io/htnn/controller/tests/integration/helper" + "mosn.io/htnn/controller/tests/pkg" + mosniov1 "mosn.io/htnn/types/apis/v1" +) + +func mustReadDynamicConfig(fn string, out *[]map[string]interface{}) { + fn = filepath.Join("testdata", "dynamicconfig", fn+".yml") + helper.MustReadInput(fn, out) +} + +var _ = Describe("DynamicConfig controller", func() { + + const ( + timeout = time.Second * 10 + interval = time.Millisecond * 250 + ) + + AfterEach(func() { + var configs mosniov1.DynamicConfigList + if err := k8sClient.List(ctx, &configs); err == nil { + for _, e := range configs.Items { + pkg.DeleteK8sResource(ctx, k8sClient, &e) + } + } + + var envoyfilters istiov1a3.EnvoyFilterList + if err := k8sClient.List(ctx, &envoyfilters); err == nil { + for _, e := range envoyfilters.Items { + pkg.DeleteK8sResource(ctx, k8sClient, e) + } + } + }) + + Context("When reconciling DynamicConfig", func() { + It("deal with crd", func() { + ctx := context.Background() + input := []map[string]interface{}{} + mustReadDynamicConfig("dynamicconfig", &input) + for _, in := range input { + obj := pkg.MapToObj(in) + Expect(k8sClient.Create(ctx, obj)).Should(Succeed()) + } + + var configs mosniov1.DynamicConfigList + var c *mosniov1.DynamicConfig + var cs []metav1.Condition + Eventually(func() bool { + if err := k8sClient.List(ctx, &configs); err != nil { + return false + } + handled := len(configs.Items) == 1 + for _, item := range configs.Items { + item := item + if item.Name == "test" { + c = &item + cs = c.Status.Conditions + } + conds := item.Status.Conditions + if len(conds) != 1 { + handled = false + break + } + } + + return handled + }, timeout, interval).Should(BeTrue()) + Expect(c).ToNot(BeNil()) + Expect(cs[0].Type).To(Equal(string(mosniov1.ConditionAccepted))) + Expect(cs[0].Reason).To(Equal(string(mosniov1.ReasonAccepted))) + + var envoyfilters istiov1a3.EnvoyFilterList + Eventually(func() bool { + if err := k8sClient.List(ctx, &envoyfilters); err != nil { + return false + } + for _, item := range envoyfilters.Items { + if item.Name == "htnn-dynamic-config" { + return true + } + } + return false + }, timeout, interval).Should(BeTrue()) + // FIXME: check when the dynamic config is updated to invalid + }) + }) +}) diff --git a/controller/tests/integration/controller/suite_test.go b/controller/tests/integration/controller/suite_test.go index cd9ab969..90e62da1 100644 --- a/controller/tests/integration/controller/suite_test.go +++ b/controller/tests/integration/controller/suite_test.go @@ -153,6 +153,12 @@ var _ = BeforeSuite(func() { ).SetupWithManager(k8sManager) Expect(err).ToNot(HaveOccurred()) + err = (&controller.DynamicConfigReconciler{ + ResourceManager: rm, + Output: output, + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + go func() { defer GinkgoRecover() err = k8sManager.Start(ctx) diff --git a/controller/tests/integration/controller/testdata/dynamicconfig/dynamicconfig.yml b/controller/tests/integration/controller/testdata/dynamicconfig/dynamicconfig.yml new file mode 100644 index 00000000..4395ce4d --- /dev/null +++ b/controller/tests/integration/controller/testdata/dynamicconfig/dynamicconfig.yml @@ -0,0 +1,9 @@ +- apiVersion: htnn.mosn.io/v1 + kind: DynamicConfig + metadata: + name: test + namespace: default + spec: + type: cb_name + config: + key: value diff --git a/controller/tests/pkg/utils.go b/controller/tests/pkg/utils.go index bdf2e77e..3aca8d46 100644 --- a/controller/tests/pkg/utils.go +++ b/controller/tests/pkg/utils.go @@ -60,6 +60,8 @@ func MapToObj(in map[string]interface{}) client.Object { out = &mosniov1.Consumer{} case "ServiceRegistry": out = &mosniov1.ServiceRegistry{} + case "DynamicConfig": + out = &mosniov1.DynamicConfig{} } } if out == nil { diff --git a/manifests/charts/htnn-controller/templates/crds/htnn.mosn.io_dynamicconfigs.yaml b/manifests/charts/htnn-controller/templates/crds/htnn.mosn.io_dynamicconfigs.yaml new file mode 100644 index 00000000..81ea526d --- /dev/null +++ b/manifests/charts/htnn-controller/templates/crds/htnn.mosn.io_dynamicconfigs.yaml @@ -0,0 +1,132 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: dynamicconfigs.htnn.mosn.io +spec: + group: htnn.mosn.io + names: + kind: DynamicConfig + listKind: DynamicConfigList + plural: dynamicconfigs + singular: dynamicconfig + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: DynamicConfig is the Schema for the dynamicconfigs API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: DynamicConfigSpec defines the desired state of DynamicConfig + properties: + config: + type: object + x-kubernetes-preserve-unknown-fields: true + type: + type: string + required: + - config + - type + type: object + status: + description: DynamicConfigStatus defines the observed state of DynamicConfig + properties: + conditions: + description: Conditions describe the current conditions. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/manifests/charts/htnn-controller/templates/rbac/role.yaml b/manifests/charts/htnn-controller/templates/rbac/role.yaml index 32a70a76..5ecb4274 100644 --- a/manifests/charts/htnn-controller/templates/rbac/role.yaml +++ b/manifests/charts/htnn-controller/templates/rbac/role.yaml @@ -30,6 +30,32 @@ rules: - get - patch - update +- apiGroups: + - htnn.mosn.io + resources: + - dynamicconfigs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - htnn.mosn.io + resources: + - dynamicconfigs/finalizers + verbs: + - update +- apiGroups: + - htnn.mosn.io + resources: + - dynamicconfigs/status + verbs: + - get + - patch + - update - apiGroups: - htnn.mosn.io resources: diff --git a/patch/README.md b/patch/README.md index df52ed37..5fc7ba7f 100644 --- a/patch/README.md +++ b/patch/README.md @@ -13,3 +13,4 @@ This list documents each patch: * 20240510-fix-empty-ecds-with-delta-xds.patch: Backport https://github.com/istio/istio/commit/e91027cf0d5242e677a84e5f6f9dd1924d0175c5 to Istio 1.21. The bug occurred when delta xDS is enabled and using ECDS and pilot-agent. * 20240529-fix-routes-overwrite-when-merging-same-host-from-multi-virtualservices.patch: Backport https://github.com/istio/istio/commit/0cb5c33595cdfaea732178a4d70265ac0a762255 to Istio 1.21. The filename in the patch is renamed to match the file in Istio 1.21. The bug occurred sometimes when multiple virtualservices has the same domain. * 20240823-server-side-filter.patch: Add server-side filters to filter istio CRD. + * 20240903-dynamic-configs.patch: Add DynamicConfig CRD. diff --git a/patch/istio/1.21/20240903-dynamic-configs.patch b/patch/istio/1.21/20240903-dynamic-configs.patch new file mode 100644 index 00000000..6c6ccae9 --- /dev/null +++ b/patch/istio/1.21/20240903-dynamic-configs.patch @@ -0,0 +1,16 @@ +diff --git a/pilot/pkg/config/htnn/component.go b/pilot/pkg/config/htnn/component.go +index 57a257c..5362f53 100644 +--- a/pilot/pkg/config/htnn/component.go ++++ b/pilot/pkg/config/htnn/component.go +@@ -125,6 +125,11 @@ func (o *output) FromServiceRegistry(ctx context.Context, serviceEntries map[str + o.ctrl.SetServiceEntries(entries) + } + ++func (o *output) FromDynamicConfig(_ context.Context, generatedEnvoyFilters map[component.EnvoyFilterKey]*istiov1a3.EnvoyFilter) error { ++ // FIXME: implement this ++ return nil ++} ++ + type resourceManager struct { + cache model.ConfigStore + statusWriter StatusWriter diff --git a/types/apis/v1/dynamicconfig_types.go b/types/apis/v1/dynamicconfig_types.go new file mode 100644 index 00000000..484a040b --- /dev/null +++ b/types/apis/v1/dynamicconfig_types.go @@ -0,0 +1,100 @@ +/* +Copyright The HTNN Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// DynamicConfigSpec defines the desired state of DynamicConfig +type DynamicConfigSpec struct { + Type string `json:"type"` + Config runtime.RawExtension `json:"config"` +} + +// DynamicConfigStatus defines the observed state of DynamicConfig +type DynamicConfigStatus struct { + // Conditions describe the current conditions. + // + // +optional + // +listType=map + // +listMapKey=type + Conditions []metav1.Condition `json:"conditions,omitempty"` + + ChangeDetector `json:",inline"` +} + +//+genclient +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// DynamicConfig is the Schema for the dynamicconfigs API +type DynamicConfig struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec DynamicConfigSpec `json:"spec,omitempty"` + Status DynamicConfigStatus `json:"status,omitempty"` +} + +func (c *DynamicConfig) IsSpecChanged() bool { + if len(c.Status.Conditions) == 0 { + // newly created + return true + } + for _, cond := range c.Status.Conditions { + if cond.ObservedGeneration != c.Generation { + return true + } + } + return false +} + +func (c *DynamicConfig) SetAccepted(reason ConditionReason, msg ...string) { + conds, changed := addOrUpdateAcceptedCondition(c.Status.Conditions, c.Generation, reason, msg...) + c.Status.Conditions = conds + + if changed { + c.Status.MarkAsChanged() + } +} + +func (c *DynamicConfig) IsValid() bool { + for _, cond := range c.Status.Conditions { + if cond.ObservedGeneration != c.Generation { + continue + } + if cond.Type == string(ConditionAccepted) && cond.Reason == string(ReasonInvalid) { + return false + } + } + return true +} + +//+kubebuilder:object:root=true + +// DynamicConfigList contains a list of DynamicConfig +type DynamicConfigList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []DynamicConfig `json:"items"` +} + +func init() { + SchemeBuilder.Register(&DynamicConfig{}, &DynamicConfigList{}) +} diff --git a/types/apis/v1/validation.go b/types/apis/v1/validation.go index 7d727030..1d211b27 100644 --- a/types/apis/v1/validation.go +++ b/types/apis/v1/validation.go @@ -343,3 +343,8 @@ func ValidateServiceRegistry(sr *ServiceRegistry) error { _, err := registry.ParseConfig(reg, sr.Spec.Config.Raw) return err } + +func ValidateDynamicConfig(c *DynamicConfig) error { + // FIXME: implement this function + return nil +} diff --git a/types/apis/v1/zz_generated.deepcopy.go b/types/apis/v1/zz_generated.deepcopy.go index 7aed35cc..50613b8c 100644 --- a/types/apis/v1/zz_generated.deepcopy.go +++ b/types/apis/v1/zz_generated.deepcopy.go @@ -168,6 +168,104 @@ func (in *ConsumerStatus) DeepCopy() *ConsumerStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DynamicConfig) DeepCopyInto(out *DynamicConfig) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DynamicConfig. +func (in *DynamicConfig) DeepCopy() *DynamicConfig { + if in == nil { + return nil + } + out := new(DynamicConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DynamicConfig) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DynamicConfigList) DeepCopyInto(out *DynamicConfigList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]DynamicConfig, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DynamicConfigList. +func (in *DynamicConfigList) DeepCopy() *DynamicConfigList { + if in == nil { + return nil + } + out := new(DynamicConfigList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DynamicConfigList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DynamicConfigSpec) DeepCopyInto(out *DynamicConfigSpec) { + *out = *in + in.Config.DeepCopyInto(&out.Config) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DynamicConfigSpec. +func (in *DynamicConfigSpec) DeepCopy() *DynamicConfigSpec { + if in == nil { + return nil + } + out := new(DynamicConfigSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DynamicConfigStatus) DeepCopyInto(out *DynamicConfigStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + out.ChangeDetector = in.ChangeDetector +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DynamicConfigStatus. +func (in *DynamicConfigStatus) DeepCopy() *DynamicConfigStatus { + if in == nil { + return nil + } + out := new(DynamicConfigStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FilterPolicy) DeepCopyInto(out *FilterPolicy) { *out = *in diff --git a/types/pkg/client/clientset/versioned/typed/apis/v1/apis_client.go b/types/pkg/client/clientset/versioned/typed/apis/v1/apis_client.go index 3654fddf..8a0f3e65 100644 --- a/types/pkg/client/clientset/versioned/typed/apis/v1/apis_client.go +++ b/types/pkg/client/clientset/versioned/typed/apis/v1/apis_client.go @@ -30,6 +30,7 @@ import ( type ApisV1Interface interface { RESTClient() rest.Interface ConsumersGetter + DynamicConfigsGetter FilterPoliciesGetter HTTPFilterPoliciesGetter ServiceRegistriesGetter @@ -44,6 +45,10 @@ func (c *ApisV1Client) Consumers(namespace string) ConsumerInterface { return newConsumers(c, namespace) } +func (c *ApisV1Client) DynamicConfigs(namespace string) DynamicConfigInterface { + return newDynamicConfigs(c, namespace) +} + func (c *ApisV1Client) FilterPolicies(namespace string) FilterPolicyInterface { return newFilterPolicies(c, namespace) } diff --git a/types/pkg/client/clientset/versioned/typed/apis/v1/dynamicconfig.go b/types/pkg/client/clientset/versioned/typed/apis/v1/dynamicconfig.go new file mode 100644 index 00000000..bce687fd --- /dev/null +++ b/types/pkg/client/clientset/versioned/typed/apis/v1/dynamicconfig.go @@ -0,0 +1,196 @@ +/* +Copyright The HTNN Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1 + +import ( + "context" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" + + v1 "mosn.io/htnn/types/apis/v1" + scheme "mosn.io/htnn/types/pkg/client/clientset/versioned/scheme" +) + +// DynamicConfigsGetter has a method to return a DynamicConfigInterface. +// A group's client should implement this interface. +type DynamicConfigsGetter interface { + DynamicConfigs(namespace string) DynamicConfigInterface +} + +// DynamicConfigInterface has methods to work with DynamicConfig resources. +type DynamicConfigInterface interface { + Create(ctx context.Context, dynamicConfig *v1.DynamicConfig, opts metav1.CreateOptions) (*v1.DynamicConfig, error) + Update(ctx context.Context, dynamicConfig *v1.DynamicConfig, opts metav1.UpdateOptions) (*v1.DynamicConfig, error) + UpdateStatus(ctx context.Context, dynamicConfig *v1.DynamicConfig, opts metav1.UpdateOptions) (*v1.DynamicConfig, error) + Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error + Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.DynamicConfig, error) + List(ctx context.Context, opts metav1.ListOptions) (*v1.DynamicConfigList, error) + Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.DynamicConfig, err error) + DynamicConfigExpansion +} + +// dynamicConfigs implements DynamicConfigInterface +type dynamicConfigs struct { + client rest.Interface + ns string +} + +// newDynamicConfigs returns a DynamicConfigs +func newDynamicConfigs(c *ApisV1Client, namespace string) *dynamicConfigs { + return &dynamicConfigs{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the dynamicConfig, and returns the corresponding dynamicConfig object, and an error if there is any. +func (c *dynamicConfigs) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.DynamicConfig, err error) { + result = &v1.DynamicConfig{} + err = c.client.Get(). + Namespace(c.ns). + Resource("dynamicconfigs"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of DynamicConfigs that match those selectors. +func (c *dynamicConfigs) List(ctx context.Context, opts metav1.ListOptions) (result *v1.DynamicConfigList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1.DynamicConfigList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("dynamicconfigs"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested dynamicConfigs. +func (c *dynamicConfigs) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("dynamicconfigs"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a dynamicConfig and creates it. Returns the server's representation of the dynamicConfig, and an error, if there is any. +func (c *dynamicConfigs) Create(ctx context.Context, dynamicConfig *v1.DynamicConfig, opts metav1.CreateOptions) (result *v1.DynamicConfig, err error) { + result = &v1.DynamicConfig{} + err = c.client.Post(). + Namespace(c.ns). + Resource("dynamicconfigs"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(dynamicConfig). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a dynamicConfig and updates it. Returns the server's representation of the dynamicConfig, and an error, if there is any. +func (c *dynamicConfigs) Update(ctx context.Context, dynamicConfig *v1.DynamicConfig, opts metav1.UpdateOptions) (result *v1.DynamicConfig, err error) { + result = &v1.DynamicConfig{} + err = c.client.Put(). + Namespace(c.ns). + Resource("dynamicconfigs"). + Name(dynamicConfig.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(dynamicConfig). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *dynamicConfigs) UpdateStatus(ctx context.Context, dynamicConfig *v1.DynamicConfig, opts metav1.UpdateOptions) (result *v1.DynamicConfig, err error) { + result = &v1.DynamicConfig{} + err = c.client.Put(). + Namespace(c.ns). + Resource("dynamicconfigs"). + Name(dynamicConfig.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(dynamicConfig). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the dynamicConfig and deletes it. Returns an error if one occurs. +func (c *dynamicConfigs) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("dynamicconfigs"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *dynamicConfigs) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("dynamicconfigs"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched dynamicConfig. +func (c *dynamicConfigs) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.DynamicConfig, err error) { + result = &v1.DynamicConfig{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("dynamicconfigs"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/types/pkg/client/clientset/versioned/typed/apis/v1/fake/fake_apis_client.go b/types/pkg/client/clientset/versioned/typed/apis/v1/fake/fake_apis_client.go index 5c52db61..d11df653 100644 --- a/types/pkg/client/clientset/versioned/typed/apis/v1/fake/fake_apis_client.go +++ b/types/pkg/client/clientset/versioned/typed/apis/v1/fake/fake_apis_client.go @@ -33,6 +33,10 @@ func (c *FakeApisV1) Consumers(namespace string) v1.ConsumerInterface { return &FakeConsumers{c, namespace} } +func (c *FakeApisV1) DynamicConfigs(namespace string) v1.DynamicConfigInterface { + return &FakeDynamicConfigs{c, namespace} +} + func (c *FakeApisV1) FilterPolicies(namespace string) v1.FilterPolicyInterface { return &FakeFilterPolicies{c, namespace} } diff --git a/types/pkg/client/clientset/versioned/typed/apis/v1/fake/fake_dynamicconfig.go b/types/pkg/client/clientset/versioned/typed/apis/v1/fake/fake_dynamicconfig.go new file mode 100644 index 00000000..728a2154 --- /dev/null +++ b/types/pkg/client/clientset/versioned/typed/apis/v1/fake/fake_dynamicconfig.go @@ -0,0 +1,142 @@ +/* +Copyright The HTNN Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" + + v1 "mosn.io/htnn/types/apis/v1" +) + +// FakeDynamicConfigs implements DynamicConfigInterface +type FakeDynamicConfigs struct { + Fake *FakeApisV1 + ns string +} + +var dynamicconfigsResource = v1.SchemeGroupVersion.WithResource("dynamicconfigs") + +var dynamicconfigsKind = v1.SchemeGroupVersion.WithKind("DynamicConfig") + +// Get takes name of the dynamicConfig, and returns the corresponding dynamicConfig object, and an error if there is any. +func (c *FakeDynamicConfigs) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.DynamicConfig, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(dynamicconfigsResource, c.ns, name), &v1.DynamicConfig{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.DynamicConfig), err +} + +// List takes label and field selectors, and returns the list of DynamicConfigs that match those selectors. +func (c *FakeDynamicConfigs) List(ctx context.Context, opts metav1.ListOptions) (result *v1.DynamicConfigList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(dynamicconfigsResource, dynamicconfigsKind, c.ns, opts), &v1.DynamicConfigList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1.DynamicConfigList{ListMeta: obj.(*v1.DynamicConfigList).ListMeta} + for _, item := range obj.(*v1.DynamicConfigList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested dynamicConfigs. +func (c *FakeDynamicConfigs) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(dynamicconfigsResource, c.ns, opts)) + +} + +// Create takes the representation of a dynamicConfig and creates it. Returns the server's representation of the dynamicConfig, and an error, if there is any. +func (c *FakeDynamicConfigs) Create(ctx context.Context, dynamicConfig *v1.DynamicConfig, opts metav1.CreateOptions) (result *v1.DynamicConfig, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(dynamicconfigsResource, c.ns, dynamicConfig), &v1.DynamicConfig{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.DynamicConfig), err +} + +// Update takes the representation of a dynamicConfig and updates it. Returns the server's representation of the dynamicConfig, and an error, if there is any. +func (c *FakeDynamicConfigs) Update(ctx context.Context, dynamicConfig *v1.DynamicConfig, opts metav1.UpdateOptions) (result *v1.DynamicConfig, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(dynamicconfigsResource, c.ns, dynamicConfig), &v1.DynamicConfig{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.DynamicConfig), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeDynamicConfigs) UpdateStatus(ctx context.Context, dynamicConfig *v1.DynamicConfig, opts metav1.UpdateOptions) (*v1.DynamicConfig, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(dynamicconfigsResource, "status", c.ns, dynamicConfig), &v1.DynamicConfig{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.DynamicConfig), err +} + +// Delete takes name of the dynamicConfig and deletes it. Returns an error if one occurs. +func (c *FakeDynamicConfigs) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(dynamicconfigsResource, c.ns, name, opts), &v1.DynamicConfig{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeDynamicConfigs) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { + action := testing.NewDeleteCollectionAction(dynamicconfigsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1.DynamicConfigList{}) + return err +} + +// Patch applies the patch and returns the patched dynamicConfig. +func (c *FakeDynamicConfigs) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.DynamicConfig, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(dynamicconfigsResource, c.ns, name, pt, data, subresources...), &v1.DynamicConfig{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.DynamicConfig), err +} diff --git a/types/pkg/client/clientset/versioned/typed/apis/v1/generated_expansion.go b/types/pkg/client/clientset/versioned/typed/apis/v1/generated_expansion.go index 2952fd21..2e77c072 100644 --- a/types/pkg/client/clientset/versioned/typed/apis/v1/generated_expansion.go +++ b/types/pkg/client/clientset/versioned/typed/apis/v1/generated_expansion.go @@ -20,6 +20,8 @@ package v1 type ConsumerExpansion interface{} +type DynamicConfigExpansion interface{} + type FilterPolicyExpansion interface{} type HTTPFilterPolicyExpansion interface{}