Skip to content
This repository has been archived by the owner on Oct 21, 2024. It is now read-only.

Commit

Permalink
feat: add logic for shared annotation (#6)
Browse files Browse the repository at this point in the history
* port over existing pr

* handle templating + shared services

* this works

* clarifying comments
this version works

* one less todo

* cleanup

* chose original
  • Loading branch information
h4ck3rk3y authored Aug 20, 2024
1 parent 6d83383 commit fe88e57
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 20 deletions.
5 changes: 5 additions & 0 deletions kontrol-service/constants/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package constants

const (
SharedVersionVersionString = "shared"
)
5 changes: 5 additions & 0 deletions kontrol-service/engine/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ func generateClusterTopology(serviceConfigs []apitypes.ServiceConfig, ingressCon
clusterTopologyService.IsExternal = true
}

isShared, ok := serviceAnnotations["kardinal.dev.service/shared"]
if ok && isShared == "true" {
clusterTopologyService.IsShared = true
}

// Service plugin?
sPlugins, ok := serviceAnnotations["kardinal.dev.service/plugins"]
if ok {
Expand Down
45 changes: 45 additions & 0 deletions kontrol-service/engine/flow/dev_flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package flow

import (
"fmt"
"kardinal.kontrol-service/constants"

"github.com/kurtosis-tech/stacktrace"

Expand Down Expand Up @@ -66,6 +67,28 @@ func CreateDevFlow(pluginRunner *plugins.PluginRunner, baseClusterTopologyMaybeW
}
}

// TODO(shared-annotation) - we could store "shared" versions somewhere so that the pointers are the same
// if we do that then the render work around isn't necessary
// perhaps top sort this; currently the following is possible
// postgres is marked as shared, we mark its parent "cartservice" as shared
// cartservice then happens in the loop and we try again (currently we don't as we check if version isn't shared)
for _, service := range topology.Services {
if service.IsShared && service.Version != "prod" && service.Version != constants.SharedVersionVersionString {
logrus.Infof("Marking service '%v' as shared, current version '%v'", service.ServiceID, service.Version)
originalVersion := service.Version
service.Version = constants.SharedVersionVersionString
service.OriginalVersionIfShared = originalVersion

if !service.IsHTTP() {
logrus.Infof("Service '%v' isn't http; marking its parents as shared", service.ServiceID)
err := markParentsAsShared(&topology, service)
if err != nil {
return nil, err
}
}
}
}

// Update service dependencies
for i, dependency := range topologyRef.ServiceDependencies {
if dependency.Service.Version == "prod" {
Expand All @@ -87,6 +110,28 @@ func CreateDevFlow(pluginRunner *plugins.PluginRunner, baseClusterTopologyMaybeW
return topologyRef, nil
}

func markParentsAsShared(topology *resolved.ClusterTopology, service *resolved.Service) error {
parents := topology.FindImmediateParents(service)
for _, parent := range parents {
if parent.Version == constants.SharedVersionVersionString {
continue
}
logrus.Infof("Marking parent '%v' as shared, current verson '%s'", parent.ServiceID, parent.Version)
parent.IsShared = true
originalVersion := parent.Version
parent.Version = constants.SharedVersionVersionString
parent.OriginalVersionIfShared = originalVersion

if !parent.IsHTTP() {
err := markParentsAsShared(topology, parent)
if err != nil {
return err
}
}
}
return nil
}

func applyPatch(
pluginRunner *plugins.PluginRunner,
topology *resolved.ClusterTopology,
Expand Down
55 changes: 42 additions & 13 deletions kontrol-service/engine/flow/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package flow

import (
"fmt"
"kardinal.kontrol-service/constants"
"strings"

"github.com/samber/lo"
Expand Down Expand Up @@ -80,8 +81,15 @@ func RenderClusterResources(clusterTopology *resolved.ClusterTopology, namespace

logrus.Infof("have total of %d envoy filters", len(envoyFilters))

sharedServiceBackupVersions := map[string][]string{}
for _, service := range clusterTopology.Services {
if service.IsShared && service.Version == constants.SharedVersionVersionString {
logrus.Infof("Found original version '%v' for service '%v'", service.OriginalVersionIfShared, service.ServiceID)
sharedServiceBackupVersions[service.ServiceID] = append(sharedServiceBackupVersions[service.ServiceID], service.OriginalVersionIfShared)
}
}
// TODO: make it to use a list of Ingresses
gatewayFilter := getEnvoyFilterForGateway(servicesAgainstVersions, versionsAgainstExtHost)
gatewayFilter := getEnvoyFilterForGateway(servicesAgainstVersions, versionsAgainstExtHost, sharedServiceBackupVersions)
envoyFilters = append(envoyFilters, *gatewayFilter)

return types.ClusterResources{
Expand Down Expand Up @@ -220,14 +228,21 @@ func getVirtualService(serviceID string, services []*resolved.Service, namespace
}

func getDestinationRule(serviceID string, services []*resolved.Service, namespace string) *istioclient.DestinationRule {
subsets := lo.Map(services, func(service *resolved.Service, _ int) *v1alpha3.Subset {
return &v1alpha3.Subset{
Name: service.Version,
Labels: map[string]string{
"version": service.Version,
},
}
})
// TODO(shared-annotation) - we could store "shared" versions somewhere so that the pointers are the same
// if we do that then the render work around isn't necessary
subsets := lo.UniqBy(
lo.Map(services, func(service *resolved.Service, _ int) *v1alpha3.Subset {
return &v1alpha3.Subset{
Name: service.Version,
Labels: map[string]string{
"version": service.Version,
},
}
}),
func(subset *v1alpha3.Subset) string {
return subset.Name
},
)

return &istioclient.DestinationRule{
TypeMeta: metav1.TypeMeta{
Expand Down Expand Up @@ -497,8 +512,8 @@ func getAuthorizationPolicy(service *resolved.Service, namespace string) *securi
}
}

func getEnvoyFilterForGateway(servicesAgainstVersions map[string][]string, serviceAndVersionAgainstExtHost map[string]string) *istioclient.EnvoyFilter {
luaScript := generateDynamicLuaScript(servicesAgainstVersions, serviceAndVersionAgainstExtHost)
func getEnvoyFilterForGateway(servicesAgainstVersions map[string][]string, serviceAndVersionAgainstExtHost map[string]string, serviceAgainstBackupVersions map[string][]string) *istioclient.EnvoyFilter {
luaScript := generateDynamicLuaScript(servicesAgainstVersions, serviceAndVersionAgainstExtHost, serviceAgainstBackupVersions)

return &istioclient.EnvoyFilter{
TypeMeta: metav1.TypeMeta{
Expand Down Expand Up @@ -554,7 +569,7 @@ func getEnvoyFilterForGateway(servicesAgainstVersions map[string][]string, servi
}
}

func generateDynamicLuaScript(servicesAgainstVersions map[string][]string, versionAgainstExtHost map[string]string) string {
func generateDynamicLuaScript(servicesAgainstVersions map[string][]string, versionAgainstExtHost map[string]string, serviceAgainstBackupVersions map[string][]string) string {
var setRouteCalls strings.Builder

// Helper function to add a setRoute call
Expand All @@ -574,11 +589,13 @@ func generateDynamicLuaScript(servicesAgainstVersions map[string][]string, versi
`, service, destination))
}

// This is the one we are interested in
// For prod host all goes to prod
// For non prod host we add a non prod mapping for the non prod service and everything else falls back
for service, versions := range servicesAgainstVersions {
for _, version := range versions {
if version == constants.SharedVersionVersionString {
continue
}
setRouteCalls.WriteString(fmt.Sprintf(`
if hostname == "%s" then`, versionAgainstExtHost[version]))
destination := fmt.Sprintf("%s-%s", service, version)
Expand All @@ -588,6 +605,18 @@ func generateDynamicLuaScript(servicesAgainstVersions map[string][]string, versi
}
}

// we handle shared versions separately by finding host markings for original version
for service, originalVersions := range serviceAgainstBackupVersions {
for _, originalVersion := range originalVersions {
setRouteCalls.WriteString(fmt.Sprintf(`
if hostname == "%s" then`, versionAgainstExtHost[originalVersion]))
destination := fmt.Sprintf("%s-%s", service, constants.SharedVersionVersionString)
addSetRouteCall(service, destination)
setRouteCalls.WriteString(`
end`)
}
}

// this gets consumed by the virtual source route for the gateway
for version, extHost := range versionAgainstExtHost {
destination := fmt.Sprintf("%s-%s", extHost, version)
Expand Down
16 changes: 9 additions & 7 deletions kontrol-service/types/cluster_topology/resolved/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ type ClusterTopology struct {
}

type Service struct {
ServiceID string `json:"serviceID"`
Version string `json:"version"`
ServiceSpec *corev1.ServiceSpec `json:"serviceSpec"`
DeploymentSpec *appsv1.DeploymentSpec `json:"deploymentSpec"`
IsExternal bool `json:"isExternal"`
IsStateful bool `json:"isStateful"`
StatefulPlugins []*StatefulPlugin `json:"statefulPlugins"`
ServiceID string `json:"serviceID"`
Version string `json:"version"`
ServiceSpec *corev1.ServiceSpec `json:"serviceSpec"`
DeploymentSpec *appsv1.DeploymentSpec `json:"deploymentSpec"`
IsExternal bool `json:"isExternal"`
IsStateful bool `json:"isStateful"`
StatefulPlugins []*StatefulPlugin `json:"statefulPlugins"`
IsShared bool `json:"isShared"`
OriginalVersionIfShared string `json:"originalVersionIfShared"`
}

type ServiceDependency struct {
Expand Down

0 comments on commit fe88e57

Please sign in to comment.