From b9c0a02b7c9b729a868e2e311254848613737958 Mon Sep 17 00:00:00 2001 From: Ricardo Zanini Date: Sat, 23 Nov 2019 17:41:31 -0300 Subject: [PATCH] [KOGITO-165] - Kogito Runtime Services integration with Infinispan --- README.md | 284 ++++++++++-------- cmd/kogito/command/deploy/deploy_service.go | 38 ++- .../command/deploy/deploy_service_test.go | 6 +- .../crds/app.kiegroup.org_kogitoapps_crd.yaml | 20 ++ deploy/examples/onboarding-example.yaml | 18 +- .../app.kiegroup.org_kogitoapps_crd.yaml | 20 ++ ...operator.v0.6.0.clusterserviceversion.yaml | 3 + pkg/apis/app/v1alpha1/kogitoapp_types.go | 31 +- .../app/v1alpha1/zz_generated.deepcopy.go | 17 ++ pkg/apis/app/v1alpha1/zz_generated.openapi.go | 8 +- .../kogitoapp/kogitoapp_controller.go | 43 +++ .../kogitoapp/kogitoapp_controller_test.go | 86 ++++-- .../kogitoapp/resource/deployment_config.go | 59 +++- .../kogitoapp/resource/requested_resources.go | 4 +- .../kogitodataindex_controller.go | 7 +- .../kogitodataindex_controller_test.go | 7 +- pkg/infrastructure/infinispan.go | 54 +++- pkg/infrastructure/kogitoinfra.go | 29 +- pkg/infrastructure/kogitoinfra_test.go | 4 +- pkg/resource/image_metadata.go | 32 +- pkg/resource/image_metadata_test.go | 43 +++ 21 files changed, 619 insertions(+), 194 deletions(-) diff --git a/README.md b/README.md index fa1899532..9458755b1 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,12 @@ Table of Contents * [For Windows](#for-windows) * [Building the Kogito CLI from source](#building-the-kogito-cli-from-source) * [Deploying a Kogito service from source with the Kogito CLI](#deploying-a-kogito-service-from-source-with-the-kogito-cli) + * [Prometheus integration with the Kogito Operator](#prometheus-integration-with-the-kogito-operator) + * [Prometheus annotations](#prometheus-annotations) + * [Prometheus Operator](#prometheus-operator) + * [Infinispan integration](#infinispan-integration) + * [Kogito Services](#kogito-services) + * [Data Index Service](#data-index-service) * [Kogito Operator development](#kogito-operator-development) * [Building the Kogito Operator](#building-the-kogito-operator) * [Deploying to OpenShift 4.x for development purposes](#deploying-to-openshift-4x-for-development-purposes) @@ -44,10 +50,6 @@ Table of Contents * [With the Kogito Operator SDK](#with-the-kogito-operator-sdk) * [With the Kogito CLI](#with-the-kogito-cli) * [Running the Kogito Operator locally](#running-the-kogito-operator-locally) - * [Prometheus integration with the Kogito Operator](#prometheus-integration-with-the-kogito-operator) - * [Prometheus annotations](#prometheus-annotations) - * [Prometheus Operator](#prometheus-operator) - * [Infinispan integration](#infinispan-integration) * [Contributing to the Kogito Operator](#contributing-to-the-kogito-operator) Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc) @@ -447,115 +449,6 @@ You can shorten the previous command as shown in the following example: ```bash $ kogito deploy-service example-drools https://github.com/kiegroup/kogito-examples --context-dir drools-quarkus-example --project ``` - -## Kogito Operator development - -Before you begin fixing issues or adding new features to the Kogito Operator, see [Contributing to the Kogito Operator](docs/CONTRIBUTING.MD) and [Kogito Operator architecture](docs/ARCHITECTURE.MD). - -### Building the Kogito Operator - -To build the Kogito Operator, use the following command: - -```bash -$ make -``` - -The output of this command is a ready-to-use Kogito Operator image that you can deploy in any namespace. - -### Deploying to OpenShift 4.x for development purposes - -To install the Kogito Operator on OpenShift 4.x for end-to-end (E2E) testing, ensure that you have access to a `quay.io` account to create an application repository. Follow the Operator Courier [authentication](https://github.com/operator-framework/operator-courier/#authentication) instructions to obtain an account token. This token is in the format `basic XXXXXXXXX` and both words are required for the command. - -Push the Operator bundle to your quay application repository as shown in the following example: - -```bash -$ operator-courier push deploy/olm-catalog/kogito-cloud-operator/ namespace kogitocloud-operator 0.6.0 "basic XXXXXXXXX" -``` - -If you push to another quay repository, replace `namespace` with your user name or the other namespace. The push command does not overwrite an existing repository, so you must delete the bundle before you can build and upload a new version. After you upload the bundle, create an [Operator Source](https://github.com/operator-framework/community-operators/blob/master/docs/testing-operators.md#linking-the-quay-application-repository-to-your-openshift-40-cluster) to load your operator bundle in OpenShift. - -The OpenShift cluster needs access to the created application. Ensure that the application is **public** or that you have configured the private repository credentials in the cluster. To make the application public, go to your `quay.io` account, and in the **Applications** tab look for the `kogitocloud-operator` application. Under the settings section, click **make public**. - -```bash -## Kogito imagestreams should already be installed and available, for example: -$ oc apply -f https://raw.githubusercontent.com/kiegroup/kogito-cloud/master/s2i/kogito-imagestream.yaml -n openshift -$ oc create -f deploy/olm-catalog/kogito-cloud-operator/kogitocloud-operatorsource.yaml -``` - -Replace `registryNamespace` in the `kogitocloud-operatorsource.yaml` file with your quay namespace. The name, display name, and publisher of the Operator are the only other attributes that you can modify. - -After several minutes, the Operator appears under **Catalog** -> **OperatorHub** in the OpenShift Web Console. To find the Operator, filter the provider type by _Custom_. - -To verify the operator status, run the following command: - -```bash -$ oc describe operatorsource.operators.coreos.com/kogitocloud-operator -n openshift-marketplace -``` - -### Running End-to-End (E2E) tests - -#### With the Kogito Operator SDK - -If you have an OpenShift cluster and admin privileges, you can run E2E tests with the following command: - -```bash -$ make run-e2e namespace= tag= maven_mirror= image= tests= -``` - -where: - -- `namespace` (required) is a given temporary namespace where the test will run. You do not need to create the namespace because it will be created and deleted after the test runs. -- `tag` (optional, default is current release) is the image tag for the Kogito image builds, for example, `0.6.0-rc1`. This is helpful in situations where [Kogito S2I images](https://github.com/kiegroup/kogito-cloud/tree/master/s2i) have not been released yet and are under a temporary tag. -- `maven_mirror` (optional, default is empty) is the Maven mirror URL. This is helpful when you need to speed up the build time by referring to a closer Maven repository. -- `image` (optional, default is empty) indicates whether the E2E test should be executed against a specified Kogito Operator image. If the value is empty, then the local Operator source code is used for the test execution. -- `tests` (optional, default is `full`) indicates what types of tests should be executed. Possible values are `full`, `jvm`, and `native`. If you specify `full` or specify no parameter, then both JVM and native tests are executed. - -If any errors are detected during this test, a detailed log appears in your command terminal. - -To save the test output in a local file for future reference, run the following command: - -```bash -make run-e2e namespace=kogito-e2e 2>&1 | tee log.out -``` - -#### With the Kogito CLI - -You can run a smoke test using the Kogito CLI during development to make sure that at least the basic use case is covered. - -On OpenShift 4.x, before you run this test, install the Kogito Operator in the namespace where the test will run. On OpenShift 3.11, the Kogito CLI installs the Kogito Operator for you. - -To run an E2E test using the Kogito CLI, run the following command: - - ```bash - $ make run-e2e-cli namespace= tag= native= maven_mirror= skip_build= - ``` - -where: - -- `namespace` (required) is a given temporary namespace where the test will run. -- `tag` (optional, default is current release) is the image tag for the Kogito image builds, for example, `0.6.0-rc1`. This is helpful in situations where [Kogito S2I images](https://github.com/kiegroup/kogito-cloud/tree/master/s2i) have not been released yet and are under a temporary tag. -- `native` (optional, default is `false`) indicates whether the E2E test should use `native` or `jvm` builds. For more information, see [Native X JVM builds](#native-x-jvm-builds). -- `maven_mirror` (optional, default is empty) is the Maven mirror URL. This is helpful when you need to speed up the build time by referring to a closer Maven repository. -- `skip_build` (optional, default is `true`) is set to `true` to skip building the CLI before running the test. - -### Running the Kogito Operator locally - -To run the Kogito Operator locally, change the log level at runtime with the `DEBUG` environment variable, as shown in the following example: - -```bash -$ make mod -$ make clean -$ DEBUG=true operator-sdk up local --namespace= -``` - -Before submitting a [pull request](https://help.github.com/en/articles/about-pull-requests) to the Kogito Operator repository, review the instructions for [Contributing to the Kogito Operator](docs/CONTRIBUTING.MD). - -You can use the following command to vet, format, lint, and test your code: - -```bash -$ make test -``` - ## Prometheus integration with the Kogito Operator ### Prometheus annotations @@ -657,13 +550,13 @@ For more information about the Prometheus Operator, see the [Prometheus Operator To help you start and run an Infinispan Server instance in your project, the Kogito Operator has a resource called `KogitoInfra` to handle Infinispan deployment for you. -For the Data Index Service, if you do not provide a service URL to connect to Infinispan Server, a URL is deployed using the [Infinispan Operator](https://github.com/infinispan/infinispan-operator). - -A random password for the `developer` user is created and injected into the Data Index automatically. You do not need to do anything for both services to work together. +The `KogitoInfra` resource use the [Infinispan Operator](https://github.com/infinispan/infinispan-operator) to deploy new Infinispan server instances if needed. -If you have plans to scale the Infinispan cluster, you can edit the [Infinispan CR](https://github.com/infinispan/infinispan-operator/blob/master/pkg/apis/infinispan/v1/infinispan_types.go) to meet your requirements. +You can freely edit and manage the Infinispan instance. Kogito Operator do not manage or handle the Infinispan instances. +For example, if you have plans to scale the Infinispan cluster, you can edit the `replicas` field in the [Infinispan CR](https://github.com/infinispan/infinispan-operator/blob/master/pkg/apis/infinispan/v1/infinispan_types.go) to meet your requirements. -By default, the `KogitoInfra` resource creates a secret that holds the user name and password to authenticate this server. To view the credentials, run the following command: +By default, the `KogitoInfra` resource creates a secret that holds the user name and password for Infinispan authentication. +To view the credentials, run the following command: ```bash $ oc get secret/kogito-infinispan-credential -o yaml @@ -676,7 +569,8 @@ kind: Secret (...) ``` -The key values are masked by a Base64 algorithm. To view the password from the previous example output in your terminal, run the following command: +The key values are masked by a Base64 algorithm. To view the password from the previous example output in your terminal, +run the following command: ```bash $ echo VzNCcW9DeXdpMVdXdlZJZQ== | base64 -d @@ -684,6 +578,158 @@ $ echo VzNCcW9DeXdpMVdXdlZJZQ== | base64 -d W3BqoCywi1WWvVIe ``` +For more information about Infinispan Operator, please see [their official documentation](https://infinispan.org/infinispan-operator/master/documentation/asciidoc/titles/operator.html). + +### Kogito Services + +If your Kogito Service depends on the [persistence add-on](https://github.com/kiegroup/kogito-runtimes/wiki/Persistence), +Kogito Operator installs Infinispan and inject the connection properties as environment variables into the service. +Depending on the runtime, this variables will differ. See the table below: + + +|Quarkus Runtime |Springboot Runtime | Description |Example | +|-----------------------------------------|--------------------------------|---------------------------------------------------|-----------------------| +|QUARKUS_INFINISPAN_CLIENT_SERVER_LIST |INFINISPAN_REMOTE_SERVER_LIST |Service URI from deployed Infinispan |kogito-infinispan:11222| +|QUARKUS_INFINISPAN_CLIENT_AUTH_USERNAME |INFINISPAN_REMOTE_AUTH_USER_NAME|Default username generated by Infinispan Operator |developer | +|QUARKUS_INFINISPAN_CLIENT_AUTH_PASSWORD |INFINISPAN_REMOTE_AUTH_PASSWORD |Random password generated by Infinispan Operator |Z1Nz34JpuVdzMQKi | +|QUARKUS_INFINISPAN_CLIENT_SASL_MECHANISM |INFINISPAN_REMOTE_SASL_MECHANISM|Default to `PLAIN` |`PLAIN` | + +Just make sure that your Kogito Service can read these properties in runtime. +Those variables names are the same as the ones used by Infinispan clients from Quarkus and Springboot. + +On Quarkus, make sure that your `aplication.properties` file has the properties listed like the example below: + +```properties +quarkus.infinispan-client.server-list= +quarkus.infinispan-client.auth-username= +quarkus.infinispan-client.auth-password= +quarkus.infinispan-client.sasl-mechanism= +``` + +These properties are replaced by the environment variables in runtime. + +You can control the installation method for the Infinispan by using the flag `infinispan-install` in the Kogito CLI or +editing the `spec.infra.installInfinispan` in `KogitoApp` custom resource: + +- **`Auto`** - The operator tries to discover if the service needs persistence by scanning the runtime image for the `org.kie/persistence/required` label attribute +- **`Always`** - Infinispan is installed in the namespace without checking if the service needs persistence or not +- **`Never`** - Infinispan is not installed, even if the service requires persistence. Use this option only if you intend to deploy your own persistence mechanism and you know how to configure your service to access it + +### Data Index Service + +For the Data Index Service, if you do not provide a service URL to connect to Infinispan, a new server is deployed via `KogitoInfra`. + +A random password for the `developer` user is created and injected into the Data Index automatically. +You do not need to do anything for both services to work together. + +## Kogito Operator development + +Before you begin fixing issues or adding new features to the Kogito Operator, see [Contributing to the Kogito Operator](docs/CONTRIBUTING.MD) and [Kogito Operator architecture](docs/ARCHITECTURE.MD). + +### Building the Kogito Operator + +To build the Kogito Operator, use the following command: + +```bash +$ make +``` + +The output of this command is a ready-to-use Kogito Operator image that you can deploy in any namespace. + +### Deploying to OpenShift 4.x for development purposes + +To install the Kogito Operator on OpenShift 4.x for end-to-end (E2E) testing, ensure that you have access to a `quay.io` account to create an application repository. Follow the Operator Courier [authentication](https://github.com/operator-framework/operator-courier/#authentication) instructions to obtain an account token. This token is in the format `basic XXXXXXXXX` and both words are required for the command. + +Push the Operator bundle to your quay application repository as shown in the following example: + +```bash +$ operator-courier push deploy/olm-catalog/kogito-cloud-operator/ namespace kogitocloud-operator 0.6.0 "basic XXXXXXXXX" +``` + +If you push to another quay repository, replace `namespace` with your user name or the other namespace. The push command does not overwrite an existing repository, so you must delete the bundle before you can build and upload a new version. After you upload the bundle, create an [Operator Source](https://github.com/operator-framework/community-operators/blob/master/docs/testing-operators.md#linking-the-quay-application-repository-to-your-openshift-40-cluster) to load your operator bundle in OpenShift. + +The OpenShift cluster needs access to the created application. Ensure that the application is **public** or that you have configured the private repository credentials in the cluster. To make the application public, go to your `quay.io` account, and in the **Applications** tab look for the `kogitocloud-operator` application. Under the settings section, click **make public**. + +```bash +## Kogito imagestreams should already be installed and available, for example: +$ oc apply -f https://raw.githubusercontent.com/kiegroup/kogito-cloud/master/s2i/kogito-imagestream.yaml -n openshift +$ oc create -f deploy/olm-catalog/kogito-cloud-operator/kogitocloud-operatorsource.yaml +``` + +Replace `registryNamespace` in the `kogitocloud-operatorsource.yaml` file with your quay namespace. The name, display name, and publisher of the Operator are the only other attributes that you can modify. + +After several minutes, the Operator appears under **Catalog** -> **OperatorHub** in the OpenShift Web Console. To find the Operator, filter the provider type by _Custom_. + +To verify the operator status, run the following command: + +```bash +$ oc describe operatorsource.operators.coreos.com/kogitocloud-operator -n openshift-marketplace +``` + +### Running End-to-End (E2E) tests + +#### With the Kogito Operator SDK + +If you have an OpenShift cluster and admin privileges, you can run E2E tests with the following command: + +```bash +$ make run-e2e namespace= tag= maven_mirror= image= tests= +``` + +where: + +- `namespace` (required) is a given temporary namespace where the test will run. You do not need to create the namespace because it will be created and deleted after the test runs. +- `tag` (optional, default is current release) is the image tag for the Kogito image builds, for example, `0.6.0-rc1`. This is helpful in situations where [Kogito S2I images](https://github.com/kiegroup/kogito-cloud/tree/master/s2i) have not been released yet and are under a temporary tag. +- `maven_mirror` (optional, default is empty) is the Maven mirror URL. This is helpful when you need to speed up the build time by referring to a closer Maven repository. +- `image` (optional, default is empty) indicates whether the E2E test should be executed against a specified Kogito Operator image. If the value is empty, then the local Operator source code is used for the test execution. +- `tests` (optional, default is `full`) indicates what types of tests should be executed. Possible values are `full`, `jvm`, and `native`. If you specify `full` or specify no parameter, then both JVM and native tests are executed. + +If any errors are detected during this test, a detailed log appears in your command terminal. + +To save the test output in a local file for future reference, run the following command: + +```bash +make run-e2e namespace=kogito-e2e 2>&1 | tee log.out +``` + +#### With the Kogito CLI + +You can run a smoke test using the Kogito CLI during development to make sure that at least the basic use case is covered. + +On OpenShift 4.x, before you run this test, install the Kogito Operator in the namespace where the test will run. On OpenShift 3.11, the Kogito CLI installs the Kogito Operator for you. + +To run an E2E test using the Kogito CLI, run the following command: + + ```bash + $ make run-e2e-cli namespace= tag= native= maven_mirror= skip_build= + ``` + +where: + +- `namespace` (required) is a given temporary namespace where the test will run. +- `tag` (optional, default is current release) is the image tag for the Kogito image builds, for example, `0.6.0-rc1`. This is helpful in situations where [Kogito S2I images](https://github.com/kiegroup/kogito-cloud/tree/master/s2i) have not been released yet and are under a temporary tag. +- `native` (optional, default is `false`) indicates whether the E2E test should use `native` or `jvm` builds. For more information, see [Native X JVM builds](#native-x-jvm-builds). +- `maven_mirror` (optional, default is empty) is the Maven mirror URL. This is helpful when you need to speed up the build time by referring to a closer Maven repository. +- `skip_build` (optional, default is `true`) is set to `true` to skip building the CLI before running the test. + +### Running the Kogito Operator locally + +To run the Kogito Operator locally, change the log level at runtime with the `DEBUG` environment variable, as shown in the following example: + +```bash +$ make mod +$ make clean +$ DEBUG=true operator-sdk up local --namespace= +``` + +Before submitting a [pull request](https://help.github.com/en/articles/about-pull-requests) to the Kogito Operator repository, review the instructions for [Contributing to the Kogito Operator](docs/CONTRIBUTING.MD). + +You can use the following command to vet, format, lint, and test your code: + +```bash +$ make test +``` + ## Contributing to the Kogito Operator For information about submitting bug fixes or proposed new features for the Kogito Operator, see [Contributing to the Kogito Operator](docs/CONTRIBUTING.MD). diff --git a/cmd/kogito/command/deploy/deploy_service.go b/cmd/kogito/command/deploy/deploy_service.go index 9a961968c..9324f35cb 100644 --- a/cmd/kogito/command/deploy/deploy_service.go +++ b/cmd/kogito/command/deploy/deploy_service.go @@ -29,28 +29,31 @@ import ( ) const ( - defaultDeployRuntime = string(v1alpha1.QuarkusRuntimeType) + defaultDeployRuntime = string(v1alpha1.QuarkusRuntimeType) + defaultInstallInfinispan = string(v1alpha1.KogitoAppInfraInstallInfinispanAuto) ) var ( - deployRuntimeValidEntries = []string{string(v1alpha1.QuarkusRuntimeType), string(v1alpha1.SpringbootRuntimeType)} + deployRuntimeValidEntries = []string{string(v1alpha1.QuarkusRuntimeType), string(v1alpha1.SpringbootRuntimeType)} + installInfinispanValidEntries = []string{string(v1alpha1.KogitoAppInfraInstallInfinispanAuto), string(v1alpha1.KogitoAppInfraInstallInfinispanNever), string(v1alpha1.KogitoAppInfraInstallInfinispanAlways)} ) type deployFlags struct { CommonFlags - name string - runtime string - serviceLabels []string - incrementalBuild bool - buildEnv []string - reference string - contextDir string - source string - imageS2I string - imageRuntime string - native bool - buildLimits []string - buildRequests []string + name string + runtime string + serviceLabels []string + incrementalBuild bool + buildEnv []string + reference string + contextDir string + source string + imageS2I string + imageRuntime string + native bool + buildLimits []string + buildRequests []string + installInfinispan string } type deployCommand struct { @@ -110,6 +113,9 @@ func (i *deployCommand) RegisterHook() { if !util.Contains(i.flags.runtime, deployRuntimeValidEntries) { return fmt.Errorf("runtime not valid. Valid runtimes are %s. Received %s", deployRuntimeValidEntries, i.flags.runtime) } + if !util.Contains(i.flags.installInfinispan, installInfinispanValidEntries) { + return fmt.Errorf("install-infinispan not valid. Valid entries are %s. Received %s", installInfinispanValidEntries, i.flags.installInfinispan) + } if err := CheckImageTag(i.flags.imageRuntime); err != nil { return err } @@ -139,6 +145,7 @@ func (i *deployCommand) InitHook() { i.command.Flags().StringSliceVar(&i.flags.buildRequests, "build-requests", nil, "Resource requests for the s2i build pod. Valid values are 'cpu' and 'memory'. For example 'cpu=1'. Can be set more than once.") i.command.Flags().StringVar(&i.flags.imageS2I, "image-s2i", "", "Image tag (namespace/name:tag) for using during the s2i build, e.g: openshift/kogito-quarkus-ubi8-s2i:latest") i.command.Flags().StringVar(&i.flags.imageRuntime, "image-runtime", "", "Image tag (namespace/name:tag) for using during service runtime, e.g: openshift/kogito-quarkus-ubi8:latest") + i.command.Flags().StringVar(&i.flags.installInfinispan, "install-infinispan", defaultInstallInfinispan, "Infinispan installation mode: \"Always\", \"Never\" or \"Auto\". \"Always\" will install Infinispan in the same namespace no matter what, \"Never\" won't install Infinispan even if the service requires it and \"Auto\" will install only if the service requires persistence.") } func (i *deployCommand) Exec(cmd *cobra.Command, args []string) error { @@ -200,6 +207,7 @@ func (i *deployCommand) Exec(cmd *cobra.Command, args []string) error { Limits: shared.FromStringArrayToControllerResourceMap(i.flags.Limits), Requests: shared.FromStringArrayToControllerResourceMap(i.flags.Requests), }, + Infra: v1alpha1.KogitoAppInfra{InstallInfinispan: v1alpha1.KogitoAppInfraInstallInfinispanType(i.flags.installInfinispan)}, }, Status: v1alpha1.KogitoAppStatus{ Conditions: []v1alpha1.Condition{}, diff --git a/cmd/kogito/command/deploy/deploy_service_test.go b/cmd/kogito/command/deploy/deploy_service_test.go index 8d8ee379c..fd1dd5ea8 100644 --- a/cmd/kogito/command/deploy/deploy_service_test.go +++ b/cmd/kogito/command/deploy/deploy_service_test.go @@ -49,7 +49,8 @@ func Test_DeployCmd_CustomDeployment(t *testing.T) { -v --context-dir drools-quarkus-example --project %s --image-s2i=myimage --image-runtime=myimage:0.2 --limits cpu=1 --limits memory=1Gi --requests cpu=1,memory=1Gi - --build-limits cpu=1 --build-limits memory=1Gi --build-requests cpu=1,memory=2Gi`, ns) + --build-limits cpu=1 --build-limits memory=1Gi --build-requests cpu=1,memory=2Gi + --install-infinispan Always`, ns) // Clean up after the command above cli = strings.Join(strings.Fields(cli), " ") ctx := test.SetupCliTest(cli, @@ -82,6 +83,7 @@ func Test_DeployCmd_CustomDeployment(t *testing.T) { assert.Equal(t, kogitoApp.Spec.Build.ImageS2I.ImageStreamName, "myimage") assert.Equal(t, kogitoApp.Spec.Build.ImageRuntime.ImageStreamName, "myimage") assert.Equal(t, kogitoApp.Spec.Build.ImageRuntime.ImageStreamTag, "0.2") + assert.Equal(t, v1alpha1.KogitoAppInfraInstallInfinispanAlways, kogitoApp.Spec.Infra.InstallInfinispan) } func Test_DeployCmd_CustomImage(t *testing.T) { @@ -111,4 +113,6 @@ func Test_DeployCmd_CustomImage(t *testing.T) { assert.Equal(t, "openshift", instance.Spec.Build.ImageRuntime.ImageStreamNamespace) assert.Equal(t, "myimage", instance.Spec.Build.ImageRuntime.ImageStreamName) assert.Equal(t, "0.2", instance.Spec.Build.ImageRuntime.ImageStreamTag) + + assert.Equal(t, v1alpha1.KogitoAppInfraInstallInfinispanAuto, instance.Spec.Infra.InstallInfinispan) } diff --git a/deploy/crds/app.kiegroup.org_kogitoapps_crd.yaml b/deploy/crds/app.kiegroup.org_kogitoapps_crd.yaml index 36a289b11..cbd10d87f 100644 --- a/deploy/crds/app.kiegroup.org_kogitoapps_crd.yaml +++ b/deploy/crds/app.kiegroup.org_kogitoapps_crd.yaml @@ -168,6 +168,26 @@ spec: type: string type: object type: array + infra: + description: Infrastructure definition + properties: + installInfinispan: + description: 'By default Kogito Operator installs an Infinispan + instance in the namespace if the service needs persistence (''Auto''). + Set to ''Never'' to disable this behavior, e.g. if the service + will use another persistence mechanism. Set to ''Always'' to always + install Infinispan, even if the service won''t need persistence. + For Quarkus runtime, it sets QUARKUS_INFINISPAN_CLIENT_* environment + variables. For Spring Boot, these variables start with SPRING_INFINISPAN_CLIENT_*. + More info: https://github.com/kiegroup/kogito-cloud-operator#infinispan-environment-variables. + Default to false, which means it installs Infinispan if the service + requires persistence.' + enum: + - Always + - Never + - Auto + type: string + type: object replicas: description: 'Number of replicas that the service will have deployed in the cluster Default value: 1' diff --git a/deploy/examples/onboarding-example.yaml b/deploy/examples/onboarding-example.yaml index 5f4e5dc76..79d08b545 100644 --- a/deploy/examples/onboarding-example.yaml +++ b/deploy/examples/onboarding-example.yaml @@ -8,9 +8,9 @@ spec: uri: https://github.com/kiegroup/kogito-examples contextDir: onboarding-example/onboarding imageRuntime: - imageStreamTag: 0.5.0 + imageStreamTag: 0.6.0 imageS2I: - imageStreamTag: 0.5.0 + imageStreamTag: 0.6.0 env: # optional, but will improve your build time quite a lot - name: MAVEN_MIRROR_URL @@ -33,9 +33,9 @@ spec: uri: https://github.com/kiegroup/kogito-examples contextDir: onboarding-example/hr imageRuntime: - imageStreamTag: 0.5.0 + imageStreamTag: 0.6.0 imageS2I: - imageStreamTag: 0.5.0 + imageStreamTag: 0.6.0 env: # optional, but will improve your build time quite a lot - name: MAVEN_MIRROR_URL @@ -57,15 +57,15 @@ spec: uri: https://github.com/kiegroup/kogito-examples contextDir: onboarding-example/payroll imageRuntime: - imageStreamTag: 0.5.0 + imageStreamTag: 0.6.0 imageS2I: - imageStreamTag: 0.5.0 + imageStreamTag: 0.6.0 env: # optional, but will improve your build time quite a lot - name: MAVEN_MIRROR_URL value: "" service: labels: - taxRate: process - vacationDays: process - paymentDate: process + taxes/rate: process + vacations/days: process + payments/date: process diff --git a/deploy/olm-catalog/kogito-cloud-operator/0.6.0/app.kiegroup.org_kogitoapps_crd.yaml b/deploy/olm-catalog/kogito-cloud-operator/0.6.0/app.kiegroup.org_kogitoapps_crd.yaml index 36a289b11..cbd10d87f 100644 --- a/deploy/olm-catalog/kogito-cloud-operator/0.6.0/app.kiegroup.org_kogitoapps_crd.yaml +++ b/deploy/olm-catalog/kogito-cloud-operator/0.6.0/app.kiegroup.org_kogitoapps_crd.yaml @@ -168,6 +168,26 @@ spec: type: string type: object type: array + infra: + description: Infrastructure definition + properties: + installInfinispan: + description: 'By default Kogito Operator installs an Infinispan + instance in the namespace if the service needs persistence (''Auto''). + Set to ''Never'' to disable this behavior, e.g. if the service + will use another persistence mechanism. Set to ''Always'' to always + install Infinispan, even if the service won''t need persistence. + For Quarkus runtime, it sets QUARKUS_INFINISPAN_CLIENT_* environment + variables. For Spring Boot, these variables start with SPRING_INFINISPAN_CLIENT_*. + More info: https://github.com/kiegroup/kogito-cloud-operator#infinispan-environment-variables. + Default to false, which means it installs Infinispan if the service + requires persistence.' + enum: + - Always + - Never + - Auto + type: string + type: object replicas: description: 'Number of replicas that the service will have deployed in the cluster Default value: 1' diff --git a/deploy/olm-catalog/kogito-cloud-operator/0.6.0/kogito-cloud-operator.v0.6.0.clusterserviceversion.yaml b/deploy/olm-catalog/kogito-cloud-operator/0.6.0/kogito-cloud-operator.v0.6.0.clusterserviceversion.yaml index c0b55b7ad..ba2671cce 100644 --- a/deploy/olm-catalog/kogito-cloud-operator/0.6.0/kogito-cloud-operator.v0.6.0.clusterserviceversion.yaml +++ b/deploy/olm-catalog/kogito-cloud-operator/0.6.0/kogito-cloud-operator.v0.6.0.clusterserviceversion.yaml @@ -110,6 +110,9 @@ spec: path: route x-descriptors: - urn:alm:descriptor:org.w3:link + - description: Infinispan Installation Mode + displayName: Infinispan Installation Mode + path: infra.installInfinispan - description: Conditions History displayName: Conditions path: conditions diff --git a/pkg/apis/app/v1alpha1/kogitoapp_types.go b/pkg/apis/app/v1alpha1/kogitoapp_types.go index 0e2ac5178..ce7c40c5f 100644 --- a/pkg/apis/app/v1alpha1/kogitoapp_types.go +++ b/pkg/apis/app/v1alpha1/kogitoapp_types.go @@ -61,6 +61,9 @@ type KogitoAppSpec struct { // Kubernetes Service configuration // Default value: nil Service KogitoAppServiceObject `json:"service,omitempty"` + + // Infrastructure definition + Infra KogitoAppInfra `json:"infra,omitempty"` } // Resources Data to define Resources needed for each deployed pod @@ -187,7 +190,31 @@ type KogitoAppStatus struct { Builds Builds `json:"builds"` } -// RuntimeType is the type of condition +// KogitoAppInfraInstallInfinispanType defines the Infinispan installation mode +type KogitoAppInfraInstallInfinispanType string + +const ( + // KogitoAppInfraInstallInfinispanAlways - Always installs Infinispan + KogitoAppInfraInstallInfinispanAlways KogitoAppInfraInstallInfinispanType = "Always" + // KogitoAppInfraInstallInfinispanNever - Never installs Infinispan + KogitoAppInfraInstallInfinispanNever KogitoAppInfraInstallInfinispanType = "Never" + // KogitoAppInfraInstallInfinispanAuto - The Operator will try to discover if the service needs persistence by scanning the runtime image metadata + KogitoAppInfraInstallInfinispanAuto KogitoAppInfraInstallInfinispanType = "Auto" +) + +// KogitoAppInfra defines details regarding the Kogito Infrastructure to support the deployed Kogito Service +type KogitoAppInfra struct { + // By default Kogito Operator installs an Infinispan instance in the namespace if the service needs persistence ('Auto'). + // Set to 'Never' to disable this behavior, e.g. if the service will use another persistence mechanism. + // Set to 'Always' to always install Infinispan, even if the service won't need persistence. + // For Quarkus runtime, it sets QUARKUS_INFINISPAN_CLIENT_* environment variables. For Spring Boot, these variables start with SPRING_INFINISPAN_CLIENT_*. + // More info: https://github.com/kiegroup/kogito-cloud-operator#infinispan-environment-variables. + // Default to false, which means it installs Infinispan if the service requires persistence. + // +kubebuilder:validation:Enum=Always;Never;Auto + InstallInfinispan KogitoAppInfraInstallInfinispanType `json:"installInfinispan,omitempty"` +} + +// RuntimeType - type of condition type RuntimeType string const ( @@ -238,6 +265,8 @@ const ( BuildS2IFailedReason ReasonType = "BuildS2IFailedReason" // BuildRuntimeFailedReason - Unable to build the runtime image BuildRuntimeFailedReason ReasonType = "BuildRuntimeFailedReason" + // DeployKogitoInfraFailedReason - Unable to deploy Kogito Infra + DeployKogitoInfraFailedReason ReasonType = "DeployKogitoInfraFailedReason" // UnknownReason - Unable to determine the error UnknownReason ReasonType = "Unknown" ) diff --git a/pkg/apis/app/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/app/v1alpha1/zz_generated.deepcopy.go index fbd5bc986..d2c4f87fb 100644 --- a/pkg/apis/app/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/app/v1alpha1/zz_generated.deepcopy.go @@ -331,6 +331,22 @@ func (in *KogitoAppBuildObject) DeepCopy() *KogitoAppBuildObject { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KogitoAppInfra) DeepCopyInto(out *KogitoAppInfra) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KogitoAppInfra. +func (in *KogitoAppInfra) DeepCopy() *KogitoAppInfra { + if in == nil { + return nil + } + out := new(KogitoAppInfra) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KogitoAppList) DeepCopyInto(out *KogitoAppList) { *out = *in @@ -407,6 +423,7 @@ func (in *KogitoAppSpec) DeepCopyInto(out *KogitoAppSpec) { (*in).DeepCopyInto(*out) } in.Service.DeepCopyInto(&out.Service) + out.Infra = in.Infra return } diff --git a/pkg/apis/app/v1alpha1/zz_generated.openapi.go b/pkg/apis/app/v1alpha1/zz_generated.openapi.go index 1e5c0db72..a0fe19345 100644 --- a/pkg/apis/app/v1alpha1/zz_generated.openapi.go +++ b/pkg/apis/app/v1alpha1/zz_generated.openapi.go @@ -663,12 +663,18 @@ func schema_pkg_apis_app_v1alpha1_KogitoAppSpec(ref common.ReferenceCallback) co Ref: ref("github.com/kiegroup/kogito-cloud-operator/pkg/apis/app/v1alpha1.KogitoAppServiceObject"), }, }, + "infra": { + SchemaProps: spec.SchemaProps{ + Description: "Infrastructure definition", + Ref: ref("github.com/kiegroup/kogito-cloud-operator/pkg/apis/app/v1alpha1.KogitoAppInfra"), + }, + }, }, Required: []string{"build"}, }, }, Dependencies: []string{ - "github.com/kiegroup/kogito-cloud-operator/pkg/apis/app/v1alpha1.Env", "github.com/kiegroup/kogito-cloud-operator/pkg/apis/app/v1alpha1.KogitoAppBuildObject", "github.com/kiegroup/kogito-cloud-operator/pkg/apis/app/v1alpha1.KogitoAppServiceObject", "github.com/kiegroup/kogito-cloud-operator/pkg/apis/app/v1alpha1.Resources"}, + "github.com/kiegroup/kogito-cloud-operator/pkg/apis/app/v1alpha1.Env", "github.com/kiegroup/kogito-cloud-operator/pkg/apis/app/v1alpha1.KogitoAppBuildObject", "github.com/kiegroup/kogito-cloud-operator/pkg/apis/app/v1alpha1.KogitoAppInfra", "github.com/kiegroup/kogito-cloud-operator/pkg/apis/app/v1alpha1.KogitoAppServiceObject", "github.com/kiegroup/kogito-cloud-operator/pkg/apis/app/v1alpha1.Resources"}, } } diff --git a/pkg/controller/kogitoapp/kogitoapp_controller.go b/pkg/controller/kogitoapp/kogitoapp_controller.go index a85b13b33..8e00f5b54 100644 --- a/pkg/controller/kogitoapp/kogitoapp_controller.go +++ b/pkg/controller/kogitoapp/kogitoapp_controller.go @@ -17,6 +17,7 @@ package kogitoapp import ( "fmt" "github.com/kiegroup/kogito-cloud-operator/pkg/infrastructure" + "github.com/openshift/api/image/docker10" "reflect" "time" @@ -135,6 +136,10 @@ func (r *ReconcileKogitoApp) Reconcile(request reconcile.Request) (result reconc instance.Spec.Runtime = v1alpha1.QuarkusRuntimeType } + if &instance.Spec.Infra == nil || len(instance.Spec.Infra.InstallInfinispan) == 0 { + instance.Spec.Infra = v1alpha1.KogitoAppInfra{InstallInfinispan: v1alpha1.KogitoAppInfraInstallInfinispanAuto} + } + requeue, err := r.ensureKogitoImageStream(instance) if err != nil { return reconcile.Result{}, err @@ -167,6 +172,18 @@ func (r *ReconcileKogitoApp) Reconcile(request reconcile.Request) (result reconc return } + requeue, err = r.ensureKogitoInfra(instance, kogitoResources.RuntimeImage, kogitoResources.DeploymentConfig) + if err != nil { + updateResourceResult.Err = err + updateResourceResult.ErrorReason = v1alpha1.DeployKogitoInfraFailedReason + return + } + if requeue { + result.Requeue = true + result.RequeueAfter = 5 * time.Second + return + } + deployedRes, err := kogitores.GetDeployedResources(instance, r.client) if err != nil { updateResourceResult.Err = err @@ -377,3 +394,29 @@ func (r *ReconcileKogitoApp) ensureKogitoImageStream(instance *v1alpha1.KogitoAp } return false, nil } + +func (r *ReconcileKogitoApp) ensureKogitoInfra(instance *v1alpha1.KogitoApp, runtimeImage *docker10.DockerImage, requestedDeployment *oappsv1.DeploymentConfig) (requeue bool, err error) { + log.Debug("Verify if we need to deploy Infinispan") + if instance.Spec.Infra.InstallInfinispan == v1alpha1.KogitoAppInfraInstallInfinispanAlways || + (instance.Spec.Infra.InstallInfinispan == v1alpha1.KogitoAppInfraInstallInfinispanAuto && resource.IsPersistenceEnabled(runtimeImage)) { + infra, created, ready, err := infrastructure.EnsureInfinispanWithKogitoInfra(instance.Namespace, r.client) + if err != nil { + return true, err + } + if created { + // since we just created a new Infra instance, let's wait for it to provision everything before proceeding + log.Debug("Returning to reconcile phase to give some time for the Infinispan Operator to deploy") + return true, nil + } + if ready { + if err := kogitores.SetInfinispanEnvVars(r.client, infra, instance, requestedDeployment); err != nil { + return true, err + } + log.Debug("KogitoInfra is ready, proceed!") + return false, nil + } + log.Debug("KogitoInfra is not ready, requeue") + return true, nil + } + return false, nil +} diff --git a/pkg/controller/kogitoapp/kogitoapp_controller_test.go b/pkg/controller/kogitoapp/kogitoapp_controller_test.go index ce02a2c5a..b4958ede3 100644 --- a/pkg/controller/kogitoapp/kogitoapp_controller_test.go +++ b/pkg/controller/kogitoapp/kogitoapp_controller_test.go @@ -18,6 +18,8 @@ import ( "context" "encoding/json" "fmt" + "github.com/kiegroup/kogito-cloud-operator/pkg/infrastructure" + utilres "github.com/kiegroup/kogito-cloud-operator/pkg/resource" "reflect" "testing" "time" @@ -74,19 +76,6 @@ var ( } ) -func TestNewContainerWithResource(t *testing.T) { - container := corev1.Container{ - Name: cr.Name, - Env: shared.FromEnvToEnvVar(cr.Spec.Env), - Resources: shared.FromResourcesToResourcesRequirements(cr.Spec.Resources), - ImagePullPolicy: corev1.PullAlways, - } - assert.NotNil(t, container) - cpuQty := resource.MustParse(cpuValue) - assert.Equal(t, container.Resources.Limits.Cpu(), &cpuQty) - assert.Equal(t, container.Resources.Requests.Cpu(), &resource.Quantity{Format: resource.DecimalSI}) -} - func createFakeKogitoApp() *v1alpha1.KogitoApp { gitURL := "https://github.com/kiegroup/kogito-examples/" kogitoapp := &cr @@ -106,12 +95,14 @@ func createFakeKogitoApp() *v1alpha1.KogitoApp { return kogitoapp } -func createFakeImages(kogitoAppName string) []runtime.Object { +func createFakeImages(kogitoAppName string, runtimeLabels map[string]string) []runtime.Object { + if nil == runtimeLabels { + runtimeLabels = map[string]string{} + runtimeLabels[openshift.ImageLabelForExposeServices] = "8080:http" + } dockerImageRaw, _ := json.Marshal(&dockerv10.DockerImage{ Config: &dockerv10.DockerConfig{ - Labels: map[string]string{ - openshift.ImageLabelForExposeServices: "8080:http", - }, + Labels: runtimeLabels, }, }) @@ -209,11 +200,23 @@ func createFakeImages(kogitoAppName string) []runtime.Object { return []runtime.Object{&isTag, &isTagBuild, &image1, &image2, &image3, &image4, &image5, &image6} } +func TestNewContainerWithResource(t *testing.T) { + container := corev1.Container{ + Name: cr.Name, + Env: shared.FromEnvToEnvVar(cr.Spec.Env), + Resources: shared.FromResourcesToResourcesRequirements(cr.Spec.Resources), + ImagePullPolicy: corev1.PullAlways, + } + assert.NotNil(t, container) + cpuQty := resource.MustParse(cpuValue) + assert.Equal(t, container.Resources.Limits.Cpu(), &cpuQty) + assert.Equal(t, container.Resources.Requests.Cpu(), &resource.Quantity{Format: resource.DecimalSI}) +} + func TestKogitoAppWithResource(t *testing.T) { kogitoapp := createFakeKogitoApp() - images := createFakeImages(kogitoapp.Name) + images := createFakeImages(kogitoapp.Name, nil) objs := []runtime.Object{kogitoapp} - s := meta.GetRegisteredSchema() fakeClient := test.CreateFakeClient(objs, images, []runtime.Object{}) // ********** sanity check @@ -224,12 +227,12 @@ func TestKogitoAppWithResource(t *testing.T) { } assert.True(t, len(kogitoAppList.Items) > 0) assert.True(t, kogitoAppList.Items[0].Spec.Resources.Limits[0].Resource == cpuResource) - cachefake := &cachev1.FakeInformers{} + fakeCache := &cachev1.FakeInformers{} // call reconcile object and mock image and build clients r := &ReconcileKogitoApp{ client: fakeClient, - scheme: s, - cache: cachefake, + scheme: meta.GetRegisteredSchema(), + cache: fakeCache, } req := reconcile.Request{ NamespacedName: types.NamespacedName{ @@ -350,7 +353,7 @@ func TestReconcileKogitoApp_updateKogitoAppStatus(t *testing.T) { } runtimeObjs := []runtime.Object{kogitoapp, route} - imageObjs := createFakeImages(kogitoapp.Name) + imageObjs := createFakeImages(kogitoapp.Name, nil) buildObjs := []runtime.Object{buildconfigRuntime, buildconfigS2I, buildList} clientfake := test.CreateFakeClient(runtimeObjs, imageObjs, buildObjs) @@ -481,6 +484,43 @@ func TestReconcileKogitoApp_updateKogitoAppStatus(t *testing.T) { } } +func TestReconcileKogitoApp_PersistenceEnabledWithInfra(t *testing.T) { + kogitoApp := createFakeKogitoApp() + imgs := createFakeImages(kogitoApp.Name, map[string]string{utilres.LabelKeyOrgKiePersistenceRequired: "true"}) + kogitoApp.Spec.Infra = v1alpha1.KogitoAppInfra{} + fakeClient := test.CreateFakeClient([]runtime.Object{kogitoApp}, imgs, nil) + fakeCache := &cachev1.FakeInformers{} + // call reconcile object and mock image and build clients + r := &ReconcileKogitoApp{ + client: fakeClient, + scheme: meta.GetRegisteredSchema(), + cache: fakeCache, + } + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: kogitoApp.Name, + Namespace: kogitoApp.Namespace, + }, + } + // first reconcile + result, err := r.Reconcile(req) + assert.NoError(t, err) + assert.NotNil(t, result) + // requeue for kogitoInfra + assert.True(t, result.Requeue) + + kogitoInfra, created, ready, err := infrastructure.EnsureInfinispanWithKogitoInfra(kogitoApp.Namespace, fakeClient) + assert.NoError(t, err) + assert.False(t, created) // created in reconciliation phase + assert.False(t, ready) // not ready, we don't have status + assert.NotNil(t, kogitoInfra) // must exist a infra + + dc := &appsv1.DeploymentConfig{ObjectMeta: metav1.ObjectMeta{Name: kogitoApp.Name, Namespace: kogitoApp.Namespace}} + exists, err := kubernetes.ResourceC(fakeClient).Fetch(dc) + assert.NoError(t, err) + assert.False(t, exists) // we don't have a dc yet because Infinispan is not ready +} + type mockCache struct { cache.Cache kogitoApp *v1alpha1.KogitoApp diff --git a/pkg/controller/kogitoapp/resource/deployment_config.go b/pkg/controller/kogitoapp/resource/deployment_config.go index ed516f917..56dc02392 100644 --- a/pkg/controller/kogitoapp/resource/deployment_config.go +++ b/pkg/controller/kogitoapp/resource/deployment_config.go @@ -17,10 +17,12 @@ package resource import ( "fmt" "github.com/kiegroup/kogito-cloud-operator/pkg/apis/app/v1alpha1" + "github.com/kiegroup/kogito-cloud-operator/pkg/client" "github.com/kiegroup/kogito-cloud-operator/pkg/client/meta" "github.com/kiegroup/kogito-cloud-operator/pkg/controller/kogitoapp/shared" + "github.com/kiegroup/kogito-cloud-operator/pkg/infrastructure" "github.com/kiegroup/kogito-cloud-operator/pkg/resource" - + "github.com/kiegroup/kogito-cloud-operator/pkg/util" appsv1 "github.com/openshift/api/apps/v1" buildv1 "github.com/openshift/api/build/v1" dockerv10 "github.com/openshift/api/image/docker10" @@ -32,6 +34,33 @@ const ( defaultReplicas = int32(1) // ServiceAccountName is the name of service account used by Kogito Services Runtimes ServiceAccountName = "kogito-service-viewer" + + envVarInfinispanServerList = "SERVER_LIST" + envVarInfinispanUser = "USERNAME" + envVarInfinispanPassword = "PASSWORD" + envVarInfinispanSaslMechanism = "SASL_MECHANISM" + defaultInfinispanSaslMechanism = v1alpha1.SASLPlain +) + +var ( + /* + Infinispan variables for the KogitoInfra deployed infrastructure. + For Quarkus: https://quarkus.io/guides/infinispan-client#quarkus-infinispan-client_configuration + For Spring: https://github.com/infinispan/infinispan-spring-boot/blob/master/infinispan-spring-boot-starter-remote/src/test/resources/test-application.properties + */ + + envVarInfinispanQuarkus = map[string]string{ + envVarInfinispanServerList: "QUARKUS_INFINISPAN_CLIENT_SERVER_LIST", + envVarInfinispanUser: "QUARKUS_INFINISPAN_CLIENT_AUTH_USERNAME", + envVarInfinispanPassword: "QUARKUS_INFINISPAN_CLIENT_AUTH_PASSWORD", + envVarInfinispanSaslMechanism: "QUARKUS_INFINISPAN_CLIENT_SASL_MECHANISM", + } + envVarInfinispanSpring = map[string]string{ + envVarInfinispanServerList: "INFINISPAN_REMOTE_SERVER_LIST", + envVarInfinispanUser: "INFINISPAN_REMOTE_AUTH_USER_NAME", + envVarInfinispanPassword: "INFINISPAN_REMOTE_AUTH_PASSWORD", + envVarInfinispanSaslMechanism: "INFINISPAN_REMOTE_SASL_MECHANISM", + } ) // NewDeploymentConfig creates a new DeploymentConfig resource for the KogitoApp based on the BuildConfig runner image @@ -98,3 +127,31 @@ func setReplicas(kogitoApp *v1alpha1.KogitoApp, dc *appsv1.DeploymentConfig) { } dc.Spec.Replicas = replicas } + +// SetInfinispanEnvVars sets Infinispan variables to the given KogitoApp instance DeploymentConfig by reading information from the KogitoInfra +func SetInfinispanEnvVars(cli *client.Client, kogitoInfra *v1alpha1.KogitoInfra, kogitoApp *v1alpha1.KogitoApp, dc *appsv1.DeploymentConfig) error { + if dc != nil && kogitoApp != nil && + (kogitoInfra != nil && &kogitoInfra.Status != nil && &kogitoInfra.Status.Infinispan != nil) { + uri, err := infrastructure.GetInfinispanServiceURI(cli, kogitoInfra) + if err != nil { + return err + } + user, password, err := infrastructure.GetInfinispanCredentials(cli, kogitoInfra) + if err != nil { + return err + } + + // inject credentials to deploymentConfig container + if len(dc.Spec.Template.Spec.Containers) > 0 { + vars := envVarInfinispanQuarkus + if kogitoApp.Spec.Runtime == v1alpha1.SpringbootRuntimeType { + vars = envVarInfinispanSpring + } + util.SetEnvVar(vars[envVarInfinispanServerList], uri, &dc.Spec.Template.Spec.Containers[0]) + util.SetEnvVar(vars[envVarInfinispanUser], user, &dc.Spec.Template.Spec.Containers[0]) + util.SetEnvVar(vars[envVarInfinispanPassword], password, &dc.Spec.Template.Spec.Containers[0]) + util.SetEnvVar(vars[envVarInfinispanSaslMechanism], string(defaultInfinispanSaslMechanism), &dc.Spec.Template.Spec.Containers[0]) + } + } + return nil +} diff --git a/pkg/controller/kogitoapp/resource/requested_resources.go b/pkg/controller/kogitoapp/resource/requested_resources.go index a02c09f23..6ce2c66de 100644 --- a/pkg/controller/kogitoapp/resource/requested_resources.go +++ b/pkg/controller/kogitoapp/resource/requested_resources.go @@ -56,7 +56,7 @@ func GetRequestedResources(context *Context) (*KogitoAppResources, error) { AndBuild(deploymentConfigBuilder). AndBuild(serviceBuilder). AndBuild(routeBuilder). - AndBuild(servicemonitorBuilder) + AndBuild(serviceMonitorBuilder) return chain.Resources, chain.Error } @@ -142,7 +142,7 @@ func routeBuilder(chain *builderChain) *builderChain { return chain } -func servicemonitorBuilder(chain *builderChain) *builderChain { +func serviceMonitorBuilder(chain *builderChain) *builderChain { if chain.Resources.RuntimeImage != nil && chain.Resources.Service != nil { sm, err := NewServiceMonitor(chain.Context.KogitoApp, chain.Resources.RuntimeImage, chain.Resources.Service, chain.Context.Client) if err != nil { diff --git a/pkg/controller/kogitodataindex/kogitodataindex_controller.go b/pkg/controller/kogitodataindex/kogitodataindex_controller.go index 8a185881e..083162572 100644 --- a/pkg/controller/kogitodataindex/kogitodataindex_controller.go +++ b/pkg/controller/kogitodataindex/kogitodataindex_controller.go @@ -192,7 +192,7 @@ func (r *ReconcileKogitoDataIndex) ensureKogitoInfra(instance *appv1alpha1.Kogit // Overrides any parameters not set if instance.Spec.Infinispan.UseKogitoInfra { // ensure infra - infra, created, err := infrastructure.CreateOrFetchInfra(instance.Namespace, r.client) + infra, created, ready, err := infrastructure.EnsureInfinispanWithKogitoInfra(instance.Namespace, r.client) if err != nil { return &reconcile.Result{}, err } @@ -202,7 +202,8 @@ func (r *ReconcileKogitoDataIndex) ensureKogitoInfra(instance *appv1alpha1.Kogit return &reconcile.Result{RequeueAfter: time.Second * 10}, nil } - // we're good? + // We do this check to not have to update the Data Index instance every time with the same data. + // Should be removed once we solve this: https://issues.jboss.org/browse/KOGITO-601 if instance.Spec.Infinispan.ServiceURI == infra.Status.Infinispan.Service && instance.Spec.Infinispan.Credentials.SecretName == infra.Status.Infinispan.CredentialSecret && instance.Spec.Infinispan.Credentials.PasswordKey == infinispan.SecretPasswordKey && @@ -211,7 +212,7 @@ func (r *ReconcileKogitoDataIndex) ensureKogitoInfra(instance *appv1alpha1.Kogit } log.Debugf("Checking KogitoInfra status to make sure we are ready to use Infinispan. Status are: %s", infra.Status.Infinispan) - if infrastructure.IsInfinispanDeployed(infra) { + if ready { log.Debug("Looks ok, we are ready to use Infinispan!") instance.Spec.Infinispan.ServiceURI = infra.Status.Infinispan.Service instance.Spec.Infinispan.Credentials.SecretName = infra.Status.Infinispan.CredentialSecret diff --git a/pkg/controller/kogitodataindex/kogitodataindex_controller_test.go b/pkg/controller/kogitodataindex/kogitodataindex_controller_test.go index a4584978f..36d8fbc02 100644 --- a/pkg/controller/kogitodataindex/kogitodataindex_controller_test.go +++ b/pkg/controller/kogitodataindex/kogitodataindex_controller_test.go @@ -63,9 +63,10 @@ func TestReconcileKogitoDataIndex_Reconcile(t *testing.T) { } // check infra - infra, created, err := infrastructure.CreateOrFetchInfra(ns, client) + infra, created, ready, err := infrastructure.EnsureInfinispanWithKogitoInfra(ns, client) assert.NoError(t, err) - assert.False(t, created) - assert.NotNil(t, infra) + assert.False(t, created) // the created = true were returned when the infra was created during the reconcile phase + assert.False(t, ready) // we don't have status defined since the KogitoInfra controller is not running + assert.NotNil(t, infra) // we have a infra instance created during reconciliation phase assert.Equal(t, infrastructure.DefaultKogitoInfraName, infra.GetName()) } diff --git a/pkg/infrastructure/infinispan.go b/pkg/infrastructure/infinispan.go index 5c808bb42..6bea6088e 100644 --- a/pkg/infrastructure/infinispan.go +++ b/pkg/infrastructure/infinispan.go @@ -15,10 +15,14 @@ package infrastructure import ( + "fmt" + "github.com/kiegroup/kogito-cloud-operator/pkg/apis/app/v1alpha1" "github.com/kiegroup/kogito-cloud-operator/pkg/client" "github.com/kiegroup/kogito-cloud-operator/pkg/client/kubernetes" + "github.com/kiegroup/kogito-cloud-operator/pkg/controller/kogitoinfra/infinispan" "k8s.io/api/apps/v1" - v12 "k8s.io/apimachinery/pkg/apis/meta/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const ( @@ -33,7 +37,7 @@ func IsInfinispanOperatorAvailable(cli *client.Client, namespace string) (bool, if cli.HasServerGroup(infinispanServerGroup) { log.Debugf("Infinispan CRDs available. Checking if Infinispan Operator is deployed in the namespace %s", namespace) // then check if there's a Infinispan Operator deployed - deployment := &v1.Deployment{ObjectMeta: v12.ObjectMeta{Namespace: namespace, Name: operatorName}} + deployment := &v1.Deployment{ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: operatorName}} exists := false var err error if exists, err = kubernetes.ResourceC(cli).Fetch(deployment); err != nil { @@ -49,3 +53,49 @@ func IsInfinispanOperatorAvailable(cli *client.Client, namespace string) (bool, log.Debugf("Looks like Infinispan Operator is not available in the namespace %s", namespace) return false, nil } + +// GetInfinispanServiceURI fetches for the Infinispan service linked with the given KogitoInfra and returns a formatted URI +func GetInfinispanServiceURI(cli *client.Client, infra *v1alpha1.KogitoInfra) (uri string, err error) { + if &infra == nil || &infra.Status == nil || &infra.Status.Infinispan == nil { + return "", nil + } + + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: infra.Status.Infinispan.Service, Namespace: infra.Namespace}, + } + exists := false + if exists, err = kubernetes.ResourceC(cli).Fetch(service); err != nil { + return "", err + } + + if exists && len(service.Spec.Ports) > 0 { + return fmt.Sprintf("%s:%d", service.Name, service.Spec.Ports[0].TargetPort.IntVal), nil + } + + return "", nil +} + +// GetInfinispanCredentials fetches the Infinispan secret credentials managed by the given KogitoInfra and returns the credentials stored +func GetInfinispanCredentials(cli *client.Client, infra *v1alpha1.KogitoInfra) (user, password string, err error) { + user = "" + password = "" + err = nil + if &infra == nil || &infra.Status == nil || &infra.Status.Infinispan == nil { + return + } + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: infra.Status.Infinispan.CredentialSecret, Namespace: infra.Namespace}, + } + exists := false + if exists, err = kubernetes.ResourceC(cli).Fetch(secret); err != nil { + return + } + + if exists { + user = string(secret.Data[infinispan.SecretUsernameKey]) + password = string(secret.Data[infinispan.SecretPasswordKey]) + } + + return +} diff --git a/pkg/infrastructure/kogitoinfra.go b/pkg/infrastructure/kogitoinfra.go index 7c538408b..1959f8bb8 100644 --- a/pkg/infrastructure/kogitoinfra.go +++ b/pkg/infrastructure/kogitoinfra.go @@ -28,9 +28,30 @@ const ( DefaultKogitoInfraName = "kogito-infra" ) -// CreateOrFetchInfra will fetch for any reference of KogitoInfra in the given namespace. +// EnsureInfinispanWithKogitoInfra creates a new instance of KogitoInfra if not exists with an Infinispan deployed if not exists. If exists, checks if Infinispan is deployed. +func EnsureInfinispanWithKogitoInfra(namespace string, cli *client.Client) (infra *v1alpha1.KogitoInfra, created, ready bool, err error) { + ready = false + infra, created, err = createOrFetchInfra(namespace, cli) + if err != nil { + return + } + if created { + // since we just created a new Infra instance, let's wait for it to provision everything before proceeding + return + } + if !infra.Spec.InstallInfinispan { + infra.Spec.InstallInfinispan = true + err = kubernetes.ResourceC(cli).Update(infra) + ready = false + return + } + ready = isInfinispanDeployed(infra) + return +} + +// createOrFetchInfra will fetch for any reference of KogitoInfra in the given namespace. // If not exists, a new one with Infinispan enabled will be created and returned -func CreateOrFetchInfra(namespace string, cli *client.Client) (infra *v1alpha1.KogitoInfra, created bool, err error) { +func createOrFetchInfra(namespace string, cli *client.Client) (infra *v1alpha1.KogitoInfra, created bool, err error) { log := logger.GetLogger("infrastructure_kogitoinfra") log.Debug("Fetching for KogitoInfra list in namespace") // let's look for the deployed infra @@ -56,8 +77,8 @@ func CreateOrFetchInfra(namespace string, cli *client.Client) (infra *v1alpha1.K return infra, true, nil } -// IsInfinispanDeployed will verify if the given KogitoInfra has Infinispan deployed -func IsInfinispanDeployed(infra *v1alpha1.KogitoInfra) bool { +// isInfinispanDeployed will verify if the given KogitoInfra has Infinispan deployed +func isInfinispanDeployed(infra *v1alpha1.KogitoInfra) bool { if &infra.Status != nil && &infra.Status.Infinispan != nil && len(infra.Status.Infinispan.Condition) > 0 { diff --git a/pkg/infrastructure/kogitoinfra_test.go b/pkg/infrastructure/kogitoinfra_test.go index ee9c104d0..6dc33300e 100644 --- a/pkg/infrastructure/kogitoinfra_test.go +++ b/pkg/infrastructure/kogitoinfra_test.go @@ -26,7 +26,7 @@ import ( func Test_CreateOrFetchKogitoInfra_NotExists(t *testing.T) { ns := t.Name() cli := test.CreateFakeClient(nil, nil, nil) - infra, created, err := CreateOrFetchInfra(ns, cli) + infra, created, err := createOrFetchInfra(ns, cli) assert.NoError(t, err) assert.NotNil(t, infra) assert.True(t, created) @@ -40,7 +40,7 @@ func Test_CreateOrFetchKogitoInfra_Exists(t *testing.T) { Spec: v1alpha1.KogitoInfraSpec{InstallInfinispan: false}, } cli := test.CreateFakeClient([]runtime.Object{infra}, nil, nil) - infra, created, err := CreateOrFetchInfra(ns, cli) + infra, created, err := createOrFetchInfra(ns, cli) assert.NoError(t, err) assert.NotNil(t, infra) assert.False(t, created) diff --git a/pkg/resource/image_metadata.go b/pkg/resource/image_metadata.go index f6b77c0a4..aa9932073 100644 --- a/pkg/resource/image_metadata.go +++ b/pkg/resource/image_metadata.go @@ -34,15 +34,17 @@ import ( const ( // DefaultExportedPort is the default protocol exposed by inner services specified in image metadata DefaultExportedPort = "http" - // LabelKeyPrometheus is the label key for Prometheus metadata - LabelKeyPrometheus = "prometheus.io" // LabelKeyOrgKie is the label key for KIE metadata LabelKeyOrgKie = "org.kie" + labelNamespaceSep // LabelKeyOrgKiePersistence is the label key for Persistence metadata - LabelKeyOrgKiePersistence = "org.kie" + labelNamespaceSep + "persistence" + LabelKeyOrgKiePersistence = LabelKeyOrgKie + "persistence" + // LabelKeyOrgKiePersistenceRequired is the label key to check if persistence is enabled or not + LabelKeyOrgKiePersistenceRequired = LabelKeyOrgKiePersistence + labelNamespaceSep + "required" // LabelKeyOrgKieProtoBuf is the label key for ProtoBuf metadata - LabelKeyOrgKieProtoBuf = "org.kie" + labelNamespaceSep + "persistence" + labelNamespaceSep + "proto" + LabelKeyOrgKieProtoBuf = LabelKeyOrgKiePersistence + labelNamespaceSep + "proto" + // LabelKeyPrometheus is the label key for Prometheus metadata + LabelKeyPrometheus = "prometheus.io" // LabelPrometheusScrape is the label key for prometheus scrape configuration LabelPrometheusScrape = LabelKeyPrometheus + "/scrape" // LabelPrometheusPath is the label key for prometheus metrics path @@ -98,22 +100,24 @@ func decompressBase64GZip(contents string) (string, error) { var reader *gzip.Reader defer func() { if reader != nil { - reader.Close() + if err := reader.Close(); err != nil { + log.Errorf("Error closing gzip reader ", err) + } } }() if decode, err = base64.StdEncoding.DecodeString(contents); err != nil { - return "", fmt.Errorf("Error while converting contents from base64: %s", err) + return "", fmt.Errorf("Error while converting contents from base64: %s ", err) } if reader, err = gzip.NewReader(bytes.NewReader(decode)); err != nil { // the file might not being compressed, we should support old versions where the labels are not compressed - err = fmt.Errorf("Error while decompressing contents: %s", err) + err = fmt.Errorf("Error while decompressing contents: %s ", err) if strings.Contains(err.Error(), "invalid header") { return string(decode), err } return "", err } if decode, err = ioutil.ReadAll(reader); err != nil { - return "", fmt.Errorf("Error while reading contents after decompressing: %s", err) + return "", fmt.Errorf("Error while reading contents after decompressing: %s ", err) } return string(decode), nil } @@ -238,3 +242,15 @@ func ExtractPrometheusConfigurationFromImage(dockerImage *dockerv10.DockerImage) return } + +// IsPersistenceEnabled verifies if the image has labels indicating that persistence is enabled +func IsPersistenceEnabled(dockerImage *dockerv10.DockerImage) (enabled bool) { + if !dockerImageHasLabels(dockerImage) { + return false + } + var err error + if enabled, err = strconv.ParseBool(dockerImage.Config.Labels[LabelKeyOrgKiePersistenceRequired]); err != nil { + return false + } + return enabled +} diff --git a/pkg/resource/image_metadata_test.go b/pkg/resource/image_metadata_test.go index ffb480c34..3b30faabb 100644 --- a/pkg/resource/image_metadata_test.go +++ b/pkg/resource/image_metadata_test.go @@ -349,3 +349,46 @@ func Test_decompressBase64GZip(t *testing.T) { }) } } + +func TestIsPersistenceEnabled(t *testing.T) { + imageWithLabel := &dockerv10.DockerImage{ + Config: &dockerv10.DockerConfig{Labels: map[string]string{LabelKeyOrgKiePersistenceRequired: "true"}}, + } + imageWithoutLabel := &dockerv10.DockerImage{ + Config: &dockerv10.DockerConfig{Labels: map[string]string{}}, + } + imageWithEmptyLabel := &dockerv10.DockerImage{ + Config: &dockerv10.DockerConfig{Labels: map[string]string{LabelKeyOrgKiePersistenceRequired: ""}}, + } + type args struct { + dockerImage *dockerv10.DockerImage + } + tests := []struct { + name string + args args + want bool + }{ + { + "Image has label", + args{imageWithLabel}, + true, + }, + { + "Image hasn't label", + args{imageWithoutLabel}, + false, + }, + { + "Image has empty label", + args{imageWithEmptyLabel}, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsPersistenceEnabled(tt.args.dockerImage); got != tt.want { + t.Errorf("IsPersistenceEnabled() = %v, want %v", got, tt.want) + } + }) + } +}