From bf88ca1b79376d37afea48a5ef84d1a69c9827b4 Mon Sep 17 00:00:00 2001 From: Bruno Custodio Date: Thu, 10 Jan 2019 11:03:38 +0000 Subject: [PATCH] translator: make edgelb pool name optional Signed-off-by: Bruno Custodio --- docs/usage/10-provisioning-services.adoc | 12 ++++-- pkg/controllers/ingress.go | 2 +- pkg/controllers/service.go | 2 +- pkg/translator/base_options.go | 13 +++---- pkg/translator/base_options_utils.go | 21 ++++++++++ pkg/translator/ingress_options.go | 4 +- pkg/translator/ingress_options_test.go | 42 +++++++++++--------- pkg/translator/service_options.go | 4 +- pkg/translator/service_options_test.go | 49 ++++++++++++++---------- pkg/translator/service_utils.go | 6 +-- pkg/util/strings/strings.go | 15 +++++--- pkg/util/strings/strings_test.go | 49 +++++++++++++++++++++--- test/e2e/e2e_suite_test.go | 4 +- test/e2e/framework/framework.go | 26 +++++++++++++ test/e2e/service_test.go | 20 ++++++---- 15 files changed, 191 insertions(+), 78 deletions(-) create mode 100644 pkg/translator/base_options_utils.go diff --git a/docs/usage/10-provisioning-services.adoc b/docs/usage/10-provisioning-services.adoc index 91294f3..5160b74 100644 --- a/docs/usage/10-provisioning-services.adoc +++ b/docs/usage/10-provisioning-services.adoc @@ -29,17 +29,21 @@ In particular, exposing UDP or SCTP services is **NOT** supported. === Using `dklb` to provision a Kubernetes service To expose a TCP application running on MKE to either inside or outside the DC/OS cluster, a Kubernetes https://kubernetes.io/docs/concepts/services-networking/service/[`Service`] resource of type https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer[`LoadBalancer`] must be created. -Furthermore, and in order for `dklb` to provision said service using EdgeLB, the service must be https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/[annotated] with the following annotation: +`dklb` will react to this event by provisioning an EdgeLB pool (henceforth referred to as the _target EdgeLB pool_) for the `Service` resource based on its specification. + +=== Customizing the name of the EdgeLB pool + +By default, `dklb` uses the MKE cluster's name and the `Service` resource's namespace and name to compute the name of the target EdgeLB pool. +To specify a custom name for said EdgeLB pool, one may https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/[annotate] the `Service` resource with the following annotation: [source,text] ---- kubernetes.dcos.io/edgelb-pool-name: "" ---- -This annotation lets `dklb` know about the EdgeLB pool it should use to provision the service (henceforth referred to as the _target EdgeLB pool_). -Depending on whether the target EdgeLB pool exists or not, `dklb` will create or update it in order to expose all ports defined in the `Service` resource. +Depending on whether the "" EdgeLB pool exists or not, `dklb` will create or update it in order to expose all ports defined in the `Service` resource. -IMPORTANT: One must not remove or change the value of this annotation at any time after the `Service` resource is created. +IMPORTANT: One must not remove or change the value of this annotation at any time after it is added to a given `Service` resource. === Intra-DC/OS vs external exposure diff --git a/pkg/controllers/ingress.go b/pkg/controllers/ingress.go index 307513f..61fff68 100644 --- a/pkg/controllers/ingress.go +++ b/pkg/controllers/ingress.go @@ -126,7 +126,7 @@ func (c *IngressController) processQueueItem(workItem WorkItem) error { er := kubernetesutil.NewEventRecorderForNamespace(c.kubeClient, ingress.Namespace) // Compute the set of options that will be used to translate the Ingress resource into an EdgeLB pool. - options, err := translator.ComputeIngressTranslationOptions(ingress) + options, err := translator.ComputeIngressTranslationOptions(c.clusterName, ingress) if err != nil { // Emit an event and log an error, but do not re-enqueue as the resource's spec was found to be invalid. er.Eventf(ingress, corev1.EventTypeWarning, constants.ReasonInvalidAnnotations, "the resource's annotations are not valid: %v", err) diff --git a/pkg/controllers/service.go b/pkg/controllers/service.go index 54d4b87..4597cd8 100644 --- a/pkg/controllers/service.go +++ b/pkg/controllers/service.go @@ -133,7 +133,7 @@ func (c *ServiceController) processQueueItem(workItem WorkItem) error { er := kubernetesutil.NewEventRecorderForNamespace(c.kubeClient, service.Namespace) // Compute the set of options that will be used to translate the Service resource into an EdgeLB pool. - options, err := translator.ComputeServiceTranslationOptions(service) + options, err := translator.ComputeServiceTranslationOptions(c.clusterName, service) if err != nil { // Emit an event and log an error, but do not re-enqueue as the resource's spec was found to be invalid. er.Eventf(service, corev1.EventTypeWarning, constants.ReasonInvalidAnnotations, "the resource's annotations are not valid: %v", err) diff --git a/pkg/translator/base_options.go b/pkg/translator/base_options.go index f5269f8..18bc855 100644 --- a/pkg/translator/base_options.go +++ b/pkg/translator/base_options.go @@ -31,17 +31,16 @@ type BaseTranslationOptions struct { // parseBaseTranslationOptions attempts to compute base, common translation options from the specified set of annotations. // In case options cannot be computed or are invalid, the error message MUST be suitable to be used as the message for a Kubernetes event associated with the resource. -func parseBaseTranslationOptions(annotations map[string]string) (*BaseTranslationOptions, error) { +func parseBaseTranslationOptions(clusterName, namespace, name string, annotations map[string]string) (*BaseTranslationOptions, error) { // Create a "BaseTranslationOptions" struct to hold the computed options. res := &BaseTranslationOptions{} - // Parse the name of the target EdgeLB pool. - // This annotation is MANDATORY. - v, exists := annotations[constants.EdgeLBPoolNameAnnotationKey] - if !exists || v == "" { - return nil, fmt.Errorf("required annotation %q has not been provided", constants.EdgeLBPoolNameAnnotationKey) + // Parse or compute the name of the target EdgeLB pool. + if v, exists := annotations[constants.EdgeLBPoolNameAnnotationKey]; !exists || v == "" { + res.EdgeLBPoolName = computeEdgeLBPoolName(clusterName, namespace, name) + } else { + res.EdgeLBPoolName = v } - res.EdgeLBPoolName = v // Parse the role of the target EdgeLB pool. if v, exists := annotations[constants.EdgeLBPoolRoleAnnotationKey]; !exists || v == "" { diff --git a/pkg/translator/base_options_utils.go b/pkg/translator/base_options_utils.go new file mode 100644 index 0000000..53042c5 --- /dev/null +++ b/pkg/translator/base_options_utils.go @@ -0,0 +1,21 @@ +package translator + +import ( + "fmt" + + "github.com/mesosphere/dklb/pkg/util/strings" +) + +const ( + // edgeLBPoolNameComponentSeparator is the string used as to separate the components of an EdgeLB pool's name. + // "--" is chosen as the value since the name of an EdgeLB pool must match the "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$" regular expression. + edgeLBPoolNameComponentSeparator = "--" + // edgeLBPoolNameFormatString is the format string used to compute the name of an EdgeLB pool corresponding to a given Kubernetes resource. + // The resulting name is of the form "----". + edgeLBPoolNameFormatString = "%s" + edgeLBPoolNameComponentSeparator + "%s" + edgeLBPoolNameComponentSeparator + "%s" +) + +// computeEdgeLBPoolName computes the name of the EdgeLB pool that corresponds to the Kubernetes resource corresponding to the provided coordinates. +func computeEdgeLBPoolName(clusterName, namespace, name string) string { + return fmt.Sprintf(edgeLBPoolNameFormatString, strings.ReplaceForwardSlashes(clusterName, edgeLBPoolNameComponentSeparator), namespace, name) +} diff --git a/pkg/translator/ingress_options.go b/pkg/translator/ingress_options.go index 0c7dc66..3552852 100644 --- a/pkg/translator/ingress_options.go +++ b/pkg/translator/ingress_options.go @@ -19,7 +19,7 @@ type IngressTranslationOptions struct { // ComputeIngressTranslationOptions computes the set of options to use for "translating" the specified Ingress resource into an EdgeLB pool. // In case options cannot be computed or are invalid, the error message MUST be suitable to be used as the message for a Kubernetes event associated with the resource. -func ComputeIngressTranslationOptions(obj *extsv1beta1.Ingress) (*IngressTranslationOptions, error) { +func ComputeIngressTranslationOptions(clusterName string, obj *extsv1beta1.Ingress) (*IngressTranslationOptions, error) { // Create an "IngressTranslationOptions" struct to hold the computed options. res := &IngressTranslationOptions{} var annotations map[string]string @@ -33,7 +33,7 @@ func ComputeIngressTranslationOptions(obj *extsv1beta1.Ingress) (*IngressTransla } // Parse base translation options. - b, err := parseBaseTranslationOptions(annotations) + b, err := parseBaseTranslationOptions(clusterName, obj.Namespace, obj.Name, annotations) if err != nil { return nil, err } diff --git a/pkg/translator/ingress_options_test.go b/pkg/translator/ingress_options_test.go index 1211333..e33f91e 100644 --- a/pkg/translator/ingress_options_test.go +++ b/pkg/translator/ingress_options_test.go @@ -20,18 +20,28 @@ func TestComputeIngressTranslationOptions(t *testing.T) { options *translator.IngressTranslationOptions error error }{ - // Test computing options for an Ingress resource without the required annotations. - // Make sure an error is returned. + // Test computing options for an Ingress resource without any annotations. + // Make sure the name of the EdgeLB pool is computed as expected, and that the default values are used everywhere else. { - description: "compute options for an Ingress resource without the required annotations", + description: "compute options for an Ingress resource without any annotations", annotations: map[string]string{}, - options: nil, - error: fmt.Errorf("required annotation %q has not been provided", constants.EdgeLBPoolNameAnnotationKey), + options: &translator.IngressTranslationOptions{ + BaseTranslationOptions: translator.BaseTranslationOptions{ + EdgeLBPoolName: "dev--kubernetes01--foo--bar", + EdgeLBPoolRole: translator.DefaultEdgeLBPoolRole, + EdgeLBPoolCpus: translator.DefaultEdgeLBPoolCpus, + EdgeLBPoolMem: translator.DefaultEdgeLBPoolMem, + EdgeLBPoolSize: translator.DefaultEdgeLBPoolSize, + EdgeLBPoolCreationStrategy: translator.DefaultEdgeLBPoolCreationStrategy, + }, + EdgeLBPoolPort: translator.DefaultEdgeLBPoolPort, + }, + error: nil, }, - // Test computing options for an Ingress resource with just the required annotations. + // Test computing options for an Ingress resource specifying the name of the target EdgeLB pool. // Make sure the name of the EdgeLB pool is captured as expected, and that the default values are used everywhere else. { - description: "compute options for an Ingress resource with just the required annotations", + description: "compute options for an Ingress resource specifying the name of the target EdgeLB pool", annotations: map[string]string{ constants.EdgeLBPoolNameAnnotationKey: "foo", }, @@ -49,16 +59,15 @@ func TestComputeIngressTranslationOptions(t *testing.T) { error: nil, }, // Test computing options for an Ingress resource defining a custom frontend bind port. - // Make sure the name of the EdgeLB pool and the frontend bind port are captured as expected, and that the default values are used everywhere else. + // Make sure the frontend bind port is captured as expected, and that the default values are used everywhere else. { description: "compute options for an Ingress resource defining a custom frontend bind port", annotations: map[string]string{ - constants.EdgeLBPoolNameAnnotationKey: "foo", - constants.EdgeLBPoolPortKey: "14708", + constants.EdgeLBPoolPortKey: "14708", }, options: &translator.IngressTranslationOptions{ BaseTranslationOptions: translator.BaseTranslationOptions{ - EdgeLBPoolName: "foo", + EdgeLBPoolName: "dev--kubernetes01--foo--bar", EdgeLBPoolRole: translator.DefaultEdgeLBPoolRole, EdgeLBPoolCpus: translator.DefaultEdgeLBPoolCpus, EdgeLBPoolMem: translator.DefaultEdgeLBPoolMem, @@ -74,8 +83,7 @@ func TestComputeIngressTranslationOptions(t *testing.T) { { description: "compute options for an Ingress resource defining an invalid value for the frontend bind port", annotations: map[string]string{ - constants.EdgeLBPoolNameAnnotationKey: "foo", - constants.EdgeLBPoolPortKey: "74511", + constants.EdgeLBPoolPortKey: "74511", }, options: nil, error: fmt.Errorf("%d is not a valid port number", 74511), @@ -113,8 +121,7 @@ func TestComputeIngressTranslationOptions(t *testing.T) { { description: "compute options for an Ingress resource with a custom but invalid port mapping", annotations: map[string]string{ - constants.EdgeLBPoolNameAnnotationKey: "foo", - constants.EdgeLBPoolPortKey: "74511", + constants.EdgeLBPoolPortKey: "74511", }, options: nil, error: fmt.Errorf("%d is not a valid port number", 74511), @@ -124,8 +131,7 @@ func TestComputeIngressTranslationOptions(t *testing.T) { { description: "compute options for a Service resource having an invalid port mapping", annotations: map[string]string{ - constants.EdgeLBPoolNameAnnotationKey: "foo", - constants.EdgeLBPoolPortKey: "foo", + constants.EdgeLBPoolPortKey: "foo", }, options: nil, error: fmt.Errorf("failed to parse %q as the frontend bind port to use: %v", "foo", "strconv.Atoi: parsing \"foo\": invalid syntax"), @@ -137,7 +143,7 @@ func TestComputeIngressTranslationOptions(t *testing.T) { // Create a dummy Ingress resource containing the annotations for the current test. r := ingresstestutil.DummyIngressResource("foo", "bar", ingresstestutil.WithAnnotations(test.annotations)) // Compute the translation options for the resource. - o, err := translator.ComputeIngressTranslationOptions(r) + o, err := translator.ComputeIngressTranslationOptions(testClusterName, r) if err != nil { // Make sure the error matches the expected one. assert.EqualError(t, err, test.error.Error()) diff --git a/pkg/translator/service_options.go b/pkg/translator/service_options.go index e09dbb9..14657fb 100644 --- a/pkg/translator/service_options.go +++ b/pkg/translator/service_options.go @@ -19,7 +19,7 @@ type ServiceTranslationOptions struct { // ComputeServiceTranslationOptions computes the set of options to use for "translating" the specified Service resource into an EdgeLB pool. // In case options cannot be computed or are invalid, the error message MUST be suitable to be used as the message for a Kubernetes event associated with the resource. -func ComputeServiceTranslationOptions(obj *corev1.Service) (*ServiceTranslationOptions, error) { +func ComputeServiceTranslationOptions(clusterName string, obj *corev1.Service) (*ServiceTranslationOptions, error) { // Create an "ServiceTranslationOptions" struct to hold the computed options. res := &ServiceTranslationOptions{ EdgeLBPoolPortMap: make(map[int32]int32, len(obj.Spec.Ports)), @@ -35,7 +35,7 @@ func ComputeServiceTranslationOptions(obj *corev1.Service) (*ServiceTranslationO } // Parse base translation options. - b, err := parseBaseTranslationOptions(annotations) + b, err := parseBaseTranslationOptions(clusterName, obj.Namespace, obj.Name, annotations) if err != nil { return nil, err } diff --git a/pkg/translator/service_options_test.go b/pkg/translator/service_options_test.go index 8c17256..b8eb7e5 100644 --- a/pkg/translator/service_options_test.go +++ b/pkg/translator/service_options_test.go @@ -21,18 +21,35 @@ func TestComputeServiceTranslationOptions(t *testing.T) { options *translator.ServiceTranslationOptions error error }{ - // Test computing options for a Service resource without the required annotations. - // Make sure an error is returned. + // Test computing options for a Service resource without any annotations. + // Make sure the name of the EdgeLB pool is computed as expected, and that the default values are used everywhere else. { description: "compute options for a Service resource without the required annotations", annotations: map[string]string{}, - options: nil, - error: fmt.Errorf("required annotation %q has not been provided", constants.EdgeLBPoolNameAnnotationKey), + ports: []corev1.ServicePort{ + { + Port: 80, + }, + }, + options: &translator.ServiceTranslationOptions{ + BaseTranslationOptions: translator.BaseTranslationOptions{ + EdgeLBPoolName: "dev--kubernetes01--foo--bar", + EdgeLBPoolRole: translator.DefaultEdgeLBPoolRole, + EdgeLBPoolCpus: translator.DefaultEdgeLBPoolCpus, + EdgeLBPoolMem: translator.DefaultEdgeLBPoolMem, + EdgeLBPoolSize: translator.DefaultEdgeLBPoolSize, + EdgeLBPoolCreationStrategy: translator.DefaultEdgeLBPoolCreationStrategy, + }, + EdgeLBPoolPortMap: map[int32]int32{ + 80: 80, + }, + }, + error: fmt.Errorf("required annotation %q has not been provided", constants.EdgeLBPoolNameAnnotationKey), }, - // Test computing options for a Service resource with just the required annotations. + // Test computing options for a Service resource specifying the name of the target EdgeLB pool. // Make sure the name of the EdgeLB pool is captured as expected, and that the default values are used everywhere else. { - description: "compute options for a Service resource with just the required annotations", + description: "compute options for a Service resource specifying the name of the target EdgeLB pool", annotations: map[string]string{ constants.EdgeLBPoolNameAnnotationKey: "foo", }, @@ -57,11 +74,10 @@ func TestComputeServiceTranslationOptions(t *testing.T) { error: nil, }, // Test computing options for a Service resource with a custom port mapping. - // Make sure the name of the EdgeLB pool and the port mapping are captured as expected, and that the default values are used everywhere else. + // Make sure the port mapping is captured as expected, and that the default values are used everywhere else. { description: "compute options for a Service resource with a custom port mapping", annotations: map[string]string{ - constants.EdgeLBPoolNameAnnotationKey: "foo", fmt.Sprintf("%s%d", constants.EdgeLBPoolPortMapKeyPrefix, 80): "8080", }, ports: []corev1.ServicePort{ @@ -74,7 +90,7 @@ func TestComputeServiceTranslationOptions(t *testing.T) { }, options: &translator.ServiceTranslationOptions{ BaseTranslationOptions: translator.BaseTranslationOptions{ - EdgeLBPoolName: "foo", + EdgeLBPoolName: "dev--kubernetes01--foo--bar", EdgeLBPoolRole: translator.DefaultEdgeLBPoolRole, EdgeLBPoolCpus: translator.DefaultEdgeLBPoolCpus, EdgeLBPoolMem: translator.DefaultEdgeLBPoolMem, @@ -93,7 +109,6 @@ func TestComputeServiceTranslationOptions(t *testing.T) { { description: "compute options for a Service resource with a custom but invalid port mapping", annotations: map[string]string{ - constants.EdgeLBPoolNameAnnotationKey: "foo", fmt.Sprintf("%s%d", constants.EdgeLBPoolPortMapKeyPrefix, 443): "74511", }, ports: []corev1.ServicePort{ @@ -111,9 +126,7 @@ func TestComputeServiceTranslationOptions(t *testing.T) { // Make sure an error is returned. { description: "compute options for a Service resource using an unsupported protocol", - annotations: map[string]string{ - constants.EdgeLBPoolNameAnnotationKey: "foo", - }, + annotations: map[string]string{}, ports: []corev1.ServicePort{ { Port: 80, @@ -128,7 +141,6 @@ func TestComputeServiceTranslationOptions(t *testing.T) { { description: "compute options for a Service resource having duplicate port mappings", annotations: map[string]string{ - constants.EdgeLBPoolNameAnnotationKey: "foo", fmt.Sprintf("%s%d", constants.EdgeLBPoolPortMapKeyPrefix, 8080): "18080", fmt.Sprintf("%s%d", constants.EdgeLBPoolPortMapKeyPrefix, 8081): "18080", }, @@ -150,7 +162,6 @@ func TestComputeServiceTranslationOptions(t *testing.T) { { description: "compute options for a Service resource having an invalid port mapping", annotations: map[string]string{ - constants.EdgeLBPoolNameAnnotationKey: "foo", fmt.Sprintf("%s%d", constants.EdgeLBPoolPortMapKeyPrefix, 8080): "18080", fmt.Sprintf("%s%d", constants.EdgeLBPoolPortMapKeyPrefix, 8081): "foo", }, @@ -170,7 +181,6 @@ func TestComputeServiceTranslationOptions(t *testing.T) { { description: "compute options for a Service resource having an invalid CPU request", annotations: map[string]string{ - constants.EdgeLBPoolNameAnnotationKey: "foo", constants.EdgeLBPoolCpusAnnotationKey: "foo", }, ports: []corev1.ServicePort{ @@ -186,8 +196,7 @@ func TestComputeServiceTranslationOptions(t *testing.T) { { description: "compute options for a Service resource having an invalid memory request", annotations: map[string]string{ - constants.EdgeLBPoolNameAnnotationKey: "foo", - constants.EdgeLBPoolMemAnnotationKey: "foo", + constants.EdgeLBPoolMemAnnotationKey: "foo", }, ports: []corev1.ServicePort{ { @@ -202,7 +211,6 @@ func TestComputeServiceTranslationOptions(t *testing.T) { { description: "compute options for a Service resource having an invalid (malformed) size request", annotations: map[string]string{ - constants.EdgeLBPoolNameAnnotationKey: "foo", constants.EdgeLBPoolSizeAnnotationKey: "foo", }, ports: []corev1.ServicePort{ @@ -218,7 +226,6 @@ func TestComputeServiceTranslationOptions(t *testing.T) { { description: "compute options for a Service resource having an invalid (negative) size request", annotations: map[string]string{ - constants.EdgeLBPoolNameAnnotationKey: "foo", constants.EdgeLBPoolSizeAnnotationKey: "-1", }, ports: []corev1.ServicePort{ @@ -236,7 +243,7 @@ func TestComputeServiceTranslationOptions(t *testing.T) { // Create a dummy Service resource containing the annotations for the current test. r := servicetestutil.DummyServiceResource("foo", "bar", servicetestutil.WithAnnotations(test.annotations), servicetestutil.WithPorts(test.ports)) // Compute the translation options for the resource. - o, err := translator.ComputeServiceTranslationOptions(r) + o, err := translator.ComputeServiceTranslationOptions(testClusterName, r) if err != nil { // Make sure the error matches the expected one. assert.EqualError(t, err, test.error.Error()) diff --git a/pkg/translator/service_utils.go b/pkg/translator/service_utils.go index 31da107..4bca03c 100644 --- a/pkg/translator/service_utils.go +++ b/pkg/translator/service_utils.go @@ -32,12 +32,12 @@ type servicePortBackendFrontend struct { // backendNameForServicePort computes the name of the backend used for the specified service port. func backendNameForServicePort(clusterName string, service *corev1.Service, port corev1.ServicePort) string { - return fmt.Sprintf(serviceBackendNameFormatString, stringsutil.ReplaceSlashes(clusterName), service.Namespace, service.Name, port.Port) + return fmt.Sprintf(serviceBackendNameFormatString, stringsutil.ReplaceForwardSlashesWithDots(clusterName), service.Namespace, service.Name, port.Port) } // frontendNameForServicePort computes the name of the frontend used for the specified service port. func frontendNameForServicePort(clusterName string, service *corev1.Service, port corev1.ServicePort) string { - return fmt.Sprintf(serviceFrontendNameFormatString, stringsutil.ReplaceSlashes(clusterName), service.Namespace, service.Name, port.Port) + return fmt.Sprintf(serviceFrontendNameFormatString, stringsutil.ReplaceForwardSlashesWithDots(clusterName), service.Namespace, service.Name, port.Port) } // serviceOwnedEdgeLBObjectMetadata groups together information about about the Service resource that owns a given EdgeLB backend/frontend. @@ -136,7 +136,7 @@ func computeServiceOwnedEdgeLBObjectMetadata(name string) (*serviceOwnedEdgeLBOb return nil, errors.New("invalid backend/frontend name for service") } return &serviceOwnedEdgeLBObjectMetadata{ - ClusterName: stringsutil.ReplaceDots(parts[0]), + ClusterName: stringsutil.ReplaceDotsWithForwardSlashes(parts[0]), Namespace: parts[1], Name: parts[2], ServicePort: int32(p), diff --git a/pkg/util/strings/strings.go b/pkg/util/strings/strings.go index c939e4b..7daca75 100644 --- a/pkg/util/strings/strings.go +++ b/pkg/util/strings/strings.go @@ -4,12 +4,17 @@ import ( "strings" ) -// ReplaceDots returns a string built from the specified one by replacing all dots ("/") with a forward slash ("/"). -func ReplaceDots(v string) string { +// ReplaceDotsWithForwardSlashes returns a string built from the specified one by replacing all dots ("/") with a forward slash ("/"). +func ReplaceDotsWithForwardSlashes(v string) string { return strings.Replace(v, ".", "/", -1) } -// ReplaceSlashes returns a string built from the specified one by replacing all forward slashes ("/") with a dot ("."). -func ReplaceSlashes(v string) string { - return strings.Replace(v, "/", ".", -1) +// ReplaceForwardSlashes returns a string built from the specified one by replacing all forward slashes ("/") with the provided replacement string. +func ReplaceForwardSlashes(v, r string) string { + return strings.Replace(v, "/", r, -1) +} + +// ReplaceForwardSlashesWithDots returns a string built from the specified one by replacing all forward slashes ("/") with a dot ("."). +func ReplaceForwardSlashesWithDots(v string) string { + return ReplaceForwardSlashes(v, ".") } diff --git a/pkg/util/strings/strings_test.go b/pkg/util/strings/strings_test.go index 082f330..8250889 100644 --- a/pkg/util/strings/strings_test.go +++ b/pkg/util/strings/strings_test.go @@ -8,7 +8,46 @@ import ( "github.com/mesosphere/dklb/pkg/util/strings" ) -// TestRoundTrip tests the "ReplaceDots" and "ReplaceSlashes" functions by performing a round-trip and making sure all outputs match the expectations. +// TestReplaceForwardSlashes tests the "ReplaceForwardSlashes" function. +func TestReplaceForwardSlashes(t *testing.T) { + tests := []struct { + description string + v string + r string + result string + }{ + { + description: "empty string", + v: "", + r: ":", + result: "", + }, + { + description: "string without forward slashes", + v: "foo", + r: "-", + result: "foo", + }, + { + description: "string with a single forward slash", + v: "dev/kubernetes01", + r: "--", + result: "dev--kubernetes01", + }, + { + description: "string with multiple forward slashes", + v: "dev/kubernetes/k-01", + r: "--", + result: "dev--kubernetes--k-01", + }, + } + for _, test := range tests { + t.Logf("test case: %s", test.description) + assert.Equal(t, test.result, strings.ReplaceForwardSlashes(test.v, test.r)) + } +} + +// TestRoundTrip tests the "ReplaceDotsWithForwardSlashes" and "ReplaceForwardSlashesWithDots" functions by performing a round-trip and making sure all outputs match the expectations. func TestRoundTrip(t *testing.T) { tests := []struct { description string @@ -38,11 +77,11 @@ func TestRoundTrip(t *testing.T) { } for _, test := range tests { t.Logf("test case: %s", test.description) - // Call "ReplaceSlashes" on the input string and make sure the output matches our expectations. - replaceSlashesOutput := strings.ReplaceSlashes(test.originalInput) + // Call "ReplaceForwardSlashesWithDots" on the input string and make sure the output matches our expectations. + replaceSlashesOutput := strings.ReplaceForwardSlashesWithDots(test.originalInput) assert.Equal(t, test.replaceSlashesOutput, replaceSlashesOutput) - // Call "ReplaceDots" on the previous output and make sure the output matches the original input. - replaceDotsOutput := strings.ReplaceDots(replaceSlashesOutput) + // Call "ReplaceDotsWithForwardSlashes" on the previous output and make sure the output matches the original input. + replaceDotsOutput := strings.ReplaceDotsWithForwardSlashes(replaceSlashesOutput) assert.Equal(t, test.originalInput, replaceDotsOutput) } } diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go index c0ab002..e0324dd 100644 --- a/test/e2e/e2e_suite_test.go +++ b/test/e2e/e2e_suite_test.go @@ -4,11 +4,11 @@ package e2e_test import ( "flag" - "log" "testing" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + log "github.com/sirupsen/logrus" "github.com/mesosphere/dklb/pkg/constants" "github.com/mesosphere/dklb/pkg/edgelb/manager" @@ -44,6 +44,8 @@ func init() { var _ = BeforeSuite(func() { // Create a new instance of the test framework. f = e2eframework.New(edgelbOptions, kubeconfig) + // Output some information about the current MKE cluster. + log.Infof("running the end-to-end test suite against the %q cluster", f.ClusterName) }) var _ = BeforeEach(func() { diff --git a/test/e2e/framework/framework.go b/test/e2e/framework/framework.go index 886ad56..00303bf 100644 --- a/test/e2e/framework/framework.go +++ b/test/e2e/framework/framework.go @@ -10,10 +10,22 @@ import ( "k8s.io/client-go/tools/clientcmd" edgelbmanager "github.com/mesosphere/dklb/pkg/edgelb/manager" + kubernetesutil "github.com/mesosphere/dklb/pkg/util/kubernetes" +) + +const ( + // mkeClusterInfoConfigMapNamespace is the namespace where the configmap holding metadata for an MKE cluster lives. + mkeClusterInfoConfigMapNamespace = "kube-system" + // mkeClusterInfoConfigMapName is the name of the configmap holding metadata for an MKE cluster. + mkeClusterInfoConfigMapName = "mke-cluster-info" + // mkeClusterInfoConfigMapClusterNameKey is the name of the configmap key containing the MKE cluster's name. + mkeClusterInfoConfigMapClusterNameKey = "CLUSTER_NAME" ) // Framework groups together utility methods and clients used by test functions. type Framework struct { + // ClusterName is the name of the Mesos framework that corresponds to the Kubernetes cluster where testing will be performed. + ClusterName string // EdgeLBManager is the instance of the EdgeLB manager to use. EdgeLBManager edgelbmanager.EdgeLBManager // KubeClient is a client to the Kubernetes base APIs. @@ -37,7 +49,21 @@ func New(edgelbOptions edgelbmanager.EdgeLBManagerOptions, kubeconfig string) *F if err != nil { log.Fatalf("failed to build edgelb manager: %v", err) } + // Detect the name of the MKE cluster. + var ( + clusterName string + ) + m, err := kubeClient.CoreV1().ConfigMaps(mkeClusterInfoConfigMapNamespace).Get(mkeClusterInfoConfigMapName, metav1.GetOptions{}) + if err != nil { + log.Fatalf("failed to read the \"%s/%s\" configmap: %v", mkeClusterInfoConfigMapNamespace, mkeClusterInfoConfigMapName, err) + } + if v, exists := m.Data[mkeClusterInfoConfigMapClusterNameKey]; !exists || v == "" { + log.Fatalf("the mke cluster's name is not present in the %q configmap", kubernetesutil.Key(m)) + } else { + clusterName = v + } return &Framework{ + ClusterName: clusterName, EdgeLBManager: manager, KubeClient: kubeClient, } diff --git a/test/e2e/service_test.go b/test/e2e/service_test.go index 8aadb3d..b8b73de 100644 --- a/test/e2e/service_test.go +++ b/test/e2e/service_test.go @@ -13,6 +13,7 @@ import ( "github.com/mongodb/mongo-go-driver/mongo/readpref" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + log "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" @@ -21,6 +22,7 @@ import ( "github.com/mesosphere/dklb/pkg/util/kubernetes" "github.com/mesosphere/dklb/pkg/util/pointers" "github.com/mesosphere/dklb/pkg/util/retry" + "github.com/mesosphere/dklb/pkg/util/strings" "github.com/mesosphere/dklb/test/e2e/framework" ) @@ -63,8 +65,6 @@ var _ = Describe("Service", func() { // Create a service of type LoadBalancer targeting the pod created above. redisSvc, err = f.CreateServiceOfTypeLoadBalancer(namespace.Name, "redis", func(svc *corev1.Service) { svc.ObjectMeta.Annotations = map[string]string{ - // Request for the pool to be called "". - constants.EdgeLBPoolNameAnnotationKey: namespace.Name, // Request for the pool to be deployed to an agent with the "slave_public" role. constants.EdgeLBPoolRoleAnnotationKey: constants.EdgeLBRolePublic, // Request for the pool to be given 0.2 CPUs. @@ -88,17 +88,20 @@ var _ = Describe("Service", func() { }) Expect(err).NotTo(HaveOccurred(), "failed to create test service") + // Compute the (expected) name of the target EdgeLB pool. + expectedPoolName := fmt.Sprintf("%s--%s--%s", strings.ReplaceForwardSlashes(f.ClusterName, "--"), redisSvc.Namespace, redisSvc.Name) + // Wait for EdgeLB to acknowledge the pool's creation. err = retry.WithTimeout(framework.DefaultRetryTimeout, framework.DefaultRetryInterval, func() (bool, error) { ctx, fn := context.WithTimeout(context.Background(), framework.DefaultRetryInterval/2) defer fn() - pool, err = f.EdgeLBManager.GetPoolByName(ctx, redisSvc.Annotations[constants.EdgeLBPoolNameAnnotationKey]) + pool, err = f.EdgeLBManager.GetPoolByName(ctx, expectedPoolName) return err == nil, nil }) Expect(err).NotTo(HaveOccurred(), "timed out while waiting for the edgelb api server to acknowledge the pool's creation") - // Make sure the pool is reporting the requested configuration. - Expect(pool.Name).To(Equal(redisSvc.Annotations[constants.EdgeLBPoolNameAnnotationKey])) + // Make sure the pool is reporting the requested configuration, as well as a name that contains the Service resource's namespace and name. + Expect(pool.Name).To(Equal(expectedPoolName)) Expect(pool.Role).To(Equal(redisSvc.Annotations[constants.EdgeLBPoolRoleAnnotationKey])) Expect(pool.Cpus).To(Equal(0.2)) Expect(pool.Mem).To(Equal(int32(256))) @@ -320,8 +323,6 @@ var _ = Describe("Service", func() { // Create a service of type LoadBalancer targeting the pod created above. redisSvc, err = f.CreateServiceOfTypeLoadBalancer(namespace.Name, "redis", func(svc *corev1.Service) { svc.ObjectMeta.Annotations = map[string]string{ - // Request for the pool to be called "". - constants.EdgeLBPoolNameAnnotationKey: namespace.Name, // Request for the pool to be deployed to an agent with the "slave_public" role. constants.EdgeLBPoolRoleAnnotationKey: constants.EdgeLBRolePublic, // Request for the pool to be given 0.2 CPUs. @@ -345,11 +346,14 @@ var _ = Describe("Service", func() { }) Expect(err).NotTo(HaveOccurred(), "failed to create test service") + // Compute the (expected) name of the target EdgeLB pool. + expectedPoolName := fmt.Sprintf("%s--%s--%s", strings.ReplaceForwardSlashes(f.ClusterName, "--"), redisSvc.Namespace, redisSvc.Name) + // Wait for EdgeLB to acknowledge the pool's creation. err = retry.WithTimeout(framework.DefaultRetryTimeout, framework.DefaultRetryInterval, func() (bool, error) { ctx, fn := context.WithTimeout(context.Background(), framework.DefaultRetryInterval/2) defer fn() - pool, err = f.EdgeLBManager.GetPoolByName(ctx, redisSvc.Annotations[constants.EdgeLBPoolNameAnnotationKey]) + pool, err = f.EdgeLBManager.GetPoolByName(ctx, expectedPoolName) return err == nil, nil }) Expect(err).NotTo(HaveOccurred(), "timed out while waiting for the edgelb api server to acknowledge the pool's creation")