Merge pull request #228 from sjmshsh/master

validator and mutator
diff --git a/api/system/v1alpha1/zone_insight_helpers.go b/api/system/v1alpha1/zone_insight_helpers.go
new file mode 100644
index 0000000..9aa529b
--- /dev/null
+++ b/api/system/v1alpha1/zone_insight_helpers.go
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 v1alpha1
+
+func (x *ZoneInsight) IsOnline() bool {
+	for _, s := range x.GetSubscriptions() {
+		if s.ConnectTime != nil && s.DisconnectTime == nil {
+			return true
+		}
+	}
+	return false
+}
diff --git a/pkg/core/bootstrap/bootstrap.go b/pkg/core/bootstrap/bootstrap.go
index 9ead6a1..ca34cf6 100644
--- a/pkg/core/bootstrap/bootstrap.go
+++ b/pkg/core/bootstrap/bootstrap.go
@@ -19,6 +19,8 @@
 
 import (
 	"context"
+	"github.com/apache/dubbo-kubernetes/pkg/core/managers/apis/zone"
+	"github.com/apache/dubbo-kubernetes/pkg/core/resources/apis/system"
 	k8s_extensions "github.com/apache/dubbo-kubernetes/pkg/plugins/extensions/k8s"
 	"net/http"
 	"net/url"
@@ -454,6 +456,13 @@
 		),
 	)
 
+	customizableManager.Customize(
+		system.ZoneType,
+		zone.NewZoneManager(builder.ResourceStore(),
+			zone.Validator{Store: builder.ResourceStore()},
+			builder.Config().Store.UnsafeDelete,
+		))
+
 	builder.WithResourceManager(customizableManager)
 
 	if builder.Config().Store.Cache.Enabled {
diff --git a/pkg/core/managers/apis/zone/zone_manager.go b/pkg/core/managers/apis/zone/zone_manager.go
new file mode 100644
index 0000000..fd59f02
--- /dev/null
+++ b/pkg/core/managers/apis/zone/zone_manager.go
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 zone
+
+import (
+	"context"
+	core_manager "github.com/apache/dubbo-kubernetes/pkg/core/resources/manager"
+	"github.com/apache/dubbo-kubernetes/pkg/core/resources/model"
+	core_store "github.com/apache/dubbo-kubernetes/pkg/core/resources/store"
+)
+
+func NewZoneManager(store core_store.ResourceStore, validator Validator, unsafeDelete bool) core_manager.ResourceManager {
+	return &zoneManager{
+		ResourceManager: core_manager.NewResourceManager(store),
+		store:           store,
+		validator:       validator,
+		unsafeDelete:    unsafeDelete,
+	}
+}
+
+type zoneManager struct {
+	core_manager.ResourceManager
+	store        core_store.ResourceStore
+	validator    Validator
+	unsafeDelete bool
+}
+
+func (z *zoneManager) Delete(ctx context.Context, r model.Resource, opts ...core_store.DeleteOptionsFunc) error {
+	options := core_store.NewDeleteOptions(opts...)
+	if !z.unsafeDelete {
+		if err := z.validator.ValidateDelete(ctx, options.Name); err != nil {
+			return err
+		}
+	}
+	return z.ResourceManager.Delete(ctx, r, opts...)
+}
diff --git a/pkg/core/managers/apis/zone/zone_manager_suite_test.go b/pkg/core/managers/apis/zone/zone_manager_suite_test.go
new file mode 100644
index 0000000..58564df
--- /dev/null
+++ b/pkg/core/managers/apis/zone/zone_manager_suite_test.go
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 zone_test
+
+import (
+	"context"
+	"github.com/apache/dubbo-kubernetes/api/system/v1alpha1"
+	"github.com/apache/dubbo-kubernetes/pkg/util/proto"
+	"time"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	"github.com/apache/dubbo-kubernetes/pkg/core/managers/apis/zone"
+	"github.com/apache/dubbo-kubernetes/pkg/core/resources/apis/system"
+	"github.com/apache/dubbo-kubernetes/pkg/core/resources/model"
+	"github.com/apache/dubbo-kubernetes/pkg/core/resources/store"
+	"github.com/apache/dubbo-kubernetes/pkg/plugins/resources/memory"
+)
+
+var _ = Describe("Zone Manager", func() {
+	var validator zone.Validator
+	var resStore store.ResourceStore
+
+	BeforeEach(func() {
+		resStore = memory.NewStore()
+		validator = zone.Validator{Store: resStore}
+	})
+
+	It("should not delete zone if it's online", func() {
+		// given zone and zoneInsight
+		err := resStore.Create(context.Background(), system.NewZoneResource(), store.CreateByKey("zone-1", model.NoMesh))
+		Expect(err).ToNot(HaveOccurred())
+
+		err = resStore.Create(context.Background(), &system.ZoneInsightResource{
+			Spec: &v1alpha1.ZoneInsight{
+				Subscriptions: []*v1alpha1.DDSSubscription{
+					{
+						ConnectTime: proto.MustTimestampProto(time.Now()),
+					},
+				},
+			},
+		}, store.CreateByKey("zone-1", model.NoMesh))
+		Expect(err).ToNot(HaveOccurred())
+		zoneManager := zone.NewZoneManager(resStore, validator, false)
+
+		zone := system.NewZoneResource()
+		err = resStore.Get(context.Background(), zone, store.GetByKey("zone-1", model.NoMesh))
+		Expect(err).ToNot(HaveOccurred())
+
+		// when
+		err = zoneManager.Delete(context.Background(), zone, store.DeleteByKey("zone-1", model.NoMesh))
+
+		// then
+		Expect(err).To(HaveOccurred())
+		Expect(err.Error()).To(ContainSubstring("zone: unable to delete Zone, Zone CP is still connected, please shut it down first"))
+	})
+
+	It("should delete if zone is online when unsafe delete is enabled", func() {
+		// given zone and zoneInsight
+		err := resStore.Create(context.Background(), system.NewZoneResource(), store.CreateByKey("zone-1", model.NoMesh))
+		Expect(err).ToNot(HaveOccurred())
+
+		err = resStore.Create(context.Background(), &system.ZoneInsightResource{
+			Spec: &v1alpha1.ZoneInsight{
+				Subscriptions: []*v1alpha1.DDSSubscription{
+					{
+						ConnectTime: proto.MustTimestampProto(time.Now()),
+					},
+				},
+			},
+		}, store.CreateByKey("zone-1", model.NoMesh))
+		Expect(err).ToNot(HaveOccurred())
+		zoneManager := zone.NewZoneManager(resStore, validator, true)
+
+		zone := system.NewZoneResource()
+		err = resStore.Get(context.Background(), zone, store.GetByKey("zone-1", model.NoMesh))
+		Expect(err).ToNot(HaveOccurred())
+
+		// when
+		err = zoneManager.Delete(context.Background(), zone, store.DeleteByKey("zone-1", model.NoMesh))
+
+		// then
+		Expect(err).ToNot(HaveOccurred())
+	})
+})
diff --git a/pkg/core/managers/apis/zone/zone_manager_test.go b/pkg/core/managers/apis/zone/zone_manager_test.go
new file mode 100644
index 0000000..daf8e7d
--- /dev/null
+++ b/pkg/core/managers/apis/zone/zone_manager_test.go
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 zone_test
+
+import (
+	"github.com/apache/dubbo-kubernetes/pkg/test"
+	"testing"
+)
+
+func TestZoneManager(t *testing.T) {
+	test.RunSpecs(t, "Zone Manager Suite")
+}
diff --git a/pkg/core/managers/apis/zone/zone_validator.go b/pkg/core/managers/apis/zone/zone_validator.go
new file mode 100644
index 0000000..bf1df3a
--- /dev/null
+++ b/pkg/core/managers/apis/zone/zone_validator.go
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 zone
+
+import (
+	"context"
+	"github.com/apache/dubbo-kubernetes/pkg/core/resources/apis/system"
+	"github.com/apache/dubbo-kubernetes/pkg/core/resources/model"
+	"github.com/apache/dubbo-kubernetes/pkg/core/resources/store"
+	"github.com/apache/dubbo-kubernetes/pkg/core/validators"
+	"github.com/pkg/errors"
+)
+
+type Validator struct {
+	Store store.ResourceStore
+}
+
+func (v *Validator) ValidateDelete(ctx context.Context, name string) error {
+	zi := system.NewZoneInsightResource()
+	validationErr := &validators.ValidationError{}
+	if err := v.Store.Get(ctx, zi, store.GetByKey(name, model.NoMesh)); err != nil {
+		if store.IsResourceNotFound(err) {
+			return nil
+		}
+		return errors.Wrap(err, "unable to get ZoneInsight")
+	}
+	if zi.Spec.IsOnline() {
+		validationErr.AddViolation("zone", "unable to delete Zone, Zone CP is still connected, please shut it down first")
+		return validationErr
+	}
+	return nil
+}
diff --git a/pkg/plugins/runtime/k8s/plugin.go b/pkg/plugins/runtime/k8s/plugin.go
index 70b9e2f..93732af 100644
--- a/pkg/plugins/runtime/k8s/plugin.go
+++ b/pkg/plugins/runtime/k8s/plugin.go
@@ -18,6 +18,7 @@
 package k8s
 
 import (
+	"github.com/apache/dubbo-kubernetes/pkg/core/managers/apis/zone"
 	"github.com/pkg/errors"
 
 	kube_ctrl "sigs.k8s.io/controller-runtime"
@@ -113,6 +114,18 @@
 	handler := k8s_webhooks.NewValidatingWebhook(converter, core_registry.Global(), k8s_registry.Global(), rt.Config().Mode, rt.Config().IsFederatedZoneCP(), rt.Config().Multizone.Zone.DisableOriginLabelValidation)
 	composite.AddValidator(handler)
 
+	coreZoneValidator := zone.Validator{Store: rt.ResourceManager()}
+	k8sZoneValidator := k8s_webhooks.NewZoneValidatorWebhook(coreZoneValidator, rt.Config().Store.UnsafeDelete)
+	composite.AddValidator(k8sZoneValidator)
+
+	composite.AddValidator(&k8s_webhooks.PolicyNamespaceValidator{
+		SystemNamespace: rt.Config().Store.Kubernetes.SystemNamespace,
+	})
+
+	composite.AddValidator(&k8s_webhooks.ContainerPatchValidator{
+		SystemNamespace: rt.Config().Store.Kubernetes.SystemNamespace,
+	})
+
 	mgr.GetWebhookServer().Register("/validate-dubbo-io-v1alpha1", composite.IntoWebhook(mgr.GetScheme()))
 
 	return nil
diff --git a/pkg/plugins/runtime/k8s/webhooks/containerpatch_validator.go b/pkg/plugins/runtime/k8s/webhooks/containerpatch_validator.go
new file mode 100644
index 0000000..2fb28af
--- /dev/null
+++ b/pkg/plugins/runtime/k8s/webhooks/containerpatch_validator.go
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 webhooks
+
+import (
+	"context"
+	k8s_common "github.com/apache/dubbo-kubernetes/pkg/plugins/common/k8s"
+	mesh_k8s "github.com/apache/dubbo-kubernetes/pkg/plugins/resources/k8s/native/api/v1alpha1"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+)
+
+type ContainerPatchValidator struct {
+	SystemNamespace string
+}
+
+func NewContainerPatchValidatorWebhook() k8s_common.AdmissionValidator {
+	return &ContainerPatchValidator{}
+}
+
+func (h *ContainerPatchValidator) InjectDecoder(d *admission.Decoder) {
+}
+
+func (h *ContainerPatchValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
+	if req.Namespace != h.SystemNamespace {
+		return admission.Denied("ContainerPatch can only be placed in " + h.SystemNamespace + " namespace. It can be however referenced by pods in all namespaces")
+	}
+	return admission.Allowed("")
+}
+
+func (h *ContainerPatchValidator) Supports(req admission.Request) bool {
+	gvk := mesh_k8s.GroupVersion.WithKind("ContainerPatch")
+	return req.Kind.Kind == gvk.Kind && req.Kind.Version == gvk.Version && req.Kind.Group == gvk.Group
+}
diff --git a/pkg/plugins/runtime/k8s/webhooks/policy_namespace_validator.go b/pkg/plugins/runtime/k8s/webhooks/policy_namespace_validator.go
new file mode 100644
index 0000000..4b71734
--- /dev/null
+++ b/pkg/plugins/runtime/k8s/webhooks/policy_namespace_validator.go
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 webhooks
+
+import (
+	"context"
+	"fmt"
+	core_model "github.com/apache/dubbo-kubernetes/pkg/core/resources/model"
+	"github.com/apache/dubbo-kubernetes/pkg/core/resources/registry"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+)
+
+type PolicyNamespaceValidator struct {
+	Decoder         *admission.Decoder
+	SystemNamespace string
+}
+
+func (p *PolicyNamespaceValidator) InjectDecoder(decoder *admission.Decoder) {
+	p.Decoder = decoder
+}
+
+func (p *PolicyNamespaceValidator) Handle(ctx context.Context, request admission.Request) admission.Response {
+	if request.Namespace != p.SystemNamespace {
+		return admission.Denied(fmt.Sprintf("policy can only be created in the system namespace:%s", p.SystemNamespace))
+	}
+	return admission.Allowed("")
+}
+
+func (p *PolicyNamespaceValidator) Supports(request admission.Request) bool {
+	desc, err := registry.Global().DescriptorFor(core_model.ResourceType(request.Kind.Kind))
+	if err != nil {
+		return false
+	}
+	return desc.IsPluginOriginated
+}
diff --git a/pkg/plugins/runtime/k8s/webhooks/service_validator.go b/pkg/plugins/runtime/k8s/webhooks/service_validator.go
new file mode 100644
index 0000000..b71d24a
--- /dev/null
+++ b/pkg/plugins/runtime/k8s/webhooks/service_validator.go
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 webhooks
+
+import (
+	"context"
+	"fmt"
+	core_mesh "github.com/apache/dubbo-kubernetes/pkg/core/resources/apis/mesh"
+	"github.com/apache/dubbo-kubernetes/pkg/core/validators"
+	kube_core "k8s.io/api/core/v1"
+	"net/http"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+)
+
+// ServiceValidator validates Dubbo-specific annotations on Services.
+type ServiceValidator struct {
+	Decoder *admission.Decoder
+}
+
+// Handle admits a Service only if Kuma-specific annotations have proper values.
+func (v *ServiceValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
+	svc := &kube_core.Service{}
+
+	err := v.Decoder.Decode(req, svc)
+	if err != nil {
+		return admission.Errored(http.StatusBadRequest, err)
+	}
+
+	if err := v.validate(svc); err != nil {
+		if verr, ok := err.(*validators.ValidationError); ok {
+			return convertValidationErrorOf(*verr, svc, svc)
+		}
+		return admission.Denied(err.Error())
+	}
+
+	return admission.Allowed("")
+}
+
+func (v *ServiceValidator) validate(svc *kube_core.Service) error {
+	verr := &validators.ValidationError{}
+	for _, svcPort := range svc.Spec.Ports {
+		protocolAnnotation := fmt.Sprintf("%d.service.kuma.io/protocol", svcPort.Port)
+		protocolAnnotationValue, exists := svc.Annotations[protocolAnnotation]
+		if exists && core_mesh.ParseProtocol(protocolAnnotationValue) == core_mesh.ProtocolUnknown {
+			verr.AddViolationAt(validators.RootedAt("metadata").Field("annotations").Key(protocolAnnotation),
+				fmt.Sprintf("value %q is not valid. %s", protocolAnnotationValue, core_mesh.AllowedValuesHint(core_mesh.SupportedProtocols.Strings()...)))
+		}
+	}
+	return verr.OrNil()
+}
diff --git a/pkg/plugins/runtime/k8s/webhooks/zone_validator.go b/pkg/plugins/runtime/k8s/webhooks/zone_validator.go
new file mode 100644
index 0000000..16d4b03
--- /dev/null
+++ b/pkg/plugins/runtime/k8s/webhooks/zone_validator.go
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 webhooks
+
+import (
+	"context"
+	"github.com/apache/dubbo-kubernetes/pkg/core/managers/apis/zone"
+	k8s_common "github.com/apache/dubbo-kubernetes/pkg/plugins/common/k8s"
+	mesh_k8s "github.com/apache/dubbo-kubernetes/pkg/plugins/resources/k8s/native/api/v1alpha1"
+	v1 "k8s.io/api/admission/v1"
+	"net/http"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+)
+
+func NewZoneValidatorWebhook(validator zone.Validator, unsafeDelete bool) k8s_common.AdmissionValidator {
+	return &ZoneValidator{
+		validator:    validator,
+		unsafeDelete: unsafeDelete,
+	}
+}
+
+type ZoneValidator struct {
+	validator    zone.Validator
+	unsafeDelete bool
+}
+
+func (z *ZoneValidator) InjectDecoder(_ *admission.Decoder) {
+}
+
+func (z *ZoneValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
+	switch req.Operation {
+	case v1.Delete:
+		return z.ValidateDelete(ctx, req)
+	}
+	return admission.Allowed("")
+}
+
+func (z *ZoneValidator) ValidateDelete(ctx context.Context, req admission.Request) admission.Response {
+	if !z.unsafeDelete {
+		if err := z.validator.ValidateDelete(ctx, req.Name); err != nil {
+			return admission.Errored(http.StatusBadRequest, err)
+		}
+	}
+	return admission.Allowed("")
+}
+
+func (z *ZoneValidator) Supports(req admission.Request) bool {
+	gvk := mesh_k8s.GroupVersion.WithKind("Zone")
+	return req.Kind.Kind == gvk.Kind && req.Kind.Version == gvk.Version && req.Kind.Group == gvk.Group
+}
diff --git a/test/app/consumer/deployment.yaml b/test/app/consumer/deployment.yaml
new file mode 100644
index 0000000..23409e7
--- /dev/null
+++ b/test/app/consumer/deployment.yaml
@@ -0,0 +1,55 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: dubbo-samples-apiserver-consumer
+  namespace: dubbo-system
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: dubbo-samples-apiserver-consumer
+  template:
+    metadata:
+      labels:
+        app: dubbo-samples-apiserver-consumer
+    spec:
+      serviceAccountName: dubbo-sa
+      containers:
+        - name: server
+          image: apache/dubbo-demo:dubbo-samples-apiserver-consumer_0.0.1
+          imagePullPolicy: Always
+          ports:
+            - containerPort: 20880
+          livenessProbe:
+            httpGet:
+              path: /live
+              port: 22222
+            initialDelaySeconds: 5
+            periodSeconds: 5
+          readinessProbe:
+            httpGet:
+              path: /ready
+              port: 22222
+            initialDelaySeconds: 5
+            periodSeconds: 5
+          startupProbe:
+            httpGet:
+              path: /startup
+              port: 22222
+            failureThreshold: 30
+            periodSeconds: 10
\ No newline at end of file
diff --git a/test/app/consumer/service.yaml b/test/app/consumer/service.yaml
new file mode 100644
index 0000000..d918e65
--- /dev/null
+++ b/test/app/consumer/service.yaml
@@ -0,0 +1,28 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+apiVersion: v1
+kind: Service
+metadata:
+  name: dubbo-samples-apiserver-consumer
+  namespace: dubbo-system
+spec:
+  clusterIP: None
+  selector:
+    app: dubbo-samples-apiserver-consumer
+  ports:
+    - protocol: TCP
+      port: 20880
+      targetPort: 20880
\ No newline at end of file
diff --git a/test/app/provider/deployment.yaml b/test/app/provider/deployment.yaml
new file mode 100644
index 0000000..d67ed5e
--- /dev/null
+++ b/test/app/provider/deployment.yaml
@@ -0,0 +1,55 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: dubbo-samples-apiserver-provider
+  namespace: dubbo-system
+spec:
+  replicas: 3
+  selector:
+    matchLabels:
+      app: dubbo-samples-apiserver-provider
+  template:
+    metadata:
+      labels:
+        app: dubbo-samples-apiserver-provider
+    spec:
+      serviceAccountName: dubbo-sa
+      containers:
+        - name: server
+          image: apache/dubbo-demo:dubbo-samples-apiserver-provider_0.0.1
+          imagePullPolicy: Always
+          ports:
+            - containerPort: 20880
+          livenessProbe:
+            httpGet:
+              path: /live
+              port: 22222
+            initialDelaySeconds: 5
+            periodSeconds: 5
+          readinessProbe:
+            httpGet:
+              path: /ready
+              port: 22222
+            initialDelaySeconds: 5
+            periodSeconds: 5
+          startupProbe:
+            httpGet:
+              path: /startup
+              port: 22222
+            failureThreshold: 30
+            periodSeconds: 10
diff --git a/test/app/provider/service.yaml b/test/app/provider/service.yaml
new file mode 100644
index 0000000..878f347
--- /dev/null
+++ b/test/app/provider/service.yaml
@@ -0,0 +1,28 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+apiVersion: v1
+kind: Service
+metadata:
+  name: dubbo-samples-apiserver-provider
+  namespace: dubbo-system
+spec:
+  clusterIP: None
+  selector:
+    app: dubbo-samples-apiserver-provider
+  ports:
+    - protocol: TCP
+      port: 20880
+      targetPort: 20880
\ No newline at end of file
diff --git a/test/app/provider/serviceaccount.yaml b/test/app/provider/serviceaccount.yaml
new file mode 100644
index 0000000..83ec3a9
--- /dev/null
+++ b/test/app/provider/serviceaccount.yaml
@@ -0,0 +1,51 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: dubbo-system
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  namespace: dubbo-system
+  name: dubbo-role
+rules:
+  - apiGroups: [ "" ]
+    resources: [ "pods" ]
+    verbs: [ "get", "watch", "list", "update", "patch" ]
+  - apiGroups: [ "", "service.dubbo.apache.org" ]
+    resources: [ "services", "endpoints", "virtualservices", "destinationrules" ]
+    verbs: [ "get", "watch", "list" ]
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: dubbo-sa
+  namespace: dubbo-system
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: dubbo-sa-bind
+  namespace: dubbo-system
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: dubbo-role
+subjects:
+  - kind: ServiceAccount
+    name: dubbo-sa
\ No newline at end of file