Skip to content

Commit

Permalink
crds: Add support for ServiceSplitter
Browse files Browse the repository at this point in the history
  • Loading branch information
ishustava committed Sep 28, 2020
1 parent e3abda7 commit b0e78fd
Show file tree
Hide file tree
Showing 27 changed files with 1,148 additions and 37 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export GIT_DESCRIBE
export GOLDFLAGS
export GOTAGS

CRD_OPTIONS ?= "crd:trivialVersions=true"
CRD_OPTIONS ?= "crd:trivialVersions=true,allowDangerousTypes=true,crdVersions=v1beta1"

################
# CI Variables #
Expand Down Expand Up @@ -157,7 +157,7 @@ ifeq (, $(shell which controller-gen))
CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\
cd $$CONTROLLER_GEN_TMP_DIR ;\
go mod init tmp ;\
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.3.0 ;\
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.4.0 ;\
rm -rf $$CONTROLLER_GEN_TMP_DIR ;\
}
CONTROLLER_GEN=$(GOBIN)/controller-gen
Expand Down
1 change: 1 addition & 0 deletions api/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const (
ProxyDefaults string = "proxydefaults"
ServiceResolver string = "serviceresolver"
ServiceRouter string = "servicerouter"
ServiceSplitter string = "servicesplitter"

Global string = "global"
DefaultConsulNamespace string = "default"
Expand Down
2 changes: 1 addition & 1 deletion api/v1alpha1/proxydefaults_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type ProxyDefaultsValidator struct {
// NOTE: The below line cannot be combined with any other comment. If it is
// it will break the code generation.
//
// +kubebuilder:webhook:verbs=create;update,path=/mutate-v1alpha1-proxydefaults,mutating=true,failurePolicy=fail,groups=consul.hashicorp.com,resources=proxydefaults,versions=v1alpha1,name=mutate-proxydefaults.consul.hashicorp.com
// +kubebuilder:webhook:verbs=create;update,path=/mutate-v1alpha1-proxydefaults,mutating=true,failurePolicy=fail,groups=consul.hashicorp.com,resources=proxydefaults,versions=v1alpha1,name=mutate-proxydefaults.consul.hashicorp.com,webhookVersions=v1beta1,sideEffects=None

func (v *ProxyDefaultsValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
var proxyDefaults ProxyDefaults
Expand Down
2 changes: 1 addition & 1 deletion api/v1alpha1/servicedefaults_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type ServiceDefaultsValidator struct {
//
// NOTE: The below line cannot be combined with any other comment. If it is it will break the code generation.
//
// +kubebuilder:webhook:verbs=create;update,path=/mutate-v1alpha1-servicedefaults,mutating=true,failurePolicy=fail,groups=consul.hashicorp.com,resources=servicedefaults,versions=v1alpha1,name=mutate-servicedefaults.consul.hashicorp.com
// +kubebuilder:webhook:verbs=create;update,path=/mutate-v1alpha1-servicedefaults,mutating=true,failurePolicy=fail,groups=consul.hashicorp.com,resources=servicedefaults,versions=v1alpha1,name=mutate-servicedefaults.consul.hashicorp.com,webhookVersions=v1beta1,sideEffects=None

func (v *ServiceDefaultsValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
var svcDefaults ServiceDefaults
Expand Down
2 changes: 1 addition & 1 deletion api/v1alpha1/serviceresolver_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type ServiceResolverValidator struct {
//
// NOTE: The below line cannot be combined with any other comment. If it is it will break the code generation.
//
// +kubebuilder:webhook:verbs=create;update,path=/mutate-v1alpha1-serviceresolver,mutating=true,failurePolicy=fail,groups=consul.hashicorp.com,resources=serviceresolvers,versions=v1alpha1,name=mutate-serviceresolver.consul.hashicorp.com
// +kubebuilder:webhook:verbs=create;update,path=/mutate-v1alpha1-serviceresolver,mutating=true,failurePolicy=fail,groups=consul.hashicorp.com,resources=serviceresolvers,versions=v1alpha1,name=mutate-serviceresolver.consul.hashicorp.com,webhookVersions=v1beta1,sideEffects=None

func (v *ServiceResolverValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
var svcResolver ServiceResolver
Expand Down
2 changes: 1 addition & 1 deletion api/v1alpha1/servicerouter_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type ServiceRouterValidator struct {
//
// NOTE: The below line cannot be combined with any other comment. If it is it will break the code generation.
//
// +kubebuilder:webhook:verbs=create;update,path=/mutate-v1alpha1-servicerouter,mutating=true,failurePolicy=fail,groups=consul.hashicorp.com,resources=servicerouter,versions=v1alpha1,name=mutate-servicerouter.consul.hashicorp.com
// +kubebuilder:webhook:verbs=create;update,path=/mutate-v1alpha1-servicerouter,mutating=true,failurePolicy=fail,groups=consul.hashicorp.com,resources=servicerouter,versions=v1alpha1,name=mutate-servicerouter.consul.hashicorp.com,webhookVersions=v1beta1,sideEffects=None

func (v *ServiceRouterValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
var svcRouter ServiceRouter
Expand Down
191 changes: 191 additions & 0 deletions api/v1alpha1/servicesplitter_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package v1alpha1

import (
"reflect"

"github.com/hashicorp/consul-k8s/api/common"
capi "github.com/hashicorp/consul/api"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
)

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status

// ServiceSplitter is the Schema for the servicesplitters API
type ServiceSplitter struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec ServiceSplitterSpec `json:"spec,omitempty"`
Status `json:"status,omitempty"`
}

type ServiceSplits []ServiceSplit

// ServiceSplitterSpec defines the desired state of ServiceSplitter
type ServiceSplitterSpec struct {
// Splits defines how much traffic to send to which set of service instances during a traffic split.
// The sum of weights across all splits must add up to 100.
Splits ServiceSplits `json:"splits,omitempty"`
}

type ServiceSplit struct {
// Weight is a value between 0 and 100 reflecting what portion of traffic should be directed to this split.
// The smallest representable weight is 1/10000 or .01%.
Weight float32 `json:"weight,omitempty"`
// Service is the service to resolve instead of the default.
Service string `json:"service,omitempty"`
// ServiceSubset is a named subset of the given service to resolve instead of one defined
// as that service's DefaultSubset. If empty the default subset is used.
ServiceSubset string `json:"serviceSubset,omitempty"`
// The namespace to resolve the service from instead of the current namespace.
// If empty the current namespace is assumed.
Namespace string `json:"namespace,omitempty"`
}

// +kubebuilder:object:root=true

// ServiceSplitterList contains a list of ServiceSplitter
type ServiceSplitterList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []ServiceSplitter `json:"items"`
}

func (in *ServiceSplitter) GetObjectMeta() metav1.ObjectMeta {
return in.ObjectMeta
}

func (in *ServiceSplitter) AddFinalizer(name string) {
in.ObjectMeta.Finalizers = append(in.Finalizers(), name)
}

func (in *ServiceSplitter) RemoveFinalizer(name string) {
var newFinalizers []string
for _, oldF := range in.Finalizers() {
if oldF != name {
newFinalizers = append(newFinalizers, oldF)
}
}
in.ObjectMeta.Finalizers = newFinalizers
}

func (in *ServiceSplitter) Finalizers() []string {
return in.ObjectMeta.Finalizers
}

func (in *ServiceSplitter) ConsulKind() string {
return capi.ServiceSplitter
}

func (in *ServiceSplitter) ConsulNamespaced() bool {
return true
}

func (in *ServiceSplitter) KubeKind() string {
return common.ServiceSplitter
}

func (in *ServiceSplitter) Name() string {
return in.ObjectMeta.Name
}

func (in *ServiceSplitter) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) {
in.Status.Conditions = Conditions{
{
Type: ConditionSynced,
Status: status,
LastTransitionTime: metav1.Now(),
Reason: reason,
Message: message,
},
}
}

func (in *ServiceSplitter) SyncedCondition() (status corev1.ConditionStatus, reason, message string) {
cond := in.Status.GetCondition(ConditionSynced)
if cond == nil {
return corev1.ConditionUnknown, "", ""
}
return cond.Status, cond.Reason, cond.Message
}

func (in *ServiceSplitter) SyncedConditionStatus() corev1.ConditionStatus {
condition := in.Status.GetCondition(ConditionSynced)
if condition == nil {
return corev1.ConditionUnknown
}
return condition.Status
}

func (in *ServiceSplitter) ToConsul() capi.ConfigEntry {
return &capi.ServiceSplitterConfigEntry{
Kind: in.ConsulKind(),
Name: in.Name(),
Splits: in.Spec.Splits.toConsul(),
}
}

func (in *ServiceSplitter) MatchesConsul(candidate capi.ConfigEntry) bool {
serviceSplitterCandidate, ok := candidate.(*capi.ServiceSplitterConfigEntry)
if !ok {
return false
}
serviceSplitterCandidate.Namespace = ""
serviceSplitterCandidate.CreateIndex = 0
serviceSplitterCandidate.ModifyIndex = 0

return reflect.DeepEqual(in.ToConsul(), candidate)
}

func (in *ServiceSplitter) Validate() error {
var errs field.ErrorList
path := field.NewPath("spec").Child("splits")

// The sum of weights across all splits must add up to 100.
sumOfWeights := float32(0)
for i, split := range in.Spec.Splits {
// Validate that the weight value is between 0.01 and 100.
if split.Weight > 100 || split.Weight < 0.01 {
errs = append(errs, field.Invalid(path.Index(i).Child("weight"), split.Weight,
"weight must be between 0.01% and 100%"))
}

// If valid, add to sumOfWeights.
sumOfWeights += split.Weight
}

if sumOfWeights != 100 {
errs = append(errs, field.Invalid(path, sumOfWeights,
"the sum of weights across all splits must add up to 100%"))
}

if len(errs) > 0 {
return apierrors.NewInvalid(
schema.GroupKind{Group: ConsulHashicorpGroup, Kind: in.KubeKind()},
in.Name(), errs)
}
return nil
}

func (in ServiceSplits) toConsul() []capi.ServiceSplit {
var consulServiceSplits []capi.ServiceSplit
for _, split := range in {
consulServiceSplits = append(consulServiceSplits, capi.ServiceSplit{
Weight: split.Weight,
Service: split.Service,
ServiceSubset: split.ServiceSubset,
Namespace: split.Namespace,
})
}

return consulServiceSplits
}

func init() {
SchemeBuilder.Register(&ServiceSplitter{}, &ServiceSplitterList{})
}
Loading

0 comments on commit b0e78fd

Please sign in to comment.