diff --git a/admiral/pkg/clusters/configwriter.go b/admiral/pkg/clusters/configwriter.go index 012718d7..ed5a1b9d 100644 --- a/admiral/pkg/clusters/configwriter.go +++ b/admiral/pkg/clusters/configwriter.go @@ -15,7 +15,13 @@ import ( networkingV1Alpha3 "istio.io/api/networking/v1alpha3" ) -const typeLabel = "type" +const ( + typeLabel = "type" + testServiceKey = "test" + defaultServiceKey = "default" + canaryPrefix = "canary" + previewPrefix = "preview" +) // IstioSEBuilder is an interface to construct Service Entry objects // from IdentityConfig objects. It can construct multiple Service Entries @@ -35,7 +41,7 @@ type ServiceEntryBuilder struct { func (b *ServiceEntryBuilder) BuildServiceEntriesFromIdentityConfig(ctxLogger *logrus.Entry, identityConfig registry.IdentityConfig) ([]*networkingV1Alpha3.ServiceEntry, error) { var ( identity = identityConfig.IdentityName - seMap = map[string]*networkingV1Alpha3.ServiceEntry{} + seMap = map[string]map[string]*networkingV1Alpha3.ServiceEntry{} serviceEntries = []*networkingV1Alpha3.ServiceEntry{} start = time.Now() err error @@ -58,37 +64,61 @@ func (b *ServiceEntryBuilder) BuildServiceEntriesFromIdentityConfig(ctxLogger *l serverCluster := identityConfigCluster.Name for _, identityConfigEnvironment := range identityConfigCluster.Environment { env := identityConfigEnvironment.Name - var tmpSe *networkingV1Alpha3.ServiceEntry - start = time.Now() - endpoints, err := getServiceEntryEndpoints(ctxLogger, b.ClientCluster, serverCluster, ingressEndpoints, identityConfigEnvironment) - util.LogElapsedTimeSince("getServiceEntryEndpoint", identity, env, b.ClientCluster, start) - if err != nil { - return serviceEntries, err + if len(identityConfigEnvironment.Services) == 0 { + return serviceEntries, fmt.Errorf("there were no services for the asset in namespace %s on cluster %s", identityConfigEnvironment.Namespace, serverCluster) } - if se, ok := seMap[env]; !ok { - tmpSe = &networkingV1Alpha3.ServiceEntry{ - Hosts: []string{common.GetCnameVal([]string{env, strings.ToLower(identity), common.GetHostnameSuffix()})}, - Ports: identityConfigEnvironment.Ports, - Location: networkingV1Alpha3.ServiceEntry_MESH_INTERNAL, - Resolution: networkingV1Alpha3.ServiceEntry_DNS, - SubjectAltNames: []string{common.SpiffePrefix + common.GetSANPrefix() + common.Slash + identity}, - Endpoints: endpoints, - ExportTo: dependentNamespaces, + + start = time.Now() + meshHosts := getMeshHosts(identity, identityConfigEnvironment) + for _, host := range meshHosts { + var tmpSe *networkingV1Alpha3.ServiceEntry + endpoints, err := getServiceEntryEndpoints(ctxLogger, b.ClientCluster, serverCluster, host, ingressEndpoints, identityConfigEnvironment) + util.LogElapsedTimeSince("getServiceEntryEndpoint", identity, env, b.ClientCluster, start) + if len(endpoints) == 0 || err != nil { + return serviceEntries, err + } + if se, ok := seMap[env][host]; !ok { + tmpSe = &networkingV1Alpha3.ServiceEntry{ + Hosts: []string{host}, + Ports: identityConfigEnvironment.Ports, + Location: networkingV1Alpha3.ServiceEntry_MESH_INTERNAL, + Resolution: networkingV1Alpha3.ServiceEntry_DNS, + SubjectAltNames: []string{common.SpiffePrefix + common.GetSANPrefix() + common.Slash + identity}, + Endpoints: endpoints, + ExportTo: dependentNamespaces, + } + } else { + tmpSe = se + tmpSe.Endpoints = append(tmpSe.Endpoints, endpoints...) } - } else { - tmpSe = se - tmpSe.Endpoints = append(tmpSe.Endpoints, endpoints...) + sort.Sort(WorkloadEntrySorted(tmpSe.Endpoints)) + seMap[env] = map[string]*networkingV1Alpha3.ServiceEntry{host: tmpSe} } - sort.Sort(WorkloadEntrySorted(tmpSe.Endpoints)) - seMap[env] = tmpSe } } - for _, se := range seMap { - serviceEntries = append(serviceEntries, se) + for _, seForEnv := range seMap { + for _, se := range seForEnv { + serviceEntries = append(serviceEntries, se) + } } return serviceEntries, err } +func getMeshHosts(identity string, identityConfigEnvironment *registry.IdentityConfigEnvironment) []string { + meshHosts := []string{} + meshHosts = append(meshHosts, common.GetCnameVal([]string{identityConfigEnvironment.Name, strings.ToLower(identity), common.GetHostnameSuffix()})) + if identityConfigEnvironment.Type[common.Rollout] != nil { + strategy := identityConfigEnvironment.Type[common.Rollout].Strategy + if strategy == bluegreenStrategy { + meshHosts = append(meshHosts, common.GetCnameVal([]string{previewPrefix, strings.ToLower(identity), common.GetHostnameSuffix()})) + } + if strategy == canaryStrategy { + meshHosts = append(meshHosts, common.GetCnameVal([]string{canaryPrefix, strings.ToLower(identity), common.GetHostnameSuffix()})) + } + } + return meshHosts +} + // getIngressEndpoints constructs the endpoint of the ingress gateway/remote endpoint for an identity // by reading the information directly from the IdentityConfigCluster. func getIngressEndpoints(clusters map[string]*registry.IdentityConfigCluster) (map[string]*networkingV1Alpha3.WorkloadEntry, error) { @@ -116,59 +146,84 @@ func getServiceEntryEndpoints( ctxLogger *logrus.Entry, clientCluster string, serverCluster string, + host string, ingressEndpoints map[string]*networkingV1Alpha3.WorkloadEntry, identityConfigEnvironment *registry.IdentityConfigEnvironment) ([]*networkingV1Alpha3.WorkloadEntry, error) { - if len(identityConfigEnvironment.Services) == 0 { - return nil, fmt.Errorf("there were no services for the asset in namespace %s on cluster %s", identityConfigEnvironment.Namespace, serverCluster) - } var err error + services := identityConfigEnvironment.Services endpoint := ingressEndpoints[serverCluster] endpoints := []*networkingV1Alpha3.WorkloadEntry{} - tmpEp := endpoint.DeepCopy() - if tmpEp.Labels == nil { - tmpEp.Labels = make(map[string]string) - } - tmpEp.Labels["security.istio.io/tlsMode"] = "istio" - services := []*registry.RegistryServiceConfig{} - for _, service := range identityConfigEnvironment.Services { - services = append(services, service) + + if services == nil { + return endpoints, fmt.Errorf("services are nil for identityConfigEnvironment %s", identityConfigEnvironment.Name) } - sort.Sort(registry.RegistryServiceConfigSorted(services)) - // Deployment won't have weights, so just sort and take the first service to use as the endpoint - if identityConfigEnvironment.Type == common.Deployment { - if clientCluster == serverCluster { - tmpEp.Address = services[0].Name + common.Sep + identityConfigEnvironment.Namespace + common.GetLocalDomainSuffix() - tmpEp.Ports = services[0].Ports - } - endpoints = append(endpoints, tmpEp) + + // Logic to determine which services should be against default (like whether have both rollout and deployment, and which service for which type) will move to state syncer + // Also state syncer will be responsible for setting the weight of the services, and removing services without weights if one has a weight + + // Services will have 2 keys at max - default and testSvc + // a. Non istio canary rollout will have only default - which will have root svc + // b. Istio canary rollout: + // 1. If weights present - Default key will have stable, canary svc with weights. The latter can be part of testSvc key as well + // 2. If no weights present - Default key will have stable svc, testSvc will have canary svc + // c. Blue green rollout will have default key with stable svc, testSvc with preview svc + // d. Deployment will have default key with root svc + + // Service structure sample: + /* + "services": { + "default": [{ + "name": "app-1-spk-stable-service", + "weight": 25, + "ports": { + "http": 8090 + } + }], + "canary": [{ + "name": "app-1-spk-desired-service", + "weight": 75, + "ports": { + "http": 8090 + } + }] + }, + */ + ep := endpoint.DeepCopy() + if ep.Labels == nil { + ep.Labels = make(map[string]string) } - // Rollout without weights is treated the same as deployment so sort and take first service - // If any of the services have weights then add them to the list of endpoints - if identityConfigEnvironment.Type == common.Rollout { - for _, service := range services { - if service.Weight > 0 { - weightedEp := tmpEp.DeepCopy() - weightedEp.Weight = uint32(service.Weight) - if clientCluster == serverCluster { - weightedEp.Address = service.Name + common.Sep + identityConfigEnvironment.Namespace + common.GetLocalDomainSuffix() - weightedEp.Ports = service.Ports + ep.Labels["security.istio.io/tlsMode"] = "istio" + if clientCluster == serverCluster { + if strings.HasPrefix(host, canaryPrefix) || strings.HasPrefix(host, previewPrefix) { + if services[testServiceKey] != nil { + ep.Address = services[testServiceKey][0].Name + common.Sep + identityConfigEnvironment.Namespace + common.GetLocalDomainSuffix() + ep.Ports = services[testServiceKey][0].Ports + endpoints = append(endpoints, ep) + } + } else { + for _, service := range services[defaultServiceKey] { + tempEp := ep.DeepCopy() + tempEp.Address = service.Name + common.Sep + identityConfigEnvironment.Namespace + common.GetLocalDomainSuffix() + tempEp.Ports = service.Ports + if service.Weight > 0 { + tempEp.Weight = uint32(service.Weight) } - endpoints = append(endpoints, weightedEp) + endpoints = append(endpoints, tempEp) } - } - // If we go through all the services associated with the rollout and none have applicable weights then endpoints is empty - // Treat the rollout like a deployment and sort and take the first service - if len(endpoints) == 0 { - if clientCluster == serverCluster { - tmpEp.Address = services[0].Name + common.Sep + identityConfigEnvironment.Namespace + common.GetLocalDomainSuffix() - tmpEp.Ports = services[0].Ports + for _, service := range services[testServiceKey] { + if service.Weight > 0 { + tempEp := ep.DeepCopy() + tempEp.Address = service.Name + common.Sep + identityConfigEnvironment.Namespace + common.GetLocalDomainSuffix() + tempEp.Ports = service.Ports + tempEp.Weight = uint32(service.Weight) + endpoints = append(endpoints, tempEp) + } } - endpoints = append(endpoints, tmpEp) } + } else { + endpoints = append(endpoints, ep) } - // TODO: type is rollout, strategy is bluegreen, need a way to know which service is preview/desired, trigger another SE - // TODO: type is rollout, strategy is canary, need a way to know which service is stable/root/desired, trigger another SE - // TODO: two types in the environment, deployment to rollout migration + sort.Sort(WorkloadEntrySorted(endpoints)) return endpoints, err } diff --git a/admiral/pkg/clusters/configwriter_test.go b/admiral/pkg/clusters/configwriter_test.go index 520eb393..4d9d7515 100644 --- a/admiral/pkg/clusters/configwriter_test.go +++ b/admiral/pkg/clusters/configwriter_test.go @@ -65,6 +65,62 @@ func createMockServiceEntry(env string, identity string, endpointAddress string, return serviceEntry } +func createMockServiceEntryWithTwoEndpoints(env string, identity string, endpointAddress string, endpointPort int, exportTo []string) networkingV1Alpha3.ServiceEntry { + serviceEntry := networkingV1Alpha3.ServiceEntry{ + Hosts: []string{env + "." + strings.ToLower(identity) + ".mesh"}, + Addresses: nil, + Ports: []*networkingV1Alpha3.ServicePort{{Number: uint32(common.DefaultServiceEntryPort), Name: util.Http, Protocol: util.Http}}, + Location: 1, + Resolution: 2, + Endpoints: []*networkingV1Alpha3.WorkloadEntry{{Address: endpointAddress, + Locality: "us-west-2", + Ports: map[string]uint32{"http": uint32(endpointPort)}, + Labels: map[string]string{"security.istio.io/tlsMode": "istio"}}, + { + Address: "def-elb.us-east-2.elb.amazonaws.com.", + Locality: "us-east-2", + Ports: map[string]uint32{"http": uint32(15443)}, + Labels: map[string]string{"security.istio.io/tlsMode": "istio"}, + }}, + WorkloadSelector: nil, + ExportTo: exportTo, + SubjectAltNames: []string{"spiffe://prefix/" + identity}, + } + return serviceEntry +} + +func createMockServiceEntryWithTwoLocalEndpoints(env string, identity string, endpointAddress1, endpointAddress2 string, endpointPort int, exportTo []string) networkingV1Alpha3.ServiceEntry { + serviceEntry := networkingV1Alpha3.ServiceEntry{ + Hosts: []string{env + "." + strings.ToLower(identity) + ".mesh"}, + Addresses: nil, + Ports: []*networkingV1Alpha3.ServicePort{{Number: uint32(common.DefaultServiceEntryPort), Name: util.Http, Protocol: util.Http}}, + Location: 1, + Resolution: 2, + Endpoints: []*networkingV1Alpha3.WorkloadEntry{ + { + Address: endpointAddress1, + Locality: "us-west-2", + Ports: map[string]uint32{"http": uint32(endpointPort)}, + Labels: map[string]string{"security.istio.io/tlsMode": "istio"}}, + { + Address: endpointAddress2, + Locality: "us-west-2", + Ports: map[string]uint32{"http": uint32(endpointPort)}, + Labels: map[string]string{"security.istio.io/tlsMode": "istio"}}, + { + Address: "def-elb.us-east-2.elb.amazonaws.com.", + Locality: "us-east-2", + Ports: map[string]uint32{"http": uint32(15443)}, + Labels: map[string]string{"security.istio.io/tlsMode": "istio"}, + }}, + WorkloadSelector: nil, + ExportTo: exportTo, + SubjectAltNames: []string{"spiffe://prefix/" + identity}, + } + sort.Sort(WorkloadEntrySorted(serviceEntry.Endpoints)) + return serviceEntry +} + func TestGetIngressEndpoints(t *testing.T) { identityConfig := registry.GetSampleIdentityConfig("sample") expectedIngressEndpoints := map[string]*networkingV1Alpha3.WorkloadEntry{ @@ -105,32 +161,44 @@ func TestGetServiceEntryEndpoints(t *testing.T) { common.ResetSync() common.InitializeConfig(admiralParams) e2eEnv := registry.GetSampleIdentityConfigEnvironment("e2e", "ns-1-usw2-e2e", "sample") + host := "e2e.sample.mesh" unweightedDeployment := registry.GetSampleIdentityConfigEnvironment("e2e", "ns-1-usw2-e2e", "sample") - unweightedDeployment.Type = common.Deployment - weightedServices := map[string]*registry.RegistryServiceConfig{ - "app-1-spk-root-service": { - Name: "app-1-spk-root-service", - Ports: map[string]uint32{ - "http": 8090, - }, + unweightedDeployment.Type = map[string]*registry.TypeConfig{ + "deployment": { + Selectors: map[string]string{"app": "app1"}, }, - "app-1-spk-desired-service": { - Name: "app-1-spk-desired-service", - Weight: 25, - Ports: map[string]uint32{ - "http": 8090, + } + weightedServices := map[string][]*registry.RegistryServiceConfig{ + "default": { + + { + Name: "app-1-spk-stable-service", + Ports: map[string]uint32{ + "http": 8090, + }, + Selectors: map[string]string{"app": "app1"}, + Weight: 75, }, }, - "app-1-spk-stable-service": { - Name: "app-1-spk-stable-service", - Weight: 75, - Ports: map[string]uint32{ - "http": 8090, + "test": { + { + Name: "app-1-spk-desired-service", + Ports: map[string]uint32{ + "http": 8090, + }, + Selectors: map[string]string{"app": "app1"}, + Weight: 25, }, }, } weightedRollout := registry.GetSampleIdentityConfigEnvironment("e2e", "ns-1-usw2-e2e", "sample") weightedRollout.Services = weightedServices + weightedRollout.Type = map[string]*registry.TypeConfig{ + "rollout": { + Selectors: map[string]string{"app": "app1"}, + Strategy: canaryStrategy, + }, + } ingressEndpoints := map[string]*networkingV1Alpha3.WorkloadEntry{"cluster1": { Address: "abc-elb.us-west-2.elb.amazonaws.com.", Locality: "us-west-2", @@ -229,7 +297,200 @@ func TestGetServiceEntryEndpoints(t *testing.T) { } for _, c := range testCases { t.Run(c.name, func(t *testing.T) { - seEndpoints, err := getServiceEntryEndpoints(ctxLogger, c.clientCluster, c.serverCluster, c.ingressEndpoints, c.identityConfigEnvironment) + seEndpoints, err := getServiceEntryEndpoints(ctxLogger, c.clientCluster, c.serverCluster, host, c.ingressEndpoints, c.identityConfigEnvironment) + if err != nil { + t.Errorf("want=nil, got=%v", err) + } + opts := cmpopts.IgnoreUnexported(networkingV1Alpha3.WorkloadEntry{}) + if !cmp.Equal(seEndpoints, c.expectedSEEndpoints, opts) { + t.Errorf("want=%v, got=%v", c.expectedSEEndpoints, seEndpoints) + } + }) + } +} + +func TestGetServiceEntryEndpointsForCanaryAndBlueGreen(t *testing.T) { + admiralParams := admiralParamsForConfigWriterTests() + common.ResetSync() + common.InitializeConfig(admiralParams) + host := "e2e.sample.mesh" + canaryRollout := registry.GetSampleIdentityConfigEnvironment("e2e", "ns-1-usw2-e2e", "sample") + canaryServices := map[string][]*registry.RegistryServiceConfig{ + "default": { + { + Name: "app-1-spk-root-service", + Ports: map[string]uint32{ + "http": 8090, + }, + Selectors: map[string]string{"app": "app1"}, + }, + { + Name: "app-1-spk-stable-service", + Ports: map[string]uint32{ + "http": 8090, + }, + Selectors: map[string]string{"app": "app1"}, + }, + }, + "test": { + { + Name: "app-1-spk-desired-service", + Ports: map[string]uint32{ + "http": 8090, + }, + Selectors: map[string]string{"app": "app1"}, + }, + }, + } + canaryRollout.Services = canaryServices + canaryRollout.Type = map[string]*registry.TypeConfig{ + "rollout": { + Selectors: map[string]string{"app": "app1"}, + Strategy: canaryStrategy, + }, + } + + blueGreenRollout := registry.GetSampleIdentityConfigEnvironment("e2e", "ns-1-usw2-e2e", "sample") + blueGreenServices := map[string][]*registry.RegistryServiceConfig{ + "test": { + {Name: "app-1-spk-preview-service", + Ports: map[string]uint32{ + "http": 8090, + }, + Selectors: map[string]string{"app": "app1"}}, + }, + "default": { + {Name: "app-1-spk-active-service", + Ports: map[string]uint32{ + "http": 8090, + }, + Selectors: map[string]string{"app": "app1"}}, + }, + } + blueGreenRollout.Services = blueGreenServices + blueGreenRollout.Type = map[string]*registry.TypeConfig{ + "rollout": { + Selectors: map[string]string{"app": "app1"}, + Strategy: bluegreenStrategy, + }, + } + ingressEndpoints := map[string]*networkingV1Alpha3.WorkloadEntry{"cluster1": { + Address: "abc-elb.us-west-2.elb.amazonaws.com.", + Locality: "us-west-2", + Ports: map[string]uint32{"http": uint32(15443)}, + Labels: map[string]string{"security.istio.io/tlsMode": "istio"}, + }, "cluster2": { + Address: "def-elb.us-west-2.elb.amazonaws.com.", + Locality: "us-west-2", + Ports: map[string]uint32{"http": uint32(15443)}, + Labels: map[string]string{"security.istio.io/tlsMode": "istio"}, + }} + + localEndpointsWithDesiredService := []*networkingV1Alpha3.WorkloadEntry{{ + Address: "app-1-spk-desired-service.ns-1-usw2-e2e.svc.cluster.local.", + Locality: "us-west-2", + Ports: map[string]uint32{"http": uint32(8090)}, + Labels: map[string]string{"security.istio.io/tlsMode": "istio"}, + }} + localEndpointsWithPreviewService := []*networkingV1Alpha3.WorkloadEntry{{ + Address: "app-1-spk-preview-service.ns-1-usw2-e2e.svc.cluster.local.", + Locality: "us-west-2", + Ports: map[string]uint32{"http": uint32(8090)}, + Labels: map[string]string{"security.istio.io/tlsMode": "istio"}, + }} + localEndpointsWithActiveService := []*networkingV1Alpha3.WorkloadEntry{{ + Address: "app-1-spk-active-service.ns-1-usw2-e2e.svc.cluster.local.", + Locality: "us-west-2", + Ports: map[string]uint32{"http": uint32(8090)}, + Labels: map[string]string{"security.istio.io/tlsMode": "istio"}, + }} + remoteEndpoints := []*networkingV1Alpha3.WorkloadEntry{{ + Address: "def-elb.us-west-2.elb.amazonaws.com.", + Locality: "us-west-2", + Ports: map[string]uint32{"http": uint32(15443)}, + Labels: map[string]string{"security.istio.io/tlsMode": "istio"}, + }} + ctx := context.Background() + ctxLogger := common.GetCtxLogger(ctx, "sample", "") + testCases := []struct { + name string + identityConfigEnvironment *registry.IdentityConfigEnvironment + ingressEndpoints map[string]*networkingV1Alpha3.WorkloadEntry + clientCluster string + serverCluster string + host string + expectedSEEndpoints []*networkingV1Alpha3.WorkloadEntry + }{ + { + name: "Given an IdentityConfigEnvironment and ingressEndpoint and a desired service, " + + "When the client cluster is the same as the server cluster" + + "Then the constructed endpoint for canary host should be a local endpoint with desired service", + identityConfigEnvironment: canaryRollout, + ingressEndpoints: ingressEndpoints, + clientCluster: "cluster1", + serverCluster: "cluster1", + host: "canary.e2e.sample.mesh", + expectedSEEndpoints: localEndpointsWithDesiredService, + }, + { + name: "Given an IdentityConfigEnvironment and ingressEndpoint and a desired service, " + + "When the client cluster is the different from the server cluster" + + "Then the constructed endpoint for canary host should be a remote endpoint of cluster 2", + identityConfigEnvironment: canaryRollout, + ingressEndpoints: ingressEndpoints, + clientCluster: "cluster1", + serverCluster: "cluster2", + host: host, + expectedSEEndpoints: remoteEndpoints, + }, + { + name: "Given an IdentityConfigEnvironment and ingressEndpoint and a preview and active service for a bluegreen rollout, " + + "When the client cluster is the same as the server cluster" + + "Then the constructed endpoint for preview host should be a local endpoint with preview service", + identityConfigEnvironment: blueGreenRollout, + ingressEndpoints: ingressEndpoints, + clientCluster: "cluster1", + serverCluster: "cluster1", + host: "preview.e2e.sample.mesh", + expectedSEEndpoints: localEndpointsWithPreviewService, + }, + { + name: "Given an IdentityConfigEnvironment and ingressEndpoint and a preview and active service for a bluegreen rollout, " + + "When the client cluster is the same as the server cluster" + + "Then the constructed endpoint for host should be a local endpoint with active service", + identityConfigEnvironment: blueGreenRollout, + ingressEndpoints: ingressEndpoints, + clientCluster: "cluster1", + serverCluster: "cluster1", + host: host, + expectedSEEndpoints: localEndpointsWithActiveService, + }, + { + name: "Given an IdentityConfigEnvironment and ingressEndpoint and a preview and active service for a bluegreen rollout, " + + "When the client cluster is the different from the server cluster" + + "Then the constructed endpoint for host should be a remote endpoint of cluster 2", + identityConfigEnvironment: blueGreenRollout, + ingressEndpoints: ingressEndpoints, + clientCluster: "cluster1", + serverCluster: "cluster2", + host: host, + expectedSEEndpoints: remoteEndpoints, + }, + { + name: "Given an IdentityConfigEnvironment and ingressEndpoint and a preview and active service for a bluegreen rollout, " + + "When the client cluster is the different from the server cluster" + + "Then the constructed endpoint for host should be a remote endpoint of cluster 2", + identityConfigEnvironment: blueGreenRollout, + ingressEndpoints: ingressEndpoints, + clientCluster: "cluster1", + serverCluster: "cluster2", + host: "preview.e2e.sample.mesh", + expectedSEEndpoints: remoteEndpoints, + }, + } + for _, c := range testCases { + t.Run(c.name, func(t *testing.T) { + seEndpoints, err := getServiceEntryEndpoints(ctxLogger, c.clientCluster, c.serverCluster, c.host, c.ingressEndpoints, c.identityConfigEnvironment) if err != nil { t.Errorf("want=nil, got=%v", err) } @@ -304,7 +565,7 @@ func TestBuildServiceEntriesFromIdentityConfig(t *testing.T) { identityConfigFailsExportTo := registry.GetSampleIdentityConfig("sample") identityConfigFailsExportTo.ClientAssets["fake"] = "fake" identityConfigFailsServiceEntryEndpoint := registry.GetSampleIdentityConfig("sample") - identityConfigFailsServiceEntryEndpoint.Clusters["cluster1"].Environment["e2e"].Services = make(map[string]*registry.RegistryServiceConfig) + identityConfigFailsServiceEntryEndpoint.Clusters["cluster1"].Environment["e2e"].Services = make(map[string][]*registry.RegistryServiceConfig) testCases := []struct { name string clientCluster string @@ -369,6 +630,108 @@ func TestBuildServiceEntriesFromIdentityConfig(t *testing.T) { } } +func TestBuildServiceEntriesFromIdentityConfig_MultipleEndpoints(t *testing.T) { + admiralParams := admiralParamsForConfigWriterTests() + common.ResetSync() + common.InitializeConfig(admiralParams) + rr, _ := InitAdmiralOperator(context.Background(), admiralParams) + ctxLogger := common.GetCtxLogger(context.Background(), "test", "") + identityConfig := registry.GetSampleIdentityConfigWithRemoteEndpoints("sample") + expectedLocalServiceEntryPRF := createMockServiceEntryWithTwoEndpoints("prf", "sample", "app-1-spk-root-service.ns-1-usw2-prf.svc.cluster.local.", 8090, []string{"istio-system", "ns-1-usw2-e2e", "ns-1-usw2-prf", "ns-1-usw2-qal"}) + expectedLocalServiceEntryE2E := createMockServiceEntryWithTwoEndpoints("e2e", "sample", "app-1-spk-root-service.ns-1-usw2-e2e.svc.cluster.local.", 8090, []string{"istio-system", "ns-1-usw2-e2e", "ns-1-usw2-prf", "ns-1-usw2-qal"}) + expectedLocalServiceEntryQAL := createMockServiceEntryWithTwoEndpoints("qal", "sample", "app-1-spk-root-service.ns-1-usw2-qal.svc.cluster.local.", 8090, []string{"istio-system", "ns-1-usw2-e2e", "ns-1-usw2-prf", "ns-1-usw2-qal"}) + expectedLocalServiceEntries := []*networkingV1Alpha3.ServiceEntry{&expectedLocalServiceEntryQAL, &expectedLocalServiceEntryPRF, &expectedLocalServiceEntryE2E} + + identityConfigForMigration := registry.GetSampleIdentityConfigWithRolloutAndDeployment("sample") + expectedLocalServiceEntryPRFForMigration := createMockServiceEntryWithTwoLocalEndpoints("prf", "sample", "app-1-spk-root-service.ns-1-usw2-prf.svc.cluster.local.", "app-1-spk-deploy-service.ns-1-usw2-prf.svc.cluster.local.", 8090, []string{"istio-system", "ns-1-usw2-e2e", "ns-1-usw2-prf", "ns-1-usw2-qal"}) + expectedLocalServiceEntryE2EForMigration := createMockServiceEntryWithTwoLocalEndpoints("e2e", "sample", "app-1-spk-root-service.ns-1-usw2-e2e.svc.cluster.local.", "app-1-spk-deploy-service.ns-1-usw2-e2e.svc.cluster.local.", 8090, []string{"istio-system", "ns-1-usw2-e2e", "ns-1-usw2-prf", "ns-1-usw2-qal"}) + expectedLocalServiceEntryQALForMigration := createMockServiceEntryWithTwoLocalEndpoints("qal", "sample", "app-1-spk-root-service.ns-1-usw2-qal.svc.cluster.local.", "app-1-spk-deploy-service.ns-1-usw2-qal.svc.cluster.local.", 8090, []string{"istio-system", "ns-1-usw2-e2e", "ns-1-usw2-prf", "ns-1-usw2-qal"}) + expectedLocalServiceEntriesForMigration := []*networkingV1Alpha3.ServiceEntry{&expectedLocalServiceEntryQALForMigration, &expectedLocalServiceEntryPRFForMigration, &expectedLocalServiceEntryE2EForMigration} + + identityConfigForMigration2 := registry.GetSampleIdentityConfigWithRolloutAndDeployment("sample") + identityConfigForMigration2.Clusters["cluster2"].Environment["prf"].Type = map[string]*registry.TypeConfig{ + "deployment": { + Selectors: map[string]string{"app": "app1"}, + }, + } + identityConfigForMigration2.Clusters["cluster2"].Environment["e2e"].Type = map[string]*registry.TypeConfig{ + "deployment": { + Selectors: map[string]string{"app": "app1"}, + }, + } + identityConfigForMigration2.Clusters["cluster2"].Environment["qal"].Type = map[string]*registry.TypeConfig{ + "deployment": { + Selectors: map[string]string{"app": "app1"}, + }, + } + expectedLocalServiceEntryPRFForMigration2 := createMockServiceEntryWithTwoLocalEndpoints("prf", "sample", "app-1-spk-root-service.ns-1-usw2-prf.svc.cluster.local.", "app-1-spk-deploy-service.ns-1-usw2-prf.svc.cluster.local.", 8090, []string{"istio-system", "ns-1-usw2-e2e", "ns-1-usw2-prf", "ns-1-usw2-qal"}) + expectedLocalServiceEntryPRFForMigration2.Endpoints[2].Labels = map[string]string{"security.istio.io/tlsMode": "istio"} + expectedLocalServiceEntryE2EForMigration2 := createMockServiceEntryWithTwoLocalEndpoints("e2e", "sample", "app-1-spk-root-service.ns-1-usw2-e2e.svc.cluster.local.", "app-1-spk-deploy-service.ns-1-usw2-e2e.svc.cluster.local.", 8090, []string{"istio-system", "ns-1-usw2-e2e", "ns-1-usw2-prf", "ns-1-usw2-qal"}) + expectedLocalServiceEntryE2EForMigration2.Endpoints[2].Labels = map[string]string{"security.istio.io/tlsMode": "istio"} + expectedLocalServiceEntryQALForMigration2 := createMockServiceEntryWithTwoLocalEndpoints("qal", "sample", "app-1-spk-root-service.ns-1-usw2-qal.svc.cluster.local.", "app-1-spk-deploy-service.ns-1-usw2-qal.svc.cluster.local.", 8090, []string{"istio-system", "ns-1-usw2-e2e", "ns-1-usw2-prf", "ns-1-usw2-qal"}) + expectedLocalServiceEntryQALForMigration2.Endpoints[2].Labels = map[string]string{"security.istio.io/tlsMode": "istio"} + expectedLocalServiceEntriesForMigration2 := []*networkingV1Alpha3.ServiceEntry{&expectedLocalServiceEntryQALForMigration2, &expectedLocalServiceEntryPRFForMigration2, &expectedLocalServiceEntryE2EForMigration2} + + testCases := []struct { + name string + clientCluster string + event admiral.EventType + identityConfig registry.IdentityConfig + expectedServiceEntries []*networkingV1Alpha3.ServiceEntry + expectedErr bool + }{ + { + name: "Given information to build an se, " + + "When the client cluster is the same as the server cluster" + + "Then the constructed se should have local endpoint and a remote endpoint and istio-system in exportTo", + clientCluster: "cluster1", + event: admiral.Add, + identityConfig: identityConfig, + expectedServiceEntries: expectedLocalServiceEntries, + expectedErr: false, + }, + { + name: "Given information to build an se has a rollout and deployment in the same namespace, and remote cluster having a rollout " + + "When the client cluster is the same as the server cluster" + + "Then the constructed se should have 2 local endpoints and a remote endpoint and istio-system in exportTo", + clientCluster: "cluster1", + event: admiral.Add, + identityConfig: identityConfigForMigration, + expectedServiceEntries: expectedLocalServiceEntriesForMigration, + expectedErr: false, + }, + { + name: "Given information to build an se has a rollout and deployment in the same namespace, and remote cluster having a deployment " + + "When the client cluster is the same as the server cluster" + + "Then the constructed se should have 2 local endpoints and a remote endpoint and istio-system in exportTo", + clientCluster: "cluster1", + event: admiral.Add, + identityConfig: identityConfigForMigration2, + expectedServiceEntries: expectedLocalServiceEntriesForMigration2, + expectedErr: false, + }, + } + for _, c := range testCases { + t.Run(c.name, func(t *testing.T) { + serviceEntryBuilder := ServiceEntryBuilder{ClientCluster: c.clientCluster, RemoteRegistry: rr} + + serviceEntries, err := serviceEntryBuilder.BuildServiceEntriesFromIdentityConfig(ctxLogger, c.identityConfig) + + if err != nil && !c.expectedErr { + t.Errorf("want=%v, \ngot=%v", nil, err) + } + opts := cmpopts.IgnoreUnexported(networkingV1Alpha3.ServiceEntry{}, networkingV1Alpha3.ServicePort{}, networkingV1Alpha3.WorkloadEntry{}) + // sort the service entries by name + sort.Sort(ServiceEntryListSorterByHost(serviceEntries)) + sort.Sort(ServiceEntryListSorterByHost(c.expectedServiceEntries)) + if !cmp.Equal(serviceEntries, c.expectedServiceEntries, opts) { + t.Errorf("want=%v, \ngot=%v", c.expectedServiceEntries, serviceEntries) + } + + }) + } +} + type ServiceEntryListSorterByHost []*networkingV1Alpha3.ServiceEntry func (s ServiceEntryListSorterByHost) Len() int { diff --git a/admiral/pkg/clusters/serviceentry.go b/admiral/pkg/clusters/serviceentry.go index d7cedbde..a399501e 100644 --- a/admiral/pkg/clusters/serviceentry.go +++ b/admiral/pkg/clusters/serviceentry.go @@ -45,6 +45,7 @@ type SeDrTuple struct { } const ( + intuitHostSuffix = "intuit" resourceCreatedByAnnotationLabel = "app.kubernetes.io/created-by" resourceCreatedByAnnotationValue = "admiral" resourceCreatedByAnnotationCartographerValue = "cartographer" @@ -54,6 +55,10 @@ const ( gtpManagedByMeshAgent = "mesh-agent" gtpManagerMeshAgentFieldValue = "ewok-mesh-agent" errorCluster = "error-cluster" + bluegreenStrategy = "bluegreen" + canaryStrategy = "canary" + deployToRolloutStrategy = "deployToRollout" + rolloutToDeployStrategy = "rolloutToDeploy" ingressVSGenerationErrorMessage = "skipped generating ingress virtual service on cluster %s due to error %w" ) @@ -215,9 +220,7 @@ func modifyServiceEntryForNewServiceOrPod( Locality: getLocality(rc), IngressEndpoint: ingressEndpoint, IngressPort: strconv.Itoa(port), - Environment: map[string]*registry.IdentityConfigEnvironment{ - env: ®istry.IdentityConfigEnvironment{}, - }, + Environment: map[string]*registry.IdentityConfigEnvironment{}, } // For Deployment <-> Rollout migration @@ -292,7 +295,7 @@ func modifyServiceEntryForNewServiceOrPod( registryConfig.Clusters[clusterId].Environment[env] = ®istry.IdentityConfigEnvironment{ Name: env, Namespace: namespace, - Type: common.Deployment, + Type: map[string]*registry.TypeConfig{common.Deployment: {Selectors: deployment.Spec.Selector.MatchLabels}}, } } @@ -341,7 +344,7 @@ func modifyServiceEntryForNewServiceOrPod( registryConfig.Clusters[clusterId].Environment[env] = ®istry.IdentityConfigEnvironment{ Name: env, Namespace: namespace, - Type: common.Rollout, + Type: map[string]*registry.TypeConfig{common.Rollout: {Selectors: rollout.Spec.Selector.MatchLabels}}, } } @@ -414,7 +417,7 @@ func modifyServiceEntryForNewServiceOrPod( } } - //PID: use partitionedIdentity because IdentityDependencyCache is filled using the partitionedIdentity - DONE + // use partitionedIdentity because IdentityDependencyCache is filled using the partitionedIdentity dependents := remoteRegistry.AdmiralCache.IdentityDependencyCache.Get(partitionedIdentity).Copy() // updates CnameDependentClusterCache and CnameDependentClusterNamespaceCache cname = strings.TrimSpace(cname) @@ -571,8 +574,13 @@ func modifyServiceEntryForNewServiceOrPod( registryConfig.Clusters[sourceCluster].Environment[env] = ®istry.IdentityConfigEnvironment{ Name: env, Namespace: namespace, - Type: common.Rollout, - Event: admiral.Delete, // TODO: we need to handle DELETE operations in admiral operator + Type: map[string]*registry.TypeConfig{ + eventResourceType: { + Selectors: serviceInstance[appType[sourceCluster]].Spec.Selector, + }, + }, + Event: admiral.Delete, + //TODO: we need to handle DELETE operations in admiral operator } continue } @@ -604,13 +612,14 @@ func modifyServiceEntryForNewServiceOrPod( sourceWeightedServices[sourceCluster], cnames, ep, sourceCluster, key) if common.IsAdmiralStateSyncerMode() { - registryConfig.Clusters[sourceCluster].Environment[env].Services = map[string]*registry.RegistryServiceConfig{ - blueGreenService.Service.Name: ®istry.RegistryServiceConfig{ + registryConfig.Clusters[sourceCluster].Environment[env].Services = map[string][]*registry.RegistryServiceConfig{ + testServiceKey: {{ Name: blueGreenService.Service.Name, Weight: -1, Ports: GetMeshPortsForRollout(sourceCluster, blueGreenService.Service, sourceRollouts[sourceCluster]), - }, + }}, } + registryConfig.Clusters[sourceCluster].Environment[env].Type[common.Rollout].Strategy = bluegreenStrategy continue } err := remoteRegistry.ConfigWriter.AddServiceEntriesWithDrToAllCluster( @@ -634,13 +643,14 @@ func modifyServiceEntryForNewServiceOrPod( canaryService := sourceRollouts[sourceCluster].Spec.Strategy.Canary.CanaryService // use only canary service for fqdn if common.IsAdmiralStateSyncerMode() { - registryConfig.Clusters[sourceCluster].Environment[env].Services = map[string]*registry.RegistryServiceConfig{ - canaryService: ®istry.RegistryServiceConfig{ + registryConfig.Clusters[sourceCluster].Environment[env].Services = map[string][]*registry.RegistryServiceConfig{ + testServiceKey: {{ Name: canaryService, Weight: -1, Ports: meshPorts, - }, + }}, } + registryConfig.Clusters[sourceCluster].Environment[env].Type[common.Rollout].Strategy = canaryStrategy continue } fqdn := canaryService + common.Sep + serviceInstance[appType[sourceCluster]].Namespace + common.GetLocalDomainSuffix() @@ -709,12 +719,12 @@ func modifyServiceEntryForNewServiceOrPod( deploymentOrRolloutName, deploymentOrRolloutNS, sourceCluster, "Updating ServiceEntry regular endpoints") // call State Syncer's config syncer for deployment if common.IsAdmiralStateSyncerMode() { - registryConfig.Clusters[sourceCluster].Environment[env].Services = map[string]*registry.RegistryServiceConfig{ - localFqdn: ®istry.RegistryServiceConfig{ + registryConfig.Clusters[sourceCluster].Environment[env].Services = map[string][]*registry.RegistryServiceConfig{ + "default": {{ Name: localFqdn, Weight: 0, Ports: meshPorts, - }, + }}, } continue } diff --git a/admiral/pkg/clusters/shard_handler.go b/admiral/pkg/clusters/shard_handler.go index 10553bbe..7ffca8ef 100644 --- a/admiral/pkg/clusters/shard_handler.go +++ b/admiral/pkg/clusters/shard_handler.go @@ -193,7 +193,10 @@ func ConsumeIdentityConfigs(ctxLogger *log.Entry, ctx context.Context, configWri // Get any type from the identityConfig for _, cv := range identityConfig.Clusters { for _, ev := range cv.Environment { - ctx = context.WithValue(ctx, common.EventResourceType, ev.Type) + for typeKey := range ev.Type { + ctx = context.WithValue(ctx, common.EventResourceType, typeKey) + break + } break } break diff --git a/admiral/pkg/clusters/testdata/expectedIdentityIdentityConfiguration.json b/admiral/pkg/clusters/testdata/expectedIdentityIdentityConfiguration.json index ef742cf7..86fe3065 100644 --- a/admiral/pkg/clusters/testdata/expectedIdentityIdentityConfiguration.json +++ b/admiral/pkg/clusters/testdata/expectedIdentityIdentityConfiguration.json @@ -13,8 +13,15 @@ "namespace": "foobar-ns", "services": null, "serviceName": "", - "type": "rollout", - "selectors": null, + "type": { + "rollout": { + "strategy": "", + "selectors": { + "app": "identity", + "identity": "identity" + } + } + }, "ports": [ { "number": 80, diff --git a/admiral/pkg/clusters/testdata/identityIdentityConfiguration.json b/admiral/pkg/clusters/testdata/identityIdentityConfiguration.json index ef742cf7..86fe3065 100644 --- a/admiral/pkg/clusters/testdata/identityIdentityConfiguration.json +++ b/admiral/pkg/clusters/testdata/identityIdentityConfiguration.json @@ -13,8 +13,15 @@ "namespace": "foobar-ns", "services": null, "serviceName": "", - "type": "rollout", - "selectors": null, + "type": { + "rollout": { + "strategy": "", + "selectors": { + "app": "identity", + "identity": "identity" + } + } + }, "ports": [ { "number": 80, diff --git a/admiral/pkg/clusters/testdata/ppdmeshtestblackholeIdentityConfiguration.json b/admiral/pkg/clusters/testdata/ppdmeshtestblackholeIdentityConfiguration.json index b794c288..f1644d1a 100644 --- a/admiral/pkg/clusters/testdata/ppdmeshtestblackholeIdentityConfiguration.json +++ b/admiral/pkg/clusters/testdata/ppdmeshtestblackholeIdentityConfiguration.json @@ -15,17 +15,19 @@ "namespace": "services-blackholed268-usw2-dev", "serviceName": "blackhole-root-service", "services": { - "blackhole-root-service": { + "default": [{ "name": "blackhole-root-service", - "weight": -1, "ports": { "http": 8090 } - } + }] }, - "type": "deployment", - "selectors": { - "app": "blackhole-gw" + "type": { + "deployment": { + "selectors": { + "app": "blackhole-gw" + } + } }, "ports": [ { @@ -122,18 +124,20 @@ "namespace": "services-blackholesh45-use2-dev", "serviceName": "blackhole-root-service", "services": { - "blackhole-root-service": { - "name": "blackhole-root-service", - "weight": -1, - "ports": { - "http": 8090 + "default": [{ + "name": "blackhole-root-service", + "ports": { + "http": 8090 + } + }] + }, + "type": { + "deployment": { + "selectors": { + "app": "blackhole-gw" } } }, - "type": "deployment", - "selectors": { - "app": "blackhole-gw" - }, "ports": [ { "name": "http", diff --git a/admiral/pkg/clusters/testdata/ppdmeshtestinboundsIdentityConfiguration.json b/admiral/pkg/clusters/testdata/ppdmeshtestinboundsIdentityConfiguration.json index bc564a40..c8ef8fcf 100644 --- a/admiral/pkg/clusters/testdata/ppdmeshtestinboundsIdentityConfiguration.json +++ b/admiral/pkg/clusters/testdata/ppdmeshtestinboundsIdentityConfiguration.json @@ -15,17 +15,19 @@ "namespace": "services-inboundd268-usw2-dev", "serviceName": "inbound-root-service", "services": { - "inbound-root-service": { + "default": [{ "name": "inbound-root-service", - "weight": -1, "ports": { "http": 8090 } - } + }] }, - "type": "deployment", - "selectors": { - "app": "inbound-gw" + "type": { + "deployment": { + "selectors": { + "app": "inbound-gw" + } + } }, "ports": [ { diff --git a/admiral/pkg/clusters/testdata/sampleIdentityConfiguration.json b/admiral/pkg/clusters/testdata/sampleIdentityConfiguration.json index 59f6eebb..b68b6523 100644 --- a/admiral/pkg/clusters/testdata/sampleIdentityConfiguration.json +++ b/admiral/pkg/clusters/testdata/sampleIdentityConfiguration.json @@ -13,19 +13,34 @@ "prf": { "name": "prf", "namespace": "ns-1-usw2-prf", - "serviceName": "app-1-spk-root-service", "services": { - "app-1-spk-root-service": { - "name": "app-1-spk-root-service", - "weight": -1, + "default": [{ + "name": "app-1-spk-stable-service", + "weight": 25, "ports": { "http": 8090 } - } + }], + "test": [{ + "name": "app-1-spk-desired-service", + "weight": 75, + "ports": { + "http": 8090 + } + }] }, - "type": "rollout", - "selectors": { - "app": "app-1" + "type": { + "rollout": { + "strategy": "canary", + "selectors": { + "app": "app-1" + } + }, + "deployment": { + "selectors": { + "app": "app-1" + } + } }, "ports": [ { @@ -109,19 +124,28 @@ "e2e": { "name": "e2e", "namespace": "ns-1-usw2-e2e", - "serviceName": "app-1-spk-root-service", + "strategy": "bluegreen", "services": { - "app-1-spk-root-service": { - "name": "app-1-spk-root-service", - "weight": -1, + "default": [{ + "name": "app-1-spk-active-service", "ports": { "http": 8090 } - } + }], + "test":[{ + "name": "app-1-spk-preview-service", + "ports": { + "http": 8090 + } + }] }, - "type": "rollout", - "selectors": { - "app": "app-1" + "type": { + "rollout": { + "strategy": "canary", + "selectors": { + "app": "app-1" + } + } }, "ports": [ { @@ -205,19 +229,22 @@ "qal": { "name": "qal", "namespace": "ns-1-usw2-qal", - "serviceName": "app-1-spk-root-service", "services": { - "app-1-spk-root-service": { - "name": "app-1-spk-root-service", - "weight": -1, + "default": [{ + "name": "app-1-spk-service", + "ports": { "http": 8090 } - } + }] }, - "type": "rollout", - "selectors": { - "app": "app-1" + "type": { + "rollout": { + "strategy": "canary", + "selectors": { + "app": "app-1" + } + } }, "ports": [ { diff --git a/admiral/pkg/clusters/util.go b/admiral/pkg/clusters/util.go index 8dfc4f80..520cbabc 100644 --- a/admiral/pkg/clusters/util.go +++ b/admiral/pkg/clusters/util.go @@ -324,11 +324,11 @@ func getIngressPortName(meshPorts map[string]uint32) string { return finalProtocol } -func parseWeightedService(weightedServices map[string]*WeightedService) map[string]*registry.RegistryServiceConfig { +func parseWeightedService(weightedServices map[string]*WeightedService) map[string][]*registry.RegistryServiceConfig { return nil } -func parseMigrationService(services []*k8sV1.Service) map[string]*registry.RegistryServiceConfig { +func parseMigrationService(services []*k8sV1.Service) map[string][]*registry.RegistryServiceConfig { return nil } diff --git a/admiral/pkg/controller/common/metrics.go b/admiral/pkg/controller/common/metrics.go index 6da097c8..7948521f 100644 --- a/admiral/pkg/controller/common/metrics.go +++ b/admiral/pkg/controller/common/metrics.go @@ -15,7 +15,7 @@ func NewGaugeFrom(name string, help string) Gauge { } opts := prometheus.GaugeOpts{Name: name, Help: help} g := prometheus.NewGauge(opts) - prometheus.MustRegister(g) + // prometheus.MustRegister(g) // https://github.com/prometheus/client_golang/issues/716 return &PromGauge{g} } diff --git a/admiral/pkg/registry/config.go b/admiral/pkg/registry/config.go index 6515ea98..529433cf 100644 --- a/admiral/pkg/registry/config.go +++ b/admiral/pkg/registry/config.go @@ -17,12 +17,13 @@ func (config *IdentityConfig) PutClusterConfig(name string, clusterConfig Identi } type IdentityConfigCluster struct { - Name string `json:"name"` - Locality string `json:"locality"` - IngressEndpoint string `json:"ingressEndpoint"` - IngressPort string `json:"ingressPort"` - IngressPortName string `json:"ingressPortName"` - Environment map[string]*IdentityConfigEnvironment `json:"environment"` + Name string `json:"name"` + Locality string `json:"locality"` + IngressEndpoint string `json:"ingressEndpoint"` + IngressPort string `json:"ingressPort"` + IngressPortName string `json:"ingressPortName"` + // env -> rollout/deploy -> IdentityConfigEnvironment + Environment map[string]*IdentityConfigEnvironment `json:"environment"` } func (config *IdentityConfigCluster) PutEnvironment(name string, environmentConfig IdentityConfigEnvironment) error { @@ -34,9 +35,10 @@ func (config *IdentityConfigCluster) PutClientAssets(clientAssets []string) erro } type RegistryServiceConfig struct { - Name string `json:"name"` - Weight int `json:"weight"` - Ports map[string]uint32 `json:"ports"` + Name string `json:"name"` + Weight int `json:"weight"` + Ports map[string]uint32 `json:"ports"` + Selectors map[string]string `json:"selectors"` } type TrafficPolicy struct { @@ -45,16 +47,20 @@ type TrafficPolicy struct { ClientConnectionConfig admiralV1Alpha1.ClientConnectionConfig `json:"clientconnectionconfig"` } +type TypeConfig struct { + Strategy string `json:"strategy"` + Selectors map[string]string `json:"selectors"` +} + type IdentityConfigEnvironment struct { - Name string `json:"name"` - Namespace string `json:"namespace"` - Services map[string]*RegistryServiceConfig `json:"services"` - ServiceName string `json:"serviceName"` - Type string `json:"type"` - Selectors map[string]string `json:"selectors"` - Ports []*networking.ServicePort `json:"ports"` - TrafficPolicy TrafficPolicy `json:"trafficPolicy"` - Event admiral.EventType `json:"event"` + Name string `json:"name"` + Namespace string `json:"namespace"` + Services map[string][]*RegistryServiceConfig `json:"services"` + ServiceName string `json:"serviceName"` + Type map[string]*TypeConfig `json:"type"` + Ports []*networking.ServicePort `json:"ports"` + TrafficPolicy TrafficPolicy `json:"trafficPolicy"` + Event admiral.EventType `json:"event"` } type RegistryServiceConfigSorted []*RegistryServiceConfig diff --git a/admiral/pkg/registry/testdata/sampleIdentityConfiguration.json b/admiral/pkg/registry/testdata/sampleIdentityConfiguration.json index 59f6eebb..462ca266 100644 --- a/admiral/pkg/registry/testdata/sampleIdentityConfiguration.json +++ b/admiral/pkg/registry/testdata/sampleIdentityConfiguration.json @@ -15,18 +15,26 @@ "namespace": "ns-1-usw2-prf", "serviceName": "app-1-spk-root-service", "services": { - "app-1-spk-root-service": { - "name": "app-1-spk-root-service", - "weight": -1, - "ports": { - "http": 8090 + "default": [ + { + "name": "app-1-spk-root-service", + + "ports": { + "http": 8090 + }, + "selectors": { + "app": "app1" + } + } + ] + }, + "type": { + "rollout": { + "selectors": { + "app": "app1" } } }, - "type": "rollout", - "selectors": { - "app": "app-1" - }, "ports": [ { "name": "http", @@ -111,17 +119,23 @@ "namespace": "ns-1-usw2-e2e", "serviceName": "app-1-spk-root-service", "services": { - "app-1-spk-root-service": { + "default": [{ "name": "app-1-spk-root-service", - "weight": -1, + "ports": { "http": 8090 + }, + "selectors": { + "app": "app1" } - } + }] }, - "type": "rollout", - "selectors": { - "app": "app-1" + "type": { + "rollout": { + "selectors": { + "app": "app1" + } + } }, "ports": [ { @@ -207,17 +221,23 @@ "namespace": "ns-1-usw2-qal", "serviceName": "app-1-spk-root-service", "services": { - "app-1-spk-root-service": { + "default": [{ "name": "app-1-spk-root-service", - "weight": -1, + "ports": { "http": 8090 + }, + "selectors": { + "app": "app1" } - } + }] }, - "type": "rollout", - "selectors": { - "app": "app-1" + "type": { + "rollout": { + "selectors": { + "app": "app1" + } + } }, "ports": [ { diff --git a/admiral/pkg/registry/testutils.go b/admiral/pkg/registry/testutils.go index df3fed29..a9ae43e1 100644 --- a/admiral/pkg/registry/testutils.go +++ b/admiral/pkg/registry/testutils.go @@ -12,18 +12,21 @@ func GetSampleIdentityConfigEnvironment(env string, namespace string, identity s Name: env, Namespace: namespace, ServiceName: "app-1-spk-root-service", - Services: map[string]*RegistryServiceConfig{ - "app-1-spk-root-service": { - Name: "app-1-spk-root-service", - Weight: -1, + Services: map[string][]*RegistryServiceConfig{ + "default": {{ + Name: "app-1-spk-root-service", Ports: map[string]uint32{ "http": 8090, }, + Selectors: map[string]string{"app": "app1"}, + }}, + }, + Type: map[string]*TypeConfig{ + "rollout": { + Selectors: map[string]string{"app": "app1"}, }, }, - Type: "rollout", - Selectors: map[string]string{"app": "app-1"}, - Ports: []*networking.ServicePort{{Name: "http", Number: uint32(80), Protocol: "http"}}, + Ports: []*networking.ServicePort{{Name: "http", Number: uint32(80), Protocol: "http"}}, TrafficPolicy: TrafficPolicy{ ClientConnectionConfig: v1alpha1.ClientConnectionConfig{ ObjectMeta: v1.ObjectMeta{ @@ -116,3 +119,136 @@ func GetSampleIdentityConfig(identity string) IdentityConfig { } return identityConfig } + +func GetSampleIdentityConfigWithRemoteEndpoints(identity string) IdentityConfig { + prfEnv := GetSampleIdentityConfigEnvironment("prf", "ns-1-usw2-prf", identity) + e2eEnv := GetSampleIdentityConfigEnvironment("e2e", "ns-1-usw2-e2e", identity) + qalEnv := GetSampleIdentityConfigEnvironment("qal", "ns-1-usw2-qal", identity) + environments := map[string]*IdentityConfigEnvironment{ + "prf": prfEnv, + "e2e": e2eEnv, + "qal": qalEnv, + } + clientAssets := map[string]string{ + "sample": "sample", + } + cluster := IdentityConfigCluster{ + Name: "cluster1", + Locality: "us-west-2", + IngressEndpoint: "abc-elb.us-west-2.elb.amazonaws.com.", + IngressPort: "15443", + IngressPortName: "http", + Environment: environments, + } + cluster2 := IdentityConfigCluster{ + Name: "cluster2", + Locality: "us-east-2", + IngressEndpoint: "def-elb.us-east-2.elb.amazonaws.com.", + IngressPort: "15443", + IngressPortName: "http", + Environment: environments, + } + identityConfig := IdentityConfig{ + IdentityName: identity, + Clusters: map[string]*IdentityConfigCluster{ + "cluster1": &cluster, + "cluster2": &cluster2, + }, + ClientAssets: clientAssets, + } + return identityConfig +} + +func GetSampleIdentityConfigWithRolloutAndDeployment(identity string) IdentityConfig { + prfEnv := GetSampleIdentityConfigEnvironment("prf", "ns-1-usw2-prf", identity) + e2eEnv := GetSampleIdentityConfigEnvironment("e2e", "ns-1-usw2-e2e", identity) + qalEnv := GetSampleIdentityConfigEnvironment("qal", "ns-1-usw2-qal", identity) + + prfEnv.Type["deployment"] = &TypeConfig{ + Selectors: map[string]string{"deploy-app": "app1"}, + } + prfEnv.Type["rollout"] = &TypeConfig{ + Selectors: map[string]string{"app": "app1"}, + } + + e2eEnv.Type["deployment"] = &TypeConfig{ + Selectors: map[string]string{"deploy-app": "app1"}, + } + e2eEnv.Type["rollout"] = &TypeConfig{ + Selectors: map[string]string{"app": "app1"}, + } + + qalEnv.Type["deployment"] = &TypeConfig{ + Selectors: map[string]string{"deploy-app": "app1"}, + } + qalEnv.Type["rollout"] = &TypeConfig{ + Selectors: map[string]string{"app": "app1"}, + } + + services := map[string][]*RegistryServiceConfig{ + "default": {{ + Name: "app-1-spk-deploy-service", + + Ports: map[string]uint32{ + "http": 8090, + }, + Selectors: map[string]string{"deploy-app": "app1"}, + }, + { + Name: "app-1-spk-root-service", + + Ports: map[string]uint32{ + "http": 8090, + }, + Selectors: map[string]string{"app": "app1"}, + }}, + } + + e2eEnv.Services = services + prfEnv.Services = services + qalEnv.Services = services + environments := map[string]*IdentityConfigEnvironment{ + "prf": prfEnv, + "e2e": e2eEnv, + "qal": qalEnv, + } + clientAssets := map[string]string{ + "sample": "sample", + } + cluster := IdentityConfigCluster{ + Name: "cluster1", + Locality: "us-west-2", + IngressEndpoint: "abc-elb.us-west-2.elb.amazonaws.com.", + IngressPort: "15443", + IngressPortName: "http", + Environment: environments, + } + + prfEnv1 := GetSampleIdentityConfigEnvironment("prf", "ns-1-usw2-prf", identity) + e2eEnv1 := GetSampleIdentityConfigEnvironment("e2e", "ns-1-usw2-e2e", identity) + qalEnv1 := GetSampleIdentityConfigEnvironment("qal", "ns-1-usw2-qal", identity) + + environments1 := map[string]*IdentityConfigEnvironment{ + "prf": prfEnv1, + "e2e": e2eEnv1, + "qal": qalEnv1, + } + + cluster2 := IdentityConfigCluster{ + Name: "cluster2", + Locality: "us-east-2", + IngressEndpoint: "def-elb.us-east-2.elb.amazonaws.com.", + IngressPort: "15443", + IngressPortName: "http", + Environment: environments1, + } + identityConfig := IdentityConfig{ + IdentityName: identity, + Clusters: map[string]*IdentityConfigCluster{ + "cluster1": &cluster, + "cluster2": &cluster2, + }, + ClientAssets: clientAssets, + } + return identityConfig +}