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

Generate VirtualServices based on DependencyProxy #282

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,9 @@ gen-yaml:
kustomize build ./install/sample/overlays/rollout-canary > ./out/yaml/sample-greeting-rollout-canary.yaml
kustomize build ./install/sample/overlays/rollout-bluegreen > ./out/yaml/sample-greeting-rollout-bluegreen.yaml
kustomize build ./install/sample/overlays/remote > ./out/yaml/remotecluster_sample.yaml
cp ./install/sample/proxy.yaml ./out/yaml/proxy.yaml
cp ./install/sample/sample_dep.yaml ./out/yaml/sample_dep.yaml
cp ./install/sample/depProxyExample.yaml ./out/yaml/depProxyExample.yaml
cp ./install/sample/greeting_preview.yaml ./out/yaml/greeting_preview.yaml
cp ./install/sample/gtp.yaml ./out/yaml/gtp.yaml
cp ./install/sample/gtp_failover.yaml ./out/yaml/gtp_failover.yaml
Expand Down
121 changes: 121 additions & 0 deletions admiral/pkg/clusters/dependencyproxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package clusters

import (
"context"
"fmt"
"sync"

v1 "github.com/istio-ecosystem/admiral/admiral/pkg/apis/admiral/v1"
"github.com/istio-ecosystem/admiral/admiral/pkg/controller/common"
networkingv1alpha3 "istio.io/api/networking/v1alpha3"
"istio.io/client-go/pkg/apis/networking/v1alpha3"
)

type dependencyProxyVirtualServiceCache struct {
identityVSCache map[string]map[string]*v1alpha3.VirtualService
mutex *sync.Mutex
}

func (d *dependencyProxyVirtualServiceCache) put(outerKey string, innerKey string, value *v1alpha3.VirtualService) {
d.mutex.Lock()
defer d.mutex.Unlock()
if _, ok := d.identityVSCache[outerKey]; !ok {
d.identityVSCache[outerKey] = make(map[string]*v1alpha3.VirtualService)
}
d.identityVSCache[outerKey][innerKey] = value
}

func (d *dependencyProxyVirtualServiceCache) get(key string) map[string]*v1alpha3.VirtualService {
d.mutex.Lock()
defer d.mutex.Unlock()
return d.identityVSCache[key]
}

// updateIdentityDependencyProxyCache adds/updates the map-of-map cache with destination identity as the outer key
// and the admiral.io/env+destinationIdentity as the inner key with the value of generate virtualservice.
// Example: <greeting>:{<stage-greeting>:*v1alpha1.VirtualService}
func updateIdentityDependencyProxyCache(ctx context.Context, cache *dependencyProxyVirtualServiceCache,
dependencyProxyObj *v1.DependencyProxy, dependencyProxyDefaultHostNameGenerator DependencyProxyDefaultHostNameGenerator) error {

if cache == nil {
return fmt.Errorf("update dependency proxy cache failed with error: dependencyProxyVirtualServiceCache is nil")
}
if cache.identityVSCache == nil {
return fmt.Errorf("update dependency proxy cache failed with error: dependencyProxyVirtualServiceCache.identityVSCache is nil")
}

if dependencyProxyObj == nil {
return fmt.Errorf("update dependency proxy cache failed with error: dependencyProxyObj is nil")
}
if dependencyProxyObj.Annotations == nil {
return fmt.Errorf("update dependency proxy cache failed with error: dependencyProxyObj.Annotations is nil")
}

env := dependencyProxyObj.Annotations[common.GetEnvKey()]
if env == "" {
return fmt.Errorf("%s is empty", common.GetEnvKey())
}

if dependencyProxyObj.Spec.Destination == nil {
return fmt.Errorf("update dependency proxy cache failed with error: dependencyProxyObj.Spec.Destination is nil")
}
if dependencyProxyObj.Spec.Destination.Identity == "" {
return fmt.Errorf("update dependency proxy cache failed with error: dependencyProxyObj.Spec.Destination.Identity is empty")
}

vs, err := generateVSFromDependencyProxy(ctx, dependencyProxyObj, dependencyProxyDefaultHostNameGenerator)
if err != nil {
return err
}

cache.put(dependencyProxyObj.Spec.Destination.Identity, fmt.Sprintf("%s-%s", env, dependencyProxyObj.Spec.Destination.Identity), vs)
return nil
}

// generateVSFromDependencyProxy will generate VirtualServices from the configurations provided in the
// *v1.DependencyProxy object
func generateVSFromDependencyProxy(ctx context.Context, dependencyProxyObj *v1.DependencyProxy,
shriramsharma marked this conversation as resolved.
Show resolved Hide resolved
dependencyProxyDefaultHostNameGenerator DependencyProxyDefaultHostNameGenerator) (*v1alpha3.VirtualService, error) {

if dependencyProxyDefaultHostNameGenerator == nil {
return nil, fmt.Errorf("failed to generate proxy VirtualService due to error: dependencyProxyDefaultHostNameGenerator is nil")
}

proxyCNAME, err := GenerateProxyDestinationHostName(dependencyProxyObj)
if err != nil {
return nil, fmt.Errorf("failed to generate proxy VirtualService due to error: %w", err)
}
virtualServiceHostnames, err := GenerateVirtualServiceHostNames(dependencyProxyObj, dependencyProxyDefaultHostNameGenerator)
if err != nil {
return nil, fmt.Errorf("failed to generate proxy VirtualService due to error: %w", err)
}

defaultVSName := getIstioResourceName(virtualServiceHostnames[0], "-vs")

vsRoutes := []*networkingv1alpha3.HTTPRouteDestination{
{
Destination: &networkingv1alpha3.Destination{
Host: proxyCNAME,
Port: &networkingv1alpha3.PortSelector{
Number: common.DefaultServiceEntryPort,
},
},
},
}
vs := networkingv1alpha3.VirtualService{
Hosts: virtualServiceHostnames,
Http: []*networkingv1alpha3.HTTPRoute{
{
Route: vsRoutes,
},
},
}

syncNamespace := common.GetSyncNamespace()
if syncNamespace == "" {
return nil, fmt.Errorf("failed to generate proxy VirtualService due to error: syncnamespace is empty")
}

// nolint
return createVirtualServiceSkeleton(vs, defaultVSName, syncNamespace), nil
}
116 changes: 116 additions & 0 deletions admiral/pkg/clusters/dependencyproxyConverter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package clusters

import (
"fmt"
"strings"

v1 "github.com/istio-ecosystem/admiral/admiral/pkg/apis/admiral/v1"
"github.com/istio-ecosystem/admiral/admiral/pkg/controller/common"
)

// DependencyProxyDefaultHostNameGenerator provides functions that help convert
// the given configuration to the corresponding VirtualService
type DependencyProxyDefaultHostNameGenerator interface {
defaultHostNameGenerator(dependencyProxyObj *v1.DependencyProxy) (string, error)
}

// VirtualServiceDestinationHostGenerator generates the hostname of the proxy
// mesh service
type VirtualServiceDestinationHostGenerator interface {
GenerateProxyDestinationHostName(dependencyProxyObj *v1.DependencyProxy) (string, error)
}

// VirtualServiceHostsGenerator generates all the host names
type VirtualServiceHostsGenerator interface {
GenerateVirtualServiceHostNames(dependencyProxyObj *v1.DependencyProxy) ([]string, error)
}

type dependencyProxyDefaultHostNameGenerator struct {
}

// GenerateProxyDestinationHostName generates the VirtualServices's destination host which in this case
// would be the endpoint of the proxy identity
func GenerateProxyDestinationHostName(dependencyProxyObj *v1.DependencyProxy) (string, error) {
err := validate(dependencyProxyObj)
if err != nil {
return "", fmt.Errorf("failed to generate virtual service destination hostname due to error: %w", err)
}
proxyEnv := dependencyProxyObj.ObjectMeta.Annotations[common.GetEnvKey()]
proxyIdentity := dependencyProxyObj.Spec.Proxy.Identity
return strings.ToLower(common.GetCnameVal([]string{proxyEnv, proxyIdentity, common.GetHostnameSuffix()})), nil
}

// GenerateVirtualServiceHostNames generates all the VirtualService's hostnames using the information in the
// *v1.DependencyProxy object. In addition it also generates a default hostname by concatenating the
// admiral.io/env value with destinationServiceIdentity+dnsSuffix
func GenerateVirtualServiceHostNames(dependencyProxyObj *v1.DependencyProxy, hostnameGenerator DependencyProxyDefaultHostNameGenerator) ([]string, error) {
err := validate(dependencyProxyObj)
if err != nil {
return nil, fmt.Errorf("failed to generate virtual service hostnames due to error: %w", err)
}
destinationServiceIdentity := dependencyProxyObj.Spec.Destination.Identity
dnsSuffix := dependencyProxyObj.Spec.Destination.DnsSuffix

defaultVSHostName, err := hostnameGenerator.defaultHostNameGenerator(dependencyProxyObj)
if err != nil {
return nil, fmt.Errorf("failed to generate virtual service hostnames due to error: %w", err)
}
vsHostNames := make([]string, 0)
vsHostNames = append(vsHostNames, defaultVSHostName)
if dependencyProxyObj.Spec.Destination.DnsPrefixes == nil {
return vsHostNames, nil
}
dnsPrefixes := dependencyProxyObj.Spec.Destination.DnsPrefixes
for _, prefix := range dnsPrefixes {
vsHostNames = append(vsHostNames, common.GetCnameVal([]string{prefix, destinationServiceIdentity, dnsSuffix}))
}
return vsHostNames, nil
}

func (*dependencyProxyDefaultHostNameGenerator) defaultHostNameGenerator(dependencyProxyObj *v1.DependencyProxy) (string, error) {
err := validate(dependencyProxyObj)
if err != nil {
return "", fmt.Errorf("failed to generate default hostname due to error: %w", err)
}

destinationServiceIdentity := dependencyProxyObj.Spec.Destination.Identity
dnsSuffix := dependencyProxyObj.Spec.Destination.DnsSuffix
proxyEnv := dependencyProxyObj.ObjectMeta.Annotations[common.GetEnvKey()]

return strings.ToLower(common.GetCnameVal([]string{proxyEnv, destinationServiceIdentity, dnsSuffix})), nil
}

func validate(dependencyProxyObj *v1.DependencyProxy) error {

if dependencyProxyObj == nil {
return fmt.Errorf("dependencyProxyObj is nil")
}
if dependencyProxyObj.ObjectMeta.Annotations == nil {
nirvanagit marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("dependencyProxyObj.ObjectMeta.Annotations is nil")
}
proxyEnv := dependencyProxyObj.ObjectMeta.Annotations[common.GetEnvKey()]
if proxyEnv == "" {
return fmt.Errorf("%s is empty", common.GetEnvKey())
}
if dependencyProxyObj.Spec.Proxy == nil {
return fmt.Errorf("dependencyProxyObj.Spec.Proxy is nil")
}
proxyIdentity := dependencyProxyObj.Spec.Proxy.Identity
if proxyIdentity == "" {
return fmt.Errorf("dependencyProxyObj.Spec.Proxy.Identity is empty")
}

if dependencyProxyObj.Spec.Destination == nil {
return fmt.Errorf("dependencyProxyObj.Spec.Destination is nil")
}
destinationServiceIdentity := dependencyProxyObj.Spec.Destination.Identity
if destinationServiceIdentity == "" {
return fmt.Errorf("dependencyProxyObj.Spec.Destination.Identity is empty")
}
dnsSuffix := dependencyProxyObj.Spec.Destination.DnsSuffix
if dnsSuffix == "" {
return fmt.Errorf("dependencyProxyObj.Spec.Destination.DnsSuffix is empty")
}

return nil
}
Loading