From 6e33b731c7bafac40e85bd260db26c6409befb6b Mon Sep 17 00:00:00 2001 From: Domenico Francesco Bruscino Date: Fri, 16 Feb 2024 15:13:05 +0100 Subject: [PATCH] [#557] Add expose mode The expose mode affects how the internal services for acceptors, connectors and console are exposed. Currently the supported modes are `route` and `ingress`. Default is `route` on OpenShift and `ingress` on Kubernetes. * `route` mode uses OpenShift Routes to expose the internal service. * `ingress` mode uses Kubernetes Nginx Ingress to expose the internal service with TLS passthrough. --- api/v1beta1/activemqartemis_types.go | 31 ++- api/v1beta1/zz_generated.deepcopy.go | 21 +- ...rtemis-operator.clusterserviceversion.yaml | 27 +++ .../broker.amq.io_activemqartemises.yaml | 30 +++ .../broker.amq.io_activemqartemises.yaml | 30 +++ ...rtemis-operator.clusterserviceversion.yaml | 27 +++ controllers/activemqartemis_controller.go | 48 ++++ .../activemqartemis_controller_test.go | 208 ++++++++++++++++++ controllers/activemqartemis_reconciler.go | 37 ++-- deploy/activemq-artemis-operator.yaml | 18 ++ deploy/crds/broker_activemqartemis_crd.yaml | 18 ++ 11 files changed, 468 insertions(+), 27 deletions(-) diff --git a/api/v1beta1/activemqartemis_types.go b/api/v1beta1/activemqartemis_types.go index 697e55f24..c8bd4aa10 100644 --- a/api/v1beta1/activemqartemis_types.go +++ b/api/v1beta1/activemqartemis_types.go @@ -448,6 +448,17 @@ type StorageType struct { StorageClassName string `json:"storageClassName,omitempty"` } +// +kubebuilder:validation:Enum=ingress;route +type ExposeMode string + +var ExposeModes = struct { + Ingress ExposeMode + Route ExposeMode +}{ + Ingress: "ingress", + Route: "route", +} + type AcceptorType struct { // The acceptor name //+operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Name",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:text"} @@ -488,6 +499,9 @@ type AcceptorType struct { // Whether or not to expose this acceptor //+operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Expose",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} Expose bool `json:"expose,omitempty"` + // Mode to expose the acceptor. Currently the supported modes are `route` and `ingress`. Default is `route` on OpenShift and `ingress` on Kubernetes. \n\n* `route` mode uses OpenShift Routes to expose the acceptor.\n* `ingress` mode uses Kubernetes Nginx Ingress to expose the acceptor with TLS passthrough.\n" + //+operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Expose Mode",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:text"} + ExposeMode *ExposeMode `json:"exposeMode,omitempty"` // To indicate which kind of routing type to use. //+operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Anycast Prefix",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:text"} AnycastPrefix string `json:"anycastPrefix,omitempty"` @@ -566,6 +580,9 @@ type ConnectorType struct { // Whether or not to expose this connector //+operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Expose",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} Expose bool `json:"expose,omitempty"` + // Mode to expose the connector. Currently the supported modes are `route` and `ingress`. Default is `route` on OpenShift and `ingress` on Kubernetes. \n\n* `route` mode uses OpenShift Routes to expose the connector.\n* `ingress` mode uses Kubernetes Nginx Ingress to expose the connector with TLS passthrough.\n" + //+operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Expose Mode",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:text"} + ExposeMode *ExposeMode `json:"exposeMode,omitempty"` // Provider used for the keystore; "SUN", "SunJCE", etc. Default is null //+operator-sdk:csv:customresourcedefinitions:type=spec,displayName="KeyStore Provider",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:text"} KeyStoreProvider string `json:"keyStoreProvider,omitempty"` @@ -587,6 +604,9 @@ type ConsoleType struct { // Whether or not to expose this port //+operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Expose",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} Expose bool `json:"expose,omitempty"` + // Mode to expose the console. Currently the supported modes are `route` and `ingress`. Default is `route` on OpenShift and `ingress` on Kubernetes. \n\n* `route` mode uses OpenShift Routes to expose the console.\n* `ingress` mode uses Kubernetes Nginx Ingress to expose the console with TLS passthrough.\n" + //+operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Expose Mode",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:text"} + ExposeMode *ExposeMode `json:"exposeMode,omitempty"` // Whether or not to enable SSL on this port //+operator-sdk:csv:customresourcedefinitions:type=spec,displayName="SSL Enabled",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} SSLEnabled bool `json:"sslEnabled,omitempty"` @@ -729,10 +749,13 @@ const ( ValidConditionMissingResourcesReason = "MissingDependentResources" ValidConditionInvalidVersionReason = "SpecVersionInvalid" - ValidConditionPDBNonNilSelectorReason = "PodDisruptionBudgetNonNilSelector" - ValidConditionFailedReservedLabelReason = "ReservedLabelReference" - ValidConditionFailedExtraMountReason = "InvalidExtraMount" - ValidConditionFailedDuplicateAcceptorPort = "DuplicateAcceptorPort" + ValidConditionPDBNonNilSelectorReason = "PodDisruptionBudgetNonNilSelector" + ValidConditionFailedReservedLabelReason = "ReservedLabelReference" + ValidConditionFailedExtraMountReason = "InvalidExtraMount" + ValidConditionFailedDuplicateAcceptorPort = "DuplicateAcceptorPort" + ValidConditionFailedAcceptorWithInvalidExposeMode = "AcceptorWithInvalidExposeMode" + ValidConditionFailedConnectorWithInvalidExposeMode = "ConnectorWithInvalidExposeMode" + ValidConditionFailedConsoleWithInvalidExposeMode = "ConsoelWithInvalidExposeMode" ReadyConditionType = "Ready" ReadyConditionReason = "ResourceReady" diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 6399958ec..8249a148e 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -31,6 +31,11 @@ import ( // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AcceptorType) DeepCopyInto(out *AcceptorType) { *out = *in + if in.ExposeMode != nil { + in, out := &in.ExposeMode, &out.ExposeMode + *out = new(ExposeMode) + **out = **in + } if in.SupportAdvisory != nil { in, out := &in.SupportAdvisory, &out.SupportAdvisory *out = new(bool) @@ -437,9 +442,11 @@ func (in *ActiveMQArtemisSpec) DeepCopyInto(out *ActiveMQArtemisSpec) { if in.Connectors != nil { in, out := &in.Connectors, &out.Connectors *out = make([]ConnectorType, len(*in)) - copy(*out, *in) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } - out.Console = in.Console + in.Console.DeepCopyInto(&out.Console) out.Upgrades = in.Upgrades in.AddressSettings.DeepCopyInto(&out.AddressSettings) if in.BrokerProperties != nil { @@ -1118,6 +1125,11 @@ func (in *ConnectorConfigType) DeepCopy() *ConnectorConfigType { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ConnectorType) DeepCopyInto(out *ConnectorType) { *out = *in + if in.ExposeMode != nil { + in, out := &in.ExposeMode, &out.ExposeMode + *out = new(ExposeMode) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectorType. @@ -1133,6 +1145,11 @@ func (in *ConnectorType) DeepCopy() *ConnectorType { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ConsoleType) DeepCopyInto(out *ConsoleType) { *out = *in + if in.ExposeMode != nil { + in, out := &in.ExposeMode, &out.ExposeMode + *out = new(ExposeMode) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConsoleType. diff --git a/bundle/manifests/activemq-artemis-operator.clusterserviceversion.yaml b/bundle/manifests/activemq-artemis-operator.clusterserviceversion.yaml index 2e8feedc5..f00f4b635 100644 --- a/bundle/manifests/activemq-artemis-operator.clusterserviceversion.yaml +++ b/bundle/manifests/activemq-artemis-operator.clusterserviceversion.yaml @@ -514,6 +514,15 @@ spec: path: acceptors[0].expose x-descriptors: - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Mode to expose the acceptor. Currently the supported modes are + `route` and `ingress`. Default is `route` on OpenShift and `ingress` on + Kubernetes. \n\n* `route` mode uses OpenShift Routes to expose the acceptor.\n* + `ingress` mode uses Kubernetes Nginx Ingress to expose the acceptor with + TLS passthrough.\n" + displayName: Expose Mode + path: acceptors[0].exposeMode + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text - description: 'Host for Ingress and Route resources of the acceptor. It supports the following variables: $(CR_NAME), $(CR_NAMESPACE), $(BROKER_ORDINAL), $(ITEM_NAME), $(RES_NAME) and $(INGRESS_DOMAIN). Default is $(CR_NAME)-$(ITEM_NAME)-$(BROKER_ORDINAL)-svc-$(RES_TYPE).$(INGRESS_DOMAIN)' @@ -1033,6 +1042,15 @@ spec: path: connectors[0].expose x-descriptors: - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Mode to expose the connector. Currently the supported modes are + `route` and `ingress`. Default is `route` on OpenShift and `ingress` on + Kubernetes. \n\n* `route` mode uses OpenShift Routes to expose the connector.\n* + `ingress` mode uses Kubernetes Nginx Ingress to expose the connector with + TLS passthrough.\n" + displayName: Expose Mode + path: connectors[0].exposeMode + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text - description: Hostname or IP to connect to displayName: Host path: connectors[0].host @@ -1127,6 +1145,15 @@ spec: path: console.expose x-descriptors: - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Mode to expose the console. Currently the supported modes are + `route` and `ingress`. Default is `route` on OpenShift and `ingress` on + Kubernetes. \n\n* `route` mode uses OpenShift Routes to expose the console.\n* + `ingress` mode uses Kubernetes Nginx Ingress to expose the console with + TLS passthrough.\n" + displayName: Expose Mode + path: console.exposeMode + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text - description: 'Host for Ingress and Route resources of the acceptor. It supports the following variables: $(CR_NAME), $(CR_NAMESPACE), $(BROKER_ORDINAL), $(ITEM_NAME), $(RES_NAME) and $(INGRESS_DOMAIN). Default is $(CR_NAME)-$(ITEM_NAME)-$(BROKER_ORDINAL)-svc-$(RES_TYPE).$(INGRESS_DOMAIN)' diff --git a/bundle/manifests/broker.amq.io_activemqartemises.yaml b/bundle/manifests/broker.amq.io_activemqartemises.yaml index fb6453fdd..b243b5470 100644 --- a/bundle/manifests/broker.amq.io_activemqartemises.yaml +++ b/bundle/manifests/broker.amq.io_activemqartemises.yaml @@ -62,6 +62,16 @@ spec: expose: description: Whether or not to expose this acceptor type: boolean + exposeMode: + description: Mode to expose the acceptor. Currently the supported + modes are `route` and `ingress`. Default is `route` on OpenShift + and `ingress` on Kubernetes. \n\n* `route` mode uses OpenShift + Routes to expose the acceptor.\n* `ingress` mode uses Kubernetes + Nginx Ingress to expose the acceptor with TLS passthrough.\n" + enum: + - ingress + - route + type: string ingressHost: description: 'Host for Ingress and Route resources of the acceptor. It supports the following variables: $(CR_NAME), $(CR_NAMESPACE), @@ -475,6 +485,16 @@ spec: expose: description: Whether or not to expose this connector type: boolean + exposeMode: + description: Mode to expose the connector. Currently the supported + modes are `route` and `ingress`. Default is `route` on OpenShift + and `ingress` on Kubernetes. \n\n* `route` mode uses OpenShift + Routes to expose the connector.\n* `ingress` mode uses Kubernetes + Nginx Ingress to expose the connector with TLS passthrough.\n" + enum: + - ingress + - route + type: string host: description: Hostname or IP to connect to type: string @@ -547,6 +567,16 @@ spec: expose: description: Whether or not to expose this port type: boolean + exposeMode: + description: Mode to expose the console. Currently the supported + modes are `route` and `ingress`. Default is `route` on OpenShift + and `ingress` on Kubernetes. \n\n* `route` mode uses OpenShift + Routes to expose the console.\n* `ingress` mode uses Kubernetes + Nginx Ingress to expose the console with TLS passthrough.\n" + enum: + - ingress + - route + type: string ingressHost: description: 'Host for Ingress and Route resources of the acceptor. It supports the following variables: $(CR_NAME), $(CR_NAMESPACE), diff --git a/config/crd/bases/broker.amq.io_activemqartemises.yaml b/config/crd/bases/broker.amq.io_activemqartemises.yaml index c0518340f..515bc3c86 100644 --- a/config/crd/bases/broker.amq.io_activemqartemises.yaml +++ b/config/crd/bases/broker.amq.io_activemqartemises.yaml @@ -63,6 +63,16 @@ spec: expose: description: Whether or not to expose this acceptor type: boolean + exposeMode: + description: Mode to expose the acceptor. Currently the supported + modes are `route` and `ingress`. Default is `route` on OpenShift + and `ingress` on Kubernetes. \n\n* `route` mode uses OpenShift + Routes to expose the acceptor.\n* `ingress` mode uses Kubernetes + Nginx Ingress to expose the acceptor with TLS passthrough.\n" + enum: + - ingress + - route + type: string ingressHost: description: 'Host for Ingress and Route resources of the acceptor. It supports the following variables: $(CR_NAME), $(CR_NAMESPACE), @@ -476,6 +486,16 @@ spec: expose: description: Whether or not to expose this connector type: boolean + exposeMode: + description: Mode to expose the connector. Currently the supported + modes are `route` and `ingress`. Default is `route` on OpenShift + and `ingress` on Kubernetes. \n\n* `route` mode uses OpenShift + Routes to expose the connector.\n* `ingress` mode uses Kubernetes + Nginx Ingress to expose the connector with TLS passthrough.\n" + enum: + - ingress + - route + type: string host: description: Hostname or IP to connect to type: string @@ -548,6 +568,16 @@ spec: expose: description: Whether or not to expose this port type: boolean + exposeMode: + description: Mode to expose the console. Currently the supported + modes are `route` and `ingress`. Default is `route` on OpenShift + and `ingress` on Kubernetes. \n\n* `route` mode uses OpenShift + Routes to expose the console.\n* `ingress` mode uses Kubernetes + Nginx Ingress to expose the console with TLS passthrough.\n" + enum: + - ingress + - route + type: string ingressHost: description: 'Host for Ingress and Route resources of the acceptor. It supports the following variables: $(CR_NAME), $(CR_NAMESPACE), diff --git a/config/manifests/bases/activemq-artemis-operator.clusterserviceversion.yaml b/config/manifests/bases/activemq-artemis-operator.clusterserviceversion.yaml index 0cd78dbc1..9eec67aeb 100644 --- a/config/manifests/bases/activemq-artemis-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/activemq-artemis-operator.clusterserviceversion.yaml @@ -288,6 +288,15 @@ spec: path: acceptors[0].expose x-descriptors: - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Mode to expose the acceptor. Currently the supported modes are + `route` and `ingress`. Default is `route` on OpenShift and `ingress` on + Kubernetes. \n\n* `route` mode uses OpenShift Routes to expose the acceptor.\n* + `ingress` mode uses Kubernetes Nginx Ingress to expose the acceptor with + TLS passthrough.\n" + displayName: Expose Mode + path: acceptors[0].exposeMode + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text - description: 'Host for Ingress and Route resources of the acceptor. It supports the following variables: $(CR_NAME), $(CR_NAMESPACE), $(BROKER_ORDINAL), $(ITEM_NAME), $(RES_NAME) and $(INGRESS_DOMAIN). Default is $(CR_NAME)-$(ITEM_NAME)-$(BROKER_ORDINAL)-svc-$(RES_TYPE).$(INGRESS_DOMAIN)' @@ -807,6 +816,15 @@ spec: path: connectors[0].expose x-descriptors: - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Mode to expose the connector. Currently the supported modes are + `route` and `ingress`. Default is `route` on OpenShift and `ingress` on + Kubernetes. \n\n* `route` mode uses OpenShift Routes to expose the connector.\n* + `ingress` mode uses Kubernetes Nginx Ingress to expose the connector with + TLS passthrough.\n" + displayName: Expose Mode + path: connectors[0].exposeMode + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text - description: Hostname or IP to connect to displayName: Host path: connectors[0].host @@ -901,6 +919,15 @@ spec: path: console.expose x-descriptors: - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Mode to expose the console. Currently the supported modes are + `route` and `ingress`. Default is `route` on OpenShift and `ingress` on + Kubernetes. \n\n* `route` mode uses OpenShift Routes to expose the console.\n* + `ingress` mode uses Kubernetes Nginx Ingress to expose the console with + TLS passthrough.\n" + displayName: Expose Mode + path: console.exposeMode + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:text - description: 'Host for Ingress and Route resources of the acceptor. It supports the following variables: $(CR_NAME), $(CR_NAMESPACE), $(BROKER_ORDINAL), $(ITEM_NAME), $(RES_NAME) and $(INGRESS_DOMAIN). Default is $(CR_NAME)-$(ITEM_NAME)-$(BROKER_ORDINAL)-svc-$(RES_TYPE).$(INGRESS_DOMAIN)' diff --git a/controllers/activemqartemis_controller.go b/controllers/activemqartemis_controller.go index f426bd194..5f8728469 100644 --- a/controllers/activemqartemis_controller.go +++ b/controllers/activemqartemis_controller.go @@ -41,6 +41,7 @@ import ( "github.com/go-logr/logr" "github.com/pkg/errors" + "github.com/artemiscloud/activemq-artemis-operator/api/v1beta1" brokerv1beta1 "github.com/artemiscloud/activemq-artemis-operator/api/v1beta1" "github.com/artemiscloud/activemq-artemis-operator/pkg/utils/common" "github.com/artemiscloud/activemq-artemis-operator/pkg/utils/selectors" @@ -242,6 +243,13 @@ func validate(customResource *brokerv1beta1.ActiveMQArtemis, client rtclient.Cli } } + if validationCondition.Status == metav1.ConditionTrue { + condition, retry = validateExposeModes(customResource, client, scheme, namer) + if condition != nil { + validationCondition = *condition + } + } + validationCondition.ObservedGeneration = customResource.Generation meta.SetStatusCondition(&customResource.Status.Conditions, validationCondition) @@ -295,6 +303,46 @@ func validateAcceptorPorts(customResource *brokerv1beta1.ActiveMQArtemis, client return nil, false } +func validateExposeModes(customResource *brokerv1beta1.ActiveMQArtemis, client rtclient.Client, scheme *runtime.Scheme, namer common.Namers) (*metav1.Condition, bool) { + isOpenshift, _ := common.DetectOpenshift() + + if !isOpenshift { + for _, acceptor := range customResource.Spec.Acceptors { + if acceptor.Expose && acceptor.ExposeMode != nil && *acceptor.ExposeMode == v1beta1.ExposeModes.Route { + return &metav1.Condition{ + Type: brokerv1beta1.ValidConditionType, + Status: metav1.ConditionFalse, + Reason: brokerv1beta1.ValidConditionFailedAcceptorWithInvalidExposeMode, + Message: fmt.Sprintf(".Spec.Acceptors %q has invalid expose mode route, it is only supported on OpenShift", acceptor.Name), + }, false + } + } + + for _, connector := range customResource.Spec.Connectors { + if connector.Expose && connector.ExposeMode != nil && *connector.ExposeMode == v1beta1.ExposeModes.Route { + return &metav1.Condition{ + Type: brokerv1beta1.ValidConditionType, + Status: metav1.ConditionFalse, + Reason: brokerv1beta1.ValidConditionFailedConnectorWithInvalidExposeMode, + Message: fmt.Sprintf(".Spec.Connectors %q has invalid expose mode route, it is only supported on OpenShift", connector.Name), + }, false + } + } + + console := customResource.Spec.Console + if console.Expose && console.ExposeMode != nil && *console.ExposeMode == v1beta1.ExposeModes.Route { + return &metav1.Condition{ + Type: brokerv1beta1.ValidConditionType, + Status: metav1.ConditionFalse, + Reason: brokerv1beta1.ValidConditionFailedConsoleWithInvalidExposeMode, + Message: ".Spec.Console has invalid expose mode route, it is only supported on OpenShift", + }, false + } + } + + return nil, false +} + func validateSSLEnabledSecrets(customResource *brokerv1beta1.ActiveMQArtemis, client rtclient.Client, scheme *runtime.Scheme, namer common.Namers) (*metav1.Condition, bool) { var retry = true diff --git a/controllers/activemqartemis_controller_test.go b/controllers/activemqartemis_controller_test.go index 44e83cf7d..e6af3ff78 100644 --- a/controllers/activemqartemis_controller_test.go +++ b/controllers/activemqartemis_controller_test.go @@ -2298,6 +2298,214 @@ var _ = Describe("artemis controller", func() { }) }) + Context("Expose mode test", func() { + It("expose console with ingress mode", Label("console", "ingress"), func() { + By("Deploying a broker with console") + brokerCr, createdBrokerCr := DeployCustomBroker(defaultNamespace, func(candidate *brokerv1beta1.ActiveMQArtemis) { + candidate.Spec.Console.Expose = true + candidate.Spec.Console.ExposeMode = &brokerv1beta1.ExposeModes.Ingress + + candidate.Spec.Acceptors = []brokerv1beta1.AcceptorType{ + { + Name: "acceptor", + Port: 61616, + Expose: true, + ExposeMode: &brokerv1beta1.ExposeModes.Ingress, + }, + } + + candidate.Spec.Connectors = []brokerv1beta1.ConnectorType{ + { + Name: "connector", + Port: 61616, + Expose: true, + ExposeMode: &brokerv1beta1.ExposeModes.Ingress, + }, + } + }) + + By("check ingress is created for console") + ingKey := types.NamespacedName{ + Name: brokerCr.Name + "-wconsj-0-svc-ing", + Namespace: defaultNamespace, + } + ingress := netv1.Ingress{} + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, ingKey, &ingress)).To(Succeed()) + + g.Expect(len(ingress.Spec.Rules)).To(Equal(1)) + g.Expect(ingress.Spec.Rules[0].Host).To(ContainSubstring(ingressHostDomainSubString)) + g.Expect(len(ingress.Spec.Rules[0].HTTP.Paths)).To(BeEquivalentTo(1)) + g.Expect(ingress.Spec.Rules[0].HTTP.Paths[0].Backend.Service.Name).To(BeEquivalentTo(brokerCr.Name + "-wconsj-0-svc")) + g.Expect(ingress.Spec.Rules[0].HTTP.Paths[0].Backend.Service.Port.Name).To(BeEquivalentTo("wconsj-0")) + g.Expect(ingress.Spec.Rules[0].HTTP.Paths[0].Path).To(BeEquivalentTo("/")) + g.Expect(*ingress.Spec.Rules[0].HTTP.Paths[0].PathType).To(BeEquivalentTo(netv1.PathTypePrefix)) + + }, existingClusterTimeout, existingClusterInterval).Should(Succeed()) + + By("check ingress is created for acceptor") + ingKey = types.NamespacedName{ + Name: brokerCr.Name + "-acceptor-0-svc-ing", + Namespace: defaultNamespace, + } + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, ingKey, &ingress)).To(Succeed()) + + g.Expect(len(ingress.Spec.Rules)).To(Equal(1)) + g.Expect(ingress.Spec.Rules[0].Host).To(Equal(brokerCr.Name + "-acceptor-0-svc-ing." + ingressHostDomainSubString)) + g.Expect(len(ingress.Spec.Rules[0].HTTP.Paths)).To(BeEquivalentTo(1)) + g.Expect(ingress.Spec.Rules[0].HTTP.Paths[0].Backend.Service.Name).To(Equal(brokerCr.Name + "-acceptor-0-svc")) + g.Expect(ingress.Spec.Rules[0].HTTP.Paths[0].Backend.Service.Port.Name).To(Equal("acceptor-0")) + g.Expect(ingress.Spec.Rules[0].HTTP.Paths[0].Path).To(Equal("/")) + g.Expect(*ingress.Spec.Rules[0].HTTP.Paths[0].PathType).To(Equal(netv1.PathTypePrefix)) + + }, existingClusterTimeout, existingClusterInterval).Should(Succeed()) + + By("check ingress is created for connector") + ingKey = types.NamespacedName{ + Name: brokerCr.Name + "-connector-0-svc-ing", + Namespace: defaultNamespace, + } + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, ingKey, &ingress)).To(Succeed()) + + g.Expect(len(ingress.Spec.Rules)).To(Equal(1)) + g.Expect(ingress.Spec.Rules[0].Host).To(Equal(brokerCr.Name + "-connector-0-svc-ing." + ingressHostDomainSubString)) + g.Expect(len(ingress.Spec.Rules[0].HTTP.Paths)).To(BeEquivalentTo(1)) + g.Expect(ingress.Spec.Rules[0].HTTP.Paths[0].Backend.Service.Name).To(Equal(brokerCr.Name + "-connector-0-svc")) + g.Expect(ingress.Spec.Rules[0].HTTP.Paths[0].Backend.Service.Port.Name).To(Equal("connector-0")) + g.Expect(ingress.Spec.Rules[0].HTTP.Paths[0].Path).To(Equal("/")) + g.Expect(*ingress.Spec.Rules[0].HTTP.Paths[0].PathType).To(Equal(netv1.PathTypePrefix)) + + }, existingClusterTimeout, existingClusterInterval).Should(Succeed()) + + CleanResource(createdBrokerCr, createdBrokerCr.Name, defaultNamespace) + }) + + It("expose console with route mode", Label("console", "ingress"), func() { + By("Deploying a broker with console") + brokerCr, createdBrokerCr := DeployCustomBroker(defaultNamespace, func(candidate *brokerv1beta1.ActiveMQArtemis) { + candidate.Spec.Console.Expose = true + candidate.Spec.Console.ExposeMode = &brokerv1beta1.ExposeModes.Route + + candidate.Spec.Acceptors = []brokerv1beta1.AcceptorType{ + { + Name: "acceptor", + Port: 61616, + Expose: true, + ExposeMode: &brokerv1beta1.ExposeModes.Route, + }, + } + + candidate.Spec.Connectors = []brokerv1beta1.ConnectorType{ + { + Name: "connector", + Port: 61616, + Expose: true, + ExposeMode: &brokerv1beta1.ExposeModes.Route, + }, + } + }) + + isOpenshift, _ := common.DetectOpenshift() + + if isOpenshift { + By("check route is created for console") + routeKey := types.NamespacedName{ + Name: brokerCr.Name + "-wconsj-0-svc-rte", + Namespace: defaultNamespace, + } + route := routev1.Route{} + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, routeKey, &route)).To(Succeed()) + g.Expect(route.Spec.Port.TargetPort).To(Equal(intstr.FromString("wconsj-0"))) + g.Expect(route.Spec.To.Kind).To(Equal("Service")) + g.Expect(route.Spec.To.Name).To(Equal(brokerCr.Name + "-wconsj-0-svc")) + g.Expect(route.Spec.TLS.Termination).To(BeEquivalentTo(routev1.TLSTerminationPassthrough)) + g.Expect(route.Spec.TLS.InsecureEdgeTerminationPolicy).To(BeEquivalentTo(routev1.InsecureEdgeTerminationPolicyNone)) + }, existingClusterTimeout, existingClusterInterval).Should(Succeed()) + + By("checking route is created for acceptor") + routeKey = types.NamespacedName{ + Name: brokerCr.Name + "-acceptor-0-svc-rte", + Namespace: defaultNamespace, + } + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, routeKey, &route)).To(Succeed()) + g.Expect(route.Spec.Port.TargetPort).To(Equal(intstr.FromString("acceptor-0"))) + g.Expect(route.Spec.To.Kind).To(Equal("Service")) + g.Expect(route.Spec.To.Name).To(Equal(brokerCr.Name + "-acceptor-0-svc")) + g.Expect(route.Spec.TLS.Termination).To(BeEquivalentTo(routev1.TLSTerminationPassthrough)) + g.Expect(route.Spec.TLS.InsecureEdgeTerminationPolicy).To(BeEquivalentTo(routev1.InsecureEdgeTerminationPolicyNone)) + }, existingClusterTimeout, existingClusterInterval).Should(Succeed()) + + By("checking route is created for connector") + routeKey = types.NamespacedName{ + Name: brokerCr.Name + "-connector-0-svc-rte", + Namespace: defaultNamespace, + } + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, routeKey, &route)).To(Succeed()) + g.Expect(route.Spec.Port.TargetPort).To(Equal(intstr.FromString("connector-0"))) + g.Expect(route.Spec.To.Kind).To(Equal("Service")) + g.Expect(route.Spec.To.Name).To(Equal(brokerCr.Name + "-connector-0-svc")) + g.Expect(route.Spec.TLS.Termination).To(BeEquivalentTo(routev1.TLSTerminationPassthrough)) + g.Expect(route.Spec.TLS.InsecureEdgeTerminationPolicy).To(BeEquivalentTo(routev1.InsecureEdgeTerminationPolicyNone)) + }, existingClusterTimeout, existingClusterInterval).Should(Succeed()) + } else { + brokerKey := types.NamespacedName{Name: brokerCr.Name, Namespace: brokerCr.Namespace} + deployedCrd := &brokerv1beta1.ActiveMQArtemis{} + + By("verify invalid acceptor expose mode") + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, brokerKey, deployedCrd)).Should(Succeed()) + g.Expect(meta.IsStatusConditionTrue(deployedCrd.Status.Conditions, brokerv1beta1.ValidConditionType)).Should(BeFalse()) + + validation := meta.FindStatusCondition(deployedCrd.Status.Conditions, brokerv1beta1.ValidConditionType) + g.Expect(validation).ShouldNot(BeNil()) + g.Expect(validation.Reason).Should(Equal(brokerv1beta1.ValidConditionFailedAcceptorWithInvalidExposeMode)) + g.Expect(validation.Message).Should(ContainSubstring(".Spec.Acceptors \"acceptor\" has invalid expose mode route, it is only supported on OpenShift")) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, brokerKey, deployedCrd)).Should(Succeed()) + deployedCrd.Spec.Acceptors[0].ExposeMode = nil + g.Expect(k8sClient.Update(ctx, deployedCrd)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + + By("verify invalid connector expose mode") + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, brokerKey, deployedCrd)).Should(Succeed()) + g.Expect(meta.IsStatusConditionTrue(deployedCrd.Status.Conditions, brokerv1beta1.ValidConditionType)).Should(BeFalse()) + + validation := meta.FindStatusCondition(deployedCrd.Status.Conditions, brokerv1beta1.ValidConditionType) + g.Expect(validation).ShouldNot(BeNil()) + g.Expect(validation.Reason).Should(Equal(brokerv1beta1.ValidConditionFailedConnectorWithInvalidExposeMode)) + g.Expect(validation.Message).Should(ContainSubstring(".Spec.Connectors \"connector\" has invalid expose mode route, it is only supported on OpenShift")) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, brokerKey, deployedCrd)).Should(Succeed()) + deployedCrd.Spec.Connectors[0].ExposeMode = nil + g.Expect(k8sClient.Update(ctx, deployedCrd)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + + By("verify invalid console expose mode") + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, brokerKey, deployedCrd)).Should(Succeed()) + g.Expect(meta.IsStatusConditionTrue(deployedCrd.Status.Conditions, brokerv1beta1.ValidConditionType)).Should(BeFalse()) + + validation := meta.FindStatusCondition(deployedCrd.Status.Conditions, brokerv1beta1.ValidConditionType) + g.Expect(validation).ShouldNot(BeNil()) + g.Expect(validation.Reason).Should(Equal(brokerv1beta1.ValidConditionFailedConsoleWithInvalidExposeMode)) + g.Expect(validation.Message).Should(ContainSubstring(".Spec.Console has invalid expose mode route, it is only supported on OpenShift")) + }, timeout, interval).Should(Succeed()) + } + + CleanResource(createdBrokerCr, createdBrokerCr.Name, defaultNamespace) + }) + }) + It("ssl console secret validation", func() { crd := generateArtemisSpec(defaultNamespace) diff --git a/controllers/activemqartemis_reconciler.go b/controllers/activemqartemis_reconciler.go index a4a8b4dce..b98512bdf 100644 --- a/controllers/activemqartemis_reconciler.go +++ b/controllers/activemqartemis_reconciler.go @@ -54,6 +54,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/artemiscloud/activemq-artemis-operator/api/v1beta1" brokerv1beta1 "github.com/artemiscloud/activemq-artemis-operator/api/v1beta1" "strconv" @@ -763,7 +764,7 @@ func (reconciler *ActiveMQArtemisReconcilerImpl) configureAcceptorsExposure(cust reconciler.trackDesired(serviceDefinition) if acceptor.Expose { - exposureDefinition := reconciler.ExposureDefinitionForCR(customResource, namespacedName, serviceRoutelabels, acceptor.SSLEnabled, acceptor.IngressHost, ordinalString, acceptor.Name) + exposureDefinition := reconciler.ExposureDefinitionForCR(customResource, namespacedName, serviceRoutelabels, acceptor.SSLEnabled, acceptor.IngressHost, ordinalString, acceptor.Name, acceptor.ExposeMode) reconciler.trackDesired(exposureDefinition) } } @@ -779,12 +780,15 @@ func (reconciler *ActiveMQArtemisReconcilerImpl) ServiceDefinitionForCR(serviceN return svc.NewServiceDefinitionForCR(serviceName, client, nameSuffix, portNumber, selectorLabels, labels, serviceDefinition) } -func (reconciler *ActiveMQArtemisReconcilerImpl) ExposureDefinitionForCR(customResource *brokerv1beta1.ActiveMQArtemis, namespacedName types.NamespacedName, labels map[string]string, passthroughTLS bool, ingressHost string, ordinalString string, itemName string) rtclient.Object { +func (reconciler *ActiveMQArtemisReconcilerImpl) ExposureDefinitionForCR(customResource *brokerv1beta1.ActiveMQArtemis, namespacedName types.NamespacedName, labels map[string]string, passthroughTLS bool, ingressHost string, ordinalString string, itemName string, exposeMode *v1beta1.ExposeMode) rtclient.Object { targetPortName := itemName + "-" + ordinalString targetServiceName := customResource.Name + "-" + targetPortName + "-" + ServiceTypePostfix - if isOpenshift, err := common.DetectOpenshift(); isOpenshift && err == nil { + isOpenshift, err := common.DetectOpenshift() + exposeWithRoute := (exposeMode == nil && isOpenshift && err == nil) || (exposeMode != nil && *exposeMode == v1beta1.ExposeModes.Route) + + if exposeWithRoute { reconciler.log.V(1).Info("creating route for "+targetPortName, "service", targetServiceName) var existing *routev1.Route = nil @@ -1004,7 +1008,7 @@ func (reconciler *ActiveMQArtemisReconcilerImpl) configureConnectorsExposure(cus if connector.Expose { - exposureDefinition := reconciler.ExposureDefinitionForCR(customResource, namespacedName, serviceRoutelabels, connector.SSLEnabled, connector.IngressHost, ordinalString, connector.Name) + exposureDefinition := reconciler.ExposureDefinitionForCR(customResource, namespacedName, serviceRoutelabels, connector.SSLEnabled, connector.IngressHost, ordinalString, connector.Name, connector.ExposeMode) reconciler.trackDesired(exposureDefinition) } @@ -1051,9 +1055,10 @@ func (reconciler *ActiveMQArtemisReconcilerImpl) configureConsoleExposure(custom reconciler.checkExistingService(customResource, serviceDefinition, client) reconciler.trackDesired(serviceDefinition) - isOpenshift := false - isOpenshift, _ = common.DetectOpenshift() - if isOpenshift { + isOpenshift, err := common.DetectOpenshift() + exposeWithRoute := (console.ExposeMode == nil && isOpenshift && err == nil) || (console.ExposeMode != nil && *console.ExposeMode == v1beta1.ExposeModes.Route) + + if exposeWithRoute { reconciler.log.V(2).Info("routeDefinition for " + targetPortName) var existing *routev1.Route = nil obj := reconciler.cloneOfDeployed(reflect.TypeOf(routev1.Route{}), targetServiceName+"-"+RouteTypePostfix) @@ -1529,27 +1534,17 @@ func (r *ActiveMQArtemisReconcilerImpl) checkExistingPersistentVolumes(instance var orderedTypes *([]reflect.Type) func getOrderedTypeList() []reflect.Type { - return genOrderedTypesLists() -} - -func genOrderedTypesLists() []reflect.Type { - if orderedTypes == nil { - isOpenshift, _ := common.DetectOpenshift() - types := make([]reflect.Type, 6) + types := make([]reflect.Type, 7) // we want to create/update in this order types[0] = reflect.TypeOf(corev1.Secret{}) types[1] = reflect.TypeOf(corev1.ConfigMap{}) types[2] = reflect.TypeOf(appsv1.StatefulSet{}) types[3] = reflect.TypeOf(corev1.Service{}) - - if isOpenshift { - types[4] = reflect.TypeOf(routev1.Route{}) - } else { - types[4] = reflect.TypeOf(netv1.Ingress{}) - } - types[5] = reflect.TypeOf(policyv1.PodDisruptionBudget{}) + types[4] = reflect.TypeOf(netv1.Ingress{}) + types[5] = reflect.TypeOf(routev1.Route{}) + types[6] = reflect.TypeOf(policyv1.PodDisruptionBudget{}) orderedTypes = &types } return *orderedTypes diff --git a/deploy/activemq-artemis-operator.yaml b/deploy/activemq-artemis-operator.yaml index 16e0549ad..6394c8bb8 100644 --- a/deploy/activemq-artemis-operator.yaml +++ b/deploy/activemq-artemis-operator.yaml @@ -399,6 +399,12 @@ spec: expose: description: Whether or not to expose this acceptor type: boolean + exposeMode: + description: Mode to expose the acceptor. Currently the supported modes are `route` and `ingress`. Default is `route` on OpenShift and `ingress` on Kubernetes. \n\n* `route` mode uses OpenShift Routes to expose the acceptor.\n* `ingress` mode uses Kubernetes Nginx Ingress to expose the acceptor with TLS passthrough.\n" + enum: + - ingress + - route + type: string ingressHost: description: 'Host for Ingress and Route resources of the acceptor. It supports the following variables: $(CR_NAME), $(CR_NAMESPACE), $(BROKER_ORDINAL), $(ITEM_NAME), $(RES_NAME) and $(INGRESS_DOMAIN). Default is $(CR_NAME)-$(ITEM_NAME)-$(BROKER_ORDINAL)-svc-$(RES_TYPE).$(INGRESS_DOMAIN)' type: string @@ -712,6 +718,12 @@ spec: expose: description: Whether or not to expose this connector type: boolean + exposeMode: + description: Mode to expose the connector. Currently the supported modes are `route` and `ingress`. Default is `route` on OpenShift and `ingress` on Kubernetes. \n\n* `route` mode uses OpenShift Routes to expose the connector.\n* `ingress` mode uses Kubernetes Nginx Ingress to expose the connector with TLS passthrough.\n" + enum: + - ingress + - route + type: string host: description: Hostname or IP to connect to type: string @@ -770,6 +782,12 @@ spec: expose: description: Whether or not to expose this port type: boolean + exposeMode: + description: Mode to expose the console. Currently the supported modes are `route` and `ingress`. Default is `route` on OpenShift and `ingress` on Kubernetes. \n\n* `route` mode uses OpenShift Routes to expose the console.\n* `ingress` mode uses Kubernetes Nginx Ingress to expose the console with TLS passthrough.\n" + enum: + - ingress + - route + type: string ingressHost: description: 'Host for Ingress and Route resources of the acceptor. It supports the following variables: $(CR_NAME), $(CR_NAMESPACE), $(BROKER_ORDINAL), $(ITEM_NAME), $(RES_NAME) and $(INGRESS_DOMAIN). Default is $(CR_NAME)-$(ITEM_NAME)-$(BROKER_ORDINAL)-svc-$(RES_TYPE).$(INGRESS_DOMAIN)' type: string diff --git a/deploy/crds/broker_activemqartemis_crd.yaml b/deploy/crds/broker_activemqartemis_crd.yaml index 32c790069..bfae3bbdd 100644 --- a/deploy/crds/broker_activemqartemis_crd.yaml +++ b/deploy/crds/broker_activemqartemis_crd.yaml @@ -56,6 +56,12 @@ spec: expose: description: Whether or not to expose this acceptor type: boolean + exposeMode: + description: Mode to expose the acceptor. Currently the supported modes are `route` and `ingress`. Default is `route` on OpenShift and `ingress` on Kubernetes. \n\n* `route` mode uses OpenShift Routes to expose the acceptor.\n* `ingress` mode uses Kubernetes Nginx Ingress to expose the acceptor with TLS passthrough.\n" + enum: + - ingress + - route + type: string ingressHost: description: 'Host for Ingress and Route resources of the acceptor. It supports the following variables: $(CR_NAME), $(CR_NAMESPACE), $(BROKER_ORDINAL), $(ITEM_NAME), $(RES_NAME) and $(INGRESS_DOMAIN). Default is $(CR_NAME)-$(ITEM_NAME)-$(BROKER_ORDINAL)-svc-$(RES_TYPE).$(INGRESS_DOMAIN)' type: string @@ -369,6 +375,12 @@ spec: expose: description: Whether or not to expose this connector type: boolean + exposeMode: + description: Mode to expose the connector. Currently the supported modes are `route` and `ingress`. Default is `route` on OpenShift and `ingress` on Kubernetes. \n\n* `route` mode uses OpenShift Routes to expose the connector.\n* `ingress` mode uses Kubernetes Nginx Ingress to expose the connector with TLS passthrough.\n" + enum: + - ingress + - route + type: string host: description: Hostname or IP to connect to type: string @@ -427,6 +439,12 @@ spec: expose: description: Whether or not to expose this port type: boolean + exposeMode: + description: Mode to expose the console. Currently the supported modes are `route` and `ingress`. Default is `route` on OpenShift and `ingress` on Kubernetes. \n\n* `route` mode uses OpenShift Routes to expose the console.\n* `ingress` mode uses Kubernetes Nginx Ingress to expose the console with TLS passthrough.\n" + enum: + - ingress + - route + type: string ingressHost: description: 'Host for Ingress and Route resources of the acceptor. It supports the following variables: $(CR_NAME), $(CR_NAMESPACE), $(BROKER_ORDINAL), $(ITEM_NAME), $(RES_NAME) and $(INGRESS_DOMAIN). Default is $(CR_NAME)-$(ITEM_NAME)-$(BROKER_ORDINAL)-svc-$(RES_TYPE).$(INGRESS_DOMAIN)' type: string