Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

MESH-5405-Create Additional SE #335

Closed
193 changes: 135 additions & 58 deletions admiral/pkg/clusters/configwriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package clusters

import (
"fmt"
"reflect"
"sort"
"strconv"
"strings"
Expand All @@ -15,7 +16,13 @@ import (
networkingV1Alpha3 "istio.io/api/networking/v1alpha3"
)

const typeLabel = "type"
const (
typeLabel = "type"
previewServiceKey = "preview"
activeServiceKey = "active"
desiredServiceKey = "desired"
rootServiceKey = "root"
)

// IstioSEBuilder is an interface to construct Service Entry objects
// from IdentityConfig objects. It can construct multiple Service Entries
Expand All @@ -35,7 +42,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
Expand All @@ -58,37 +65,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 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{previewServiceKey, strings.ToLower(identity), common.GetHostnameSuffix()}))
}
if strategy == canaryStrategy {
meshHosts = append(meshHosts, common.GetCnameVal([]string{canaryStrategy, 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) {
Expand Down Expand Up @@ -116,56 +147,102 @@ 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
endpoint := ingressEndpoints[serverCluster]
endpointsMap := map[string]*networkingV1Alpha3.WorkloadEntry{}
endpoints := []*networkingV1Alpha3.WorkloadEntry{}
tmpEp := endpoint.DeepCopy()
tmpEp.Labels[typeLabel] = identityConfigEnvironment.Type
services := []*registry.RegistryServiceConfig{}
for _, service := range identityConfigEnvironment.Services {
services = append(services, service)
}
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
rolloutServicesMap := map[string]*registry.RegistryServiceConfig{}
deploymentServicesMap := map[string]*registry.RegistryServiceConfig{}
rolloutServices := []*registry.RegistryServiceConfig{}
deploymentServices := []*registry.RegistryServiceConfig{}
endpointFromRollout := false
for resourceType, _ := range identityConfigEnvironment.Type {
for serviceKey, service := range identityConfigEnvironment.Services {
if resourceType == common.Rollout && reflect.DeepEqual(service.Selectors, identityConfigEnvironment.Type[resourceType].Selectors) {
rolloutServicesMap[serviceKey] = service
rolloutServices = append(rolloutServices, service)
}
if resourceType == common.Deployment && reflect.DeepEqual(service.Selectors, identityConfigEnvironment.Type[resourceType].Selectors) {
deploymentServicesMap[serviceKey] = service
deploymentServices = append(deploymentServices, service)
}
}
endpoints = append(endpoints, tmpEp)
}
// 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
sort.Sort(registry.RegistryServiceConfigSorted(rolloutServices))
sort.Sort(registry.RegistryServiceConfigSorted(deploymentServices))
// Deployment won't have weights, so just sort and take the first service to use as the endpoint
for resourceType, _ := range identityConfigEnvironment.Type {
// Rollout without weights is treated the same as deployment so sort and take first service
// If any of the rolloutServicesMap have weights then add them to the list of endpointsMap
if resourceType == common.Rollout && len(rolloutServicesMap) > 0 {
ep := tmpEp.DeepCopy()
if clientCluster == serverCluster {
if identityConfigEnvironment.Type[resourceType].Strategy == canaryStrategy {
if strings.HasPrefix(host, canaryStrategy) {
ep.Address = rolloutServicesMap[desiredServiceKey].Name + common.Sep + identityConfigEnvironment.Namespace + common.GetLocalDomainSuffix()
ep.Ports = rolloutServices[0].Ports
ep.Labels[typeLabel] = resourceType
endpointsMap[ep.Address] = ep
endpointFromRollout = true
} else {
for _, service := range rolloutServicesMap {
if service.Weight > 0 {
weightedep := ep.DeepCopy()
weightedep.Ports = rolloutServices[0].Ports
weightedep.Address = service.Name + common.Sep + identityConfigEnvironment.Namespace + common.GetLocalDomainSuffix()
weightedep.Weight = uint32(service.Weight)
weightedep.Labels[typeLabel] = resourceType
endpointsMap[weightedep.Address] = weightedep
endpointFromRollout = true
}
}
}
} else if identityConfigEnvironment.Type[resourceType].Strategy == bluegreenStrategy {
if strings.HasPrefix(host, previewServiceKey) {
ep.Address = rolloutServicesMap[previewServiceKey].Name + common.Sep + identityConfigEnvironment.Namespace + common.GetLocalDomainSuffix()
} else {
ep.Address = rolloutServicesMap[activeServiceKey].Name + common.Sep + identityConfigEnvironment.Namespace + common.GetLocalDomainSuffix()
}
ep.Ports = rolloutServices[0].Ports
ep.Labels[typeLabel] = resourceType
endpointsMap[ep.Address] = ep
endpointFromRollout = true
}
endpoints = append(endpoints, weightedEp)
}
}
// If we go through all the services associated with the rollout and none have applicable weights then endpoints is empty
// If we go through all the rolloutServicesMap associated with the rollout and none have applicable weights then endpointsMap is empty
// Treat the rollout like a deployment and sort and take the first service
if len(endpoints) == 0 {
if !endpointFromRollout || resourceType == common.Deployment {
tmpEpCopy := tmpEp.DeepCopy()
if clientCluster == serverCluster {
tmpEp.Address = services[0].Name + common.Sep + identityConfigEnvironment.Namespace + common.GetLocalDomainSuffix()
tmpEp.Ports = services[0].Ports
if resourceType == common.Rollout && len(rolloutServicesMap) > 0 {
if _, ok := rolloutServicesMap[rootServiceKey]; ok {
tmpEpCopy.Address = rolloutServicesMap[rootServiceKey].Name + common.Sep + identityConfigEnvironment.Namespace + common.GetLocalDomainSuffix()
} else {
tmpEpCopy.Address = rolloutServices[0].Name + common.Sep + identityConfigEnvironment.Namespace + common.GetLocalDomainSuffix()
}
tmpEpCopy.Ports = rolloutServices[0].Ports
}
if resourceType == common.Deployment && len(deploymentServicesMap) > 0 {
tmpEpCopy.Address = deploymentServices[0].Name + common.Sep + identityConfigEnvironment.Namespace + common.GetLocalDomainSuffix()
tmpEpCopy.Ports = deploymentServices[0].Ports
}
}
tmpEpCopy.Labels[typeLabel] = resourceType
if _, ok := endpointsMap[tmpEpCopy.Address]; !ok {
endpointsMap[tmpEpCopy.Address] = tmpEpCopy
}
endpoints = append(endpoints, tmpEp)
}
}
// 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

for _, ep := range endpointsMap {
endpoints = append(endpoints, ep)
}
sort.Sort(WorkloadEntrySorted(endpoints))
return endpoints, err
}

Expand Down
Loading