From d51f81dd1391283f1ae00aa2eabcfc8ac57f8225 Mon Sep 17 00:00:00 2001 From: Nathan Mittler Date: Wed, 14 Aug 2019 11:17:40 -0700 Subject: [PATCH] Moving config schema out of pilot (#16061) --- galley/pkg/crd/validation/validation.go | 11 +- galley/pkg/crd/validation/webhook.go | 12 +- galley/pkg/crd/validation/webhook_test.go | 5 +- istioctl/cmd/convert_ingress.go | 16 +- istioctl/cmd/deprecated_cmd.go | 71 +-- istioctl/cmd/istioctl_test.go | 29 +- istioctl/pkg/auth/validator.go | 6 +- istioctl/pkg/validate/validate.go | 9 +- pilot/pkg/bootstrap/server.go | 13 +- pilot/pkg/config/aggregate/config.go | 9 +- pilot/pkg/config/aggregate/config_test.go | 21 +- .../aggregate/fakes/config_store_cache.go | 19 +- pilot/pkg/config/coredatamodel/controller.go | 23 +- .../config/coredatamodel/controller_test.go | 110 ++-- pilot/pkg/config/kube/crd/config.go | 3 +- .../pkg/config/kube/crd/controller/client.go | 106 ++-- .../config/kube/crd/controller/controller.go | 23 +- pilot/pkg/config/kube/crd/conversion.go | 16 +- pilot/pkg/config/kube/crd/conversion_test.go | 11 +- pilot/pkg/config/kube/crd/types.go | 109 ++-- pilot/pkg/config/kube/ingress/controller.go | 20 +- pilot/pkg/config/kube/ingress/conversion.go | 13 +- pilot/pkg/config/memory/config.go | 15 +- pilot/pkg/config/memory/config_test.go | 4 +- pilot/pkg/config/memory/controller.go | 3 +- pilot/pkg/config/memory/monitor_test.go | 5 +- pilot/pkg/config/monitor/file_snapshot.go | 13 +- .../pkg/config/monitor/file_snapshot_test.go | 6 +- pilot/pkg/config/monitor/monitor_test.go | 7 +- pilot/pkg/model/authorization.go | 6 +- pilot/pkg/model/authorization_test.go | 20 +- pilot/pkg/model/config.go | 313 +---------- pilot/pkg/model/config_test.go | 74 +-- pilot/pkg/model/conversion_test.go | 21 +- pilot/pkg/model/push_context.go | 11 +- pilot/pkg/model/validation.go | 37 -- pilot/pkg/model/validation_test.go | 136 ----- .../networking/core/v1alpha3/cluster_test.go | 7 +- .../v1alpha3/fakes/fake_istio_config_store.go | 19 +- .../networking/core/v1alpha3/gateway_test.go | 7 +- .../core/v1alpha3/httproute_test.go | 13 +- .../networking/core/v1alpha3/listener_test.go | 5 +- .../loadbalancer/loadbalancer_test.go | 10 +- .../core/v1alpha3/route/route_test.go | 49 +- pilot/pkg/proxy/envoy/v2/discovery.go | 3 +- .../security/authz/builder/builder_test.go | 3 +- pilot/pkg/security/authz/policy/helper.go | 11 +- .../external/controller_test.go | 9 +- .../external/conversion_test.go | 23 +- .../external/servicediscovery.go | 3 +- .../external/servicediscovery_test.go | 5 +- pilot/test/mock/config.go | 76 +-- pilot/tools/generate_config_crd_types.go | 17 +- pilot/tools/types.go.tmpl | 13 +- .../config/schema/instance.go | 60 +- pkg/config/schema/set.go | 82 +++ pkg/config/schemas/mock.go | 41 ++ pkg/config/schemas/schemas.go | 231 ++++++++ pkg/config/schemas/schemas_test.go | 531 ++++++++++++++++++ tests/e2e/tests/controller/controller_test.go | 10 +- .../tests/pilot/cloudfoundry/copilot_test.go | 27 +- tests/e2e/tests/pilot/mcp_test.go | 13 +- .../pilot/performance/serviceentry_test.go | 17 +- 63 files changed, 1577 insertions(+), 1034 deletions(-) rename pilot/pkg/model/conversion.go => pkg/config/schema/instance.go (50%) create mode 100644 pkg/config/schema/set.go create mode 100644 pkg/config/schemas/mock.go create mode 100644 pkg/config/schemas/schemas.go create mode 100644 pkg/config/schemas/schemas_test.go diff --git a/galley/pkg/crd/validation/validation.go b/galley/pkg/crd/validation/validation.go index 5283cb2e4cb7..e309ea4922e8 100644 --- a/galley/pkg/crd/validation/validation.go +++ b/galley/pkg/crd/validation/validation.go @@ -23,14 +23,15 @@ import ( "regexp" "time" - multierror "github.com/hashicorp/go-multierror" + "github.com/hashicorp/go-multierror" + + "istio.io/pkg/log" + "istio.io/pkg/probe" mixervalidate "istio.io/istio/mixer/pkg/validate" - "istio.io/istio/pilot/pkg/model" "istio.io/istio/pkg/cmd" + "istio.io/istio/pkg/config/schemas" "istio.io/istio/pkg/kube" - "istio.io/pkg/log" - "istio.io/pkg/probe" ) const ( @@ -81,7 +82,7 @@ func RunValidation(ready, stopCh chan struct{}, vc *WebhookParameters, kubeConfi log.Fatalf("could not create k8s clientset: %v", err) } vc.MixerValidator = mixerValidator - vc.PilotDescriptor = model.IstioConfigTypes + vc.PilotDescriptor = schemas.Istio vc.Clientset = clientset wh, err := NewWebhook(*vc) if err != nil { diff --git a/galley/pkg/crd/validation/webhook.go b/galley/pkg/crd/validation/webhook.go index fbc6cb8b82dc..2a679af08bcd 100644 --- a/galley/pkg/crd/validation/webhook.go +++ b/galley/pkg/crd/validation/webhook.go @@ -38,7 +38,7 @@ import ( mixerCrd "istio.io/istio/mixer/pkg/config/crd" "istio.io/istio/mixer/pkg/config/store" "istio.io/istio/pilot/pkg/config/kube/crd" - "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pkg/config/schema" ) var ( @@ -73,7 +73,7 @@ type WebhookParameters struct { MixerValidator store.BackendValidator // PilotDescriptor provides a description of all pilot configuration resources. - PilotDescriptor model.ConfigDescriptor + PilotDescriptor schema.Set // DomainSuffix is the DNS domain suffix for Pilot CRD resources, // e.g. cluster.local. @@ -176,7 +176,7 @@ type Webhook struct { cert *tls.Certificate // pilot - descriptor model.ConfigDescriptor + descriptor schema.Set domainSuffix string // mixer @@ -344,21 +344,21 @@ func (wh *Webhook) admitPilot(request *admissionv1beta1.AdmissionRequest) *admis return toAdmissionResponse(fmt.Errorf("cannot decode configuration: %v", err)) } - schema, exists := wh.descriptor.GetByType(crd.CamelCaseToKebabCase(obj.Kind)) + s, exists := wh.descriptor.GetByType(crd.CamelCaseToKebabCase(obj.Kind)) if !exists { scope.Infof("unrecognized type %v", obj.Kind) reportValidationFailed(request, reasonUnknownType) return toAdmissionResponse(fmt.Errorf("unrecognized type %v", obj.Kind)) } - out, err := crd.ConvertObject(schema, &obj, wh.domainSuffix) + out, err := crd.ConvertObject(s, &obj, wh.domainSuffix) if err != nil { scope.Infof("error decoding configuration: %v", err) reportValidationFailed(request, reasonCRDConversionError) return toAdmissionResponse(fmt.Errorf("error decoding configuration: %v", err)) } - if err := schema.Validate(out.Name, out.Namespace, out.Spec); err != nil { + if err := s.Validate(out.Name, out.Namespace, out.Spec); err != nil { scope.Infof("configuration is invalid: %v", err) reportValidationFailed(request, reasonInvalidConfig) return toAdmissionResponse(fmt.Errorf("configuration is invalid: %v", err)) diff --git a/galley/pkg/crd/validation/webhook_test.go b/galley/pkg/crd/validation/webhook_test.go index e9b29020ab31..d9093af35c11 100644 --- a/galley/pkg/crd/validation/webhook_test.go +++ b/galley/pkg/crd/validation/webhook_test.go @@ -45,6 +45,7 @@ import ( "istio.io/istio/pilot/pkg/config/kube/crd" "istio.io/istio/pilot/pkg/model" "istio.io/istio/pilot/test/mock" + "istio.io/istio/pkg/config/schemas" "istio.io/istio/pkg/mcp/testing/testcerts" testConfig "istio.io/istio/pkg/test/config" ) @@ -224,7 +225,7 @@ func makePilotConfig(t *testing.T, i int, validConfig bool, includeBogusKey bool name := fmt.Sprintf("%s%d", "mock-config", i) config := model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.MockConfig.Type, + Type: schemas.MockConfig.Type, Name: name, Labels: map[string]string{ "key": name, @@ -241,7 +242,7 @@ func makePilotConfig(t *testing.T, i int, validConfig bool, includeBogusKey bool }}, }, } - obj, err := crd.ConvertConfig(model.MockConfig, config) + obj, err := crd.ConvertConfig(schemas.MockConfig, config) if err != nil { t.Fatalf("ConvertConfig(%v) failed: %v", config.Name, err) } diff --git a/istioctl/cmd/convert_ingress.go b/istioctl/cmd/convert_ingress.go index dd3d96753fd8..1ba20002a6be 100644 --- a/istioctl/cmd/convert_ingress.go +++ b/istioctl/cmd/convert_ingress.go @@ -29,6 +29,8 @@ import ( "istio.io/istio/istioctl/pkg/convert" "istio.io/istio/pilot/pkg/config/kube/crd" "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pkg/config/schema" + "istio.io/istio/pkg/config/schemas" "istio.io/istio/pkg/config/validation" "istio.io/pkg/log" @@ -40,9 +42,9 @@ var ( ) func convertConfigs(readers []io.Reader, writer io.Writer) error { - configDescriptor := model.ConfigDescriptor{ - model.VirtualService, - model.Gateway, + configDescriptor := schema.Set{ + schemas.VirtualService, + schemas.Gateway, } configs, ingresses, err := readConfigs(readers) @@ -130,14 +132,14 @@ func readConfigs(readers []io.Reader) ([]model.Config, []*v1beta1.Ingress, error return out, outIngresses, nil } -func writeYAMLOutput(descriptor model.ConfigDescriptor, configs []model.Config, writer io.Writer) { +func writeYAMLOutput(descriptor schema.Set, configs []model.Config, writer io.Writer) { for i, cfg := range configs { - schema, exists := descriptor.GetByType(cfg.Type) + s, exists := descriptor.GetByType(cfg.Type) if !exists { log.Errorf("Unknown kind %q for %v", crd.ResourceName(cfg.Type), cfg.Name) continue } - obj, err := crd.ConvertConfig(schema, cfg) + obj, err := crd.ConvertConfig(s, cfg) if err != nil { log.Errorf("Could not decode %v: %v", cfg.Name, err) continue @@ -157,7 +159,7 @@ func writeYAMLOutput(descriptor model.ConfigDescriptor, configs []model.Config, func validateConfigs(configs []model.Config) error { var errs error for _, cfg := range configs { - if cfg.Type == model.VirtualService.Type { + if cfg.Type == schemas.VirtualService.Type { if err := validation.ValidateVirtualService(cfg.Name, cfg.Namespace, cfg.Spec); err != nil { errs = multierror.Append(err, errs) } diff --git a/istioctl/cmd/deprecated_cmd.go b/istioctl/cmd/deprecated_cmd.go index 40347b4a0fa6..8150c6f79e0e 100644 --- a/istioctl/cmd/deprecated_cmd.go +++ b/istioctl/cmd/deprecated_cmd.go @@ -29,12 +29,12 @@ import ( "time" "github.com/ghodss/yaml" - multierror "github.com/hashicorp/go-multierror" + "github.com/hashicorp/go-multierror" "github.com/spf13/cobra" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" + kubeSchema "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/client-go/discovery" "k8s.io/client-go/rest" @@ -42,12 +42,15 @@ import ( clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "istio.io/api/networking/v1alpha3" + "istio.io/pkg/log" + "istio.io/istio/istioctl/pkg/util/handlers" "istio.io/istio/pilot/pkg/config/kube/crd" "istio.io/istio/pilot/pkg/config/kube/crd/controller" "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pkg/config/schema" + "istio.io/istio/pkg/config/schemas" kubecfg "istio.io/istio/pkg/kube" - "istio.io/pkg/log" ) const ( @@ -65,28 +68,28 @@ var ( // sortWeight defines the output order for "get all". We show the V3 types first. sortWeight = map[string]int{ - model.Gateway.Type: 10, - model.VirtualService.Type: 5, - model.DestinationRule.Type: 3, - model.ServiceEntry.Type: 1, + schemas.Gateway.Type: 10, + schemas.VirtualService.Type: 5, + schemas.DestinationRule.Type: 3, + schemas.ServiceEntry.Type: 1, } // mustList tracks which Istio types we SHOULD NOT silently ignore if we can't list. // The user wants reasonable error messages when doing `get all` against a different // server version. mustList = map[string]bool{ - model.Gateway.Type: true, - model.VirtualService.Type: true, - model.DestinationRule.Type: true, - model.ServiceEntry.Type: true, - model.HTTPAPISpec.Type: true, - model.HTTPAPISpecBinding.Type: true, - model.QuotaSpec.Type: true, - model.QuotaSpecBinding.Type: true, - model.AuthenticationPolicy.Type: true, - model.ServiceRole.Type: true, - model.ServiceRoleBinding.Type: true, - model.RbacConfig.Type: true, + schemas.Gateway.Type: true, + schemas.VirtualService.Type: true, + schemas.DestinationRule.Type: true, + schemas.ServiceEntry.Type: true, + schemas.HTTPAPISpec.Type: true, + schemas.HTTPAPISpecBinding.Type: true, + schemas.QuotaSpec.Type: true, + schemas.QuotaSpecBinding.Type: true, + schemas.AuthenticationPolicy.Type: true, + schemas.ServiceRole.Type: true, + schemas.ServiceRoleBinding.Type: true, + schemas.RbacConfig.Type: true, } // Headings for short format listing specific to type @@ -107,7 +110,7 @@ var ( // all resources will be migrated out of config.istio.io to their own api group mapping to package path. // TODO(xiaolanz) legacy group exists until we find out a client for mixer - legacyIstioAPIGroupVersion = schema.GroupVersion{ + legacyIstioAPIGroupVersion = kubeSchema.GroupVersion{ Group: "config.istio.io", Version: "v1alpha2", } @@ -306,7 +309,7 @@ istioctl get virtualservice bookinfo return errors.New("a resource cannot be retrieved by name across all namespaces") } - var typs []model.ProtoSchema + var typs []schema.Instance if !getByName && strings.EqualFold(args[0], "all") { typs = configClient.ConfigDescriptor() } else { @@ -315,7 +318,7 @@ istioctl get virtualservice bookinfo c.Println(c.UsageString()) return err } - typs = []model.ProtoSchema{typ} + typs = []schema.Instance{typ} } var ns string @@ -364,8 +367,8 @@ istioctl get virtualservice bookinfo return errs }, - ValidArgs: configTypeResourceNames(model.IstioConfigTypes), - ArgAliases: configTypePluralResourceNames(model.IstioConfigTypes), + ValidArgs: configTypeResourceNames(schemas.Istio), + ArgAliases: configTypePluralResourceNames(schemas.Istio), } deleteCmd = &cobra.Command{ @@ -464,8 +467,8 @@ istioctl delete virtualservice bookinfo return errs }, - ValidArgs: configTypeResourceNames(model.IstioConfigTypes), - ArgAliases: configTypePluralResourceNames(model.IstioConfigTypes), + ValidArgs: configTypeResourceNames(schemas.Istio), + ArgAliases: configTypePluralResourceNames(schemas.Istio), } contextCmd = &cobra.Command{ @@ -535,17 +538,17 @@ istioctl context-create --api-server http://127.0.0.1:8080 ) // The protoSchema is based on the kind (for example "virtualservice" or "destinationrule") -func protoSchema(configClient model.ConfigStore, typ string) (model.ProtoSchema, error) { +func protoSchema(configClient model.ConfigStore, typ string) (schema.Instance, error) { for _, desc := range configClient.ConfigDescriptor() { switch strings.ToLower(typ) { case crd.ResourceName(desc.Type), crd.ResourceName(desc.Plural): return desc, nil case desc.Type, desc.Plural: // legacy hyphenated resources names - return model.ProtoSchema{}, fmt.Errorf("%q not recognized. Please use non-hyphenated resource name %q", + return schema.Instance{}, fmt.Errorf("%q not recognized. Please use non-hyphenated resource name %q", typ, crd.ResourceName(typ)) } } - return model.ProtoSchema{}, fmt.Errorf("configuration type %s not found, the types are %v", + return schema.Instance{}, fmt.Errorf("configuration type %s not found, the types are %v", typ, strings.Join(supportedTypes(configClient), ", ")) } @@ -710,12 +713,12 @@ func printShortGateway(config model.Config, w io.Writer) { func printYamlOutput(writer io.Writer, configClient model.ConfigStore, configList []model.Config) { descriptor := configClient.ConfigDescriptor() for _, config := range configList { - schema, exists := descriptor.GetByType(config.Type) + s, exists := descriptor.GetByType(config.Type) if !exists { log.Errorf("Unknown kind %q for %v", crd.ResourceName(config.Type), config.Name) continue } - obj, err := crd.ConvertConfig(schema, config) + obj, err := crd.ConvertConfig(s, config) if err != nil { log.Errorf("Could not decode %v: %v", config.Name, err) continue @@ -731,7 +734,7 @@ func printYamlOutput(writer io.Writer, configClient model.ConfigStore, configLis } func newClient() (model.ConfigStore, error) { - return controller.NewClient(kubeconfig, configContext, model.IstioConfigTypes, "") + return controller.NewClient(kubeconfig, configContext, schemas.Istio, "") } func supportedTypes(configClient model.ConfigStore) []string { @@ -823,7 +826,7 @@ func prepareClientForOthers(configs []crd.IstioKind) (*rest.RESTClient, map[stri return client, resources, nil } -func configTypeResourceNames(configTypes model.ConfigDescriptor) []string { +func configTypeResourceNames(configTypes schema.Set) []string { resourceNames := make([]string, len(configTypes)) for _, typ := range configTypes { resourceNames = append(resourceNames, crd.ResourceName(typ.Type)) @@ -831,7 +834,7 @@ func configTypeResourceNames(configTypes model.ConfigDescriptor) []string { return resourceNames } -func configTypePluralResourceNames(configTypes model.ConfigDescriptor) []string { +func configTypePluralResourceNames(configTypes schema.Set) []string { resourceNames := make([]string, len(configTypes)) for _, typ := range configTypes { resourceNames = append(resourceNames, crd.ResourceName(typ.Plural)) diff --git a/istioctl/cmd/istioctl_test.go b/istioctl/cmd/istioctl_test.go index 860923e6cbc3..6287fcec9419 100644 --- a/istioctl/cmd/istioctl_test.go +++ b/istioctl/cmd/istioctl_test.go @@ -23,9 +23,12 @@ import ( "testing" networking "istio.io/api/networking/v1alpha3" + "istio.io/istio/pilot/pkg/config/memory" "istio.io/istio/pilot/pkg/model" "istio.io/istio/pilot/test/util" + "istio.io/istio/pkg/config/schema" + "istio.io/istio/pkg/config/schemas" ) // sortedConfigStore lets us facade any ConfigStore (such as memory.Make()'s) providing @@ -52,9 +55,9 @@ var ( ConfigMeta: model.ConfigMeta{ Name: "bookinfo-gateway", Namespace: "default", - Type: model.Gateway.Type, - Group: model.Gateway.Group, - Version: model.Gateway.Version, + Type: schemas.Gateway.Type, + Group: schemas.Gateway.Group, + Version: schemas.Gateway.Version, }, Spec: &networking.Gateway{ Selector: map[string]string{"istio": "ingressgateway"}, @@ -77,9 +80,9 @@ var ( ConfigMeta: model.ConfigMeta{ Name: "bookinfo", Namespace: "default", - Type: model.VirtualService.Type, - Group: model.VirtualService.Group, - Version: model.VirtualService.Version, + Type: schemas.VirtualService.Type, + Group: schemas.VirtualService.Group, + Version: schemas.VirtualService.Version, }, Spec: &networking.VirtualService{ Hosts: []string{"*"}, @@ -129,9 +132,9 @@ var ( ConfigMeta: model.ConfigMeta{ Name: "googleapis", Namespace: "default", - Type: model.DestinationRule.Type, - Group: model.DestinationRule.Group, - Version: model.DestinationRule.Version, + Type: schemas.DestinationRule.Type, + Group: schemas.DestinationRule.Group, + Version: schemas.DestinationRule.Version, }, Spec: &networking.DestinationRule{ Host: "*.googleapis.com", @@ -149,9 +152,9 @@ var ( ConfigMeta: model.ConfigMeta{ Name: "googleapis", Namespace: "default", - Type: model.ServiceEntry.Type, - Group: model.ServiceEntry.Group, - Version: model.ServiceEntry.Version, + Type: schemas.ServiceEntry.Type, + Group: schemas.ServiceEntry.Group, + Version: schemas.ServiceEntry.Version, }, Spec: &networking.ServiceEntry{ Hosts: []string{"*.googleapis.com"}, @@ -314,7 +317,7 @@ func (cs sortedConfigStore) Delete(typ, name, namespace string) error { return cs.store.Delete(typ, name, namespace) } -func (cs sortedConfigStore) ConfigDescriptor() model.ConfigDescriptor { +func (cs sortedConfigStore) ConfigDescriptor() schema.Set { return cs.store.ConfigDescriptor() } diff --git a/istioctl/pkg/auth/validator.go b/istioctl/pkg/auth/validator.go index 9b1e4d49be29..5ff3d1545467 100644 --- a/istioctl/pkg/auth/validator.go +++ b/istioctl/pkg/auth/validator.go @@ -19,7 +19,9 @@ import ( "strings" rbacproto "istio.io/api/rbac/v1alpha1" + "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pkg/config/schemas" ) type Validator struct { @@ -102,11 +104,11 @@ func (v *Validator) getRoleAndBindingLists() error { if err != nil { return err } - for _, role := range configsFromFiles[model.ServiceRole.Type] { + for _, role := range configsFromFiles[schemas.ServiceRole.Type] { roleKey := getRoleKey(role.Namespace, role.Name) v.RoleKeyToServiceRole[roleKey] = role } - v.serviceRoleBindings = configsFromFiles[model.ServiceRoleBinding.Type] + v.serviceRoleBindings = configsFromFiles[schemas.ServiceRoleBinding.Type] if len(v.serviceRoleBindings) == 0 && len(v.RoleKeyToServiceRole) == 0 { v.Report.WriteString(ValidButNoRBACFound) return nil diff --git a/istioctl/pkg/validate/validate.go b/istioctl/pkg/validate/validate.go index 90d798764e66..1609a856033a 100644 --- a/istioctl/pkg/validate/validate.go +++ b/istioctl/pkg/validate/validate.go @@ -21,18 +21,19 @@ import ( "os" "strings" - multierror "github.com/hashicorp/go-multierror" + "github.com/hashicorp/go-multierror" "github.com/spf13/cobra" - yaml "gopkg.in/yaml.v2" + "gopkg.in/yaml.v2" mixercrd "istio.io/istio/mixer/pkg/config/crd" mixerstore "istio.io/istio/mixer/pkg/config/store" "istio.io/istio/mixer/pkg/runtime/config/constant" mixervalidate "istio.io/istio/mixer/pkg/validate" "istio.io/istio/pilot/pkg/config/kube/crd" - "istio.io/istio/pilot/pkg/model" "istio.io/istio/pilot/pkg/serviceregistry/kube/controller" "istio.io/istio/pkg/config/protocol" + "istio.io/istio/pkg/config/schemas" + "istio.io/pkg/log" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -86,7 +87,7 @@ func checkFields(un *unstructured.Unstructured) error { } func (v *validator) validateResource(istioNamespace string, un *unstructured.Unstructured) error { - schema, exists := model.IstioConfigTypes.GetByType(crd.CamelCaseToKebabCase(un.GetKind())) + schema, exists := schemas.Istio.GetByType(crd.CamelCaseToKebabCase(un.GetKind())) if exists { obj, err := crd.ConvertObjectFromUnstructured(schema, un, "") if err != nil { diff --git a/pilot/pkg/bootstrap/server.go b/pilot/pkg/bootstrap/server.go index 1aefec0f5664..75668c492981 100644 --- a/pilot/pkg/bootstrap/server.go +++ b/pilot/pkg/bootstrap/server.go @@ -75,6 +75,7 @@ import ( "istio.io/istio/pkg/config/constants" "istio.io/istio/pkg/config/host" "istio.io/istio/pkg/config/mesh" + "istio.io/istio/pkg/config/schemas" istiokeepalive "istio.io/istio/pkg/keepalive" kubelib "istio.io/istio/pkg/kube" configz "istio.io/istio/pkg/mcp/configz/client" @@ -516,8 +517,8 @@ func (c *mockController) Run(<-chan struct{}) {} func (s *Server) initMCPConfigController(args *PilotArgs) error { clientNodeID := "" - collections := make([]sink.CollectionOptions, len(model.IstioConfigTypes)) - for i, t := range model.IstioConfigTypes { + collections := make([]sink.CollectionOptions, len(schemas.Istio)) + for i, t := range schemas.Istio { collections[i] = sink.CollectionOptions{Name: t.Collection, Incremental: false} } @@ -547,7 +548,7 @@ func (s *Server) initMCPConfigController(args *PilotArgs) error { cancel() return fmt.Errorf("invalid fs config URL %s, contains no file path", configSource.Address) } - store := memory.Make(model.IstioConfigTypes) + store := memory.Make(schemas.Istio) configController := memory.NewController(store) err := s.makeFileMonitor(srcAddress.Path, configController) @@ -700,7 +701,7 @@ func (s *Server) initConfigController(args *PilotArgs) error { } else if args.Config.Controller != nil { s.configController = args.Config.Controller } else if args.Config.FileDir != "" { - store := memory.Make(model.IstioConfigTypes) + store := memory.Make(schemas.Istio) configController := memory.NewController(store) err := s.makeFileMonitor(args.Config.FileDir, configController) @@ -757,7 +758,7 @@ func (s *Server) initConfigController(args *PilotArgs) error { func (s *Server) makeKubeConfigController(args *PilotArgs) (model.ConfigStoreCache, error) { kubeCfgFile := s.getKubeCfgFile(args) - configClient, err := controller.NewClient(kubeCfgFile, "", model.IstioConfigTypes, args.Config.ControllerOptions.DomainSuffix) + configClient, err := controller.NewClient(kubeCfgFile, "", schemas.Istio, args.Config.ControllerOptions.DomainSuffix) if err != nil { return nil, multierror.Prefix(err, "failed to open a config client.") } @@ -772,7 +773,7 @@ func (s *Server) makeKubeConfigController(args *PilotArgs) (model.ConfigStoreCac } func (s *Server) makeFileMonitor(fileDir string, configController model.ConfigStore) error { - fileSnapshot := configmonitor.NewFileSnapshot(fileDir, model.IstioConfigTypes) + fileSnapshot := configmonitor.NewFileSnapshot(fileDir, schemas.Istio) fileMonitor := configmonitor.NewMonitor("file-monitor", configController, FilepathWalkInterval, fileSnapshot.ReadConfigFiles) // Defer starting the file monitor until after the service is created. diff --git a/pilot/pkg/config/aggregate/config.go b/pilot/pkg/config/aggregate/config.go index 2d83dc9b4e1f..de91266d3d0a 100644 --- a/pilot/pkg/config/aggregate/config.go +++ b/pilot/pkg/config/aggregate/config.go @@ -19,9 +19,10 @@ import ( "errors" "fmt" - multierror "github.com/hashicorp/go-multierror" + "github.com/hashicorp/go-multierror" "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pkg/config/schema" ) var errorUnsupported = errors.New("unsupported operation: the config aggregator is read-only") @@ -29,7 +30,7 @@ var errorUnsupported = errors.New("unsupported operation: the config aggregator // Make creates an aggregate config store from several config stores and // unifies their descriptors func Make(stores []model.ConfigStore) (model.ConfigStore, error) { - union := model.ConfigDescriptor{} + union := schema.Set{} storeTypes := make(map[string][]model.ConfigStore) for _, store := range stores { for _, descriptor := range store.ConfigDescriptor() { @@ -67,13 +68,13 @@ func MakeCache(caches []model.ConfigStoreCache) (model.ConfigStoreCache, error) type store struct { // descriptor is the unified - descriptor model.ConfigDescriptor + descriptor schema.Set // stores is a mapping from config type to a store stores map[string][]model.ConfigStore } -func (cr *store) ConfigDescriptor() model.ConfigDescriptor { +func (cr *store) ConfigDescriptor() schema.Set { return cr.descriptor } diff --git a/pilot/pkg/config/aggregate/config_test.go b/pilot/pkg/config/aggregate/config_test.go index 0138e854951e..c406b2bd2e81 100644 --- a/pilot/pkg/config/aggregate/config_test.go +++ b/pilot/pkg/config/aggregate/config_test.go @@ -22,6 +22,7 @@ import ( "istio.io/istio/pilot/pkg/config/aggregate" "istio.io/istio/pilot/pkg/config/aggregate/fakes" "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pkg/config/schema" ) func TestAggregateStoreBasicMake(t *testing.T) { @@ -30,13 +31,13 @@ func TestAggregateStoreBasicMake(t *testing.T) { storeOne := &fakes.ConfigStoreCache{} storeTwo := &fakes.ConfigStoreCache{} - storeOne.ConfigDescriptorReturns([]model.ProtoSchema{{ + storeOne.ConfigDescriptorReturns([]schema.Instance{{ Type: "some-config", Plural: "some-configs", MessageName: "istio.networking.v1alpha3.DestinationRule", }}) - storeTwo.ConfigDescriptorReturns([]model.ProtoSchema{{ + storeTwo.ConfigDescriptorReturns([]schema.Instance{{ Type: "other-config", Plural: "other-configs", MessageName: "istio.networking.v1alpha3.Gateway", @@ -49,7 +50,7 @@ func TestAggregateStoreBasicMake(t *testing.T) { descriptors := store.ConfigDescriptor() g.Expect(descriptors).To(gomega.HaveLen(2)) - g.Expect(descriptors).To(gomega.ConsistOf([]model.ProtoSchema{ + g.Expect(descriptors).To(gomega.ConsistOf([]schema.Instance{ { Type: "some-config", Plural: "some-configs", @@ -67,7 +68,7 @@ func TestAggregateStoreMakeValidationFailure(t *testing.T) { g := gomega.NewGomegaWithT(t) storeOne := &fakes.ConfigStoreCache{} - storeOne.ConfigDescriptorReturns([]model.ProtoSchema{{ + storeOne.ConfigDescriptorReturns([]schema.Instance{{ Type: "some-config", Plural: "some-configs", MessageName: "broken message name", @@ -86,7 +87,7 @@ func TestAggregateStoreGet(t *testing.T) { storeOne := &fakes.ConfigStoreCache{} storeTwo := &fakes.ConfigStoreCache{} - storeOne.ConfigDescriptorReturns([]model.ProtoSchema{{ + storeOne.ConfigDescriptorReturns([]schema.Instance{{ Type: "some-config", Plural: "some-configs", MessageName: "istio.networking.v1alpha3.DestinationRule", @@ -116,13 +117,13 @@ func TestAggregateStoreList(t *testing.T) { storeOne := &fakes.ConfigStoreCache{} storeTwo := &fakes.ConfigStoreCache{} - storeTwo.ConfigDescriptorReturns([]model.ProtoSchema{{ + storeTwo.ConfigDescriptorReturns([]schema.Instance{{ Type: "some-config", Plural: "some-configs", MessageName: "istio.networking.v1alpha3.Gateway", }}) - storeOne.ConfigDescriptorReturns([]model.ProtoSchema{{ + storeOne.ConfigDescriptorReturns([]schema.Instance{{ Type: "some-config", Plural: "some-configs", MessageName: "istio.networking.v1alpha3.DestinationRule", @@ -159,7 +160,7 @@ func TestAggregateStoreFails(t *testing.T) { g := gomega.NewGomegaWithT(t) storeOne := &fakes.ConfigStoreCache{} - storeOne.ConfigDescriptorReturns([]model.ProtoSchema{{ + storeOne.ConfigDescriptorReturns([]schema.Instance{{ Type: "other-config", Plural: "other-configs", MessageName: "istio.networking.v1alpha3.Gateway", @@ -200,13 +201,13 @@ func TestAggregateStoreCache(t *testing.T) { storeOne := &fakes.ConfigStoreCache{} storeTwo := &fakes.ConfigStoreCache{} - storeOne.ConfigDescriptorReturns([]model.ProtoSchema{{ + storeOne.ConfigDescriptorReturns([]schema.Instance{{ Type: "some-config", Plural: "some-configs", MessageName: "istio.networking.v1alpha3.DestinationRule", }}) - storeTwo.ConfigDescriptorReturns([]model.ProtoSchema{{ + storeTwo.ConfigDescriptorReturns([]schema.Instance{{ Type: "other-config", Plural: "other-configs", MessageName: "istio.networking.v1alpha3.Gateway", diff --git a/pilot/pkg/config/aggregate/fakes/config_store_cache.go b/pilot/pkg/config/aggregate/fakes/config_store_cache.go index 80a99fbf2db2..eeeec2178514 100644 --- a/pilot/pkg/config/aggregate/fakes/config_store_cache.go +++ b/pilot/pkg/config/aggregate/fakes/config_store_cache.go @@ -5,17 +5,18 @@ import ( "sync" "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pkg/config/schema" ) type ConfigStoreCache struct { - ConfigDescriptorStub func() model.ConfigDescriptor + ConfigDescriptorStub func() schema.Set configDescriptorMutex sync.RWMutex configDescriptorArgsForCall []struct{} configDescriptorReturns struct { - result1 model.ConfigDescriptor + result1 schema.Set } configDescriptorReturnsOnCall map[int]struct { - result1 model.ConfigDescriptor + result1 schema.Set } GetStub func(typ, name, namespace string) *model.Config getMutex sync.RWMutex @@ -107,7 +108,7 @@ type ConfigStoreCache struct { invocationsMutex sync.RWMutex } -func (fake *ConfigStoreCache) ConfigDescriptor() model.ConfigDescriptor { +func (fake *ConfigStoreCache) ConfigDescriptor() schema.Set { fake.configDescriptorMutex.Lock() ret, specificReturn := fake.configDescriptorReturnsOnCall[len(fake.configDescriptorArgsForCall)] fake.configDescriptorArgsForCall = append(fake.configDescriptorArgsForCall, struct{}{}) @@ -128,22 +129,22 @@ func (fake *ConfigStoreCache) ConfigDescriptorCallCount() int { return len(fake.configDescriptorArgsForCall) } -func (fake *ConfigStoreCache) ConfigDescriptorReturns(result1 model.ConfigDescriptor) { +func (fake *ConfigStoreCache) ConfigDescriptorReturns(result1 schema.Set) { fake.ConfigDescriptorStub = nil fake.configDescriptorReturns = struct { - result1 model.ConfigDescriptor + result1 schema.Set }{result1} } -func (fake *ConfigStoreCache) ConfigDescriptorReturnsOnCall(i int, result1 model.ConfigDescriptor) { +func (fake *ConfigStoreCache) ConfigDescriptorReturnsOnCall(i int, result1 schema.Set) { fake.ConfigDescriptorStub = nil if fake.configDescriptorReturnsOnCall == nil { fake.configDescriptorReturnsOnCall = make(map[int]struct { - result1 model.ConfigDescriptor + result1 schema.Set }) } fake.configDescriptorReturnsOnCall[i] = struct { - result1 model.ConfigDescriptor + result1 schema.Set }{result1} } diff --git a/pilot/pkg/config/coredatamodel/controller.go b/pilot/pkg/config/coredatamodel/controller.go index f3de74875421..6a089b3ca5f3 100644 --- a/pilot/pkg/config/coredatamodel/controller.go +++ b/pilot/pkg/config/coredatamodel/controller.go @@ -23,9 +23,12 @@ import ( "github.com/gogo/protobuf/types" + "istio.io/pkg/log" + "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pkg/config/schema" + "istio.io/istio/pkg/config/schemas" "istio.io/istio/pkg/mcp/sink" - "istio.io/pkg/log" ) var errUnsupported = errors.New("this operation is not supported by mcp controller") @@ -49,7 +52,7 @@ type Controller struct { configStoreMu sync.RWMutex // keys [type][namespace][name] configStore map[string]map[string]map[string]*model.Config - descriptorsByCollection map[string]model.ProtoSchema + descriptorsByCollection map[string]schema.Instance options Options eventHandlers map[string][]func(model.Config, model.Event) @@ -59,9 +62,9 @@ type Controller struct { // NewController provides a new CoreDataModel controller func NewController(options Options) CoreDataModel { - descriptorsByMessageName := make(map[string]model.ProtoSchema, len(model.IstioConfigTypes)) + descriptorsByMessageName := make(map[string]schema.Instance, len(schemas.Istio)) synced := make(map[string]bool) - for _, descriptor := range model.IstioConfigTypes { + for _, descriptor := range schemas.Istio { // don't register duplicate descriptors for the same collection if _, ok := descriptorsByMessageName[descriptor.Collection]; !ok { descriptorsByMessageName[descriptor.Collection] = descriptor @@ -80,8 +83,8 @@ func NewController(options Options) CoreDataModel { // ConfigDescriptor returns all the ConfigDescriptors that this // controller is responsible for -func (c *Controller) ConfigDescriptor() model.ConfigDescriptor { - return model.IstioConfigTypes +func (c *Controller) ConfigDescriptor() schema.Set { + return schemas.Istio } // List returns all the config that is stored by type and namespace @@ -123,7 +126,7 @@ func (c *Controller) Apply(change *sink.Change) error { return fmt.Errorf("apply type not supported %s", change.Collection) } - schema, valid := c.ConfigDescriptor().GetByType(descriptor.Type) + s, valid := c.ConfigDescriptor().GetByType(descriptor.Type) if !valid { return fmt.Errorf("descriptor type not supported %s", descriptor.Type) } @@ -163,7 +166,7 @@ func (c *Controller) Apply(change *sink.Change) error { Spec: obj.Body, } - if err := schema.Validate(conf.Name, conf.Namespace, conf.Spec); err != nil { + if err := s.Validate(conf.Name, conf.Namespace, conf.Spec); err != nil { // Do not return an error, instead discard the resources so that Pilot can process the rest. log.Warnf("Discarding incoming MCP resource: validation failed (%s/%s): %v", conf.Namespace, conf.Name, err) continue @@ -186,7 +189,7 @@ func (c *Controller) Apply(change *sink.Change) error { c.configStore[descriptor.Type] = innerStore c.configStoreMu.Unlock() - if descriptor.Type == model.ServiceEntry.Type { + if descriptor.Type == schemas.ServiceEntry.Type { c.serviceEntryEvents(innerStore, prevStore) } else { c.options.ClearDiscoveryServerCache() @@ -249,7 +252,7 @@ func (c *Controller) Delete(typ, name, namespace string) error { func (c *Controller) serviceEntryEvents(currentStore, prevStore map[string]map[string]*model.Config) { dispatch := func(model model.Config, event model.Event) {} - if handlers, ok := c.eventHandlers[model.ServiceEntry.Type]; ok { + if handlers, ok := c.eventHandlers[schemas.ServiceEntry.Type]; ok { dispatch = func(model model.Config, event model.Event) { log.Debugf("MCP event dispatch: key=%v event=%v", model.Key(), event.String()) for _, handler := range handlers { diff --git a/pilot/pkg/config/coredatamodel/controller_test.go b/pilot/pkg/config/coredatamodel/controller_test.go index afba66f673e6..2195a2cd7a2b 100644 --- a/pilot/pkg/config/coredatamodel/controller_test.go +++ b/pilot/pkg/config/coredatamodel/controller_test.go @@ -27,8 +27,10 @@ import ( authn "istio.io/api/authentication/v1alpha1" mcpapi "istio.io/api/mcp/v1alpha1" networking "istio.io/api/networking/v1alpha3" + "istio.io/istio/pilot/pkg/config/coredatamodel" "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pkg/config/schemas" "istio.io/istio/pkg/mcp/sink" ) @@ -123,33 +125,33 @@ func TestOptions(t *testing.T) { } controller := coredatamodel.NewController(testControllerOptions) - message := convertToResource(g, model.ServiceEntry.MessageName, []proto.Message{serviceEntry}) + message := convertToResource(g, schemas.ServiceEntry.MessageName, []proto.Message{serviceEntry}) change := convert( []proto.Message{message[0]}, []string{"service-bar"}, - model.ServiceEntry.Collection, - model.ServiceEntry.MessageName) + schemas.ServiceEntry.Collection, + schemas.ServiceEntry.MessageName) err := controller.Apply(change) g.Expect(err).ToNot(gomega.HaveOccurred()) - c, err := controller.List(model.ServiceEntry.Type, "") + c, err := controller.List(schemas.ServiceEntry.Type, "") g.Expect(c).ToNot(gomega.BeNil()) g.Expect(err).ToNot(gomega.HaveOccurred()) g.Expect(c[0].Domain).To(gomega.Equal(testControllerOptions.DomainSuffix)) g.Expect(cacheCleared).To(gomega.Equal(false)) - message = convertToResource(g, model.Gateway.MessageName, []proto.Message{gateway}) + message = convertToResource(g, schemas.Gateway.MessageName, []proto.Message{gateway}) change = convert( []proto.Message{message[0]}, []string{"gateway-foo"}, - model.Gateway.Collection, - model.Gateway.MessageName) + schemas.Gateway.Collection, + schemas.Gateway.MessageName) err = controller.Apply(change) g.Expect(err).ToNot(gomega.HaveOccurred()) - c, err = controller.List(model.Gateway.Type, "") + c, err = controller.List(schemas.Gateway.Type, "") g.Expect(c).ToNot(gomega.BeNil()) g.Expect(err).ToNot(gomega.HaveOccurred()) g.Expect(c[0].Domain).To(gomega.Equal(testControllerOptions.DomainSuffix)) @@ -168,7 +170,7 @@ func TestConfigDescriptor(t *testing.T) { controller := coredatamodel.NewController(testControllerOptions) descriptors := controller.ConfigDescriptor() - g.Expect(descriptors).To(gomega.Equal(model.IstioConfigTypes)) + g.Expect(descriptors).To(gomega.Equal(schemas.Istio)) } func TestListInvalidType(t *testing.T) { @@ -194,12 +196,12 @@ func TestListAllNameSpace(t *testing.T) { g := gomega.NewGomegaWithT(t) controller := coredatamodel.NewController(testControllerOptions) - messages := convertToResource(g, model.Gateway.MessageName, []proto.Message{gateway, gateway2, gateway3}) + messages := convertToResource(g, schemas.Gateway.MessageName, []proto.Message{gateway, gateway2, gateway3}) message, message2, message3 := messages[0], messages[1], messages[2] change := convert( []proto.Message{message, message2, message3}, []string{"namespace1/some-gateway1", "default/some-other-gateway", "some-other-gateway3"}, - model.Gateway.Collection, model.Gateway.MessageName) + schemas.Gateway.Collection, schemas.Gateway.MessageName) err := controller.Apply(change) g.Expect(err).ToNot(gomega.HaveOccurred()) @@ -209,7 +211,7 @@ func TestListAllNameSpace(t *testing.T) { g.Expect(len(c)).To(gomega.Equal(3)) for _, conf := range c { - g.Expect(conf.Type).To(gomega.Equal(model.Gateway.Type)) + g.Expect(conf.Type).To(gomega.Equal(schemas.Gateway.Type)) if conf.Name == "some-gateway1" { g.Expect(conf.Spec).To(gomega.Equal(message)) g.Expect(conf.Namespace).To(gomega.Equal("namespace1")) @@ -228,13 +230,13 @@ func TestListSpecificNameSpace(t *testing.T) { g := gomega.NewGomegaWithT(t) controller := coredatamodel.NewController(testControllerOptions) - messages := convertToResource(g, model.Gateway.MessageName, []proto.Message{gateway, gateway2, gateway3}) + messages := convertToResource(g, schemas.Gateway.MessageName, []proto.Message{gateway, gateway2, gateway3}) message, message2, message3 := messages[0], messages[1], messages[2] change := convert( []proto.Message{message, message2, message3}, []string{"namespace1/some-gateway1", "default/some-other-gateway", "namespace1/some-other-gateway3"}, - model.Gateway.Collection, model.Gateway.MessageName) + schemas.Gateway.Collection, schemas.Gateway.MessageName) err := controller.Apply(change) g.Expect(err).ToNot(gomega.HaveOccurred()) @@ -244,7 +246,7 @@ func TestListSpecificNameSpace(t *testing.T) { g.Expect(len(c)).To(gomega.Equal(2)) for _, conf := range c { - g.Expect(conf.Type).To(gomega.Equal(model.Gateway.Type)) + g.Expect(conf.Type).To(gomega.Equal(schemas.Gateway.Type)) g.Expect(conf.Namespace).To(gomega.Equal("namespace1")) if conf.Name == "some-gateway1" { g.Expect(conf.Spec).To(gomega.Equal(message)) @@ -259,7 +261,7 @@ func TestApplyInvalidType(t *testing.T) { g := gomega.NewGomegaWithT(t) controller := coredatamodel.NewController(testControllerOptions) - message := convertToResource(g, model.Gateway.MessageName, []proto.Message{gateway}) + message := convertToResource(g, schemas.Gateway.MessageName, []proto.Message{gateway}) change := convert([]proto.Message{message[0]}, []string{"some-gateway"}, "bad-collection", "bad-type") @@ -287,11 +289,11 @@ func TestApplyValidTypeWithNoBaseURL(t *testing.T) { marshaledGateway, err := proto.Marshal(gateway) g.Expect(err).ToNot(gomega.HaveOccurred()) - message, err := makeMessage(marshaledGateway, model.Gateway.MessageName) + message, err := makeMessage(marshaledGateway, schemas.Gateway.MessageName) g.Expect(err).ToNot(gomega.HaveOccurred()) change := convert([]proto.Message{message}, []string{"some-gateway"}, - model.Gateway.Collection, model.Gateway.MessageName) + schemas.Gateway.Collection, schemas.Gateway.MessageName) err = controller.Apply(change) g.Expect(err).ToNot(gomega.HaveOccurred()) @@ -299,7 +301,7 @@ func TestApplyValidTypeWithNoBaseURL(t *testing.T) { g.Expect(err).ToNot(gomega.HaveOccurred()) g.Expect(len(c)).To(gomega.Equal(1)) g.Expect(c[0].Name).To(gomega.Equal("some-gateway")) - g.Expect(c[0].Type).To(gomega.Equal(model.Gateway.Type)) + g.Expect(c[0].Type).To(gomega.Equal(schemas.Gateway.Type)) g.Expect(c[0].Spec).To(gomega.Equal(message)) g.Expect(c[0].Spec).To(gomega.ContainSubstring(fmt.Sprintf("number:%d", port))) } @@ -311,10 +313,10 @@ func TestApplyMetadataNameIncludesNamespace(t *testing.T) { g := gomega.NewGomegaWithT(t) controller := coredatamodel.NewController(testControllerOptions) - message := convertToResource(g, model.Gateway.MessageName, []proto.Message{gateway}) + message := convertToResource(g, schemas.Gateway.MessageName, []proto.Message{gateway}) change := convert([]proto.Message{message[0]}, []string{"istio-namespace/some-gateway"}, - model.Gateway.Collection, model.Gateway.MessageName) + schemas.Gateway.Collection, schemas.Gateway.MessageName) err := controller.Apply(change) g.Expect(err).ToNot(gomega.HaveOccurred()) @@ -322,7 +324,7 @@ func TestApplyMetadataNameIncludesNamespace(t *testing.T) { g.Expect(err).ToNot(gomega.HaveOccurred()) g.Expect(len(c)).To(gomega.Equal(1)) g.Expect(c[0].Name).To(gomega.Equal("some-gateway")) - g.Expect(c[0].Type).To(gomega.Equal(model.Gateway.Type)) + g.Expect(c[0].Type).To(gomega.Equal(schemas.Gateway.Type)) g.Expect(c[0].Spec).To(gomega.Equal(message[0])) } @@ -330,9 +332,9 @@ func TestApplyMetadataNameWithoutNamespace(t *testing.T) { g := gomega.NewGomegaWithT(t) controller := coredatamodel.NewController(testControllerOptions) - message := convertToResource(g, model.Gateway.MessageName, []proto.Message{gateway}) + message := convertToResource(g, schemas.Gateway.MessageName, []proto.Message{gateway}) - change := convert([]proto.Message{message[0]}, []string{"some-gateway"}, model.Gateway.Collection, model.Gateway.MessageName) + change := convert([]proto.Message{message[0]}, []string{"some-gateway"}, schemas.Gateway.Collection, schemas.Gateway.MessageName) err := controller.Apply(change) g.Expect(err).ToNot(gomega.HaveOccurred()) @@ -340,7 +342,7 @@ func TestApplyMetadataNameWithoutNamespace(t *testing.T) { g.Expect(err).ToNot(gomega.HaveOccurred()) g.Expect(len(c)).To(gomega.Equal(1)) g.Expect(c[0].Name).To(gomega.Equal("some-gateway")) - g.Expect(c[0].Type).To(gomega.Equal(model.Gateway.Type)) + g.Expect(c[0].Type).To(gomega.Equal(schemas.Gateway.Type)) g.Expect(c[0].Spec).To(gomega.Equal(message[0])) } @@ -348,8 +350,8 @@ func TestApplyChangeNoObjects(t *testing.T) { g := gomega.NewGomegaWithT(t) controller := coredatamodel.NewController(testControllerOptions) - message := convertToResource(g, model.Gateway.MessageName, []proto.Message{gateway}) - change := convert([]proto.Message{message[0]}, []string{"some-gateway"}, model.Gateway.Collection, model.Gateway.MessageName) + message := convertToResource(g, schemas.Gateway.MessageName, []proto.Message{gateway}) + change := convert([]proto.Message{message[0]}, []string{"some-gateway"}, schemas.Gateway.Collection, schemas.Gateway.MessageName) err := controller.Apply(change) g.Expect(err).ToNot(gomega.HaveOccurred()) @@ -357,10 +359,10 @@ func TestApplyChangeNoObjects(t *testing.T) { g.Expect(err).ToNot(gomega.HaveOccurred()) g.Expect(len(c)).To(gomega.Equal(1)) g.Expect(c[0].Name).To(gomega.Equal("some-gateway")) - g.Expect(c[0].Type).To(gomega.Equal(model.Gateway.Type)) + g.Expect(c[0].Type).To(gomega.Equal(schemas.Gateway.Type)) g.Expect(c[0].Spec).To(gomega.Equal(message[0])) - change = convert([]proto.Message{}, []string{"some-gateway"}, model.Gateway.Collection, model.Gateway.MessageName) + change = convert([]proto.Message{}, []string{"some-gateway"}, schemas.Gateway.Collection, schemas.Gateway.MessageName) err = controller.Apply(change) g.Expect(err).ToNot(gomega.HaveOccurred()) @@ -373,69 +375,69 @@ func TestApplyClusterScopedAuthPolicy(t *testing.T) { g := gomega.NewGomegaWithT(t) controller := coredatamodel.NewController(testControllerOptions) - message0 := convertToResource(g, model.AuthenticationPolicy.MessageName, []proto.Message{authnPolicy0}) - message1 := convertToResource(g, model.AuthenticationMeshPolicy.MessageName, []proto.Message{authnPolicy1}) + message0 := convertToResource(g, schemas.AuthenticationPolicy.MessageName, []proto.Message{authnPolicy0}) + message1 := convertToResource(g, schemas.AuthenticationMeshPolicy.MessageName, []proto.Message{authnPolicy1}) change := convert( []proto.Message{message0[0]}, []string{"bar-namespace/foo"}, - model.AuthenticationPolicy.Collection, model.AuthenticationPolicy.MessageName) + schemas.AuthenticationPolicy.Collection, schemas.AuthenticationPolicy.MessageName) err := controller.Apply(change) g.Expect(err).ToNot(gomega.HaveOccurred()) change = convert( []proto.Message{message1[0]}, []string{"default"}, - model.AuthenticationMeshPolicy.Collection, model.AuthenticationMeshPolicy.MessageName) + schemas.AuthenticationMeshPolicy.Collection, schemas.AuthenticationMeshPolicy.MessageName) err = controller.Apply(change) g.Expect(err).ToNot(gomega.HaveOccurred()) - c, err := controller.List(model.AuthenticationPolicy.Type, "bar-namespace") + c, err := controller.List(schemas.AuthenticationPolicy.Type, "bar-namespace") g.Expect(err).ToNot(gomega.HaveOccurred()) g.Expect(len(c)).To(gomega.Equal(1)) g.Expect(c[0].Name).To(gomega.Equal("foo")) g.Expect(c[0].Namespace).To(gomega.Equal("bar-namespace")) - g.Expect(c[0].Type).To(gomega.Equal(model.AuthenticationPolicy.Type)) + g.Expect(c[0].Type).To(gomega.Equal(schemas.AuthenticationPolicy.Type)) g.Expect(c[0].Spec).To(gomega.Equal(message0[0])) - c, err = controller.List(model.AuthenticationMeshPolicy.Type, "") + c, err = controller.List(schemas.AuthenticationMeshPolicy.Type, "") g.Expect(err).ToNot(gomega.HaveOccurred()) g.Expect(len(c)).To(gomega.Equal(1)) g.Expect(c[0].Name).To(gomega.Equal("default")) g.Expect(c[0].Namespace).To(gomega.Equal("")) - g.Expect(c[0].Type).To(gomega.Equal(model.AuthenticationMeshPolicy.Type)) + g.Expect(c[0].Type).To(gomega.Equal(schemas.AuthenticationMeshPolicy.Type)) g.Expect(c[0].Spec).To(gomega.Equal(message1[0])) // verify the namespace scoped resource can be deleted change = convert( []proto.Message{}, []string{}, - model.AuthenticationPolicy.Collection, model.AuthenticationPolicy.MessageName) + schemas.AuthenticationPolicy.Collection, schemas.AuthenticationPolicy.MessageName) err = controller.Apply(change) g.Expect(err).ToNot(gomega.HaveOccurred()) - c, err = controller.List(model.AuthenticationMeshPolicy.Type, "") + c, err = controller.List(schemas.AuthenticationMeshPolicy.Type, "") g.Expect(err).ToNot(gomega.HaveOccurred()) g.Expect(len(c)).To(gomega.Equal(1)) g.Expect(c[0].Name).To(gomega.Equal("default")) g.Expect(c[0].Namespace).To(gomega.Equal("")) - g.Expect(c[0].Type).To(gomega.Equal(model.AuthenticationMeshPolicy.Type)) + g.Expect(c[0].Type).To(gomega.Equal(schemas.AuthenticationMeshPolicy.Type)) g.Expect(c[0].Spec).To(gomega.Equal(message1[0])) // verify the namespace scoped resource can be added and mesh-scoped resource removed change = convert( []proto.Message{message0[0]}, []string{"bar-namespace/foo"}, - model.AuthenticationPolicy.Collection, model.AuthenticationPolicy.MessageName) + schemas.AuthenticationPolicy.Collection, schemas.AuthenticationPolicy.MessageName) err = controller.Apply(change) g.Expect(err).ToNot(gomega.HaveOccurred()) - c, err = controller.List(model.AuthenticationPolicy.Type, "bar-namespace") + c, err = controller.List(schemas.AuthenticationPolicy.Type, "bar-namespace") g.Expect(err).ToNot(gomega.HaveOccurred()) g.Expect(len(c)).To(gomega.Equal(1)) g.Expect(c[0].Name).To(gomega.Equal("foo")) g.Expect(c[0].Namespace).To(gomega.Equal("bar-namespace")) - g.Expect(c[0].Type).To(gomega.Equal(model.AuthenticationPolicy.Type)) + g.Expect(c[0].Type).To(gomega.Equal(schemas.AuthenticationPolicy.Type)) g.Expect(c[0].Spec).To(gomega.Equal(message0[0])) } @@ -451,12 +453,12 @@ func TestEventHandler(t *testing.T) { model.EventUpdate: {}, model.EventDelete: {}, } - controller.RegisterEventHandler(model.ServiceEntry.Type, func(m model.Config, e model.Event) { + controller.RegisterEventHandler(schemas.ServiceEntry.Type, func(m model.Config, e model.Event) { gotEvents[e][makeName(m.Namespace, m.Name)] = m }) typeURL := "type.googleapis.com/istio.networking.v1alpha3.ServiceEntry" - collection := model.ServiceEntry.Collection + collection := schemas.ServiceEntry.Collection fakeCreateTime, _ := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z") fakeCreateTimeProto, err := types.TimestampProto(fakeCreateTime) @@ -483,9 +485,9 @@ func TestEventHandler(t *testing.T) { makeServiceEntryModel := func(name, host, version string) model.Config { return model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.ServiceEntry.Type, - Group: model.ServiceEntry.Group, - Version: model.ServiceEntry.Version, + Type: schemas.ServiceEntry.Type, + Group: schemas.ServiceEntry.Group, + Version: schemas.ServiceEntry.Version, Name: name, Namespace: "default", Domain: "cluster.local", @@ -679,16 +681,16 @@ func TestInvalidResource(t *testing.T) { gw := proto.Clone(gateway).(*networking.Gateway) gw.Servers[0].Hosts = nil - message0 := convertToResource(g, model.Gateway.MessageName, []proto.Message{gw}) + message0 := convertToResource(g, schemas.Gateway.MessageName, []proto.Message{gw}) change := convert( []proto.Message{message0[0]}, []string{"bar-namespace/foo"}, - model.Gateway.Collection, model.Gateway.MessageName) + schemas.Gateway.Collection, schemas.Gateway.MessageName) err := controller.Apply(change) g.Expect(err).ToNot(gomega.HaveOccurred()) - entries, err := controller.List(model.Gateway.Type, "") + entries, err := controller.List(schemas.Gateway.Type, "") g.Expect(err).ToNot(gomega.HaveOccurred()) g.Expect(entries).To(gomega.HaveLen(0)) } @@ -697,11 +699,11 @@ func TestInvalidResource_BadTimestamp(t *testing.T) { g := gomega.NewGomegaWithT(t) controller := coredatamodel.NewController(testControllerOptions) - message0 := convertToResource(g, model.Gateway.MessageName, []proto.Message{gateway}) + message0 := convertToResource(g, schemas.Gateway.MessageName, []proto.Message{gateway}) change := convert( []proto.Message{message0[0]}, []string{"bar-namespace/foo"}, - model.Gateway.Collection, model.Gateway.MessageName) + schemas.Gateway.Collection, schemas.Gateway.MessageName) change.Objects[0].Metadata.CreateTime = &types.Timestamp{ Seconds: -1, Nanos: -1, @@ -710,7 +712,7 @@ func TestInvalidResource_BadTimestamp(t *testing.T) { err := controller.Apply(change) g.Expect(err).ToNot(gomega.HaveOccurred()) - entries, err := controller.List(model.Gateway.Type, "") + entries, err := controller.List(schemas.Gateway.Type, "") g.Expect(err).ToNot(gomega.HaveOccurred()) g.Expect(entries).To(gomega.HaveLen(0)) } diff --git a/pilot/pkg/config/kube/crd/config.go b/pilot/pkg/config/kube/crd/config.go index 0882c0a7c101..14cb39d27bf0 100644 --- a/pilot/pkg/config/kube/crd/config.go +++ b/pilot/pkg/config/kube/crd/config.go @@ -19,6 +19,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pkg/config/schema" ) // IstioKind is the generic Kubernetes API object wrapper @@ -139,7 +140,7 @@ type IstioObjectList interface { GetItems() []IstioObject } -func APIVersion(schema *model.ProtoSchema) string { +func APIVersion(schema *schema.Instance) string { return ResourceGroup(schema) + "/" + schema.Version } diff --git a/pilot/pkg/config/kube/crd/controller/client.go b/pilot/pkg/config/kube/crd/controller/client.go index ca0c8a497b7f..eee31499aa2f 100644 --- a/pilot/pkg/config/kube/crd/controller/client.go +++ b/pilot/pkg/config/kube/crd/controller/client.go @@ -22,23 +22,25 @@ import ( "time" "github.com/golang/sync/errgroup" - multierror "github.com/hashicorp/go-multierror" + "github.com/hashicorp/go-multierror" apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" apierrors "k8s.io/apimachinery/pkg/api/errors" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" + kubeSchema "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/util/wait" // import GKE cluster authentication plugin _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" // import OIDC cluster authentication plugin, e.g. for Tectonic _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" "k8s.io/client-go/rest" + "istio.io/pkg/log" + "istio.io/istio/pilot/pkg/config/kube/crd" "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pkg/config/schema" kubecfg "istio.io/istio/pkg/kube" - "istio.io/pkg/log" ) // Client is a basic REST client for CRDs implementing config store @@ -51,10 +53,10 @@ type Client struct { } type restClient struct { - apiVersion schema.GroupVersion + apiVersion kubeSchema.GroupVersion // descriptor from the same apiVersion. - descriptor model.ConfigDescriptor + descriptor schema.Set // types of the schema and objects in the descriptor. types []*crd.SchemaType @@ -66,7 +68,7 @@ type restClient struct { dynamic *rest.RESTClient } -func newClientSet(descriptor model.ConfigDescriptor) (map[string]*restClient, error) { +func newClientSet(descriptor schema.Set) (map[string]*restClient, error) { cs := make(map[string]*restClient) for _, typ := range descriptor { s, exists := crd.KnownTypes[typ.Type] @@ -78,7 +80,7 @@ func newClientSet(descriptor model.ConfigDescriptor) (map[string]*restClient, er if !ok { // create a new client if one doesn't already exist rc = &restClient{ - apiVersion: schema.GroupVersion{ + apiVersion: kubeSchema.GroupVersion{ Group: crd.ResourceGroup(&typ), Version: typ.Version, }, @@ -130,7 +132,7 @@ func (rc *restClient) updateRESTConfig(cfg *rest.Config) (config *rest.Config, e } // NewForConfig creates a client to the Kubernetes API using a rest config. -func NewForConfig(cfg *rest.Config, descriptor model.ConfigDescriptor, domainSuffix string) (*Client, error) { +func NewForConfig(cfg *rest.Config, descriptor schema.Set, domainSuffix string) (*Client, error) { cs, err := newClientSet(descriptor) if err != nil { return nil, err @@ -154,7 +156,7 @@ func NewForConfig(cfg *rest.Config, descriptor model.ConfigDescriptor, domainSuf // Use an empty value for `kubeconfig` to use the in-cluster config. // If the kubeconfig file is empty, defaults to in-cluster config as well. // You can also choose a config context by providing the desired context name. -func NewClient(config string, context string, descriptor model.ConfigDescriptor, domainSuffix string) (*Client, error) { +func NewClient(config string, context string, descriptor schema.Set, domainSuffix string) (*Client, error) { cfg, err := kubecfg.BuildClientConfig(config, context) if err != nil { return nil, err @@ -183,8 +185,8 @@ func (rc *restClient) registerResources() error { } skipCreate := true - for _, schema := range rc.descriptor { - name := crd.ResourceName(schema.Plural) + "." + crd.ResourceGroup(&schema) + for _, s := range rc.descriptor { + name := crd.ResourceName(s.Plural) + "." + crd.ResourceGroup(&s) crd, errGet := cs.ApiextensionsV1beta1().CustomResourceDefinitions().Get(name, meta_v1.GetOptions{}) if errGet != nil { skipCreate = false @@ -211,11 +213,11 @@ func (rc *restClient) registerResources() error { return nil } - for _, schema := range rc.descriptor { - g := crd.ResourceGroup(&schema) - name := crd.ResourceName(schema.Plural) + "." + g + for _, s := range rc.descriptor { + g := crd.ResourceGroup(&s) + name := crd.ResourceName(s.Plural) + "." + g crdScope := apiextensionsv1beta1.NamespaceScoped - if schema.ClusterScoped { + if s.ClusterScoped { crdScope = apiextensionsv1beta1.ClusterScoped } crd := &apiextensionsv1beta1.CustomResourceDefinition{ @@ -224,11 +226,11 @@ func (rc *restClient) registerResources() error { }, Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{ Group: g, - Version: schema.Version, + Version: s.Version, Scope: crdScope, Names: apiextensionsv1beta1.CustomResourceDefinitionNames{ - Plural: crd.ResourceName(schema.Plural), - Kind: crd.KebabCaseToCamelCase(schema.Type), + Plural: crd.ResourceName(s.Plural), + Kind: crd.KebabCaseToCamelCase(s.Type), }, }, } @@ -242,8 +244,8 @@ func (rc *restClient) registerResources() error { // wait for CRD being established errPoll := wait.Poll(500*time.Millisecond, 60*time.Second, func() (bool, error) { descriptor: - for _, schema := range rc.descriptor { - name := crd.ResourceName(schema.Plural) + "." + crd.ResourceGroup(&schema) + for _, s := range rc.descriptor { + name := crd.ResourceName(s.Plural) + "." + crd.ResourceGroup(&s) crd, errGet := cs.ApiextensionsV1beta1().CustomResourceDefinitions().Get(name, meta_v1.GetOptions{}) if errGet != nil { return false, errGet @@ -293,8 +295,8 @@ func (rc *restClient) deregisterResources() error { } var errs error - for _, schema := range rc.descriptor { - name := crd.ResourceName(schema.Plural) + "." + crd.ResourceGroup(&schema) + for _, s := range rc.descriptor { + name := crd.ResourceName(s.Plural) + "." + crd.ResourceGroup(&s) err := cs.ApiextensionsV1beta1().CustomResourceDefinitions().Delete(name, nil) errs = multierror.Append(errs, err) } @@ -302,8 +304,8 @@ func (rc *restClient) deregisterResources() error { } // ConfigDescriptor for the store -func (cl *Client) ConfigDescriptor() model.ConfigDescriptor { - d := make(model.ConfigDescriptor, 0, len(cl.clientset)) +func (cl *Client) ConfigDescriptor() schema.Set { + d := make(schema.Set, 0, len(cl.clientset)) for _, rc := range cl.clientset { d = append(d, rc.descriptor...) } @@ -312,27 +314,27 @@ func (cl *Client) ConfigDescriptor() model.ConfigDescriptor { // Get implements store interface func (cl *Client) Get(typ, name, namespace string) *model.Config { - s, ok := crd.KnownTypes[typ] + t, ok := crd.KnownTypes[typ] if !ok { log.Warn("unknown type " + typ) return nil } - rc, ok := cl.clientset[crd.APIVersion(&s.Schema)] + rc, ok := cl.clientset[crd.APIVersion(&t.Schema)] if !ok { log.Warn("cannot find client for type " + typ) return nil } - schema, exists := rc.descriptor.GetByType(typ) + s, exists := rc.descriptor.GetByType(typ) if !exists { log.Warn("cannot find proto schema for type " + typ) return nil } - config := s.Object.DeepCopyObject().(crd.IstioObject) + config := t.Object.DeepCopyObject().(crd.IstioObject) err := rc.dynamic.Get(). Namespace(namespace). - Resource(crd.ResourceName(schema.Plural)). + Resource(crd.ResourceName(s.Plural)). Name(name). Do().Into(config) @@ -341,7 +343,7 @@ func (cl *Client) Get(typ, name, namespace string) *model.Config { return nil } - out, err := crd.ConvertObject(schema, config, cl.domainSuffix) + out, err := crd.ConvertObject(s, config, cl.domainSuffix) if err != nil { log.Warna(err) return nil @@ -356,24 +358,24 @@ func (cl *Client) Create(config model.Config) (string, error) { return "", fmt.Errorf("unrecognized apiVersion %q", config) } - schema, exists := rc.descriptor.GetByType(config.Type) + s, exists := rc.descriptor.GetByType(config.Type) if !exists { return "", fmt.Errorf("unrecognized type %q", config.Type) } - if err := schema.Validate(config.Name, config.Namespace, config.Spec); err != nil { + if err := s.Validate(config.Name, config.Namespace, config.Spec); err != nil { return "", multierror.Prefix(err, "validation error:") } - out, err := crd.ConvertConfig(schema, config) + out, err := crd.ConvertConfig(s, config) if err != nil { return "", err } - obj := crd.KnownTypes[schema.Type].Object.DeepCopyObject().(crd.IstioObject) + obj := crd.KnownTypes[s.Type].Object.DeepCopyObject().(crd.IstioObject) err = rc.dynamic.Post(). Namespace(out.GetObjectMeta().Namespace). - Resource(crd.ResourceName(schema.Plural)). + Resource(crd.ResourceName(s.Plural)). Body(out). Do().Into(obj) if err != nil { @@ -389,12 +391,12 @@ func (cl *Client) Update(config model.Config) (string, error) { if !ok { return "", fmt.Errorf("unrecognized apiVersion %q", config) } - schema, exists := rc.descriptor.GetByType(config.Type) + s, exists := rc.descriptor.GetByType(config.Type) if !exists { return "", fmt.Errorf("unrecognized type %q", config.Type) } - if err := schema.Validate(config.Name, config.Namespace, config.Spec); err != nil { + if err := s.Validate(config.Name, config.Namespace, config.Spec); err != nil { return "", multierror.Prefix(err, "validation error:") } @@ -402,15 +404,15 @@ func (cl *Client) Update(config model.Config) (string, error) { return "", fmt.Errorf("revision is required") } - out, err := crd.ConvertConfig(schema, config) + out, err := crd.ConvertConfig(s, config) if err != nil { return "", err } - obj := crd.KnownTypes[schema.Type].Object.DeepCopyObject().(crd.IstioObject) + obj := crd.KnownTypes[s.Type].Object.DeepCopyObject().(crd.IstioObject) err = rc.dynamic.Put(). Namespace(out.GetObjectMeta().Namespace). - Resource(crd.ResourceName(schema.Plural)). + Resource(crd.ResourceName(s.Plural)). Name(out.GetObjectMeta().Name). Body(out). Do().Into(obj) @@ -423,50 +425,50 @@ func (cl *Client) Update(config model.Config) (string, error) { // Delete implements store interface func (cl *Client) Delete(typ, name, namespace string) error { - s, ok := crd.KnownTypes[typ] + t, ok := crd.KnownTypes[typ] if !ok { return fmt.Errorf("unrecognized type %q", typ) } - rc, ok := cl.clientset[crd.APIVersion(&s.Schema)] + rc, ok := cl.clientset[crd.APIVersion(&t.Schema)] if !ok { - return fmt.Errorf("unrecognized apiVersion %v", s.Schema) + return fmt.Errorf("unrecognized apiVersion %v", t.Schema) } - schema, exists := rc.descriptor.GetByType(typ) + s, exists := rc.descriptor.GetByType(typ) if !exists { return fmt.Errorf("missing type %q", typ) } return rc.dynamic.Delete(). Namespace(namespace). - Resource(crd.ResourceName(schema.Plural)). + Resource(crd.ResourceName(s.Plural)). Name(name). Do().Error() } // List implements store interface func (cl *Client) List(typ, namespace string) ([]model.Config, error) { - s, ok := crd.KnownTypes[typ] + t, ok := crd.KnownTypes[typ] if !ok { return nil, fmt.Errorf("unrecognized type %q", typ) } - rc, ok := cl.clientset[crd.APIVersion(&s.Schema)] + rc, ok := cl.clientset[crd.APIVersion(&t.Schema)] if !ok { - return nil, fmt.Errorf("unrecognized apiVersion %v", s.Schema) + return nil, fmt.Errorf("unrecognized apiVersion %v", t.Schema) } - schema, exists := rc.descriptor.GetByType(typ) + s, exists := rc.descriptor.GetByType(typ) if !exists { return nil, fmt.Errorf("missing type %q", typ) } - list := crd.KnownTypes[schema.Type].Collection.DeepCopyObject().(crd.IstioObjectList) + list := crd.KnownTypes[s.Type].Collection.DeepCopyObject().(crd.IstioObjectList) errs := rc.dynamic.Get(). Namespace(namespace). - Resource(crd.ResourceName(schema.Plural)). + Resource(crd.ResourceName(s.Plural)). Do().Into(list) out := make([]model.Config, 0) for _, item := range list.GetItems() { - obj, err := crd.ConvertObject(schema, item, cl.domainSuffix) + obj, err := crd.ConvertObject(s, item, cl.domainSuffix) if err != nil { errs = multierror.Append(errs, err) } else { diff --git a/pilot/pkg/config/kube/crd/controller/controller.go b/pilot/pkg/config/kube/crd/controller/controller.go index 56972357167a..ced3d532774b 100644 --- a/pilot/pkg/config/kube/crd/controller/controller.go +++ b/pilot/pkg/config/kube/crd/controller/controller.go @@ -34,6 +34,7 @@ import ( "istio.io/istio/pilot/pkg/monitoring" "istio.io/istio/pilot/pkg/serviceregistry/kube" controller2 "istio.io/istio/pilot/pkg/serviceregistry/kube/controller" + "istio.io/istio/pkg/config/schema" ) // controller is a collection of synchronized resource watchers. @@ -89,14 +90,14 @@ func NewController(client *Client, options controller2.Options) model.ConfigStor } // add stores for CRD kinds - for _, schema := range client.ConfigDescriptor() { - out.addInformer(schema, options.WatchedNamespace, options.ResyncPeriod) + for _, s := range client.ConfigDescriptor() { + out.addInformer(s, options.WatchedNamespace, options.ResyncPeriod) } return out } -func (c *controller) addInformer(schema model.ProtoSchema, namespace string, resyncPeriod time.Duration) { +func (c *controller) addInformer(schema schema.Instance, namespace string, resyncPeriod time.Duration) { c.kinds[schema.Type] = c.createInformer(crd.KnownTypes[schema.Type].Object.DeepCopyObject(), schema.Type, resyncPeriod, func(opts meta_v1.ListOptions) (result runtime.Object, err error) { result = crd.KnownTypes[schema.Type].Collection.DeepCopyObject() @@ -186,16 +187,16 @@ func incrementEvent(kind, event string) { } func (c *controller) RegisterEventHandler(typ string, f func(model.Config, model.Event)) { - schema, exists := c.ConfigDescriptor().GetByType(typ) + s, exists := c.ConfigDescriptor().GetByType(typ) if !exists { return } c.kinds[typ].handler.Append(func(object interface{}, ev model.Event) error { item, ok := object.(crd.IstioObject) if ok { - config, err := crd.ConvertObject(schema, item, c.client.domainSuffix) + config, err := crd.ConvertObject(s, item, c.client.domainSuffix) if err != nil { - log.Warnf("error translating object for schema %#v : %v\n Object:\n%#v", schema, err, object) + log.Warnf("error translating object for schema %#v : %v\n Object:\n%#v", s, err, object) } else { f(*config, ev) } @@ -228,12 +229,12 @@ func (c *controller) Run(stop <-chan struct{}) { log.Info("controller terminated") } -func (c *controller) ConfigDescriptor() model.ConfigDescriptor { +func (c *controller) ConfigDescriptor() schema.Set { return c.client.ConfigDescriptor() } func (c *controller) Get(typ, name, namespace string) *model.Config { - schema, exists := c.client.ConfigDescriptor().GetByType(typ) + s, exists := c.client.ConfigDescriptor().GetByType(typ) if !exists { return nil } @@ -254,7 +255,7 @@ func (c *controller) Get(typ, name, namespace string) *model.Config { return nil } - config, err := crd.ConvertObject(schema, obj, c.client.domainSuffix) + config, err := crd.ConvertObject(s, obj, c.client.domainSuffix) if err != nil { return nil } @@ -275,7 +276,7 @@ func (c *controller) Delete(typ, name, namespace string) error { } func (c *controller) List(typ, namespace string) ([]model.Config, error) { - schema, ok := c.client.ConfigDescriptor().GetByType(typ) + s, ok := c.client.ConfigDescriptor().GetByType(typ) if !ok { return nil, fmt.Errorf("missing type %q", typ) } @@ -300,7 +301,7 @@ func (c *controller) List(typ, namespace string) ([]model.Config, error) { continue } - config, err := crd.ConvertObject(schema, item, c.client.domainSuffix) + config, err := crd.ConvertObject(s, item, c.client.domainSuffix) if err != nil { key := item.GetObjectMeta().Namespace + "/" + item.GetObjectMeta().Name log.Errorf("Failed to convert %s object, ignoring: %s %v %v", typ, key, err, item.GetSpec()) diff --git a/pilot/pkg/config/kube/crd/conversion.go b/pilot/pkg/config/kube/crd/conversion.go index 8571acce53f7..1b2b6d8bfb88 100644 --- a/pilot/pkg/config/kube/crd/conversion.go +++ b/pilot/pkg/config/kube/crd/conversion.go @@ -29,12 +29,14 @@ import ( "istio.io/istio/pilot/pkg/model" "istio.io/istio/pkg/config/constants" + "istio.io/istio/pkg/config/schema" + "istio.io/istio/pkg/config/schemas" "istio.io/istio/pkg/util/protomarshal" ) // ConvertObject converts an IstioObject k8s-style object to the // internal configuration model. -func ConvertObject(schema model.ProtoSchema, object IstioObject, domain string) (*model.Config, error) { +func ConvertObject(schema schema.Instance, object IstioObject, domain string) (*model.Config, error) { data, err := schema.FromJSONMap(object.GetSpec()) if err != nil { return nil, err @@ -60,7 +62,7 @@ func ConvertObject(schema model.ProtoSchema, object IstioObject, domain string) // ConvertObjectFromUnstructured converts an IstioObject k8s-style object to the // internal configuration model. -func ConvertObjectFromUnstructured(schema model.ProtoSchema, un *unstructured.Unstructured, domain string) (*model.Config, error) { +func ConvertObjectFromUnstructured(schema schema.Instance, un *unstructured.Unstructured, domain string) (*model.Config, error) { data, err := schema.FromJSONMap(un.Object["spec"]) if err != nil { return nil, err @@ -84,7 +86,7 @@ func ConvertObjectFromUnstructured(schema model.ProtoSchema, un *unstructured.Un } // ConvertConfig translates Istio config to k8s config JSON -func ConvertConfig(schema model.ProtoSchema, cfg model.Config) (IstioObject, error) { +func ConvertConfig(schema schema.Instance, cfg model.Config) (IstioObject, error) { spec, err := protomarshal.ToJSONMap(cfg.Spec) if err != nil { return nil, err @@ -113,7 +115,7 @@ func ResourceName(s string) string { } // ResourceGroup generates the k8s API group for each schema. -func ResourceGroup(schema *model.ProtoSchema) string { +func ResourceGroup(schema *schema.Instance) string { return schema.Group + constants.IstioAPIGroupDomain } @@ -183,20 +185,20 @@ func parseInputsImpl(inputs string, withValidate bool) ([]model.Config, []IstioK continue } - schema, exists := model.IstioConfigTypes.GetByType(CamelCaseToKebabCase(obj.Kind)) + s, exists := schemas.Istio.GetByType(CamelCaseToKebabCase(obj.Kind)) if !exists { log.Debugf("unrecognized type %v", obj.Kind) others = append(others, obj) continue } - cfg, err := ConvertObject(schema, &obj, "") + cfg, err := ConvertObject(s, &obj, "") if err != nil { return nil, nil, fmt.Errorf("cannot parse proto message: %v", err) } if withValidate { - if err := schema.Validate(cfg.Name, cfg.Namespace, cfg.Spec); err != nil { + if err := s.Validate(cfg.Name, cfg.Namespace, cfg.Spec); err != nil { return nil, nil, fmt.Errorf("configuration is invalid: %v", err) } } diff --git a/pilot/pkg/config/kube/crd/conversion_test.go b/pilot/pkg/config/kube/crd/conversion_test.go index 4af8d67af35a..6e2965bc52b2 100644 --- a/pilot/pkg/config/kube/crd/conversion_test.go +++ b/pilot/pkg/config/kube/crd/conversion_test.go @@ -20,6 +20,7 @@ import ( "istio.io/istio/pilot/pkg/model" "istio.io/istio/pilot/test/mock" + "istio.io/istio/pkg/config/schemas" ) var ( @@ -44,15 +45,15 @@ func TestCamelKebab(t *testing.T) { } func TestConvert(t *testing.T) { - if _, err := ConvertConfig(model.VirtualService, model.Config{}); err == nil { + if _, err := ConvertConfig(schemas.VirtualService, model.Config{}); err == nil { t.Errorf("expected error for converting empty config") } - if _, err := ConvertObject(model.VirtualService, &IstioKind{Spec: map[string]interface{}{"x": 1}}, "local"); err != nil { + if _, err := ConvertObject(schemas.VirtualService, &IstioKind{Spec: map[string]interface{}{"x": 1}}, "local"); err != nil { t.Errorf("error for converting object: %s", err) } config := model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.VirtualService.Type, + Type: schemas.VirtualService.Type, Group: "networking.istio.io", Version: "v1alpha3", Name: "test", @@ -65,11 +66,11 @@ func TestConvert(t *testing.T) { Spec: mock.ExampleVirtualService, } - obj, err := ConvertConfig(model.VirtualService, config) + obj, err := ConvertConfig(schemas.VirtualService, config) if err != nil { t.Errorf("ConvertConfig() => unexpected error %v", err) } - got, err := ConvertObject(model.VirtualService, obj, "cluster") + got, err := ConvertObject(schemas.VirtualService, obj, "cluster") if err != nil { t.Errorf("ConvertObject() => unexpected error %v", err) } diff --git a/pilot/pkg/config/kube/crd/types.go b/pilot/pkg/config/kube/crd/types.go index aaace51d5760..1888f1284d53 100644 --- a/pilot/pkg/config/kube/crd/types.go +++ b/pilot/pkg/config/kube/crd/types.go @@ -19,188 +19,189 @@ package crd // This file contains Go definitions for Custom Resource Definition kinds // to adhere to the idiomatic use of k8s API machinery. // These definitions are synthesized from Istio configuration type descriptors -// as declared in the Pilot config model. +// as declared in the Istio config model. import ( meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pkg/config/schema" + "istio.io/istio/pkg/config/schemas" ) type SchemaType struct { - Schema model.ProtoSchema + Schema schema.Instance Object IstioObject Collection IstioObjectList } var KnownTypes = map[string]SchemaType{ - model.MockConfig.Type: { - Schema: model.MockConfig, + schemas.MockConfig.Type: { + Schema: schemas.MockConfig, Object: &MockConfig{ TypeMeta: meta_v1.TypeMeta{ Kind: "MockConfig", - APIVersion: APIVersion(&model.MockConfig), + APIVersion: APIVersion(&schemas.MockConfig), }, }, Collection: &MockConfigList{}, }, - model.VirtualService.Type: { - Schema: model.VirtualService, + schemas.VirtualService.Type: { + Schema: schemas.VirtualService, Object: &VirtualService{ TypeMeta: meta_v1.TypeMeta{ Kind: "VirtualService", - APIVersion: APIVersion(&model.VirtualService), + APIVersion: APIVersion(&schemas.VirtualService), }, }, Collection: &VirtualServiceList{}, }, - model.Gateway.Type: { - Schema: model.Gateway, + schemas.Gateway.Type: { + Schema: schemas.Gateway, Object: &Gateway{ TypeMeta: meta_v1.TypeMeta{ Kind: "Gateway", - APIVersion: APIVersion(&model.Gateway), + APIVersion: APIVersion(&schemas.Gateway), }, }, Collection: &GatewayList{}, }, - model.ServiceEntry.Type: { - Schema: model.ServiceEntry, + schemas.ServiceEntry.Type: { + Schema: schemas.ServiceEntry, Object: &ServiceEntry{ TypeMeta: meta_v1.TypeMeta{ Kind: "ServiceEntry", - APIVersion: APIVersion(&model.ServiceEntry), + APIVersion: APIVersion(&schemas.ServiceEntry), }, }, Collection: &ServiceEntryList{}, }, - model.DestinationRule.Type: { - Schema: model.DestinationRule, + schemas.DestinationRule.Type: { + Schema: schemas.DestinationRule, Object: &DestinationRule{ TypeMeta: meta_v1.TypeMeta{ Kind: "DestinationRule", - APIVersion: APIVersion(&model.DestinationRule), + APIVersion: APIVersion(&schemas.DestinationRule), }, }, Collection: &DestinationRuleList{}, }, - model.EnvoyFilter.Type: { - Schema: model.EnvoyFilter, + schemas.EnvoyFilter.Type: { + Schema: schemas.EnvoyFilter, Object: &EnvoyFilter{ TypeMeta: meta_v1.TypeMeta{ Kind: "EnvoyFilter", - APIVersion: APIVersion(&model.EnvoyFilter), + APIVersion: APIVersion(&schemas.EnvoyFilter), }, }, Collection: &EnvoyFilterList{}, }, - model.Sidecar.Type: { - Schema: model.Sidecar, + schemas.Sidecar.Type: { + Schema: schemas.Sidecar, Object: &Sidecar{ TypeMeta: meta_v1.TypeMeta{ Kind: "Sidecar", - APIVersion: APIVersion(&model.Sidecar), + APIVersion: APIVersion(&schemas.Sidecar), }, }, Collection: &SidecarList{}, }, - model.HTTPAPISpec.Type: { - Schema: model.HTTPAPISpec, + schemas.HTTPAPISpec.Type: { + Schema: schemas.HTTPAPISpec, Object: &HTTPAPISpec{ TypeMeta: meta_v1.TypeMeta{ Kind: "HTTPAPISpec", - APIVersion: APIVersion(&model.HTTPAPISpec), + APIVersion: APIVersion(&schemas.HTTPAPISpec), }, }, Collection: &HTTPAPISpecList{}, }, - model.HTTPAPISpecBinding.Type: { - Schema: model.HTTPAPISpecBinding, + schemas.HTTPAPISpecBinding.Type: { + Schema: schemas.HTTPAPISpecBinding, Object: &HTTPAPISpecBinding{ TypeMeta: meta_v1.TypeMeta{ Kind: "HTTPAPISpecBinding", - APIVersion: APIVersion(&model.HTTPAPISpecBinding), + APIVersion: APIVersion(&schemas.HTTPAPISpecBinding), }, }, Collection: &HTTPAPISpecBindingList{}, }, - model.QuotaSpec.Type: { - Schema: model.QuotaSpec, + schemas.QuotaSpec.Type: { + Schema: schemas.QuotaSpec, Object: &QuotaSpec{ TypeMeta: meta_v1.TypeMeta{ Kind: "QuotaSpec", - APIVersion: APIVersion(&model.QuotaSpec), + APIVersion: APIVersion(&schemas.QuotaSpec), }, }, Collection: &QuotaSpecList{}, }, - model.QuotaSpecBinding.Type: { - Schema: model.QuotaSpecBinding, + schemas.QuotaSpecBinding.Type: { + Schema: schemas.QuotaSpecBinding, Object: &QuotaSpecBinding{ TypeMeta: meta_v1.TypeMeta{ Kind: "QuotaSpecBinding", - APIVersion: APIVersion(&model.QuotaSpecBinding), + APIVersion: APIVersion(&schemas.QuotaSpecBinding), }, }, Collection: &QuotaSpecBindingList{}, }, - model.AuthenticationPolicy.Type: { - Schema: model.AuthenticationPolicy, + schemas.AuthenticationPolicy.Type: { + Schema: schemas.AuthenticationPolicy, Object: &Policy{ TypeMeta: meta_v1.TypeMeta{ Kind: "Policy", - APIVersion: APIVersion(&model.AuthenticationPolicy), + APIVersion: APIVersion(&schemas.AuthenticationPolicy), }, }, Collection: &PolicyList{}, }, - model.AuthenticationMeshPolicy.Type: { - Schema: model.AuthenticationMeshPolicy, + schemas.AuthenticationMeshPolicy.Type: { + Schema: schemas.AuthenticationMeshPolicy, Object: &MeshPolicy{ TypeMeta: meta_v1.TypeMeta{ Kind: "MeshPolicy", - APIVersion: APIVersion(&model.AuthenticationMeshPolicy), + APIVersion: APIVersion(&schemas.AuthenticationMeshPolicy), }, }, Collection: &MeshPolicyList{}, }, - model.ServiceRole.Type: { - Schema: model.ServiceRole, + schemas.ServiceRole.Type: { + Schema: schemas.ServiceRole, Object: &ServiceRole{ TypeMeta: meta_v1.TypeMeta{ Kind: "ServiceRole", - APIVersion: APIVersion(&model.ServiceRole), + APIVersion: APIVersion(&schemas.ServiceRole), }, }, Collection: &ServiceRoleList{}, }, - model.ServiceRoleBinding.Type: { - Schema: model.ServiceRoleBinding, + schemas.ServiceRoleBinding.Type: { + Schema: schemas.ServiceRoleBinding, Object: &ServiceRoleBinding{ TypeMeta: meta_v1.TypeMeta{ Kind: "ServiceRoleBinding", - APIVersion: APIVersion(&model.ServiceRoleBinding), + APIVersion: APIVersion(&schemas.ServiceRoleBinding), }, }, Collection: &ServiceRoleBindingList{}, }, - model.RbacConfig.Type: { - Schema: model.RbacConfig, + schemas.RbacConfig.Type: { + Schema: schemas.RbacConfig, Object: &RbacConfig{ TypeMeta: meta_v1.TypeMeta{ Kind: "RbacConfig", - APIVersion: APIVersion(&model.RbacConfig), + APIVersion: APIVersion(&schemas.RbacConfig), }, }, Collection: &RbacConfigList{}, }, - model.ClusterRbacConfig.Type: { - Schema: model.ClusterRbacConfig, + schemas.ClusterRbacConfig.Type: { + Schema: schemas.ClusterRbacConfig, Object: &ClusterRbacConfig{ TypeMeta: meta_v1.TypeMeta{ Kind: "ClusterRbacConfig", - APIVersion: APIVersion(&model.ClusterRbacConfig), + APIVersion: APIVersion(&schemas.ClusterRbacConfig), }, }, Collection: &ClusterRbacConfigList{}, diff --git a/pilot/pkg/config/kube/ingress/controller.go b/pilot/pkg/config/kube/ingress/controller.go index 2f075e585a47..38dcad0b5b4e 100644 --- a/pilot/pkg/config/kube/ingress/controller.go +++ b/pilot/pkg/config/kube/ingress/controller.go @@ -34,6 +34,8 @@ import ( "istio.io/istio/pilot/pkg/serviceregistry/kube" kubecontroller "istio.io/istio/pilot/pkg/serviceregistry/kube/controller" "istio.io/istio/pkg/config/constants" + "istio.io/istio/pkg/config/schema" + "istio.io/istio/pkg/config/schemas" ) // In 1.0, the Gateway is defined in the namespace where the actual controller runs, and needs to be managed by @@ -148,10 +150,10 @@ func (c *controller) RegisterEventHandler(typ string, f func(model.Config, model // TODO: This works well for Add and Delete events, but not so for Update: // An updated ingress may also trigger an Add or Delete for one of its constituent sub-rules. switch typ { - case model.Gateway.Type: + case schemas.Gateway.Type: //config, _ := ConvertIngressV1alpha3(*ingress, c.domainSuffix) //f(config, event) - case model.VirtualService.Type: + case schemas.VirtualService.Type: f(model.Config{}, event) } @@ -172,14 +174,14 @@ func (c *controller) Run(stop <-chan struct{}) { <-stop } -func (c *controller) ConfigDescriptor() model.ConfigDescriptor { +func (c *controller) ConfigDescriptor() schema.Set { //TODO: are these two config descriptors right? - return model.ConfigDescriptor{model.Gateway, model.VirtualService} + return schema.Set{schemas.Gateway, schemas.VirtualService} } //TODO: we don't return out of this function now func (c *controller) Get(typ, name, namespace string) *model.Config { - if typ != model.Gateway.Type && typ != model.VirtualService.Type { + if typ != schemas.Gateway.Type && typ != schemas.VirtualService.Type { return nil } @@ -203,7 +205,7 @@ func (c *controller) Get(typ, name, namespace string) *model.Config { } func (c *controller) List(typ, namespace string) ([]model.Config, error) { - if typ != model.Gateway.Type && typ != model.VirtualService.Type { + if typ != schemas.Gateway.Type && typ != schemas.VirtualService.Type { return nil, errUnsupportedOp } @@ -222,15 +224,15 @@ func (c *controller) List(typ, namespace string) ([]model.Config, error) { } switch typ { - case model.VirtualService.Type: + case schemas.VirtualService.Type: ConvertIngressVirtualService(*ingress, c.domainSuffix, ingressByHost) - case model.Gateway.Type: + case schemas.Gateway.Type: gateways := ConvertIngressV1alpha3(*ingress, c.domainSuffix) out = append(out, gateways) } } - if typ == model.VirtualService.Type { + if typ == schemas.VirtualService.Type { for _, obj := range ingressByHost { out = append(out, *obj) } diff --git a/pilot/pkg/config/kube/ingress/conversion.go b/pilot/pkg/config/kube/ingress/conversion.go index e321c2b3a3ff..b44c9c82b867 100644 --- a/pilot/pkg/config/kube/ingress/conversion.go +++ b/pilot/pkg/config/kube/ingress/conversion.go @@ -33,6 +33,7 @@ import ( "istio.io/istio/pkg/config/constants" "istio.io/istio/pkg/config/labels" "istio.io/istio/pkg/config/protocol" + "istio.io/istio/pkg/config/schemas" ) // EncodeIngressRuleName encodes an ingress rule name for a given ingress resource name, @@ -112,9 +113,9 @@ func ConvertIngressV1alpha3(ingress v1beta1.Ingress, domainSuffix string) model. gatewayConfig := model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.Gateway.Type, - Group: model.Gateway.Group, - Version: model.Gateway.Version, + Type: schemas.Gateway.Type, + Group: schemas.Gateway.Group, + Version: schemas.Gateway.Version, Name: ingress.Name + "-" + constants.IstioIngressGatewayName, Namespace: ingressNamespace, Domain: domainSuffix, @@ -174,9 +175,9 @@ func ConvertIngressVirtualService(ingress v1beta1.Ingress, domainSuffix string, virtualServiceConfig := model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.VirtualService.Type, - Group: model.VirtualService.Group, - Version: model.VirtualService.Version, + Type: schemas.VirtualService.Type, + Group: schemas.VirtualService.Group, + Version: schemas.VirtualService.Version, Name: namePrefix + "-" + ingress.Name + "-" + constants.IstioIngressGatewayName, Namespace: ingress.Namespace, Domain: domainSuffix, diff --git a/pilot/pkg/config/memory/config.go b/pilot/pkg/config/memory/config.go index e1077f40e4e3..026a96eea32b 100644 --- a/pilot/pkg/config/memory/config.go +++ b/pilot/pkg/config/memory/config.go @@ -21,6 +21,7 @@ import ( "time" "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pkg/config/schema" ) var ( @@ -29,7 +30,7 @@ var ( ) // Make creates an in-memory config store from a config descriptor -func Make(descriptor model.ConfigDescriptor) model.ConfigStore { +func Make(descriptor schema.Set) model.ConfigStore { out := store{ descriptor: descriptor, data: make(map[string]map[string]*sync.Map), @@ -41,11 +42,11 @@ func Make(descriptor model.ConfigDescriptor) model.ConfigStore { } type store struct { - descriptor model.ConfigDescriptor + descriptor schema.Set data map[string]map[string]*sync.Map } -func (cr *store) ConfigDescriptor() model.ConfigDescriptor { +func (cr *store) ConfigDescriptor() schema.Set { return cr.descriptor } @@ -116,11 +117,11 @@ func (cr *store) Delete(typ, name, namespace string) error { func (cr *store) Create(config model.Config) (string, error) { typ := config.Type - schema, ok := cr.descriptor.GetByType(typ) + s, ok := cr.descriptor.GetByType(typ) if !ok { return "", errors.New("unknown type") } - if err := schema.Validate(config.Name, config.Namespace, config.Spec); err != nil { + if err := s.Validate(config.Name, config.Namespace, config.Spec); err != nil { return "", err } ns, exists := cr.data[typ][config.Namespace] @@ -148,11 +149,11 @@ func (cr *store) Create(config model.Config) (string, error) { func (cr *store) Update(config model.Config) (string, error) { typ := config.Type - schema, ok := cr.descriptor.GetByType(typ) + s, ok := cr.descriptor.GetByType(typ) if !ok { return "", errors.New("unknown type") } - if err := schema.Validate(config.Name, config.Namespace, config.Spec); err != nil { + if err := s.Validate(config.Name, config.Namespace, config.Spec); err != nil { return "", err } diff --git a/pilot/pkg/config/memory/config_test.go b/pilot/pkg/config/memory/config_test.go index a759620ace01..7c24d954f654 100644 --- a/pilot/pkg/config/memory/config_test.go +++ b/pilot/pkg/config/memory/config_test.go @@ -18,8 +18,8 @@ import ( "testing" "istio.io/istio/pilot/pkg/config/memory" - "istio.io/istio/pilot/pkg/model" "istio.io/istio/pilot/test/mock" + "istio.io/istio/pkg/config/schemas" ) func TestStoreInvariant(t *testing.T) { @@ -28,6 +28,6 @@ func TestStoreInvariant(t *testing.T) { } func TestIstioConfig(t *testing.T) { - store := memory.Make(model.IstioConfigTypes) + store := memory.Make(schemas.Istio) mock.CheckIstioConfigTypes(store, "some-namespace", t) } diff --git a/pilot/pkg/config/memory/controller.go b/pilot/pkg/config/memory/controller.go index 0d87544fbede..c5a7d4ef7788 100644 --- a/pilot/pkg/config/memory/controller.go +++ b/pilot/pkg/config/memory/controller.go @@ -18,6 +18,7 @@ import ( "errors" "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pkg/config/schema" ) type controller struct { @@ -49,7 +50,7 @@ func (c *controller) Run(stop <-chan struct{}) { c.monitor.Run(stop) } -func (c *controller) ConfigDescriptor() model.ConfigDescriptor { +func (c *controller) ConfigDescriptor() schema.Set { return c.configStore.ConfigDescriptor() } diff --git a/pilot/pkg/config/memory/monitor_test.go b/pilot/pkg/config/memory/monitor_test.go index f8178d92d6ee..9ef9f819d48a 100644 --- a/pilot/pkg/config/memory/monitor_test.go +++ b/pilot/pkg/config/memory/monitor_test.go @@ -21,6 +21,7 @@ import ( "istio.io/istio/pilot/pkg/config/memory" "istio.io/istio/pilot/pkg/model" "istio.io/istio/pilot/test/mock" + "istio.io/istio/pkg/config/schemas" ) func TestEventConsistency(t *testing.T) { @@ -34,7 +35,7 @@ func TestEventConsistency(t *testing.T) { lock := sync.Mutex{} - controller.RegisterEventHandler(model.MockConfig.Type, func(config model.Config, event model.Event) { + controller.RegisterEventHandler(schemas.MockConfig.Type, func(config model.Config, event model.Event) { lock.Lock() tc := testConfig @@ -77,7 +78,7 @@ func TestEventConsistency(t *testing.T) { // Test Delete Event testEvent = model.EventDelete - if err := controller.Delete(model.MockConfig.Type, testConfig.Name, TestNamespace); err != nil { + if err := controller.Delete(schemas.MockConfig.Type, testConfig.Name, TestNamespace); err != nil { t.Error(err) return } diff --git a/pilot/pkg/config/monitor/file_snapshot.go b/pilot/pkg/config/monitor/file_snapshot.go index 3575037e0ad7..02ed390b99f9 100644 --- a/pilot/pkg/config/monitor/file_snapshot.go +++ b/pilot/pkg/config/monitor/file_snapshot.go @@ -22,6 +22,9 @@ import ( "istio.io/istio/pilot/pkg/config/kube/crd" "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pkg/config/schema" + "istio.io/istio/pkg/config/schemas" + "istio.io/pkg/log" ) @@ -40,8 +43,8 @@ type FileSnapshot struct { } // NewFileSnapshot returns a snapshotter. -// If no types are provided in the descriptor, all IstioConfigTypes will be allowed. -func NewFileSnapshot(root string, descriptor model.ConfigDescriptor) *FileSnapshot { +// If no types are provided in the descriptor, all Istio types will be allowed. +func NewFileSnapshot(root string, descriptor schema.Set) *FileSnapshot { snapshot := &FileSnapshot{ root: root, configTypeFilter: make(map[string]bool), @@ -49,12 +52,12 @@ func NewFileSnapshot(root string, descriptor model.ConfigDescriptor) *FileSnapsh types := descriptor.Types() if len(types) == 0 { - types = model.IstioConfigTypes.Types() + types = schemas.Istio.Types() } for _, k := range types { - if schema, ok := model.IstioConfigTypes.GetByType(k); ok { - snapshot.configTypeFilter[schema.Type] = true + if s, ok := schemas.Istio.GetByType(k); ok { + snapshot.configTypeFilter[s.Type] = true } } diff --git a/pilot/pkg/config/monitor/file_snapshot_test.go b/pilot/pkg/config/monitor/file_snapshot_test.go index c405548525f6..41a55e6b39ab 100644 --- a/pilot/pkg/config/monitor/file_snapshot_test.go +++ b/pilot/pkg/config/monitor/file_snapshot_test.go @@ -23,8 +23,10 @@ import ( "github.com/onsi/gomega" networking "istio.io/api/networking/v1alpha3" + "istio.io/istio/pilot/pkg/config/monitor" - "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pkg/config/schema" + "istio.io/istio/pkg/config/schemas" ) var gatewayYAML = ` @@ -92,7 +94,7 @@ func TestFileSnapshotWithFilter(t *testing.T) { ts.testSetup(t) defer ts.testTeardown(t) - fileWatcher := monitor.NewFileSnapshot(ts.rootPath, model.ConfigDescriptor{model.VirtualService}) + fileWatcher := monitor.NewFileSnapshot(ts.rootPath, schema.Set{schemas.VirtualService}) configs, err := fileWatcher.ReadConfigFiles() g.Expect(err).NotTo(gomega.HaveOccurred()) g.Expect(configs).To(gomega.HaveLen(1)) diff --git a/pilot/pkg/config/monitor/monitor_test.go b/pilot/pkg/config/monitor/monitor_test.go index 9b05ac85c790..3ef9e21e9045 100644 --- a/pilot/pkg/config/monitor/monitor_test.go +++ b/pilot/pkg/config/monitor/monitor_test.go @@ -22,9 +22,12 @@ import ( "github.com/onsi/gomega" networking "istio.io/api/networking/v1alpha3" + "istio.io/istio/pilot/pkg/config/memory" "istio.io/istio/pilot/pkg/config/monitor" "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pkg/config/schema" + "istio.io/istio/pkg/config/schemas" ) const checkInterval = 100 * time.Millisecond @@ -74,7 +77,7 @@ var updateConfigSet = []*model.Config{ func TestMonitorForChange(t *testing.T) { g := gomega.NewGomegaWithT(t) - configDescriptor := model.ConfigDescriptor{model.Gateway} + configDescriptor := schema.Set{schemas.Gateway} store := memory.Make(configDescriptor) @@ -139,7 +142,7 @@ func TestMonitorForChange(t *testing.T) { func TestMonitorForError(t *testing.T) { g := gomega.NewGomegaWithT(t) - configDescriptor := model.ConfigDescriptor{model.Gateway} + configDescriptor := schema.Set{schemas.Gateway} store := memory.Make(configDescriptor) diff --git a/pilot/pkg/model/authorization.go b/pilot/pkg/model/authorization.go index 4010ca86e695..edc254f7907a 100644 --- a/pilot/pkg/model/authorization.go +++ b/pilot/pkg/model/authorization.go @@ -17,6 +17,8 @@ package model import ( rbacproto "istio.io/api/rbac/v1alpha1" istiolog "istio.io/pkg/log" + + "istio.io/istio/pkg/config/schemas" ) var ( @@ -169,7 +171,7 @@ func NewAuthzPolicies(env *Environment) (*AuthorizationPolicies, error) { IsRbacV2: false, } - roles, err := env.List(ServiceRole.Type, NamespaceAll) + roles, err := env.List(schemas.ServiceRole.Type, NamespaceAll) if err != nil { return nil, err } @@ -177,7 +179,7 @@ func NewAuthzPolicies(env *Environment) (*AuthorizationPolicies, error) { policy.AddConfig(&role) } - bindings, err := env.List(ServiceRoleBinding.Type, NamespaceAll) + bindings, err := env.List(schemas.ServiceRoleBinding.Type, NamespaceAll) if err != nil { return nil, err } diff --git a/pilot/pkg/model/authorization_test.go b/pilot/pkg/model/authorization_test.go index c9f19dae2e64..256b82c1a33d 100644 --- a/pilot/pkg/model/authorization_test.go +++ b/pilot/pkg/model/authorization_test.go @@ -21,21 +21,23 @@ import ( "github.com/gogo/protobuf/proto" rbacproto "istio.io/api/rbac/v1alpha1" + "istio.io/istio/pilot/pkg/config/memory" "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pkg/config/schemas" ) func TestAddConfig(t *testing.T) { roleCfg := model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.ServiceRole.Type, Name: "test-role-1", Namespace: model.NamespaceAll}, + Type: schemas.ServiceRole.Type, Name: "test-role-1", Namespace: model.NamespaceAll}, Spec: &rbacproto.ServiceRole{ Rules: []*rbacproto.AccessRule{{Services: []string{"test-svc-1"}}}, }, } bindingCfg := model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.ServiceRoleBinding.Type, Name: "test-binding-1", Namespace: model.NamespaceAll}, + Type: schemas.ServiceRoleBinding.Type, Name: "test-binding-1", Namespace: model.NamespaceAll}, Spec: &rbacproto.ServiceRoleBinding{ Subjects: []*rbacproto.Subject{{User: "test-user-1"}}, RoleRef: &rbacproto.RoleRef{Kind: "ServiceRole", Name: "test-role-1"}, @@ -44,7 +46,7 @@ func TestAddConfig(t *testing.T) { invalidateBindingCfg := model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.ServiceRoleBinding.Type, Name: "test-binding-1", Namespace: model.NamespaceAll}, + Type: schemas.ServiceRoleBinding.Type, Name: "test-binding-1", Namespace: model.NamespaceAll}, Spec: &rbacproto.ServiceRoleBinding{ Subjects: []*rbacproto.Subject{{User: "test-user-1"}}, RoleRef: &rbacproto.RoleRef{Kind: "ServiceRole", Name: ""}, @@ -123,12 +125,12 @@ func TestAddConfig(t *testing.T) { func TestRolesForNamespace(t *testing.T) { roleCfg := model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.ServiceRole.Type, Name: "test-role-1", Namespace: model.NamespaceAll}, + Type: schemas.ServiceRole.Type, Name: "test-role-1", Namespace: model.NamespaceAll}, Spec: &rbacproto.ServiceRole{}, } bindingCfg := model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.ServiceRoleBinding.Type, Name: "test-binding-1", Namespace: model.NamespaceAll}, + Type: schemas.ServiceRoleBinding.Type, Name: "test-binding-1", Namespace: model.NamespaceAll}, Spec: &rbacproto.ServiceRoleBinding{ Subjects: []*rbacproto.Subject{{User: "test-user-1"}}, RoleRef: &rbacproto.RoleRef{Kind: "ServiceRole", Name: "test-role-1"}, @@ -203,7 +205,7 @@ func TestRolesForNamespace(t *testing.T) { func TestRoleToBindingsForNamespace(t *testing.T) { bindingCfg := model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.ServiceRoleBinding.Type, Name: "test-binding-1", Namespace: model.NamespaceAll}, + Type: schemas.ServiceRoleBinding.Type, Name: "test-binding-1", Namespace: model.NamespaceAll}, Spec: &rbacproto.ServiceRoleBinding{ Subjects: []*rbacproto.Subject{{User: "test-user-1"}}, RoleRef: &rbacproto.RoleRef{Kind: "ServiceRole", Name: "test-role-1"}, @@ -310,12 +312,12 @@ func TestNewAuthzPolicies(t *testing.T) { } func storeWithConfig(clusterRbacConfig, rbacConfig proto.Message) model.IstioConfigStore { - store := memory.Make(model.IstioConfigTypes) + store := memory.Make(schemas.Istio) if clusterRbacConfig != nil { config := model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.ClusterRbacConfig.Type, + Type: schemas.ClusterRbacConfig.Type, Name: "default", Namespace: "default", }, @@ -326,7 +328,7 @@ func storeWithConfig(clusterRbacConfig, rbacConfig proto.Message) model.IstioCon if rbacConfig != nil { config := model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.RbacConfig.Type, + Type: schemas.RbacConfig.Type, Name: "default", Namespace: "default", }, diff --git a/pilot/pkg/model/config.go b/pilot/pkg/model/config.go index ee00248af4d9..4ad40cc5a125 100644 --- a/pilot/pkg/model/config.go +++ b/pilot/pkg/model/config.go @@ -15,7 +15,6 @@ package model import ( - "errors" "fmt" "sort" "strings" @@ -29,12 +28,11 @@ import ( mccpb "istio.io/api/mixer/v1/config/client" networking "istio.io/api/networking/v1alpha3" - "istio.io/istio/galley/pkg/metadata" "istio.io/istio/pkg/config/constants" "istio.io/istio/pkg/config/host" "istio.io/istio/pkg/config/labels" - "istio.io/istio/pkg/config/validation" - testConfig "istio.io/istio/pkg/test/config" + "istio.io/istio/pkg/config/schema" + "istio.io/istio/pkg/config/schemas" ) // ConfigMeta is metadata attached to each configuration unit. @@ -129,7 +127,7 @@ type ConfigStore interface { // ConfigDescriptor exposes the configuration type schema known by the config store. // The type schema defines the bidrectional mapping between configuration // types and the protobuf encoding schema. - ConfigDescriptor() ConfigDescriptor + ConfigDescriptor() schema.Set // Get retrieves a configuration element by a type and a key Get(typ, name, namespace string) *Config @@ -191,62 +189,6 @@ type ConfigStoreCache interface { HasSynced() bool } -// ConfigDescriptor defines the bijection between the short type name and its -// fully qualified protobuf message name -type ConfigDescriptor []ProtoSchema - -// ProtoSchema provides description of the configuration schema and its key function -// nolint: maligned -type ProtoSchema struct { - // ClusterScoped is true for resource in cluster-level. - ClusterScoped bool - - // Name of the (go) object define the schema. Leave blank to infer from the 'Type' below. - // This field is used to generate Kube CRD types map (pilot/pkg/config/kube/crd/types.go). - SchemaObjectName string - - // Type is the config proto type. - Type string - - // Plural is the type in plural. - Plural string - - // Group is the config proto group. - Group string - - // Version is the config proto version. - Version string - - // MessageName refers to the protobuf message type name corresponding to the type - MessageName string - - // Validate configuration as a protobuf message assuming the object is an - // instance of the expected message type - Validate validation.ValidateFunc - - // MCP collection for this configuration resource schema - Collection string -} - -// Types lists all known types in the config schema -func (descriptor ConfigDescriptor) Types() []string { - types := make([]string, 0, len(descriptor)) - for _, t := range descriptor { - types = append(types, t.Type) - } - return types -} - -// GetByType finds a schema by type if it is available -func (descriptor ConfigDescriptor) GetByType(name string) (ProtoSchema, bool) { - for _, schema := range descriptor { - if schema.Type == name { - return schema, true - } - } - return ProtoSchema{}, false -} - // IstioConfigStore is a specialized interface to access config store using // Istio configuration types // nolint @@ -293,9 +235,6 @@ type IstioConfigStore interface { } const ( - // Default API version of an Istio config proto message. - istioAPIVersion = "v1alpha2" - // NamespaceAll is a designated symbol for listing across all namespaces NamespaceAll = "" ) @@ -312,226 +251,6 @@ const ( an object is first read. */ -var ( - // MockConfig is used purely for testing - MockConfig = ProtoSchema{ - Type: "mock-config", - Plural: "mock-configs", - Group: "test", - Version: "v1", - MessageName: "test.MockConfig", - Validate: func(name, namespace string, config proto.Message) error { - if config.(*testConfig.MockConfig).Key == "" { - return errors.New("empty key") - } - return nil - }, - } - - // VirtualService describes v1alpha3 route rules - VirtualService = ProtoSchema{ - Type: "virtual-service", - Plural: "virtual-services", - Group: "networking", - Version: "v1alpha3", - MessageName: "istio.networking.v1alpha3.VirtualService", - Validate: validation.ValidateVirtualService, - Collection: metadata.IstioNetworkingV1alpha3Virtualservices.Collection.String(), - } - - // Gateway describes a gateway (how a proxy is exposed on the network) - Gateway = ProtoSchema{ - Type: "gateway", - Plural: "gateways", - Group: "networking", - Version: "v1alpha3", - MessageName: "istio.networking.v1alpha3.Gateway", - Validate: validation.ValidateGateway, - Collection: metadata.IstioNetworkingV1alpha3Gateways.Collection.String(), - } - - // ServiceEntry describes service entries - ServiceEntry = ProtoSchema{ - Type: "service-entry", - Plural: "service-entries", - Group: "networking", - Version: "v1alpha3", - MessageName: "istio.networking.v1alpha3.ServiceEntry", - Validate: validation.ValidateServiceEntry, - Collection: metadata.IstioNetworkingV1alpha3Serviceentries.Collection.String(), - } - - // DestinationRule describes destination rules - DestinationRule = ProtoSchema{ - Type: "destination-rule", - Plural: "destination-rules", - Group: "networking", - Version: "v1alpha3", - MessageName: "istio.networking.v1alpha3.DestinationRule", - Validate: validation.ValidateDestinationRule, - Collection: metadata.IstioNetworkingV1alpha3Destinationrules.Collection.String(), - } - - // EnvoyFilter describes additional envoy filters to be inserted by Pilot - EnvoyFilter = ProtoSchema{ - Type: "envoy-filter", - Plural: "envoy-filters", - Group: "networking", - Version: "v1alpha3", - MessageName: "istio.networking.v1alpha3.EnvoyFilter", - Validate: validation.ValidateEnvoyFilter, - Collection: metadata.IstioNetworkingV1alpha3Envoyfilters.Collection.String(), - } - - // Sidecar describes the listeners associated with sidecars in a namespace - Sidecar = ProtoSchema{ - Type: "sidecar", - Plural: "sidecars", - Group: "networking", - Version: "v1alpha3", - MessageName: "istio.networking.v1alpha3.Sidecar", - Validate: validation.ValidateSidecar, - Collection: metadata.IstioNetworkingV1alpha3Sidecars.Collection.String(), - } - - // HTTPAPISpec describes an HTTP API specification. - HTTPAPISpec = ProtoSchema{ - Type: "http-api-spec", - Plural: "http-api-specs", - Group: "config", - Version: istioAPIVersion, - MessageName: "istio.mixer.v1.config.client.HTTPAPISpec", - Validate: validation.ValidateHTTPAPISpec, - Collection: metadata.IstioConfigV1alpha2Httpapispecs.Collection.String(), - } - - // HTTPAPISpecBinding describes an HTTP API specification binding. - HTTPAPISpecBinding = ProtoSchema{ - Type: "http-api-spec-binding", - Plural: "http-api-spec-bindings", - Group: "config", - Version: istioAPIVersion, - MessageName: "istio.mixer.v1.config.client.HTTPAPISpecBinding", - Validate: validation.ValidateHTTPAPISpecBinding, - Collection: metadata.IstioConfigV1alpha2Httpapispecbindings.Collection.String(), - } - - // QuotaSpec describes an Quota specification. - QuotaSpec = ProtoSchema{ - Type: "quota-spec", - Plural: "quota-specs", - Group: "config", - Version: istioAPIVersion, - MessageName: "istio.mixer.v1.config.client.QuotaSpec", - Validate: validation.ValidateQuotaSpec, - Collection: metadata.IstioMixerV1ConfigClientQuotaspecs.Collection.String(), - } - - // QuotaSpecBinding describes an Quota specification binding. - QuotaSpecBinding = ProtoSchema{ - Type: "quota-spec-binding", - Plural: "quota-spec-bindings", - Group: "config", - Version: istioAPIVersion, - MessageName: "istio.mixer.v1.config.client.QuotaSpecBinding", - Validate: validation.ValidateQuotaSpecBinding, - Collection: metadata.IstioMixerV1ConfigClientQuotaspecbindings.Collection.String(), - } - - // AuthenticationPolicy describes an authentication policy. - AuthenticationPolicy = ProtoSchema{ - SchemaObjectName: "AuthenticationPolicy", - Type: "policy", - Plural: "policies", - Group: "authentication", - Version: "v1alpha1", - MessageName: "istio.authentication.v1alpha1.Policy", - Validate: validation.ValidateAuthenticationPolicy, - Collection: metadata.IstioAuthenticationV1alpha1Policies.Collection.String(), - } - - // AuthenticationMeshPolicy describes an authentication policy at mesh level. - AuthenticationMeshPolicy = ProtoSchema{ - ClusterScoped: true, - SchemaObjectName: "AuthenticationMeshPolicy", - Type: "mesh-policy", - Plural: "mesh-policies", - Group: "authentication", - Version: "v1alpha1", - MessageName: "istio.authentication.v1alpha1.Policy", - Validate: validation.ValidateAuthenticationPolicy, - Collection: metadata.IstioAuthenticationV1alpha1Meshpolicies.Collection.String(), - } - - // ServiceRole describes an RBAC service role. - ServiceRole = ProtoSchema{ - Type: "service-role", - Plural: "service-roles", - Group: "rbac", - Version: "v1alpha1", - MessageName: "istio.rbac.v1alpha1.ServiceRole", - Validate: validation.ValidateServiceRole, - Collection: metadata.IstioRbacV1alpha1Serviceroles.Collection.String(), - } - - // ServiceRoleBinding describes an RBAC service role. - ServiceRoleBinding = ProtoSchema{ - ClusterScoped: false, - Type: "service-role-binding", - Plural: "service-role-bindings", - Group: "rbac", - Version: "v1alpha1", - MessageName: "istio.rbac.v1alpha1.ServiceRoleBinding", - Validate: validation.ValidateServiceRoleBinding, - Collection: metadata.IstioRbacV1alpha1Servicerolebindings.Collection.String(), - } - - // RbacConfig describes the mesh level RBAC config. - // Deprecated: use ClusterRbacConfig instead. - // See https://github.com/istio/istio/issues/8825 for more details. - RbacConfig = ProtoSchema{ - Type: "rbac-config", - Plural: "rbac-configs", - Group: "rbac", - Version: "v1alpha1", - MessageName: "istio.rbac.v1alpha1.RbacConfig", - Validate: validation.ValidateRbacConfig, - Collection: metadata.IstioRbacV1alpha1Rbacconfigs.Collection.String(), - } - - // ClusterRbacConfig describes the cluster level RBAC config. - ClusterRbacConfig = ProtoSchema{ - ClusterScoped: true, - Type: "cluster-rbac-config", - Plural: "cluster-rbac-configs", - Group: "rbac", - Version: "v1alpha1", - MessageName: "istio.rbac.v1alpha1.RbacConfig", - Validate: validation.ValidateClusterRbacConfig, - Collection: metadata.IstioRbacV1alpha1Clusterrbacconfigs.Collection.String(), - } - - // IstioConfigTypes lists all Istio config types with schemas and validation - IstioConfigTypes = ConfigDescriptor{ - VirtualService, - Gateway, - ServiceEntry, - DestinationRule, - EnvoyFilter, - Sidecar, - HTTPAPISpec, - HTTPAPISpecBinding, - QuotaSpec, - QuotaSpecBinding, - AuthenticationPolicy, - AuthenticationMeshPolicy, - ServiceRole, - ServiceRoleBinding, - RbacConfig, - ClusterRbacConfig, - } -) - // ResolveHostname produces a FQDN based on either the service or // a concat of the namespace + domain // Deprecated. Do not use @@ -635,7 +354,7 @@ func MakeIstioStore(store ConfigStore) IstioConfigStore { } func (store *istioConfigStore) ServiceEntries() []Config { - configs, err := store.List(ServiceEntry.Type, NamespaceAll) + configs, err := store.List(schemas.ServiceEntry.Type, NamespaceAll) if err != nil { return nil } @@ -659,7 +378,7 @@ func sortConfigByCreationTime(configs []Config) []Config { } func (store *istioConfigStore) Gateways(workloadLabels labels.Collection) []Config { - configs, err := store.List(Gateway.Type, NamespaceAll) + configs, err := store.List(schemas.Gateway.Type, NamespaceAll) if err != nil { return nil } @@ -682,7 +401,7 @@ func (store *istioConfigStore) Gateways(workloadLabels labels.Collection) []Conf } func (store *istioConfigStore) EnvoyFilter(workloadLabels labels.Collection) *Config { - configs, err := store.List(EnvoyFilter.Type, NamespaceAll) + configs, err := store.List(schemas.EnvoyFilter.Type, NamespaceAll) if err != nil { return nil } @@ -712,11 +431,11 @@ func (store *istioConfigStore) EnvoyFilter(workloadLabels labels.Collection) *Co // HTTPAPISpecByDestination selects Mixerclient HTTP API Specs // associated with destination service instances. func (store *istioConfigStore) HTTPAPISpecByDestination(instance *ServiceInstance) []Config { - bindings, err := store.List(HTTPAPISpecBinding.Type, NamespaceAll) + bindings, err := store.List(schemas.HTTPAPISpecBinding.Type, NamespaceAll) if err != nil { return nil } - specs, err := store.List(HTTPAPISpec.Type, NamespaceAll) + specs, err := store.List(schemas.HTTPAPISpec.Type, NamespaceAll) if err != nil { return nil } @@ -836,14 +555,14 @@ func findQuotaSpecRefs(instance *ServiceInstance, bindings []Config) map[string] // associated with destination service instances. func (store *istioConfigStore) QuotaSpecByDestination(instance *ServiceInstance) []Config { log.Debugf("QuotaSpecByDestination(%v)", instance) - bindings, err := store.List(QuotaSpecBinding.Type, NamespaceAll) + bindings, err := store.List(schemas.QuotaSpecBinding.Type, NamespaceAll) if err != nil { log.Warnf("Unable to fetch QuotaSpecBindings: %v", err) return nil } log.Debugf("QuotaSpecByDestination bindings[%d] %v", len(bindings), bindings) - specs, err := store.List(QuotaSpec.Type, NamespaceAll) + specs, err := store.List(schemas.QuotaSpec.Type, NamespaceAll) if err != nil { log.Warnf("Unable to fetch QuotaSpecs: %v", err) return nil @@ -877,7 +596,7 @@ func (store *istioConfigStore) AuthenticationPolicyForWorkload(service *Service, return nil } namespace := service.Attributes.Namespace - specs, err := store.List(AuthenticationPolicy.Type, namespace) + specs, err := store.List(schemas.AuthenticationPolicy.Type, namespace) if err != nil { return nil } @@ -941,7 +660,7 @@ func (store *istioConfigStore) AuthenticationPolicyForWorkload(service *Service, // cluster-scoped (global) policy. // Note: to avoid multiple global policy, we restrict that only the one with name equals to // `DefaultAuthenticationPolicyName` ("default") will be used. Also, targets spec should be empty. - if specs, err := store.List(AuthenticationMeshPolicy.Type, ""); err == nil { + if specs, err := store.List(schemas.AuthenticationMeshPolicy.Type, ""); err == nil { for _, spec := range specs { if spec.Name == constants.DefaultAuthenticationPolicyName { return &spec @@ -953,7 +672,7 @@ func (store *istioConfigStore) AuthenticationPolicyForWorkload(service *Service, } func (store *istioConfigStore) ServiceRoles(namespace string) []Config { - roles, err := store.List(ServiceRole.Type, namespace) + roles, err := store.List(schemas.ServiceRole.Type, namespace) if err != nil { log.Errorf("failed to get ServiceRoles in namespace %s: %v", namespace, err) return nil @@ -963,7 +682,7 @@ func (store *istioConfigStore) ServiceRoles(namespace string) []Config { } func (store *istioConfigStore) ServiceRoleBindings(namespace string) []Config { - bindings, err := store.List(ServiceRoleBinding.Type, namespace) + bindings, err := store.List(schemas.ServiceRoleBinding.Type, namespace) if err != nil { log.Errorf("failed to get ServiceRoleBinding in namespace %s: %v", namespace, err) return nil @@ -973,7 +692,7 @@ func (store *istioConfigStore) ServiceRoleBindings(namespace string) []Config { } func (store *istioConfigStore) ClusterRbacConfig() *Config { - clusterRbacConfig, err := store.List(ClusterRbacConfig.Type, "") + clusterRbacConfig, err := store.List(schemas.ClusterRbacConfig.Type, "") if err != nil { log.Errorf("failed to get ClusterRbacConfig: %v", err) } @@ -986,7 +705,7 @@ func (store *istioConfigStore) ClusterRbacConfig() *Config { } func (store *istioConfigStore) RbacConfig() *Config { - rbacConfigs, err := store.List(RbacConfig.Type, "") + rbacConfigs, err := store.List(schemas.RbacConfig.Type, "") if err != nil { return nil } diff --git a/pilot/pkg/model/config_test.go b/pilot/pkg/model/config_test.go index 9b40b4cda847..144dc6f11366 100644 --- a/pilot/pkg/model/config_test.go +++ b/pilot/pkg/model/config_test.go @@ -37,26 +37,28 @@ import ( "istio.io/istio/pkg/config/host" "istio.io/istio/pkg/config/labels" "istio.io/istio/pkg/config/protocol" + "istio.io/istio/pkg/config/schema" + "istio.io/istio/pkg/config/schemas" ) // getByMessageName finds a schema by message name if it is available // In test setup, we do not have more than one descriptor with the same message type, so this // function is ok for testing purpose. -func getByMessageName(descriptor model.ConfigDescriptor, name string) (model.ProtoSchema, bool) { - for _, schema := range descriptor { - if schema.MessageName == name { - return schema, true +func getByMessageName(descriptor schema.Set, name string) (schema.Instance, bool) { + for _, s := range descriptor { + if s.MessageName == name { + return s, true } } - return model.ProtoSchema{}, false + return schema.Instance{}, false } func TestConfigDescriptor(t *testing.T) { - a := model.ProtoSchema{Type: "a", MessageName: "proxy.A"} - descriptor := model.ConfigDescriptor{ + a := schema.Instance{Type: "a", MessageName: "proxy.A"} + descriptor := schema.Set{ a, - model.ProtoSchema{Type: "b", MessageName: "proxy.B"}, - model.ProtoSchema{Type: "c", MessageName: "proxy.C"}, + schema.Instance{Type: "b", MessageName: "proxy.B"}, + schema.Instance{Type: "c", MessageName: "proxy.C"}, } want := []string{"a", "b", "c"} got := descriptor.Types() @@ -324,7 +326,7 @@ func TestResolveHostname(t *testing.T) { } func TestAuthenticationPolicyConfig(t *testing.T) { - store := model.MakeIstioStore(memory.Make(model.IstioConfigTypes)) + store := model.MakeIstioStore(memory.Make(schemas.Istio)) authNPolicies := map[string]*authn.Policy{ constants.DefaultAuthenticationPolicyName: {}, @@ -370,7 +372,7 @@ func TestAuthenticationPolicyConfig(t *testing.T) { for key, value := range authNPolicies { cfg := model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.AuthenticationPolicy.Type, + Type: schemas.AuthenticationPolicy.Type, Name: key, Group: "authentication", Version: "v1alpha2", @@ -448,7 +450,7 @@ func TestAuthenticationPolicyConfig(t *testing.T) { } func TestAuthenticationPolicyConfigWithGlobal(t *testing.T) { - store := model.MakeIstioStore(memory.Make(model.IstioConfigTypes)) + store := model.MakeIstioStore(memory.Make(schemas.Istio)) globalPolicy := authn.Policy{ Peers: []*authn.PeerAuthenticationMethod{{ @@ -497,9 +499,9 @@ func TestAuthenticationPolicyConfigWithGlobal(t *testing.T) { } if in.namespace == "" { // Cluster-scoped policy - cfg.ConfigMeta.Type = model.AuthenticationMeshPolicy.Type + cfg.ConfigMeta.Type = schemas.AuthenticationMeshPolicy.Type } else { - cfg.ConfigMeta.Type = model.AuthenticationPolicy.Type + cfg.ConfigMeta.Type = schemas.AuthenticationPolicy.Type cfg.ConfigMeta.Namespace = in.namespace } if _, err := store.Create(cfg); err != nil { @@ -639,10 +641,10 @@ func TestMostSpecificHostMatch(t *testing.T) { } func TestServiceRoles(t *testing.T) { - store := model.MakeIstioStore(memory.Make(model.IstioConfigTypes)) - addRbacConfigToStore(model.ServiceRole.Type, "role1", "istio-system", store, t) - addRbacConfigToStore(model.ServiceRole.Type, "role2", "default", store, t) - addRbacConfigToStore(model.ServiceRole.Type, "role3", "istio-system", store, t) + store := model.MakeIstioStore(memory.Make(schemas.Istio)) + addRbacConfigToStore(schemas.ServiceRole.Type, "role1", "istio-system", store, t) + addRbacConfigToStore(schemas.ServiceRole.Type, "role2", "default", store, t) + addRbacConfigToStore(schemas.ServiceRole.Type, "role3", "istio-system", store, t) tests := []struct { namespace string expectName map[string]bool @@ -667,10 +669,10 @@ func TestServiceRoles(t *testing.T) { } func TestServiceRoleBindings(t *testing.T) { - store := model.MakeIstioStore(memory.Make(model.IstioConfigTypes)) - addRbacConfigToStore(model.ServiceRoleBinding.Type, "binding1", "istio-system", store, t) - addRbacConfigToStore(model.ServiceRoleBinding.Type, "binding2", "default", store, t) - addRbacConfigToStore(model.ServiceRoleBinding.Type, "binding3", "istio-system", store, t) + store := model.MakeIstioStore(memory.Make(schemas.Istio)) + addRbacConfigToStore(schemas.ServiceRoleBinding.Type, "binding1", "istio-system", store, t) + addRbacConfigToStore(schemas.ServiceRoleBinding.Type, "binding2", "default", store, t) + addRbacConfigToStore(schemas.ServiceRoleBinding.Type, "binding3", "istio-system", store, t) tests := []struct { namespace string expectName map[string]bool @@ -695,8 +697,8 @@ func TestServiceRoleBindings(t *testing.T) { } func TestRbacConfig(t *testing.T) { - store := model.MakeIstioStore(memory.Make(model.IstioConfigTypes)) - addRbacConfigToStore(model.RbacConfig.Type, constants.DefaultRbacConfigName, "", store, t) + store := model.MakeIstioStore(memory.Make(schemas.Istio)) + addRbacConfigToStore(schemas.RbacConfig.Type, constants.DefaultRbacConfigName, "", store, t) rbacConfig := store.RbacConfig() if rbacConfig.Name != constants.DefaultRbacConfigName { t.Errorf("model.RbacConfig: expecting %s, but got %s", constants.DefaultRbacConfigName, rbacConfig.Name) @@ -704,8 +706,8 @@ func TestRbacConfig(t *testing.T) { } func TestClusterRbacConfig(t *testing.T) { - store := model.MakeIstioStore(memory.Make(model.IstioConfigTypes)) - addRbacConfigToStore(model.ClusterRbacConfig.Type, constants.DefaultRbacConfigName, "", store, t) + store := model.MakeIstioStore(memory.Make(schemas.Istio)) + addRbacConfigToStore(schemas.ClusterRbacConfig.Type, constants.DefaultRbacConfigName, "", store, t) rbacConfig := store.ClusterRbacConfig() if rbacConfig.Name != constants.DefaultRbacConfigName { t.Errorf("model.ClusterRbacConfig: expecting %s, but got %s", constants.DefaultRbacConfigName, rbacConfig.Name) @@ -715,10 +717,10 @@ func TestClusterRbacConfig(t *testing.T) { func addRbacConfigToStore(configType, name, namespace string, store model.IstioConfigStore, t *testing.T) { var value proto.Message switch configType { - case model.ServiceRole.Type: + case schemas.ServiceRole.Type: value = &rbacproto.ServiceRole{Rules: []*rbacproto.AccessRule{ {Services: []string{"service0"}, Methods: []string{"GET"}}}} - case model.ServiceRoleBinding.Type: + case schemas.ServiceRoleBinding.Type: value = &rbacproto.ServiceRoleBinding{ Subjects: []*rbacproto.Subject{{User: "User0"}}, RoleRef: &rbacproto.RoleRef{Kind: "ServiceRole", Name: "ServiceRole001"}} @@ -753,7 +755,7 @@ func TestIstioConfigStore_QuotaSpecByDestination(t *testing.T) { ns := "ns1" l := &fakeStore{ cfg: map[string][]model.Config{ - model.QuotaSpecBinding.Type: { + schemas.QuotaSpecBinding.Type: { { ConfigMeta: model.ConfigMeta{ Namespace: ns, @@ -777,7 +779,7 @@ func TestIstioConfigStore_QuotaSpecByDestination(t *testing.T) { }, }, }, - model.QuotaSpec.Type: { + schemas.QuotaSpec.Type: { { ConfigMeta: model.ConfigMeta{ Name: "request-count", @@ -863,7 +865,7 @@ func TestIstioConfigStore_ServiceEntries(t *testing.T) { ns := "ns1" l := &fakeStore{ cfg: map[string][]model.Config{ - model.ServiceEntry.Type: { + schemas.ServiceEntry.Type: { { ConfigMeta: model.ConfigMeta{ Name: "request-count-1", @@ -881,7 +883,7 @@ func TestIstioConfigStore_ServiceEntries(t *testing.T) { }, }, }, - model.QuotaSpec.Type: { + schemas.QuotaSpec.Type: { { ConfigMeta: model.ConfigMeta{ Name: "request-count-2", @@ -941,7 +943,7 @@ func TestIstioConfigStore_Gateway(t *testing.T) { l := &fakeStore{ cfg: map[string][]model.Config{ - model.Gateway.Type: {gw1, gw2, gw3}, + schemas.Gateway.Type: {gw1, gw2, gw3}, }, } ii := model.MakeIstioStore(l) @@ -965,7 +967,7 @@ func TestIstioConfigStore_EnvoyFilter(t *testing.T) { l := &fakeStore{ cfg: map[string][]model.Config{ - model.EnvoyFilter.Type: { + schemas.EnvoyFilter.Type: { { ConfigMeta: model.ConfigMeta{ Name: "request-count", @@ -1012,7 +1014,7 @@ func TestIstioConfigStore_HTTPAPISpecByDestination(t *testing.T) { ns := "ns1" l := &fakeStore{ cfg: map[string][]model.Config{ - model.HTTPAPISpec.Type: { + schemas.HTTPAPISpec.Type: { { ConfigMeta: model.ConfigMeta{ Name: "request-count", @@ -1041,7 +1043,7 @@ func TestIstioConfigStore_HTTPAPISpecByDestination(t *testing.T) { }, }, }, - model.HTTPAPISpecBinding.Type: { + schemas.HTTPAPISpecBinding.Type: { { ConfigMeta: model.ConfigMeta{ Namespace: ns, diff --git a/pilot/pkg/model/conversion_test.go b/pilot/pkg/model/conversion_test.go index 77f4145d85d1..e3ffe9e06e1c 100644 --- a/pilot/pkg/model/conversion_test.go +++ b/pilot/pkg/model/conversion_test.go @@ -27,7 +27,8 @@ import ( mccpb "istio.io/api/mixer/v1/config/client" networking "istio.io/api/networking/v1alpha3" - "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pkg/config/schema" + "istio.io/istio/pkg/config/schemas" "istio.io/istio/pkg/util/protomarshal" ) @@ -200,7 +201,7 @@ patterns: t.Error("should produce an error") } - gotFromJSON, err := model.HTTPAPISpec.FromJSON(wantJSON) + gotFromJSON, err := schemas.HTTPAPISpec.FromJSON(wantJSON) if err != nil { t.Errorf("FromJSON failed: %v", err) } @@ -220,7 +221,7 @@ patterns: t.Error("should produce an error") } - gotFromYAML, err := model.HTTPAPISpec.FromYAML(wantYAML) + gotFromYAML, err := schemas.HTTPAPISpec.FromYAML(wantYAML) if err != nil { t.Errorf("FromYAML failed: %v", err) } @@ -228,7 +229,7 @@ patterns: t.Errorf("FromYAML failed: got %+v want %+v", spew.Sdump(gotFromYAML), spew.Sdump(msg)) } - if _, err = model.HTTPAPISpec.FromYAML(":"); err == nil { + if _, err = schemas.HTTPAPISpec.FromYAML(":"); err == nil { t.Errorf("should produce an error") } @@ -244,7 +245,7 @@ patterns: t.Error("should produce an error") } - gotFromJSONMap, err := model.HTTPAPISpec.FromJSONMap(wantJSONMap) + gotFromJSONMap, err := schemas.HTTPAPISpec.FromJSONMap(wantJSONMap) if err != nil { t.Errorf("FromJSONMap failed: %v", err) } @@ -252,16 +253,16 @@ patterns: t.Errorf("FromJSONMap failed: got %+v want %+v", spew.Sdump(gotFromJSONMap), spew.Sdump(msg)) } - if _, err = model.HTTPAPISpec.FromJSONMap(1); err == nil { + if _, err = schemas.HTTPAPISpec.FromJSONMap(1); err == nil { t.Error("should produce an error") } - if _, err = model.HTTPAPISpec.FromJSON(":"); err == nil { + if _, err = schemas.HTTPAPISpec.FromJSON(":"); err == nil { t.Errorf("should produce an error") } } func TestProtoSchemaConversions(t *testing.T) { - destinationRuleSchema := &model.ProtoSchema{MessageName: model.DestinationRule.MessageName} + destinationRuleSchema := &schema.Instance{MessageName: schemas.DestinationRule.MessageName} msg := &networking.DestinationRule{ Host: "something.svc.local", @@ -318,9 +319,9 @@ trafficPolicy: }, } - badSchema := &model.ProtoSchema{MessageName: "bad-name"} + badSchema := &schema.Instance{MessageName: "bad-name"} if _, err := badSchema.FromYAML(wantYAML); err == nil { - t.Errorf("FromYAML should have failed using ProtoSchema with bad MessageName") + t.Errorf("FromYAML should have failed using Schema with bad MessageName") } gotJSON, err := protomarshal.ToJSON(msg) diff --git a/pilot/pkg/model/push_context.go b/pilot/pkg/model/push_context.go index be781f790a32..f72a2483c81d 100644 --- a/pilot/pkg/model/push_context.go +++ b/pilot/pkg/model/push_context.go @@ -28,6 +28,7 @@ import ( "istio.io/istio/pkg/config/host" "istio.io/istio/pkg/config/labels" "istio.io/istio/pkg/config/protocol" + "istio.io/istio/pkg/config/schemas" "istio.io/istio/pkg/config/visibility" ) @@ -750,7 +751,7 @@ func (ps *PushContext) initServiceAccounts(env *Environment, services []*Service // Caches list of virtual services func (ps *PushContext) initVirtualServices(env *Environment) error { - virtualServices, err := env.List(VirtualService.Type, NamespaceAll) + virtualServices, err := env.List(schemas.VirtualService.Type, NamespaceAll) if err != nil { return err } @@ -900,7 +901,7 @@ func (ps *PushContext) initDefaultExportMaps() { // with the proxy and derive listeners/routes/clusters based on the sidecar // scope. func (ps *PushContext) initSidecarScopes(env *Environment) error { - sidecarConfigs, err := env.List(Sidecar.Type, NamespaceAll) + sidecarConfigs, err := env.List(schemas.Sidecar.Type, NamespaceAll) if err != nil { return err } @@ -961,7 +962,7 @@ func (ps *PushContext) initSidecarScopes(env *Environment) error { // Split out of DestinationRule expensive conversions - once per push. func (ps *PushContext) initDestinationRules(env *Environment) error { - configs, err := env.List(DestinationRule.Type, NamespaceAll) + configs, err := env.List(schemas.DestinationRule.Type, NamespaceAll) if err != nil { return err } @@ -1069,7 +1070,7 @@ func (ps *PushContext) initAuthorizationPolicies(env *Environment) error { // pre computes envoy filters per namespace func (ps *PushContext) initEnvoyFilters(env *Environment) error { - envoyFilterConfigs, err := env.List(EnvoyFilter.Type, NamespaceAll) + envoyFilterConfigs, err := env.List(schemas.EnvoyFilter.Type, NamespaceAll) if err != nil { return err } @@ -1116,7 +1117,7 @@ func (ps *PushContext) EnvoyFilters(proxy *Proxy) []*EnvoyFilterWrapper { // pre computes gateways per namespace func (ps *PushContext) initGateways(env *Environment) error { - gatewayConfigs, err := env.List(Gateway.Type, NamespaceAll) + gatewayConfigs, err := env.List(schemas.Gateway.Type, NamespaceAll) if err != nil { return err } diff --git a/pilot/pkg/model/validation.go b/pilot/pkg/model/validation.go index f0c6107966b6..2423bd24f9bc 100644 --- a/pilot/pkg/model/validation.go +++ b/pilot/pkg/model/validation.go @@ -20,7 +20,6 @@ import ( "net" "strings" - "github.com/gogo/protobuf/proto" "github.com/hashicorp/go-multierror" "istio.io/istio/pkg/config/labels" @@ -31,42 +30,6 @@ import ( // ServiceEntry.Endpoint.Address message. const UnixAddressPrefix = "unix://" -// Validate checks that each name conforms to the spec and has a ProtoMessage -func (descriptor ConfigDescriptor) Validate() error { - var errs error - descriptorTypes := make(map[string]bool) - messages := make(map[string]bool) - clusterMessages := make(map[string]bool) - - for _, v := range descriptor { - if !labels.IsDNS1123Label(v.Type) { - errs = multierror.Append(errs, fmt.Errorf("invalid type: %q", v.Type)) - } - if !labels.IsDNS1123Label(v.Plural) { - errs = multierror.Append(errs, fmt.Errorf("invalid plural: %q", v.Type)) - } - if proto.MessageType(v.MessageName) == nil { - errs = multierror.Append(errs, fmt.Errorf("cannot discover proto message type: %q", v.MessageName)) - } - if _, exists := descriptorTypes[v.Type]; exists { - errs = multierror.Append(errs, fmt.Errorf("duplicate type: %q", v.Type)) - } - descriptorTypes[v.Type] = true - if v.ClusterScoped { - if _, exists := clusterMessages[v.MessageName]; exists { - errs = multierror.Append(errs, fmt.Errorf("duplicate message type: %q", v.MessageName)) - } - clusterMessages[v.MessageName] = true - } else { - if _, exists := messages[v.MessageName]; exists { - errs = multierror.Append(errs, fmt.Errorf("duplicate message type: %q", v.MessageName)) - } - messages[v.MessageName] = true - } - } - return errs -} - // Validate ensures that the service object is well-defined func (s *Service) Validate() error { var errs error diff --git a/pilot/pkg/model/validation_test.go b/pilot/pkg/model/validation_test.go index 3a00d1a3d2f7..cc396526bdcf 100644 --- a/pilot/pkg/model/validation_test.go +++ b/pilot/pkg/model/validation_test.go @@ -15,148 +15,12 @@ package model import ( - "fmt" - "strings" "testing" - "github.com/gogo/protobuf/proto" - "istio.io/istio/pkg/config/labels" "istio.io/istio/pkg/config/protocol" - testConfig "istio.io/istio/pkg/test/config" -) - -const ( - // Config name for testing - someName = "foo" - // Config namespace for testing. - someNamespace = "bar" ) -func TestConfigDescriptorValidate(t *testing.T) { - badLabel := strings.Repeat("a", labels.DNS1123LabelMaxLength+1) - goodLabel := strings.Repeat("a", labels.DNS1123LabelMaxLength-1) - - cases := []struct { - name string - descriptor ConfigDescriptor - wantErr bool - }{{ - name: "Valid ConfigDescriptor (IstioConfig)", - descriptor: IstioConfigTypes, - wantErr: false, - }, { - name: "Invalid DNS11234Label in ConfigDescriptor", - descriptor: ConfigDescriptor{ProtoSchema{ - Type: badLabel, - MessageName: "istio.networking.v1alpha3.Gateway", - }}, - wantErr: true, - }, { - name: "Bad MessageName in ProtoMessage", - descriptor: ConfigDescriptor{ProtoSchema{ - Type: goodLabel, - MessageName: "nonexistent", - }}, - wantErr: true, - }, { - name: "Missing key function", - descriptor: ConfigDescriptor{ProtoSchema{ - Type: "service-entry", - MessageName: "istio.networking.v1alpha3.ServiceEtrny", - }}, - wantErr: true, - }, { - name: "Duplicate type and message", - descriptor: ConfigDescriptor{DestinationRule, DestinationRule}, - wantErr: true, - }} - - for _, c := range cases { - if err := c.descriptor.Validate(); (err != nil) != c.wantErr { - t.Errorf("%v failed: got %v but wantErr=%v", c.name, err, c.wantErr) - } - } -} - -// ValidateConfig ensures that the config object is well-defined -// TODO: also check name and namespace -func descriptorValidateConfig(descriptor ConfigDescriptor, typ string, obj interface{}) error { - if obj == nil { - return fmt.Errorf("invalid nil configuration object") - } - - t, ok := descriptor.GetByType(typ) - if !ok { - return fmt.Errorf("undeclared type: %q", typ) - } - - v, ok := obj.(proto.Message) - if !ok { - return fmt.Errorf("cannot cast to a proto message") - } - - if proto.MessageName(v) != t.MessageName { - return fmt.Errorf("mismatched message type %q and type %q", - proto.MessageName(v), t.MessageName) - } - return t.Validate(someName, someNamespace, v) -} - -func TestConfigDescriptorValidateConfig(t *testing.T) { - cases := []struct { - name string - typ string - config interface{} - wantErr bool - }{ - { - name: "bad configuration object", - typ: "policy", - config: nil, - wantErr: true, - }, - { - name: "undeclared kind", - typ: "special-type", - config: nil, - wantErr: true, - }, - { - name: "non-proto object configuration", - typ: "policy", - config: "non-proto objection configuration", - wantErr: true, - }, - { - name: "message type and kind mismatch", - typ: "policy", - config: ServiceEntry, - wantErr: true, - }, - { - name: "ProtoSchema validation1", - typ: "service-entry", - config: ServiceEntry, - wantErr: true, - }, - { - name: "Successful validation", - typ: MockConfig.Type, - config: &testConfig.MockConfig{Key: "test"}, - wantErr: false, - }, - } - - types := append(IstioConfigTypes, MockConfig) - - for _, c := range cases { - if err := descriptorValidateConfig(types, c.typ, c.config); (err != nil) != c.wantErr { - t.Errorf("%v failed: got error=%v but wantErr=%v", c.name, err, c.wantErr) - } - } -} - var ( endpoint1 = NetworkEndpoint{ Address: "192.168.1.1", diff --git a/pilot/pkg/networking/core/v1alpha3/cluster_test.go b/pilot/pkg/networking/core/v1alpha3/cluster_test.go index 66b5b5e96e2c..6af7f64d9284 100644 --- a/pilot/pkg/networking/core/v1alpha3/cluster_test.go +++ b/pilot/pkg/networking/core/v1alpha3/cluster_test.go @@ -38,6 +38,7 @@ import ( "istio.io/istio/pkg/config/constants" "istio.io/istio/pkg/config/host" "istio.io/istio/pkg/config/protocol" + "istio.io/istio/pkg/config/schemas" ) type ConfigType int @@ -239,11 +240,11 @@ func buildTestClustersWithProxyMetadata(serviceHostname string, serviceResolutio configStore := &fakes.IstioConfigStore{ ListStub: func(typ, namespace string) (configs []model.Config, e error) { - if typ == model.DestinationRule.Type { + if typ == schemas.DestinationRule.Type { return []model.Config{ {ConfigMeta: model.ConfigMeta{ - Type: model.DestinationRule.Type, - Version: model.DestinationRule.Version, + Type: schemas.DestinationRule.Type, + Version: schemas.DestinationRule.Version, Name: "acme", }, Spec: destRule, diff --git a/pilot/pkg/networking/core/v1alpha3/fakes/fake_istio_config_store.go b/pilot/pkg/networking/core/v1alpha3/fakes/fake_istio_config_store.go index 41d48f8effa6..3aa1d08ed0e7 100644 --- a/pilot/pkg/networking/core/v1alpha3/fakes/fake_istio_config_store.go +++ b/pilot/pkg/networking/core/v1alpha3/fakes/fake_istio_config_store.go @@ -6,17 +6,18 @@ import ( "istio.io/istio/pilot/pkg/model" "istio.io/istio/pkg/config/labels" + "istio.io/istio/pkg/config/schema" ) type IstioConfigStore struct { - ConfigDescriptorStub func() model.ConfigDescriptor + ConfigDescriptorStub func() schema.Set configDescriptorMutex sync.RWMutex configDescriptorArgsForCall []struct{} configDescriptorReturns struct { - result1 model.ConfigDescriptor + result1 schema.Set } configDescriptorReturnsOnCall map[int]struct { - result1 model.ConfigDescriptor + result1 schema.Set } GetStub func(typ, name, namespace string) *model.Config getMutex sync.RWMutex @@ -194,7 +195,7 @@ type IstioConfigStore struct { invocationsMutex sync.RWMutex } -func (fake *IstioConfigStore) ConfigDescriptor() model.ConfigDescriptor { +func (fake *IstioConfigStore) ConfigDescriptor() schema.Set { fake.configDescriptorMutex.Lock() ret, specificReturn := fake.configDescriptorReturnsOnCall[len(fake.configDescriptorArgsForCall)] fake.configDescriptorArgsForCall = append(fake.configDescriptorArgsForCall, struct{}{}) @@ -215,22 +216,22 @@ func (fake *IstioConfigStore) ConfigDescriptorCallCount() int { return len(fake.configDescriptorArgsForCall) } -func (fake *IstioConfigStore) ConfigDescriptorReturns(result1 model.ConfigDescriptor) { +func (fake *IstioConfigStore) ConfigDescriptorReturns(result1 schema.Set) { fake.ConfigDescriptorStub = nil fake.configDescriptorReturns = struct { - result1 model.ConfigDescriptor + result1 schema.Set }{result1} } -func (fake *IstioConfigStore) ConfigDescriptorReturnsOnCall(i int, result1 model.ConfigDescriptor) { +func (fake *IstioConfigStore) ConfigDescriptorReturnsOnCall(i int, result1 schema.Set) { fake.ConfigDescriptorStub = nil if fake.configDescriptorReturnsOnCall == nil { fake.configDescriptorReturnsOnCall = make(map[int]struct { - result1 model.ConfigDescriptor + result1 schema.Set }) } fake.configDescriptorReturnsOnCall[i] = struct { - result1 model.ConfigDescriptor + result1 schema.Set }{result1} } diff --git a/pilot/pkg/networking/core/v1alpha3/gateway_test.go b/pilot/pkg/networking/core/v1alpha3/gateway_test.go index 5e300b13743d..51c93475ac14 100644 --- a/pilot/pkg/networking/core/v1alpha3/gateway_test.go +++ b/pilot/pkg/networking/core/v1alpha3/gateway_test.go @@ -31,6 +31,7 @@ import ( "istio.io/istio/pilot/pkg/networking/util" "istio.io/istio/pilot/pkg/security/model" "istio.io/istio/pkg/config/mesh" + "istio.io/istio/pkg/config/schemas" "istio.io/istio/pkg/proto" ) @@ -548,7 +549,7 @@ func TestGatewayHTTPRouteConfig(t *testing.T) { } virtualService := pilot_model.Config{ ConfigMeta: pilot_model.ConfigMeta{ - Type: pilot_model.VirtualService.Type, + Type: schemas.VirtualService.Type, Name: "virtual-service", Namespace: "default", }, @@ -556,7 +557,7 @@ func TestGatewayHTTPRouteConfig(t *testing.T) { } virtualServiceCopy := pilot_model.Config{ ConfigMeta: pilot_model.ConfigMeta{ - Type: pilot_model.VirtualService.Type, + Type: schemas.VirtualService.Type, Name: "virtual-service-copy", Namespace: "default", }, @@ -564,7 +565,7 @@ func TestGatewayHTTPRouteConfig(t *testing.T) { } virtualServiceWildcard := pilot_model.Config{ ConfigMeta: pilot_model.ConfigMeta{ - Type: pilot_model.VirtualService.Type, + Type: schemas.VirtualService.Type, Name: "virtual-service-wildcard", Namespace: "default", }, diff --git a/pilot/pkg/networking/core/v1alpha3/httproute_test.go b/pilot/pkg/networking/core/v1alpha3/httproute_test.go index cb01ed6677e6..b9daa5f6d8cb 100644 --- a/pilot/pkg/networking/core/v1alpha3/httproute_test.go +++ b/pilot/pkg/networking/core/v1alpha3/httproute_test.go @@ -29,6 +29,7 @@ import ( "istio.io/istio/pilot/pkg/networking/plugin" "istio.io/istio/pkg/config/host" "istio.io/istio/pkg/config/protocol" + "istio.io/istio/pkg/config/schemas" "istio.io/istio/pkg/config/visibility" ) @@ -300,8 +301,8 @@ func TestSidecarOutboundHTTPRouteConfig(t *testing.T) { } virtualService1 := model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.VirtualService.Type, - Version: model.VirtualService.Version, + Type: schemas.VirtualService.Type, + Version: schemas.VirtualService.Version, Name: "acme2-v1", Namespace: "not-default", }, @@ -309,8 +310,8 @@ func TestSidecarOutboundHTTPRouteConfig(t *testing.T) { } virtualService2 := model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.VirtualService.Type, - Version: model.VirtualService.Version, + Type: schemas.VirtualService.Type, + Version: schemas.VirtualService.Version, Name: "acme-v2", Namespace: "not-default", }, @@ -318,8 +319,8 @@ func TestSidecarOutboundHTTPRouteConfig(t *testing.T) { } virtualService3 := model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.VirtualService.Type, - Version: model.VirtualService.Version, + Type: schemas.VirtualService.Type, + Version: schemas.VirtualService.Version, Name: "acme-v3", Namespace: "not-default", }, diff --git a/pilot/pkg/networking/core/v1alpha3/listener_test.go b/pilot/pkg/networking/core/v1alpha3/listener_test.go index 99727087cd0a..cea05cb26d11 100644 --- a/pilot/pkg/networking/core/v1alpha3/listener_test.go +++ b/pilot/pkg/networking/core/v1alpha3/listener_test.go @@ -39,6 +39,7 @@ import ( "istio.io/istio/pkg/config/labels" "istio.io/istio/pkg/config/mesh" "istio.io/istio/pkg/config/protocol" + "istio.io/istio/pkg/config/schemas" ) const ( @@ -318,8 +319,8 @@ func TestOutboundListenerTCPWithVS(t *testing.T) { p := &fakePlugin{} virtualService := model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.VirtualService.Type, - Version: model.VirtualService.Version, + Type: schemas.VirtualService.Type, + Version: schemas.VirtualService.Version, Name: "test_vs", Namespace: "default", }, diff --git a/pilot/pkg/networking/core/v1alpha3/loadbalancer/loadbalancer_test.go b/pilot/pkg/networking/core/v1alpha3/loadbalancer/loadbalancer_test.go index 4f9ce7f76335..6a1840291c3a 100644 --- a/pilot/pkg/networking/core/v1alpha3/loadbalancer/loadbalancer_test.go +++ b/pilot/pkg/networking/core/v1alpha3/loadbalancer/loadbalancer_test.go @@ -26,9 +26,11 @@ import ( meshconfig "istio.io/api/mesh/v1alpha1" networking "istio.io/api/networking/v1alpha3" + "istio.io/istio/pilot/pkg/model" "istio.io/istio/pilot/pkg/networking/core/v1alpha3/fakes" "istio.io/istio/pkg/config/protocol" + "istio.io/istio/pkg/config/schemas" ) func TestApplyLocalitySetting(t *testing.T) { @@ -196,8 +198,8 @@ func buildEnvForClustersWithDistribute(distribute []*meshconfig.LocalityLoadBala _ = env.PushContext.InitContext(env) env.PushContext.SetDestinationRules([]model.Config{ {ConfigMeta: model.ConfigMeta{ - Type: model.DestinationRule.Type, - Version: model.DestinationRule.Version, + Type: schemas.DestinationRule.Type, + Version: schemas.DestinationRule.Version, Name: "acme", }, Spec: &networking.DestinationRule{ @@ -259,8 +261,8 @@ func buildEnvForClustersWithFailover() *model.Environment { _ = env.PushContext.InitContext(env) env.PushContext.SetDestinationRules([]model.Config{ {ConfigMeta: model.ConfigMeta{ - Type: model.DestinationRule.Type, - Version: model.DestinationRule.Version, + Type: schemas.DestinationRule.Type, + Version: schemas.DestinationRule.Version, Name: "acme", }, Spec: &networking.DestinationRule{ diff --git a/pilot/pkg/networking/core/v1alpha3/route/route_test.go b/pilot/pkg/networking/core/v1alpha3/route/route_test.go index cd1f955f6c64..d5685c913d94 100644 --- a/pilot/pkg/networking/core/v1alpha3/route/route_test.go +++ b/pilot/pkg/networking/core/v1alpha3/route/route_test.go @@ -30,6 +30,7 @@ import ( "istio.io/istio/pkg/config/labels" "istio.io/istio/pkg/config/mesh" "istio.io/istio/pkg/config/protocol" + "istio.io/istio/pkg/config/schemas" ) func TestBuildHTTPRoutes(t *testing.T) { @@ -79,8 +80,8 @@ func TestBuildHTTPRoutes(t *testing.T) { push.SetDestinationRules([]model.Config{ { ConfigMeta: model.ConfigMeta{ - Type: model.DestinationRule.Type, - Version: model.DestinationRule.Version, + Type: schemas.DestinationRule.Type, + Version: schemas.DestinationRule.Version, Name: "acme", }, Spec: &networking.DestinationRule{ @@ -123,8 +124,8 @@ func TestBuildHTTPRoutes(t *testing.T) { virtualService := model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.VirtualService.Type, - Version: model.VirtualService.Version, + Type: schemas.VirtualService.Type, + Version: schemas.VirtualService.Version, Name: "acme", }, Spec: virtualServiceWithSubset, @@ -139,8 +140,8 @@ func TestBuildHTTPRoutes(t *testing.T) { push.SetDestinationRules([]model.Config{ { ConfigMeta: model.ConfigMeta{ - Type: model.DestinationRule.Type, - Version: model.DestinationRule.Version, + Type: schemas.DestinationRule.Type, + Version: schemas.DestinationRule.Version, Name: "acme", }, Spec: &networking.DestinationRule{ @@ -168,8 +169,8 @@ func TestBuildHTTPRoutes(t *testing.T) { t.Run("for virtual service with subsets with port level settings with ring hash", func(t *testing.T) { g := gomega.NewGomegaWithT(t) - virtualService := model.Config{ConfigMeta: model.ConfigMeta{Type: model.VirtualService.Type, - Version: model.VirtualService.Version, + virtualService := model.Config{ConfigMeta: model.ConfigMeta{Type: schemas.VirtualService.Type, + Version: schemas.VirtualService.Version, Name: "acme", }, Spec: virtualServiceWithSubsetWithPortLevelSettings, @@ -186,8 +187,8 @@ func TestBuildHTTPRoutes(t *testing.T) { { ConfigMeta: model.ConfigMeta{ - Type: model.DestinationRule.Type, - Version: model.DestinationRule.Version, + Type: schemas.DestinationRule.Type, + Version: schemas.DestinationRule.Version, Name: "acme", }, Spec: portLevelDestinationRuleWithSubsetPolicy, @@ -213,8 +214,8 @@ func TestBuildHTTPRoutes(t *testing.T) { virtualService := model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.VirtualService.Type, - Version: model.VirtualService.Version, + Type: schemas.VirtualService.Type, + Version: schemas.VirtualService.Version, Name: "acme", }, Spec: virtualServiceWithSubset, @@ -222,8 +223,8 @@ func TestBuildHTTPRoutes(t *testing.T) { cnfg := model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.DestinationRule.Type, - Version: model.DestinationRule.Version, + Type: schemas.DestinationRule.Type, + Version: schemas.DestinationRule.Version, Name: "acme", }, } @@ -269,8 +270,8 @@ func TestBuildHTTPRoutes(t *testing.T) { push.SetDestinationRules([]model.Config{ { ConfigMeta: model.ConfigMeta{ - Type: model.DestinationRule.Type, - Version: model.DestinationRule.Version, + Type: schemas.DestinationRule.Type, + Version: schemas.DestinationRule.Version, Name: "acme", }, Spec: portLevelDestinationRule, @@ -315,8 +316,8 @@ func TestBuildHTTPRoutes(t *testing.T) { push.SetDestinationRules([]model.Config{ { ConfigMeta: model.ConfigMeta{ - Type: model.DestinationRule.Type, - Version: model.DestinationRule.Version, + Type: schemas.DestinationRule.Type, + Version: schemas.DestinationRule.Version, Name: "acme", }, Spec: networkingDestinationRule, @@ -335,8 +336,8 @@ func TestBuildHTTPRoutes(t *testing.T) { push.SetDestinationRules([]model.Config{ { ConfigMeta: model.ConfigMeta{ - Type: model.DestinationRule.Type, - Version: model.DestinationRule.Version, + Type: schemas.DestinationRule.Type, + Version: schemas.DestinationRule.Version, Name: "acme", }, Spec: networkingDestinationRuleWithPortLevelTrafficPolicy, @@ -415,8 +416,8 @@ var virtualServiceWithSubsetWithPortLevelSettings = &networking.VirtualService{ var virtualServicePlain = model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.VirtualService.Type, - Version: model.VirtualService.Version, + Type: schemas.VirtualService.Type, + Version: schemas.VirtualService.Version, Name: "acme", }, Spec: &networking.VirtualService{ @@ -444,8 +445,8 @@ var virtualServicePlain = model.Config{ var virtualServiceWithRedirect = model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.VirtualService.Type, - Version: model.VirtualService.Version, + Type: schemas.VirtualService.Type, + Version: schemas.VirtualService.Version, Name: "acme", }, Spec: &networking.VirtualService{ diff --git a/pilot/pkg/proxy/envoy/v2/discovery.go b/pilot/pkg/proxy/envoy/v2/discovery.go index c855378f836a..41efae6293d4 100644 --- a/pilot/pkg/proxy/envoy/v2/discovery.go +++ b/pilot/pkg/proxy/envoy/v2/discovery.go @@ -29,6 +29,7 @@ import ( "istio.io/istio/pilot/pkg/networking/core" authn_model "istio.io/istio/pilot/pkg/security/model" "istio.io/istio/pilot/pkg/serviceregistry/kube/controller" + "istio.io/istio/pkg/config/schemas" ) var ( @@ -190,7 +191,7 @@ func NewDiscoveryServer( // TODO: changes should not trigger a full recompute of LDS/RDS/CDS/EDS // (especially mixerclient HTTP and quota) configHandler := func(model.Config, model.Event) { out.clearCache() } - for _, descriptor := range model.IstioConfigTypes { + for _, descriptor := range schemas.Istio { configCache.RegisterEventHandler(descriptor.Type, configHandler) } } diff --git a/pilot/pkg/security/authz/builder/builder_test.go b/pilot/pkg/security/authz/builder/builder_test.go index 188e695e368a..8ff919825a3f 100644 --- a/pilot/pkg/security/authz/builder/builder_test.go +++ b/pilot/pkg/security/authz/builder/builder_test.go @@ -28,6 +28,7 @@ import ( authz_model "istio.io/istio/pilot/pkg/security/authz/model" "istio.io/istio/pilot/pkg/security/authz/policy" "istio.io/istio/pkg/config/host" + "istio.io/istio/pkg/config/schemas" ) func newAuthzPolicyWithRbacConfig(mode istio_rbac.RbacConfig_Mode, include *istio_rbac.RbacConfig_Target, @@ -64,7 +65,7 @@ func newService(hostname string, labels map[string]string, t *testing.T) *model. func simpleGlobalPermissiveMode() *model.Config { cfg := &model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.ClusterRbacConfig.Type, + Type: schemas.ClusterRbacConfig.Type, Name: "default", Namespace: "default", }, diff --git a/pilot/pkg/security/authz/policy/helper.go b/pilot/pkg/security/authz/policy/helper.go index 495d43f947a7..47d2b13d06a1 100644 --- a/pilot/pkg/security/authz/policy/helper.go +++ b/pilot/pkg/security/authz/policy/helper.go @@ -28,6 +28,7 @@ import ( "istio.io/istio/pilot/pkg/model" authz_model "istio.io/istio/pilot/pkg/security/authz/model" "istio.io/istio/pkg/config/host" + "istio.io/istio/pkg/config/schemas" ) // We cannot import `testing` here, as it will bring extra test flags into the binary. Instead, just include the interface here @@ -69,7 +70,7 @@ func NewAuthzPolicies(policies []*model.Config, t mockTest) *model.Authorization hasClusterRbacConfig := false for _, p := range policies { - if p.Type == model.ClusterRbacConfig.Type { + if p.Type == schemas.ClusterRbacConfig.Type { hasClusterRbacConfig = true break } @@ -78,7 +79,7 @@ func NewAuthzPolicies(policies []*model.Config, t mockTest) *model.Authorization policies = append(policies, simpleClusterRbacConfig()) } - store := model.MakeIstioStore(memory.Make(model.IstioConfigTypes)) + store := model.MakeIstioStore(memory.Make(schemas.Istio)) for _, p := range policies { if _, err := store.Create(*p); err != nil { t.Fatalf("failed to initilize authz policies: %s", err) @@ -98,7 +99,7 @@ func NewAuthzPolicies(policies []*model.Config, t mockTest) *model.Authorization func simpleClusterRbacConfig() *model.Config { cfg := &model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.ClusterRbacConfig.Type, + Type: schemas.ClusterRbacConfig.Type, Name: "default", Namespace: "default", }, @@ -126,7 +127,7 @@ func SimpleRole(name string, namespace string, service string) *model.Config { } return &model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.ServiceRole.Type, + Type: schemas.ServiceRole.Type, Name: name, Namespace: namespace, }, @@ -141,7 +142,7 @@ func BindingTag(name string) string { func SimpleBinding(name string, namespace string, role string) *model.Config { return &model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.ServiceRoleBinding.Type, + Type: schemas.ServiceRoleBinding.Type, Name: name, Namespace: namespace, }, diff --git a/pilot/pkg/serviceregistry/external/controller_test.go b/pilot/pkg/serviceregistry/external/controller_test.go index def81225d473..6b9d5db7ab49 100644 --- a/pilot/pkg/serviceregistry/external/controller_test.go +++ b/pilot/pkg/serviceregistry/external/controller_test.go @@ -21,15 +21,18 @@ import ( "time" networking "istio.io/api/networking/v1alpha3" + "istio.io/istio/pilot/pkg/config/memory" "istio.io/istio/pilot/pkg/model" "istio.io/istio/pilot/pkg/serviceregistry/external" + "istio.io/istio/pkg/config/schema" + "istio.io/istio/pkg/config/schemas" "istio.io/istio/pkg/test/util/retry" ) func TestController(t *testing.T) { - configDescriptor := model.ConfigDescriptor{ - model.ServiceEntry, + configDescriptor := schema.Set{ + schemas.ServiceEntry, } store := memory.Make(configDescriptor) configController := memory.NewController(store) @@ -53,7 +56,7 @@ func TestController(t *testing.T) { cfg := model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.ServiceEntry.Type, + Type: schemas.ServiceEntry.Type, Name: "fake", Namespace: "fake-ns", CreationTimestamp: time.Now(), diff --git a/pilot/pkg/serviceregistry/external/conversion_test.go b/pilot/pkg/serviceregistry/external/conversion_test.go index 75cc15521345..d7e5405e1a28 100644 --- a/pilot/pkg/serviceregistry/external/conversion_test.go +++ b/pilot/pkg/serviceregistry/external/conversion_test.go @@ -27,12 +27,13 @@ import ( "istio.io/istio/pkg/config/constants" "istio.io/istio/pkg/config/host" "istio.io/istio/pkg/config/protocol" + "istio.io/istio/pkg/config/schemas" ) var GlobalTime = time.Now() var httpNone = &model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.ServiceEntry.Type, + Type: schemas.ServiceEntry.Type, Name: "httpNone", Namespace: "httpNone", Domain: "svc.cluster.local", @@ -51,7 +52,7 @@ var httpNone = &model.Config{ var tcpNone = &model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.ServiceEntry.Type, + Type: schemas.ServiceEntry.Type, Name: "tcpNone", Namespace: "tcpNone", @@ -70,7 +71,7 @@ var tcpNone = &model.Config{ var httpStatic = &model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.ServiceEntry.Type, + Type: schemas.ServiceEntry.Type, Name: "httpStatic", Namespace: "httpStatic", CreationTimestamp: GlobalTime, @@ -103,7 +104,7 @@ var httpStatic = &model.Config{ var httpDNSnoEndpoints = &model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.ServiceEntry.Type, + Type: schemas.ServiceEntry.Type, Name: "httpDNSnoEndpoints", Namespace: "httpDNSnoEndpoints", CreationTimestamp: GlobalTime, @@ -121,7 +122,7 @@ var httpDNSnoEndpoints = &model.Config{ var httpDNS = &model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.ServiceEntry.Type, + Type: schemas.ServiceEntry.Type, Name: "httpDNS", Namespace: "httpDNS", CreationTimestamp: GlobalTime, @@ -153,7 +154,7 @@ var httpDNS = &model.Config{ var tcpDNS = &model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.ServiceEntry.Type, + Type: schemas.ServiceEntry.Type, Name: "tcpDNS", Namespace: "tcpDNS", CreationTimestamp: GlobalTime, @@ -178,7 +179,7 @@ var tcpDNS = &model.Config{ var tcpStatic = &model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.ServiceEntry.Type, + Type: schemas.ServiceEntry.Type, Name: "tcpStatic", Namespace: "tcpStatic", CreationTimestamp: GlobalTime, @@ -204,7 +205,7 @@ var tcpStatic = &model.Config{ var httpNoneInternal = &model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.ServiceEntry.Type, + Type: schemas.ServiceEntry.Type, Name: "httpNoneInternal", Namespace: "httpNoneInternal", CreationTimestamp: GlobalTime, @@ -222,7 +223,7 @@ var httpNoneInternal = &model.Config{ var tcpNoneInternal = &model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.ServiceEntry.Type, + Type: schemas.ServiceEntry.Type, Name: "tcpNoneInternal", Namespace: "tcpNoneInternal", CreationTimestamp: GlobalTime, @@ -240,7 +241,7 @@ var tcpNoneInternal = &model.Config{ var multiAddrInternal = &model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.ServiceEntry.Type, + Type: schemas.ServiceEntry.Type, Name: "multiAddrInternal", Namespace: "multiAddrInternal", CreationTimestamp: GlobalTime, @@ -258,7 +259,7 @@ var multiAddrInternal = &model.Config{ var udsLocal = &model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.ServiceEntry.Type, + Type: schemas.ServiceEntry.Type, Name: "udsLocal", Namespace: "udsLocal", CreationTimestamp: GlobalTime, diff --git a/pilot/pkg/serviceregistry/external/servicediscovery.go b/pilot/pkg/serviceregistry/external/servicediscovery.go index bb60fcf34a5d..818f3623b201 100644 --- a/pilot/pkg/serviceregistry/external/servicediscovery.go +++ b/pilot/pkg/serviceregistry/external/servicediscovery.go @@ -21,6 +21,7 @@ import ( "istio.io/istio/pilot/pkg/model" "istio.io/istio/pkg/config/host" "istio.io/istio/pkg/config/labels" + "istio.io/istio/pkg/config/schemas" ) // TODO: move this out of 'external' package. Either 'serviceentry' package or @@ -58,7 +59,7 @@ func NewServiceDiscovery(callbacks model.ConfigStoreCache, store model.IstioConf updateNeeded: true, } if callbacks != nil { - callbacks.RegisterEventHandler(model.ServiceEntry.Type, func(config model.Config, event model.Event) { + callbacks.RegisterEventHandler(schemas.ServiceEntry.Type, func(config model.Config, event model.Event) { // Recomputing the index here is too expensive. c.changeMutex.Lock() c.lastChange = time.Now() diff --git a/pilot/pkg/serviceregistry/external/servicediscovery_test.go b/pilot/pkg/serviceregistry/external/servicediscovery_test.go index 55400c595d57..a983efbc95ae 100644 --- a/pilot/pkg/serviceregistry/external/servicediscovery_test.go +++ b/pilot/pkg/serviceregistry/external/servicediscovery_test.go @@ -26,6 +26,7 @@ import ( "istio.io/istio/pkg/config/constants" "istio.io/istio/pkg/config/host" "istio.io/istio/pkg/config/labels" + "istio.io/istio/pkg/config/schemas" ) func createServiceEntries(configs []*model.Config, store model.IstioConfigStore, t *testing.T) { @@ -42,7 +43,7 @@ type channelTerminal struct { } func initServiceDiscovery() (model.IstioConfigStore, *ServiceEntryStore, func()) { - store := memory.Make(model.IstioConfigTypes) + store := memory.Make(schemas.Istio) configController := memory.NewController(store) stop := make(chan struct{}) @@ -189,7 +190,7 @@ func TestNonServiceConfig(t *testing.T) { // Create a non-service configuration element. This should not affect the service registry at all. cfg := model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.DestinationRule.Type, + Type: schemas.DestinationRule.Type, Name: "fakeDestinationRule", Namespace: "default", Domain: "cluster.local", diff --git a/pilot/test/mock/config.go b/pilot/test/mock/config.go index fefb77371ae1..11936fcd2d0c 100644 --- a/pilot/test/mock/config.go +++ b/pilot/test/mock/config.go @@ -33,13 +33,15 @@ import ( "istio.io/istio/pilot/pkg/model" "istio.io/istio/pkg/config/constants" + "istio.io/istio/pkg/config/schema" + "istio.io/istio/pkg/config/schemas" pkgtest "istio.io/istio/pkg/test" "istio.io/istio/pkg/test/config" ) var ( // Types defines the mock config descriptor - Types = model.ConfigDescriptor{model.MockConfig} + Types = schema.Set{schemas.MockConfig} // ExampleVirtualService is an example V2 route rule ExampleVirtualService = &networking.VirtualService{ @@ -212,7 +214,7 @@ func Make(namespace string, i int) model.Config { name := fmt.Sprintf("%s%d", "mock-config", i) return model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.MockConfig.Type, + Type: schemas.MockConfig.Type, Group: "test.istio.io", Version: "v1", Name: name, @@ -245,7 +247,7 @@ func Compare(a, b model.Config) bool { // CheckMapInvariant validates operational invariants of an empty config registry func CheckMapInvariant(r model.ConfigStore, t *testing.T, namespace string, n int) { // check that the config descriptor is the mock config descriptor - _, contains := r.ConfigDescriptor().GetByType(model.MockConfig.Type) + _, contains := r.ConfigDescriptor().GetByType(schemas.MockConfig.Type) if !contains { t.Error("expected config mock types") } @@ -270,7 +272,7 @@ func CheckMapInvariant(r model.ConfigStore, t *testing.T, namespace string, n in // check that elements are stored for i, elt := range elts { - v1 := r.Get(model.MockConfig.Type, elt.Name, elt.Namespace) + v1 := r.Get(schemas.MockConfig.Type, elt.Name, elt.Namespace) if v1 == nil || !Compare(elt, *v1) { t.Errorf("wanted %v, got %v", elt, v1) } else { @@ -286,7 +288,7 @@ func CheckMapInvariant(r model.ConfigStore, t *testing.T, namespace string, n in invalid := model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.MockConfig.Type, + Type: schemas.MockConfig.Type, Name: "invalid", ResourceVersion: revs[0], }, @@ -295,7 +297,7 @@ func CheckMapInvariant(r model.ConfigStore, t *testing.T, namespace string, n in missing := model.Config{ ConfigMeta: model.ConfigMeta{ - Type: model.MockConfig.Type, + Type: schemas.MockConfig.Type, Name: "missing", ResourceVersion: revs[0], }, @@ -339,7 +341,7 @@ func CheckMapInvariant(r model.ConfigStore, t *testing.T, namespace string, n in } // check for missing element - if cfg := r.Get(model.MockConfig.Type, "missing", ""); cfg != nil { + if cfg := r.Get(schemas.MockConfig.Type, "missing", ""); cfg != nil { t.Error("unexpected configuration object found") } @@ -354,15 +356,15 @@ func CheckMapInvariant(r model.ConfigStore, t *testing.T, namespace string, n in } // delete missing elements - if err := r.Delete(model.MockConfig.Type, "missing", ""); err == nil { + if err := r.Delete(schemas.MockConfig.Type, "missing", ""); err == nil { t.Error("expected error on deletion of missing element") } - if err := r.Delete(model.MockConfig.Type, "missing", "unknown"); err == nil { + if err := r.Delete(schemas.MockConfig.Type, "missing", "unknown"); err == nil { t.Error("expected error on deletion of missing element in unknown namespace") } // list elements - l, err := r.List(model.MockConfig.Type, namespace) + l, err := r.List(schemas.MockConfig.Type, namespace) if err != nil { t.Errorf("List error %#v, %v", l, err) } @@ -383,7 +385,7 @@ func CheckMapInvariant(r model.ConfigStore, t *testing.T, namespace string, n in // check that elements are stored for i, elt := range elts { - v1 := r.Get(model.MockConfig.Type, elts[i].Name, elts[i].Namespace) + v1 := r.Get(schemas.MockConfig.Type, elts[i].Name, elts[i].Namespace) if v1 == nil || !Compare(elt, *v1) { t.Errorf("wanted %v, got %v", elt, v1) } @@ -391,13 +393,13 @@ func CheckMapInvariant(r model.ConfigStore, t *testing.T, namespace string, n in // delete all elements for i := range elts { - if err = r.Delete(model.MockConfig.Type, elts[i].Name, elts[i].Namespace); err != nil { + if err = r.Delete(schemas.MockConfig.Type, elts[i].Name, elts[i].Namespace); err != nil { t.Error(err) } } log.Info("Delete elements") - l, err = r.List(model.MockConfig.Type, namespace) + l, err = r.List(schemas.MockConfig.Type, namespace) if err != nil { t.Error(err) } @@ -418,22 +420,22 @@ func CheckIstioConfigTypes(store model.ConfigStore, namespace string, t *testing cases := []struct { name string configName string - schema model.ProtoSchema + schema schema.Instance spec proto.Message }{ - {"VirtualService", configName, model.VirtualService, ExampleVirtualService}, - {"DestinationRule", configName, model.DestinationRule, ExampleDestinationRule}, - {"ServiceEntry", configName, model.ServiceEntry, ExampleServiceEntry}, - {"Gateway", configName, model.Gateway, ExampleGateway}, - {"HTTPAPISpec", configName, model.HTTPAPISpec, ExampleHTTPAPISpec}, - {"HTTPAPISpecBinding", configName, model.HTTPAPISpecBinding, ExampleHTTPAPISpecBinding}, - {"QuotaSpec", configName, model.QuotaSpec, ExampleQuotaSpec}, - {"QuotaSpecBinding", configName, model.QuotaSpecBinding, ExampleQuotaSpecBinding}, - {"Policy", configName, model.AuthenticationPolicy, ExampleAuthenticationPolicy}, - {"ServiceRole", configName, model.ServiceRole, ExampleServiceRole}, - {"ServiceRoleBinding", configName, model.ServiceRoleBinding, ExampleServiceRoleBinding}, - {"RbacConfig", constants.DefaultRbacConfigName, model.RbacConfig, ExampleRbacConfig}, - {"ClusterRbacConfig", constants.DefaultRbacConfigName, model.ClusterRbacConfig, ExampleRbacConfig}, + {"VirtualService", configName, schemas.VirtualService, ExampleVirtualService}, + {"DestinationRule", configName, schemas.DestinationRule, ExampleDestinationRule}, + {"ServiceEntry", configName, schemas.ServiceEntry, ExampleServiceEntry}, + {"Gateway", configName, schemas.Gateway, ExampleGateway}, + {"HTTPAPISpec", configName, schemas.HTTPAPISpec, ExampleHTTPAPISpec}, + {"HTTPAPISpecBinding", configName, schemas.HTTPAPISpecBinding, ExampleHTTPAPISpecBinding}, + {"QuotaSpec", configName, schemas.QuotaSpec, ExampleQuotaSpec}, + {"QuotaSpecBinding", configName, schemas.QuotaSpecBinding, ExampleQuotaSpecBinding}, + {"Policy", configName, schemas.AuthenticationPolicy, ExampleAuthenticationPolicy}, + {"ServiceRole", configName, schemas.ServiceRole, ExampleServiceRole}, + {"ServiceRoleBinding", configName, schemas.ServiceRoleBinding, ExampleServiceRoleBinding}, + {"RbacConfig", constants.DefaultRbacConfigName, schemas.RbacConfig, ExampleRbacConfig}, + {"ClusterRbacConfig", constants.DefaultRbacConfigName, schemas.ClusterRbacConfig, ExampleRbacConfig}, } for _, c := range cases { @@ -462,7 +464,7 @@ func CheckCacheEvents(store model.ConfigStore, cache model.ConfigStoreCache, nam stop := make(chan struct{}) defer close(stop) added, deleted := atomic.NewInt64(0), atomic.NewInt64(0) - cache.RegisterEventHandler(model.MockConfig.Type, func(_ model.Config, ev model.Event) { + cache.RegisterEventHandler(schemas.MockConfig.Type, func(_ model.Config, ev model.Event) { switch ev { case model.EventAdd: if deleted.Load() != 0 { @@ -495,8 +497,8 @@ func CheckCacheFreshness(cache model.ConfigStoreCache, namespace string, t *test o := Make(namespace, 0) // validate cache consistency - cache.RegisterEventHandler(model.MockConfig.Type, func(config model.Config, ev model.Event) { - elts, _ := cache.List(model.MockConfig.Type, namespace) + cache.RegisterEventHandler(schemas.MockConfig.Type, func(config model.Config, ev model.Event) { + elts, _ := cache.List(schemas.MockConfig.Type, namespace) elt := cache.Get(o.Type, o.Name, o.Namespace) switch ev { case model.EventAdd: @@ -522,7 +524,7 @@ func CheckCacheFreshness(cache model.ConfigStoreCache, namespace string, t *test } log.Infof("Calling Delete(%s)", config.Key()) - if err := cache.Delete(model.MockConfig.Type, config.Name, config.Namespace); err != nil { + if err := cache.Delete(schemas.MockConfig.Type, config.Name, config.Namespace); err != nil { t.Error(err) } case model.EventDelete: @@ -574,21 +576,21 @@ func CheckCacheSync(store model.ConfigStore, cache model.ConfigStoreCache, names defer close(stop) go cache.Run(stop) pkgtest.Eventually(t, "HasSynced", cache.HasSynced) - os, _ := cache.List(model.MockConfig.Type, namespace) + os, _ := cache.List(schemas.MockConfig.Type, namespace) if len(os) != n { t.Errorf("cache.List => Got %d, expected %d", len(os), n) } // remove elements directly through client for i := 0; i < n; i++ { - if err := store.Delete(model.MockConfig.Type, keys[i].Name, keys[i].Namespace); err != nil { + if err := store.Delete(schemas.MockConfig.Type, keys[i].Name, keys[i].Namespace); err != nil { t.Error(err) } } // check again in the controller cache pkgtest.Eventually(t, "no elements in cache", func() bool { - os, _ = cache.List(model.MockConfig.Type, namespace) + os, _ = cache.List(schemas.MockConfig.Type, namespace) log.Infof("cache.List => Got %d, expected %d", len(os), 0) return len(os) == 0 }) @@ -602,8 +604,8 @@ func CheckCacheSync(store model.ConfigStore, cache model.ConfigStoreCache, names // check directly through the client pkgtest.Eventually(t, "cache and backing store match", func() bool { - cs, _ := cache.List(model.MockConfig.Type, namespace) - os, _ := store.List(model.MockConfig.Type, namespace) + cs, _ := cache.List(schemas.MockConfig.Type, namespace) + os, _ := store.List(schemas.MockConfig.Type, namespace) log.Infof("cache.List => Got %d, expected %d", len(cs), n) log.Infof("store.List => Got %d, expected %d", len(os), n) return len(os) == n && len(cs) == n @@ -611,7 +613,7 @@ func CheckCacheSync(store model.ConfigStore, cache model.ConfigStoreCache, names // remove elements directly through the client for i := 0; i < n; i++ { - if err := store.Delete(model.MockConfig.Type, keys[i].Name, keys[i].Namespace); err != nil { + if err := store.Delete(schemas.MockConfig.Type, keys[i].Name, keys[i].Namespace); err != nil { t.Error(err) } } diff --git a/pilot/tools/generate_config_crd_types.go b/pilot/tools/generate_config_crd_types.go index 16e7be6207cb..9a9db8efb3aa 100644 --- a/pilot/tools/generate_config_crd_types.go +++ b/pilot/tools/generate_config_crd_types.go @@ -27,7 +27,8 @@ import ( "text/template" "istio.io/istio/pilot/pkg/config/kube/crd" - "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pkg/config/schema" + "istio.io/istio/pkg/config/schemas" ) // ConfigData is data struct to feed to types.go template. @@ -37,13 +38,13 @@ type ConfigData struct { } // MakeConfigData prepare data for code generation for the given schema. -func MakeConfigData(schema model.ProtoSchema) ConfigData { +func MakeConfigData(schema schema.Instance) ConfigData { out := ConfigData{ IstioKind: crd.KebabCaseToCamelCase(schema.Type), CrdKind: crd.KebabCaseToCamelCase(schema.Type), } - if len(schema.SchemaObjectName) > 0 { - out.IstioKind = schema.SchemaObjectName + if len(schema.VariableName) > 0 { + out.IstioKind = schema.VariableName } log.Printf("Generating Istio type %s for %s.%s CRD\n", out.IstioKind, out.CrdKind, schema.Group) return out @@ -56,12 +57,12 @@ func main() { tmpl := template.Must(template.ParseFiles(*templateFile)) - // Prepare to generate types for mock schema and all schema listed in model.IstioConfigTypes + // Prepare to generate types for mock schema and all Istio schemas typeList := []ConfigData{ - MakeConfigData(model.MockConfig), + MakeConfigData(schemas.MockConfig), } - for _, schema := range model.IstioConfigTypes { - typeList = append(typeList, MakeConfigData(schema)) + for _, s := range schemas.Istio { + typeList = append(typeList, MakeConfigData(s)) } var buffer bytes.Buffer if err := tmpl.Execute(&buffer, typeList); err != nil { diff --git a/pilot/tools/types.go.tmpl b/pilot/tools/types.go.tmpl index 869a49e79b80..b387177f419c 100644 --- a/pilot/tools/types.go.tmpl +++ b/pilot/tools/types.go.tmpl @@ -19,28 +19,29 @@ package crd // This file contains Go definitions for Custom Resource Definition kinds // to adhere to the idiomatic use of k8s API machinery. // These definitions are synthesized from Istio configuration type descriptors -// as declared in the Pilot config model. +// as declared in the Istio config model. import ( meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "istio.io/istio/pilot/pkg/model" + "istio.io/istio/pkg/config/schema" + "istio.io/istio/pkg/config/schemas" ) type SchemaType struct { - Schema model.ProtoSchema + Schema schema.Instance Object IstioObject Collection IstioObjectList } var KnownTypes = map[string]SchemaType{ -{{range $index, $element := .}}model.{{ .IstioKind }}.Type: { - Schema: model.{{ .IstioKind }}, +{{range $index, $element := .}}schemas.{{ .IstioKind }}.Type: { + Schema: schemas.{{ .IstioKind }}, Object: &{{ .CrdKind }}{ TypeMeta: meta_v1.TypeMeta{ Kind: "{{ .CrdKind }}", - APIVersion: APIVersion(&model.{{ .IstioKind }}), + APIVersion: APIVersion(&schemas.{{ .IstioKind }}), }, }, Collection: &{{ .CrdKind }}List{}, diff --git a/pilot/pkg/model/conversion.go b/pkg/config/schema/instance.go similarity index 50% rename from pilot/pkg/model/conversion.go rename to pkg/config/schema/instance.go index e0298fd6457a..fec4d04f2888 100644 --- a/pilot/pkg/model/conversion.go +++ b/pkg/config/schema/instance.go @@ -1,4 +1,4 @@ -// Copyright 2017 Istio Authors +// Copyright 2018 Istio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package model +package schema import ( "fmt" @@ -20,23 +20,57 @@ import ( "github.com/gogo/protobuf/proto" "github.com/hashicorp/go-multierror" - yaml2 "gopkg.in/yaml.v2" + "gopkg.in/yaml.v2" + "istio.io/istio/pkg/config/validation" "istio.io/istio/pkg/util/protomarshal" ) +// Instance provides description of the configuration schema and its key function +// nolint: maligned +type Instance struct { + // ClusterScoped is true for resource in cluster-level. + ClusterScoped bool + + // VariableName is the name of the generated go variable for this schema. Leave blank to infer from the 'Type' below. + // This field is used to generate Kube CRD types map (pilot/pkg/config/kube/crd/types.go). + VariableName string + + // Type is the config proto type. + Type string + + // Plural is the type in plural. + Plural string + + // Group is the config proto group. + Group string + + // Version is the config proto version. + Version string + + // MessageName refers to the protobuf message type name corresponding to the type + MessageName string + + // Validate configuration as a protobuf message assuming the object is an + // instance of the expected message type + Validate validation.ValidateFunc + + // MCP collection for this configuration resource schema + Collection string +} + // Make creates a new instance of the proto message -func (ps *ProtoSchema) Make() (proto.Message, error) { - pbt := proto.MessageType(ps.MessageName) +func (i *Instance) Make() (proto.Message, error) { + pbt := proto.MessageType(i.MessageName) if pbt == nil { - return nil, fmt.Errorf("unknown type %q", ps.MessageName) + return nil, fmt.Errorf("unknown type %q", i.MessageName) } return reflect.New(pbt.Elem()).Interface().(proto.Message), nil } // FromJSON converts a canonical JSON to a proto message -func (ps *ProtoSchema) FromJSON(js string) (proto.Message, error) { - pb, err := ps.Make() +func (i *Instance) FromJSON(js string) (proto.Message, error) { + pb, err := i.Make() if err != nil { return nil, err } @@ -47,8 +81,8 @@ func (ps *ProtoSchema) FromJSON(js string) (proto.Message, error) { } // FromYAML converts a canonical YAML to a proto message -func (ps *ProtoSchema) FromYAML(yml string) (proto.Message, error) { - pb, err := ps.Make() +func (i *Instance) FromYAML(yml string) (proto.Message, error) { + pb, err := i.Make() if err != nil { return nil, err } @@ -60,13 +94,13 @@ func (ps *ProtoSchema) FromYAML(yml string) (proto.Message, error) { // FromJSONMap converts from a generic map to a proto message using canonical JSON encoding // JSON encoding is specified here: https://developers.google.com/protocol-buffers/docs/proto3#json -func (ps *ProtoSchema) FromJSONMap(data interface{}) (proto.Message, error) { +func (i *Instance) FromJSONMap(data interface{}) (proto.Message, error) { // Marshal to YAML bytes - str, err := yaml2.Marshal(data) + str, err := yaml.Marshal(data) if err != nil { return nil, err } - out, err := ps.FromYAML(string(str)) + out, err := i.FromYAML(string(str)) if err != nil { return nil, multierror.Prefix(err, fmt.Sprintf("YAML decoding error: %v", string(str))) } diff --git a/pkg/config/schema/set.go b/pkg/config/schema/set.go new file mode 100644 index 000000000000..56fd447b9e6d --- /dev/null +++ b/pkg/config/schema/set.go @@ -0,0 +1,82 @@ +// Copyright 2018 Istio 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 schema + +import ( + "fmt" + + "github.com/gogo/protobuf/proto" + "github.com/hashicorp/go-multierror" + + "istio.io/istio/pkg/config/labels" +) + +// Set defines a set of schema Instances. +type Set []Instance + +// Types lists all known types in the schema set +func (s Set) Types() []string { + types := make([]string, 0, len(s)) + for _, t := range s { + types = append(types, t.Type) + } + return types +} + +// GetByType finds a schema by type if it is available +func (s Set) GetByType(name string) (Instance, bool) { + for _, i := range s { + if i.Type == name { + return i, true + } + } + return Instance{}, false +} + +// Validate checks that each name conforms to the spec and has a schema Instance +func (s Set) Validate() error { + var errs error + descriptorTypes := make(map[string]bool) + messages := make(map[string]bool) + clusterMessages := make(map[string]bool) + + for _, v := range s { + if !labels.IsDNS1123Label(v.Type) { + errs = multierror.Append(errs, fmt.Errorf("invalid type: %q", v.Type)) + } + if !labels.IsDNS1123Label(v.Plural) { + errs = multierror.Append(errs, fmt.Errorf("invalid plural: %q", v.Type)) + } + if proto.MessageType(v.MessageName) == nil { + errs = multierror.Append(errs, fmt.Errorf("cannot discover proto message type: %q", v.MessageName)) + } + if _, exists := descriptorTypes[v.Type]; exists { + errs = multierror.Append(errs, fmt.Errorf("duplicate type: %q", v.Type)) + } + descriptorTypes[v.Type] = true + if v.ClusterScoped { + if _, exists := clusterMessages[v.MessageName]; exists { + errs = multierror.Append(errs, fmt.Errorf("duplicate message type: %q", v.MessageName)) + } + clusterMessages[v.MessageName] = true + } else { + if _, exists := messages[v.MessageName]; exists { + errs = multierror.Append(errs, fmt.Errorf("duplicate message type: %q", v.MessageName)) + } + messages[v.MessageName] = true + } + } + return errs +} diff --git a/pkg/config/schemas/mock.go b/pkg/config/schemas/mock.go new file mode 100644 index 000000000000..6b74be8f0005 --- /dev/null +++ b/pkg/config/schemas/mock.go @@ -0,0 +1,41 @@ +// Copyright 2018 Istio 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 schemas + +import ( + "errors" + + "github.com/gogo/protobuf/proto" + + "istio.io/istio/pkg/config/schema" + "istio.io/istio/pkg/test/config" +) + +var ( + // MockConfig is used purely for testing + MockConfig = schema.Instance{ + Type: "mock-config", + Plural: "mock-configs", + Group: "test", + Version: "v1", + MessageName: "test.MockConfig", + Validate: func(name, namespace string, msg proto.Message) error { + if msg.(*config.MockConfig).Key == "" { + return errors.New("empty key") + } + return nil + }, + } +) diff --git a/pkg/config/schemas/schemas.go b/pkg/config/schemas/schemas.go new file mode 100644 index 000000000000..5934f165f5d4 --- /dev/null +++ b/pkg/config/schemas/schemas.go @@ -0,0 +1,231 @@ +// Copyright 2018 Istio 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 schemas + +import ( + "istio.io/istio/galley/pkg/metadata" + "istio.io/istio/pkg/config/schema" + "istio.io/istio/pkg/config/validation" +) + +const ( + // Default API version of an Istio config proto message. + istioAPIVersion = "v1alpha2" +) + +var ( + // VirtualService describes v1alpha3 route rules + VirtualService = schema.Instance{ + Type: "virtual-service", + Plural: "virtual-services", + Group: "networking", + Version: "v1alpha3", + MessageName: "istio.networking.v1alpha3.VirtualService", + Validate: validation.ValidateVirtualService, + Collection: metadata.IstioNetworkingV1alpha3Virtualservices.Collection.String(), + } + + // Gateway describes a gateway (how a proxy is exposed on the network) + Gateway = schema.Instance{ + Type: "gateway", + Plural: "gateways", + Group: "networking", + Version: "v1alpha3", + MessageName: "istio.networking.v1alpha3.Gateway", + Validate: validation.ValidateGateway, + Collection: metadata.IstioNetworkingV1alpha3Gateways.Collection.String(), + } + + // ServiceEntry describes service entries + ServiceEntry = schema.Instance{ + Type: "service-entry", + Plural: "service-entries", + Group: "networking", + Version: "v1alpha3", + MessageName: "istio.networking.v1alpha3.ServiceEntry", + Validate: validation.ValidateServiceEntry, + Collection: metadata.IstioNetworkingV1alpha3Serviceentries.Collection.String(), + } + + // DestinationRule describes destination rules + DestinationRule = schema.Instance{ + Type: "destination-rule", + Plural: "destination-rules", + Group: "networking", + Version: "v1alpha3", + MessageName: "istio.networking.v1alpha3.DestinationRule", + Validate: validation.ValidateDestinationRule, + Collection: metadata.IstioNetworkingV1alpha3Destinationrules.Collection.String(), + } + + // EnvoyFilter describes additional envoy filters to be inserted by Pilot + EnvoyFilter = schema.Instance{ + Type: "envoy-filter", + Plural: "envoy-filters", + Group: "networking", + Version: "v1alpha3", + MessageName: "istio.networking.v1alpha3.EnvoyFilter", + Validate: validation.ValidateEnvoyFilter, + Collection: metadata.IstioNetworkingV1alpha3Envoyfilters.Collection.String(), + } + + // Sidecar describes the listeners associated with sidecars in a namespace + Sidecar = schema.Instance{ + Type: "sidecar", + Plural: "sidecars", + Group: "networking", + Version: "v1alpha3", + MessageName: "istio.networking.v1alpha3.Sidecar", + Validate: validation.ValidateSidecar, + Collection: metadata.IstioNetworkingV1alpha3Sidecars.Collection.String(), + } + + // HTTPAPISpec describes an HTTP API specification. + HTTPAPISpec = schema.Instance{ + Type: "http-api-spec", + Plural: "http-api-specs", + Group: "config", + Version: istioAPIVersion, + MessageName: "istio.mixer.v1.config.client.HTTPAPISpec", + Validate: validation.ValidateHTTPAPISpec, + Collection: metadata.IstioConfigV1alpha2Httpapispecs.Collection.String(), + } + + // HTTPAPISpecBinding describes an HTTP API specification binding. + HTTPAPISpecBinding = schema.Instance{ + Type: "http-api-spec-binding", + Plural: "http-api-spec-bindings", + Group: "config", + Version: istioAPIVersion, + MessageName: "istio.mixer.v1.config.client.HTTPAPISpecBinding", + Validate: validation.ValidateHTTPAPISpecBinding, + Collection: metadata.IstioConfigV1alpha2Httpapispecbindings.Collection.String(), + } + + // QuotaSpec describes an Quota specification. + QuotaSpec = schema.Instance{ + Type: "quota-spec", + Plural: "quota-specs", + Group: "config", + Version: istioAPIVersion, + MessageName: "istio.mixer.v1.config.client.QuotaSpec", + Validate: validation.ValidateQuotaSpec, + Collection: metadata.IstioMixerV1ConfigClientQuotaspecs.Collection.String(), + } + + // QuotaSpecBinding describes an Quota specification binding. + QuotaSpecBinding = schema.Instance{ + Type: "quota-spec-binding", + Plural: "quota-spec-bindings", + Group: "config", + Version: istioAPIVersion, + MessageName: "istio.mixer.v1.config.client.QuotaSpecBinding", + Validate: validation.ValidateQuotaSpecBinding, + Collection: metadata.IstioMixerV1ConfigClientQuotaspecbindings.Collection.String(), + } + + // AuthenticationPolicy describes an authentication policy. + AuthenticationPolicy = schema.Instance{ + VariableName: "AuthenticationPolicy", + Type: "policy", + Plural: "policies", + Group: "authentication", + Version: "v1alpha1", + MessageName: "istio.authentication.v1alpha1.Policy", + Validate: validation.ValidateAuthenticationPolicy, + Collection: metadata.IstioAuthenticationV1alpha1Policies.Collection.String(), + } + + // AuthenticationMeshPolicy describes an authentication policy at mesh level. + AuthenticationMeshPolicy = schema.Instance{ + ClusterScoped: true, + VariableName: "AuthenticationMeshPolicy", + Type: "mesh-policy", + Plural: "mesh-policies", + Group: "authentication", + Version: "v1alpha1", + MessageName: "istio.authentication.v1alpha1.Policy", + Validate: validation.ValidateAuthenticationPolicy, + Collection: metadata.IstioAuthenticationV1alpha1Meshpolicies.Collection.String(), + } + + // ServiceRole describes an RBAC service role. + ServiceRole = schema.Instance{ + Type: "service-role", + Plural: "service-roles", + Group: "rbac", + Version: "v1alpha1", + MessageName: "istio.rbac.v1alpha1.ServiceRole", + Validate: validation.ValidateServiceRole, + Collection: metadata.IstioRbacV1alpha1Serviceroles.Collection.String(), + } + + // ServiceRoleBinding describes an RBAC service role. + ServiceRoleBinding = schema.Instance{ + ClusterScoped: false, + Type: "service-role-binding", + Plural: "service-role-bindings", + Group: "rbac", + Version: "v1alpha1", + MessageName: "istio.rbac.v1alpha1.ServiceRoleBinding", + Validate: validation.ValidateServiceRoleBinding, + Collection: metadata.IstioRbacV1alpha1Servicerolebindings.Collection.String(), + } + + // RbacConfig describes the mesh level RBAC config. + // Deprecated: use ClusterRbacConfig instead. + // See https://github.com/istio/istio/issues/8825 for more details. + RbacConfig = schema.Instance{ + Type: "rbac-config", + Plural: "rbac-configs", + Group: "rbac", + Version: "v1alpha1", + MessageName: "istio.rbac.v1alpha1.RbacConfig", + Validate: validation.ValidateRbacConfig, + Collection: metadata.IstioRbacV1alpha1Rbacconfigs.Collection.String(), + } + + // ClusterRbacConfig describes the cluster level RBAC config. + ClusterRbacConfig = schema.Instance{ + ClusterScoped: true, + Type: "cluster-rbac-config", + Plural: "cluster-rbac-configs", + Group: "rbac", + Version: "v1alpha1", + MessageName: "istio.rbac.v1alpha1.RbacConfig", + Validate: validation.ValidateClusterRbacConfig, + Collection: metadata.IstioRbacV1alpha1Clusterrbacconfigs.Collection.String(), + } + + // Istio lists all Istio schemas. + Istio = schema.Set{ + VirtualService, + Gateway, + ServiceEntry, + DestinationRule, + EnvoyFilter, + Sidecar, + HTTPAPISpec, + HTTPAPISpecBinding, + QuotaSpec, + QuotaSpecBinding, + AuthenticationPolicy, + AuthenticationMeshPolicy, + ServiceRole, + ServiceRoleBinding, + RbacConfig, + ClusterRbacConfig, + } +) diff --git a/pkg/config/schemas/schemas_test.go b/pkg/config/schemas/schemas_test.go new file mode 100644 index 000000000000..dd16f0f99ec8 --- /dev/null +++ b/pkg/config/schemas/schemas_test.go @@ -0,0 +1,531 @@ +// Copyright 2018 Istio 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 schemas_test + +import ( + "fmt" + "reflect" + "strings" + "testing" + + "github.com/davecgh/go-spew/spew" + "github.com/gogo/protobuf/proto" + + meshAPI "istio.io/api/mesh/v1alpha1" + mixerAPI "istio.io/api/mixer/v1" + mixerClientAPI "istio.io/api/mixer/v1/config/client" + networkingAPI "istio.io/api/networking/v1alpha3" + + "istio.io/istio/pkg/config/labels" + "istio.io/istio/pkg/config/schema" + "istio.io/istio/pkg/config/schemas" + "istio.io/istio/pkg/test/config" + "istio.io/istio/pkg/util/protomarshal" +) + +const ( + // Config name for testing + someName = "foo" + // Config namespace for testing. + someNamespace = "bar" +) + +func TestApplyJSON(t *testing.T) { + cases := []struct { + in string + want *meshAPI.MeshConfig + wantErr bool + }{ + { + in: `{"enableTracing": true}`, + want: &meshAPI.MeshConfig{EnableTracing: true}, + }, + { + in: `{"enableTracing": true, "unknownField": "unknownValue"}`, + want: &meshAPI.MeshConfig{EnableTracing: true}, + }, + } + for i, c := range cases { + t.Run(fmt.Sprintf("[%v]", i), func(tt *testing.T) { + var got meshAPI.MeshConfig + err := protomarshal.ApplyJSON(c.in, &got) + if err != nil { + if !c.wantErr { + tt.Fatalf("got unexpected error: %v", err) + } + } else { + if c.wantErr { + tt.Fatal("unexpected success, expected error") + } + if !reflect.DeepEqual(&got, c.want) { + tt.Fatalf("ApplyJSON(%v): got %v want %v", c.in, &got, c.want) + } + } + }) + } +} + +func TestGogoInstanceConversions(t *testing.T) { + msg := &mixerClientAPI.HTTPAPISpec{ + Attributes: &mixerAPI.Attributes{ + Attributes: map[string]*mixerAPI.Attributes_AttributeValue{ + "api.service": { + Value: &mixerAPI.Attributes_AttributeValue_StringValue{ + StringValue: "my-service", + }, + }, + "api.version": { + Value: &mixerAPI.Attributes_AttributeValue_StringValue{ + StringValue: "1.0.0", + }, + }, + }, + }, + Patterns: []*mixerClientAPI.HTTPAPISpecPattern{ + { + Attributes: &mixerAPI.Attributes{ + Attributes: map[string]*mixerAPI.Attributes_AttributeValue{ + "api.operation": { + Value: &mixerAPI.Attributes_AttributeValue_StringValue{ + StringValue: "createPet", + }, + }, + }, + }, + HttpMethod: "POST", + Pattern: &mixerClientAPI.HTTPAPISpecPattern_UriTemplate{ + UriTemplate: "/pet/{id}", + }, + }, + }, + ApiKeys: []*mixerClientAPI.APIKey{ + { + Key: &mixerClientAPI.APIKey_Query{ + Query: "api_key", + }, + }, + }, + } + + wantYAML := `apiKeys: +- query: api_key +attributes: + attributes: + api.service: + stringValue: my-service + api.version: + stringValue: 1.0.0 +patterns: +- attributes: + attributes: + api.operation: + stringValue: createPet + httpMethod: POST + uriTemplate: /pet/{id} +` + + wantJSON := ` +{ + "attributes": { + "attributes": { + "api.service": { + "stringValue": "my-service" + }, + "api.version": { + "stringValue": "1.0.0" + } + } + }, + "patterns": [ + { + "attributes": { + "attributes": { + "api.operation": { + "stringValue": "createPet" + } + } + }, + "httpMethod": "POST", + "uriTemplate": "/pet/{id}" + } + ], + "apiKeys": [ + { + "query": "api_key" + } + ] +} +` + wantJSONMap := map[string]interface{}{ + "attributes": map[string]interface{}{ + "attributes": map[string]interface{}{ + "api.service": map[string]interface{}{ + "stringValue": "my-service", + }, + "api.version": map[string]interface{}{ + "stringValue": "1.0.0", + }, + }, + }, + "patterns": []interface{}{ + map[string]interface{}{ + "httpMethod": "POST", + "uriTemplate": "/pet/{id}", + "attributes": map[string]interface{}{ + "attributes": map[string]interface{}{ + "api.operation": map[string]interface{}{ + "stringValue": "createPet", + }, + }, + }, + }, + }, + "apiKeys": []interface{}{ + map[string]interface{}{ + "query": "api_key", + }, + }, + } + + gotJSON, err := protomarshal.ToJSON(msg) + if err != nil { + t.Errorf("ToJSON failed: %v", err) + } + if gotJSON != strings.Join(strings.Fields(wantJSON), "") { + t.Errorf("ToJSON failed: \ngot %s, \nwant %s", gotJSON, strings.Join(strings.Fields(wantJSON), "")) + } + + if _, err = protomarshal.ToJSON(nil); err == nil { + t.Error("should produce an error") + } + + gotFromJSON, err := schemas.HTTPAPISpec.FromJSON(wantJSON) + if err != nil { + t.Errorf("FromJSON failed: %v", err) + } + if !reflect.DeepEqual(gotFromJSON, msg) { + t.Errorf("FromYAML failed: got %+v want %+v", spew.Sdump(gotFromJSON), spew.Sdump(msg)) + } + + gotYAML, err := protomarshal.ToYAML(msg) + if err != nil { + t.Errorf("ToYAML failed: %v", err) + } + if !reflect.DeepEqual(gotYAML, wantYAML) { + t.Errorf("ToYAML failed: \ngot %+v \nwant %+v", spew.Sdump(gotYAML), spew.Sdump(wantYAML)) + } + + if _, err = protomarshal.ToYAML(nil); err == nil { + t.Error("should produce an error") + } + + gotFromYAML, err := schemas.HTTPAPISpec.FromYAML(wantYAML) + if err != nil { + t.Errorf("FromYAML failed: %v", err) + } + if !reflect.DeepEqual(gotFromYAML, msg) { + t.Errorf("FromYAML failed: got %+v want %+v", spew.Sdump(gotFromYAML), spew.Sdump(msg)) + } + + if _, err = schemas.HTTPAPISpec.FromYAML(":"); err == nil { + t.Errorf("should produce an error") + } + + gotJSONMap, err := protomarshal.ToJSONMap(msg) + if err != nil { + t.Errorf("ToJSONMap failed: %v", err) + } + if !reflect.DeepEqual(gotJSONMap, wantJSONMap) { + t.Errorf("ToJSONMap failed: \ngot %vwant %v", spew.Sdump(gotJSONMap), spew.Sdump(wantJSONMap)) + } + + if _, err = protomarshal.ToJSONMap(nil); err == nil { + t.Error("should produce an error") + } + + gotFromJSONMap, err := schemas.HTTPAPISpec.FromJSONMap(wantJSONMap) + if err != nil { + t.Errorf("FromJSONMap failed: %v", err) + } + if !reflect.DeepEqual(gotFromJSONMap, msg) { + t.Errorf("FromJSONMap failed: got %+v want %+v", spew.Sdump(gotFromJSONMap), spew.Sdump(msg)) + } + + if _, err = schemas.HTTPAPISpec.FromJSONMap(1); err == nil { + t.Error("should produce an error") + } + if _, err = schemas.HTTPAPISpec.FromJSON(":"); err == nil { + t.Errorf("should produce an error") + } +} + +func TestInstanceConversions(t *testing.T) { + destinationRuleSchema := &schema.Instance{MessageName: schemas.DestinationRule.MessageName} + + msg := &networkingAPI.DestinationRule{ + Host: "something.svc.local", + TrafficPolicy: &networkingAPI.TrafficPolicy{ + LoadBalancer: &networkingAPI.LoadBalancerSettings{ + LbPolicy: &networkingAPI.LoadBalancerSettings_Simple{}, + }, + }, + Subsets: []*networkingAPI.Subset{ + { + Name: "foo", + Labels: map[string]string{ + "test": "label", + }, + }, + }, + } + + wantJSON := ` + { + "host":"something.svc.local", + "trafficPolicy": { + "loadBalancer":{"simple":"ROUND_ROBIN"} + }, + "subsets": [ + {"name":"foo","labels":{"test":"label"}} + ] + }` + + wantYAML := `host: something.svc.local +subsets: +- labels: + test: label + name: foo +trafficPolicy: + loadBalancer: + simple: ROUND_ROBIN +` + + wantJSONMap := map[string]interface{}{ + "host": "something.svc.local", + "trafficPolicy": map[string]interface{}{ + "loadBalancer": map[string]interface{}{ + "simple": "ROUND_ROBIN", + }, + }, + "subsets": []interface{}{ + map[string]interface{}{ + "name": "foo", + "labels": map[string]interface{}{ + "test": "label", + }, + }, + }, + } + + badSchema := &schema.Instance{MessageName: "bad-name"} + if _, err := badSchema.FromYAML(wantYAML); err == nil { + t.Errorf("FromYAML should have failed using Schema with bad MessageName") + } + + gotJSON, err := protomarshal.ToJSON(msg) + if err != nil { + t.Errorf("ToJSON failed: %v", err) + } + if gotJSON != strings.Join(strings.Fields(wantJSON), "") { + t.Errorf("ToJSON failed: got %s, want %s", gotJSON, wantJSON) + } + + if _, err = protomarshal.ToJSON(nil); err == nil { + t.Error("should produce an error") + } + + gotFromJSON, err := destinationRuleSchema.FromJSON(wantJSON) + if err != nil { + t.Errorf("FromJSON failed: %v", err) + } + if !reflect.DeepEqual(gotFromJSON, msg) { + t.Errorf("FromYAML failed: got %+v want %+v", spew.Sdump(gotFromJSON), spew.Sdump(msg)) + } + + gotYAML, err := protomarshal.ToYAML(msg) + if err != nil { + t.Errorf("ToYAML failed: %v", err) + } + if !reflect.DeepEqual(gotYAML, wantYAML) { + t.Errorf("ToYAML failed: got %+v want %+v", spew.Sdump(gotYAML), spew.Sdump(wantYAML)) + } + + if _, err = protomarshal.ToYAML(nil); err == nil { + t.Error("should produce an error") + } + + gotFromYAML, err := destinationRuleSchema.FromYAML(wantYAML) + if err != nil { + t.Errorf("FromYAML failed: %v", err) + } + if !reflect.DeepEqual(gotFromYAML, msg) { + t.Errorf("FromYAML failed: got %+v want %+v", spew.Sdump(gotFromYAML), spew.Sdump(msg)) + } + + if _, err = destinationRuleSchema.FromYAML(":"); err == nil { + t.Errorf("should produce an error") + } + + gotJSONMap, err := protomarshal.ToJSONMap(msg) + if err != nil { + t.Errorf("ToJSONMap failed: %v", err) + } + if !reflect.DeepEqual(gotJSONMap, wantJSONMap) { + t.Errorf("ToJSONMap failed: \ngot %vwant %v", spew.Sdump(gotJSONMap), spew.Sdump(wantJSONMap)) + } + + if _, err = protomarshal.ToJSONMap(nil); err == nil { + t.Error("should produce an error") + } + + gotFromJSONMap, err := destinationRuleSchema.FromJSONMap(wantJSONMap) + if err != nil { + t.Errorf("FromJSONMap failed: %v", err) + } + if !reflect.DeepEqual(gotFromJSONMap, msg) { + t.Errorf("FromJSONMap failed: got %+v want %+v", spew.Sdump(gotFromJSONMap), spew.Sdump(msg)) + } + + if _, err = destinationRuleSchema.FromJSONMap(1); err == nil { + t.Error("should produce an error") + } + if _, err = destinationRuleSchema.FromJSON(":"); err == nil { + t.Errorf("should produce an error") + } +} + +func TestSetValidate(t *testing.T) { + badLabel := strings.Repeat("a", labels.DNS1123LabelMaxLength+1) + goodLabel := strings.Repeat("a", labels.DNS1123LabelMaxLength-1) + + cases := []struct { + name string + descriptor schema.Set + wantErr bool + }{{ + name: "Valid ConfigDescriptor (IstioConfig)", + descriptor: schemas.Istio, + wantErr: false, + }, { + name: "Invalid DNS11234Label in ConfigDescriptor", + descriptor: schema.Set{schema.Instance{ + Type: badLabel, + MessageName: "istio.networking.v1alpha3.Gateway", + }}, + wantErr: true, + }, { + name: "Bad MessageName in ProtoMessage", + descriptor: schema.Set{schema.Instance{ + Type: goodLabel, + MessageName: "nonexistent", + }}, + wantErr: true, + }, { + name: "Missing key function", + descriptor: schema.Set{schema.Instance{ + Type: "service-entry", + MessageName: "istio.networking.v1alpha3.ServiceEtrny", + }}, + wantErr: true, + }, { + name: "Duplicate type and message", + descriptor: schema.Set{schemas.DestinationRule, schemas.DestinationRule}, + wantErr: true, + }} + + for _, c := range cases { + if err := c.descriptor.Validate(); (err != nil) != c.wantErr { + t.Errorf("%v failed: got %v but wantErr=%v", c.name, err, c.wantErr) + } + } +} + +// ValidateConfig ensures that the config object is well-defined +// TODO: also check name and namespace +func setValidateConfig(descriptor schema.Set, typ string, obj interface{}) error { + if obj == nil { + return fmt.Errorf("invalid nil configuration object") + } + + t, ok := descriptor.GetByType(typ) + if !ok { + return fmt.Errorf("undeclared type: %q", typ) + } + + v, ok := obj.(proto.Message) + if !ok { + return fmt.Errorf("cannot cast to a proto message") + } + + if proto.MessageName(v) != t.MessageName { + return fmt.Errorf("mismatched message type %q and type %q", + proto.MessageName(v), t.MessageName) + } + return t.Validate(someName, someNamespace, v) +} + +func TestSetValidateConfig(t *testing.T) { + cases := []struct { + name string + typ string + config interface{} + wantErr bool + }{ + { + name: "bad configuration object", + typ: "policy", + config: nil, + wantErr: true, + }, + { + name: "undeclared kind", + typ: "special-type", + config: nil, + wantErr: true, + }, + { + name: "non-proto object configuration", + typ: "policy", + config: "non-proto objection configuration", + wantErr: true, + }, + { + name: "message type and kind mismatch", + typ: "policy", + config: schemas.ServiceEntry, + wantErr: true, + }, + { + name: "Schema validation1", + typ: "service-entry", + config: schemas.ServiceEntry, + wantErr: true, + }, + { + name: "Successful validation", + typ: schemas.MockConfig.Type, + config: &config.MockConfig{Key: "test"}, + wantErr: false, + }, + } + + types := append(schemas.Istio, schemas.MockConfig) + + for _, c := range cases { + if err := setValidateConfig(types, c.typ, c.config); (err != nil) != c.wantErr { + t.Errorf("%v failed: got error=%v but wantErr=%v", c.name, err, c.wantErr) + } + } +} diff --git a/tests/e2e/tests/controller/controller_test.go b/tests/e2e/tests/controller/controller_test.go index ada66df8c6c1..239a0221b909 100644 --- a/tests/e2e/tests/controller/controller_test.go +++ b/tests/e2e/tests/controller/controller_test.go @@ -22,13 +22,15 @@ import ( "testing" "time" - multierror "github.com/hashicorp/go-multierror" + "github.com/hashicorp/go-multierror" crd "istio.io/istio/pilot/pkg/config/kube/crd/controller" "istio.io/istio/pilot/pkg/model" kube "istio.io/istio/pilot/pkg/serviceregistry/kube/controller" "istio.io/istio/pilot/test/mock" "istio.io/istio/pilot/test/util" + "istio.io/istio/pkg/config/schema" + "istio.io/istio/pkg/config/schemas" ) // Package controller tests the pilot controller using a k8s cluster or standalone apiserver. @@ -43,7 +45,7 @@ const ( resync = 1 * time.Second ) -func makeClient(desc model.ConfigDescriptor) (*crd.Client, error) { +func makeClient(desc schema.Set) (*crd.Client, error) { cl, err := crd.NewClient("", "", desc, "") if err != nil { return nil, err @@ -111,7 +113,7 @@ func makeTempClient(t *testing.T) (*crd.Client, string, func()) { if err != nil { t.Fatal(err.Error()) } - desc := append(model.IstioConfigTypes, mock.Types...) + desc := append(schemas.Istio, mock.Types...) cl, err := makeClient(desc) if err != nil { t.Fatalf(err.Error()) @@ -154,7 +156,7 @@ func istioConfig(t *testing.T, client model.ConfigStore, ns string) { } func TestUnknownConfig(t *testing.T) { - desc := model.ConfigDescriptor{model.ProtoSchema{ + desc := schema.Set{schema.Instance{ Type: "unknown-config", Plural: "unknown-configs", Group: "test", diff --git a/tests/e2e/tests/pilot/cloudfoundry/copilot_test.go b/tests/e2e/tests/pilot/cloudfoundry/copilot_test.go index 84a8117883ef..5332ba6bcaba 100644 --- a/tests/e2e/tests/pilot/cloudfoundry/copilot_test.go +++ b/tests/e2e/tests/pilot/cloudfoundry/copilot_test.go @@ -30,12 +30,13 @@ import ( "github.com/onsi/gomega" "istio.io/istio/pilot/pkg/features" + "istio.io/istio/pkg/config/schemas" meshconfig "istio.io/api/mesh/v1alpha1" networking "istio.io/api/networking/v1alpha3" + mixerEnv "istio.io/istio/mixer/test/client/env" "istio.io/istio/pilot/pkg/bootstrap" - "istio.io/istio/pilot/pkg/model" srmemory "istio.io/istio/pilot/pkg/serviceregistry/memory" "istio.io/istio/pkg/mcp/snapshot" "istio.io/istio/pkg/mcp/source" @@ -97,25 +98,25 @@ func TestWildcardHostEdgeRouterWithMockCopilot(t *testing.T) { sn := snapshot.NewInMemoryBuilder() - for _, m := range model.IstioConfigTypes { + for _, m := range schemas.Istio { sn.SetVersion(m.Collection, "v0") } - sn.SetEntry(model.Gateway.Collection, "cloudfoundry-ingress", "v1", fakeCreateTime2, nil, nil, gateway) + sn.SetEntry(schemas.Gateway.Collection, "cloudfoundry-ingress", "v1", fakeCreateTime2, nil, nil, gateway) - sn.SetEntry(model.VirtualService.Collection, "vs-1", "v1", fakeCreateTime2, nil, nil, + sn.SetEntry(schemas.VirtualService.Collection, "vs-1", "v1", fakeCreateTime2, nil, nil, virtualService(8060, "cloudfoundry-ingress", "/some/path", cfRouteOne, subsetOne)) - sn.SetEntry(model.VirtualService.Collection, "vs-2", "v1", fakeCreateTime2, nil, nil, + sn.SetEntry(schemas.VirtualService.Collection, "vs-2", "v1", fakeCreateTime2, nil, nil, virtualService(8070, "cloudfoundry-ingress", "", cfRouteTwo, subsetTwo)) - sn.SetEntry(model.DestinationRule.Collection, "dr-1", "v1", fakeCreateTime2, nil, nil, + sn.SetEntry(schemas.DestinationRule.Collection, "dr-1", "v1", fakeCreateTime2, nil, nil, destinationRule(cfRouteOne, subsetOne)) - sn.SetEntry(model.DestinationRule.Collection, "dr-2", "v1", fakeCreateTime2, nil, nil, + sn.SetEntry(schemas.DestinationRule.Collection, "dr-2", "v1", fakeCreateTime2, nil, nil, destinationRule(cfRouteTwo, subsetTwo)) - sn.SetEntry(model.ServiceEntry.Collection, "se-1", "v1", fakeCreateTime2, nil, nil, + sn.SetEntry(schemas.ServiceEntry.Collection, "se-1", "v1", fakeCreateTime2, nil, nil, serviceEntry(8060, app1ListenPort, nil, cfRouteOne, subsetOne)) - sn.SetEntry(model.ServiceEntry.Collection, "se-2", "v1", fakeCreateTime2, nil, nil, + sn.SetEntry(schemas.ServiceEntry.Collection, "se-2", "v1", fakeCreateTime2, nil, nil, serviceEntry(8070, app2ListenPort, nil, cfRouteTwo, subsetTwo)) copilotMCPServer.Cache.SetSnapshot(groups.Default, sn.Build()) @@ -206,10 +207,10 @@ func TestWildcardHostSidecarRouterWithMockCopilot(t *testing.T) { defer copilotMCPServer.Close() sn := snapshot.NewInMemoryBuilder() - for _, m := range model.IstioConfigTypes { + for _, m := range schemas.Istio { sn.SetVersion(m.Collection, "v0") } - sn.SetEntry(model.ServiceEntry.Collection, "se-1", "v1", fakeCreateTime2, nil, nil, + sn.SetEntry(schemas.ServiceEntry.Collection, "se-1", "v1", fakeCreateTime2, nil, nil, serviceEntry(sidecarServicePort, app3ListenPort, []string{"127.1.1.1"}, cfInternalRoute, subsetOne)) copilotMCPServer.Cache.SetSnapshot(groups.Default, sn.Build()) @@ -253,8 +254,8 @@ func TestWildcardHostSidecarRouterWithMockCopilot(t *testing.T) { } func startMCPCopilot() (*mcptesting.Server, error) { - collections := make([]string, len(model.IstioConfigTypes)) - for i, m := range model.IstioConfigTypes { + collections := make([]string, len(schemas.Istio)) + for i, m := range schemas.Istio { collections[i] = m.Collection } diff --git a/tests/e2e/tests/pilot/mcp_test.go b/tests/e2e/tests/pilot/mcp_test.go index 13fbbe538b33..62883386d86b 100644 --- a/tests/e2e/tests/pilot/mcp_test.go +++ b/tests/e2e/tests/pilot/mcp_test.go @@ -27,10 +27,11 @@ import ( meshconfig "istio.io/api/mesh/v1alpha1" networking "istio.io/api/networking/v1alpha3" + mixerEnv "istio.io/istio/mixer/test/client/env" "istio.io/istio/pilot/pkg/bootstrap" - "istio.io/istio/pilot/pkg/model" srmemory "istio.io/istio/pilot/pkg/serviceregistry/memory" + "istio.io/istio/pkg/config/schemas" "istio.io/istio/pkg/mcp/source" "istio.io/istio/pkg/mcp/testing/groups" "istio.io/istio/tests/util" @@ -67,12 +68,12 @@ func TestPilotMCPClient(t *testing.T) { defer mcpServer.Close() sn := snapshot.NewInMemoryBuilder() - for _, m := range model.IstioConfigTypes { + for _, m := range schemas.Istio { sn.SetVersion(m.Collection, "v0") } - sn.SetEntry(model.Gateway.Collection, "some-name", "v1", fakeCreateTime2, nil, nil, firstGateway) - sn.SetEntry(model.Gateway.Collection, "some-other name", "v1", fakeCreateTime2, nil, nil, secondGateway) + sn.SetEntry(schemas.Gateway.Collection, "some-name", "v1", fakeCreateTime2, nil, nil, firstGateway) + sn.SetEntry(schemas.Gateway.Collection, "some-other name", "v1", fakeCreateTime2, nil, nil, secondGateway) mcpServer.Cache.SetSnapshot(groups.Default, sn.Build()) @@ -96,8 +97,8 @@ func TestPilotMCPClient(t *testing.T) { } func runMcpServer() (*mcptesting.Server, error) { - collections := make([]string, len(model.IstioConfigTypes)) - for i, m := range model.IstioConfigTypes { + collections := make([]string, len(schemas.Istio)) + for i, m := range schemas.Istio { collections[i] = m.Collection } return mcptesting.NewServer(0, source.CollectionOptionsFromSlice(collections)) diff --git a/tests/e2e/tests/pilot/performance/serviceentry_test.go b/tests/e2e/tests/pilot/performance/serviceentry_test.go index ffddf70efd62..a2fe31f1ff61 100644 --- a/tests/e2e/tests/pilot/performance/serviceentry_test.go +++ b/tests/e2e/tests/pilot/performance/serviceentry_test.go @@ -34,10 +34,11 @@ import ( mcp "istio.io/api/mcp/v1alpha1" meshconfig "istio.io/api/mesh/v1alpha1" networking "istio.io/api/networking/v1alpha3" + mixerEnv "istio.io/istio/mixer/test/client/env" "istio.io/istio/pilot/pkg/bootstrap" - "istio.io/istio/pilot/pkg/model" "istio.io/istio/pkg/adsc" + "istio.io/istio/pkg/config/schemas" "istio.io/istio/pkg/mcp/snapshot" "istio.io/istio/pkg/mcp/source" mcptest "istio.io/istio/pkg/mcp/testing" @@ -128,15 +129,15 @@ func runSnapshot(mcpServer *mcptest.Server, quit chan struct{}, t *testing.T) { case <-configInterval.C: v++ version := strconv.Itoa(v) - for _, m := range model.IstioConfigTypes { - if m.MessageName == model.ServiceEntry.MessageName { - b.Set(model.ServiceEntry.Collection, version, generateServiceEntries(t)) - } else if m.MessageName == model.Gateway.MessageName { + for _, m := range schemas.Istio { + if m.MessageName == schemas.ServiceEntry.MessageName { + b.Set(schemas.ServiceEntry.Collection, version, generateServiceEntries(t)) + } else if m.MessageName == schemas.Gateway.MessageName { gw, err := generateGateway() if err != nil { t.Log(err) } - b.Set(model.Gateway.Collection, version, gw) + b.Set(schemas.Gateway.Collection, version, gw) } else { b.Set(m.Collection, version, []*mcp.Resource{}) } @@ -192,8 +193,8 @@ func adsConnectAndWait(n int, pilotAddr string, t *testing.T) (adscs []*adsc.ADS } func runMcpServer() (*mcptest.Server, error) { - collections := make([]string, len(model.IstioConfigTypes)) - for i, m := range model.IstioConfigTypes { + collections := make([]string, len(schemas.Istio)) + for i, m := range schemas.Istio { collections[i] = m.Collection } return mcptest.NewServer(0, source.CollectionOptionsFromSlice(collections))