From 318c0a5bc0b546041c8e74b293543af16be3edf9 Mon Sep 17 00:00:00 2001 From: CrowleyRajapakse Date: Thu, 29 Feb 2024 10:08:40 +0530 Subject: [PATCH] adding initial agent integration tests --- .github/workflows/agent-integration-test.yml | 129 +++++ .../agent-helm-chart/Chart.lock | 6 + .../agent-helm-chart/Chart.yaml | 24 + .../charts/cert-manager-v1.10.1.tgz | Bin 0 -> 65091 bytes .../apk-agent-server-certificate.yaml | 40 ++ .../issuers/apk-agent-root-certificate.yaml | 9 + .../issuers/self-signed-issuer.yaml | 22 + .../templates/deployment.yaml | 87 +++ .../agent-helm-chart/templates/log-conf.yaml | 59 ++ .../agent-helm-chart/templates/service.yaml | 31 ++ .../serviceAccount/agent-account-secret.yaml | 24 + .../agent-cluster-role-binding.yaml | 31 ++ .../serviceAccount/agent-cluster-role.yaml | 123 ++++ .../serviceAccount/agent-service-account.yaml | 21 + .../agent-helm-chart/values.yaml | 36 ++ .../apim-cp-helm-chart/Chart.yaml | 17 + .../apim-cp-helm-chart/README.md | 175 ++++++ .../apim-cp-helm-chart/confs/api-manager.sh | 362 ++++++++++++ .../confs/instance-1/deployment.toml | 283 ++++++++++ .../confs/instance-2/deployment.toml | 270 +++++++++ .../confs/log4j2.properties | 525 ++++++++++++++++++ .../confs/secret-conf.properties | 12 + .../templates/control-plane/_helpers.tpl | 69 +++ .../instance-1/wso2am-cp-conf.yaml | 18 + .../instance-1/wso2am-cp-deployment.yaml | 233 ++++++++ .../instance-1/wso2am-cp-service.yaml | 35 ++ .../instance-2/wso2am-cp-conf.yaml | 20 + .../instance-2/wso2am-cp-deployment.yaml | 235 ++++++++ .../instance-2/wso2am-cp-service.yaml | 37 ++ .../control-plane/wso2am-conf-script.yaml | 7 + .../wso2am-cp-conf-entrypoint.yaml | 71 +++ .../control-plane/wso2am-cp-conf-log4j2.yaml | 18 + .../wso2am-cp-conf-secret-conf.yaml | 19 + .../control-plane/wso2am-cp-ingress.yaml | 47 ++ .../control-plane/wso2am-cp-pdb.yaml | 20 + .../wso2am-cp-secret-store-provider-gcp.yaml | 24 + .../control-plane/wso2am-cp-service.yaml | 28 + .../wso2am-cp-storageclass-aws.yaml | 24 + .../wso2am-cp-storageclass-gcp.yaml | 23 + .../control-plane/wso2am-cp-volume-aws.yaml | 92 +++ .../control-plane/wso2am-cp-volume-azure.yaml | 36 ++ .../wso2am-cp-volume-claims-aws.yaml | 87 +++ .../wso2am-cp-volume-claims-azure.yaml | 87 +++ .../control-plane/wso2am-cp-volume-gcp.yaml | 105 ++++ .../wso2am-volume-claims-gcp.yaml | 85 +++ .../secrets/wso2am-cp-secret-store-csi.yaml | 22 + .../wso2am-cp-secret-store-provider-aws.yaml | 29 + ...wso2am-cp-secret-store-provider-azure.yaml | 35 ++ .../apim-cp-helm-chart/values.yaml | 448 +++++++++++++++ .../cucumber-tests/.gitattributes | 9 + .../cucumber-tests/.gitignore | 5 + .../cucumber-tests/CRs/artifacts.yaml | 2 + .../cucumber-tests/README.md | 91 +++ .../cucumber-tests/build.gradle | 57 ++ .../gradle/wrapper/gradle-wrapper.properties | 5 + .../cucumber-tests/gradlew | 240 ++++++++ .../cucumber-tests/gradlew.bat | 91 +++ .../cucumber-tests/scripts/setup-hosts.sh | 18 + .../cucumber-tests/settings.gradle | 10 + .../integration/APKIntegrationTestSuite.java | 24 + .../integration/api/APIDeploymentSteps.java | 228 ++++++++ .../integration/api/APKGenerationSteps.java | 78 +++ .../wso2/apk/integration/api/BaseSteps.java | 418 ++++++++++++++ .../integration/api/JWTGeneratorSteps.java | 113 ++++ .../integration/api/MTLSClientCertSteps.java | 73 +++ .../apk/integration/api/SharedContext.java | 151 +++++ .../wso2/apk/integration/utils/Constants.java | 56 ++ .../integration/utils/MultipartFilePart.java | 42 ++ .../org/wso2/apk/integration/utils/Utils.java | 199 +++++++ .../utils/clients/SimpleHTTPClient.java | 502 +++++++++++++++++ .../utils/exceptions/TimeoutException.java | 8 + .../artifacts/definitions/basic_auth_api.json | 225 ++++++++ .../test/resources/artifacts/jwtcert/idp1.jks | Bin 0 -> 2554 bytes .../resources/artifacts/payloads/api1.json | 19 + .../src/test/resources/testng.xml | 29 + .../resources/tests/api/Deployment.feature | 31 ++ 76 files changed, 6964 insertions(+) create mode 100644 .github/workflows/agent-integration-test.yml create mode 100644 test/apim-apk-agent-test/agent-helm-chart/Chart.lock create mode 100644 test/apim-apk-agent-test/agent-helm-chart/Chart.yaml create mode 100644 test/apim-apk-agent-test/agent-helm-chart/charts/cert-manager-v1.10.1.tgz create mode 100644 test/apim-apk-agent-test/agent-helm-chart/templates/cert-manager/certificates/apk-agent-server-certificate.yaml create mode 100644 test/apim-apk-agent-test/agent-helm-chart/templates/cert-manager/issuers/apk-agent-root-certificate.yaml create mode 100644 test/apim-apk-agent-test/agent-helm-chart/templates/cert-manager/issuers/self-signed-issuer.yaml create mode 100644 test/apim-apk-agent-test/agent-helm-chart/templates/deployment.yaml create mode 100644 test/apim-apk-agent-test/agent-helm-chart/templates/log-conf.yaml create mode 100644 test/apim-apk-agent-test/agent-helm-chart/templates/service.yaml create mode 100644 test/apim-apk-agent-test/agent-helm-chart/templates/serviceAccount/agent-account-secret.yaml create mode 100644 test/apim-apk-agent-test/agent-helm-chart/templates/serviceAccount/agent-cluster-role-binding.yaml create mode 100644 test/apim-apk-agent-test/agent-helm-chart/templates/serviceAccount/agent-cluster-role.yaml create mode 100644 test/apim-apk-agent-test/agent-helm-chart/templates/serviceAccount/agent-service-account.yaml create mode 100644 test/apim-apk-agent-test/agent-helm-chart/values.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/Chart.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/README.md create mode 100755 test/apim-apk-agent-test/apim-cp-helm-chart/confs/api-manager.sh create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/confs/instance-1/deployment.toml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/confs/instance-2/deployment.toml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/confs/log4j2.properties create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/confs/secret-conf.properties create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/_helpers.tpl create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/instance-1/wso2am-cp-conf.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/instance-1/wso2am-cp-deployment.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/instance-1/wso2am-cp-service.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/instance-2/wso2am-cp-conf.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/instance-2/wso2am-cp-deployment.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/instance-2/wso2am-cp-service.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-conf-script.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-conf-entrypoint.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-conf-log4j2.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-conf-secret-conf.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-ingress.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-pdb.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-secret-store-provider-gcp.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-service.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-storageclass-aws.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-storageclass-gcp.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-volume-aws.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-volume-azure.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-volume-claims-aws.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-volume-claims-azure.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-volume-gcp.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-volume-claims-gcp.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/secrets/wso2am-cp-secret-store-csi.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/secrets/wso2am-cp-secret-store-provider-aws.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/templates/secrets/wso2am-cp-secret-store-provider-azure.yaml create mode 100644 test/apim-apk-agent-test/apim-cp-helm-chart/values.yaml create mode 100644 test/apim-apk-agent-test/cucumber-tests/.gitattributes create mode 100644 test/apim-apk-agent-test/cucumber-tests/.gitignore create mode 100644 test/apim-apk-agent-test/cucumber-tests/CRs/artifacts.yaml create mode 100644 test/apim-apk-agent-test/cucumber-tests/README.md create mode 100644 test/apim-apk-agent-test/cucumber-tests/build.gradle create mode 100644 test/apim-apk-agent-test/cucumber-tests/gradle/wrapper/gradle-wrapper.properties create mode 100755 test/apim-apk-agent-test/cucumber-tests/gradlew create mode 100644 test/apim-apk-agent-test/cucumber-tests/gradlew.bat create mode 100644 test/apim-apk-agent-test/cucumber-tests/scripts/setup-hosts.sh create mode 100644 test/apim-apk-agent-test/cucumber-tests/settings.gradle create mode 100644 test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/APKIntegrationTestSuite.java create mode 100644 test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/APIDeploymentSteps.java create mode 100644 test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/APKGenerationSteps.java create mode 100644 test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BaseSteps.java create mode 100644 test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/JWTGeneratorSteps.java create mode 100644 test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/MTLSClientCertSteps.java create mode 100644 test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java create mode 100644 test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Constants.java create mode 100644 test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/MultipartFilePart.java create mode 100644 test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Utils.java create mode 100644 test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/SimpleHTTPClient.java create mode 100644 test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/exceptions/TimeoutException.java create mode 100644 test/apim-apk-agent-test/cucumber-tests/src/test/resources/artifacts/definitions/basic_auth_api.json create mode 100644 test/apim-apk-agent-test/cucumber-tests/src/test/resources/artifacts/jwtcert/idp1.jks create mode 100644 test/apim-apk-agent-test/cucumber-tests/src/test/resources/artifacts/payloads/api1.json create mode 100644 test/apim-apk-agent-test/cucumber-tests/src/test/resources/testng.xml create mode 100644 test/apim-apk-agent-test/cucumber-tests/src/test/resources/tests/api/Deployment.feature diff --git a/.github/workflows/agent-integration-test.yml b/.github/workflows/agent-integration-test.yml new file mode 100644 index 000000000..bdefdbf7e --- /dev/null +++ b/.github/workflows/agent-integration-test.yml @@ -0,0 +1,129 @@ +name: start and run agent cucumber integration tests +on: + workflow_dispatch: + pull_request_target: + types: [labeled] +concurrency: + group: integration-test-${{ github.event.number || github.run_id }} + cancel-in-progress: true +env: + GH_TOKEN: ${{ secrets.APK_BOT_TOKEN }} +jobs: + runs_agent_cucumber_integration_tests_on_pull_request: + if: github.event_name == 'pull_request_target' && contains(github.event.label.name, 'trigger-action') + runs-on: ubuntu-latest + steps: + - uses: azure/login@v1 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + - name: Create AKS Cluster and set context + uses: azure/CLI@v1 + with: + azcliversion: 2.44.1 + inlineScript: | + az aks create --resource-group "${{ secrets.AZURE_RESOURCE_GROUP }}" --name "agent-integ-${{ secrets.AKS_CLUSTER_NAME }}-${{ github.event.number || github.run_id }}" --enable-cluster-autoscaler --min-count 1 --max-count 3 --location "southeastasia" --generate-ssh-keys + - uses: azure/aks-set-context@v3 + with: + resource-group: '${{ secrets.AZURE_RESOURCE_GROUP }}' + cluster-name: 'go-integ-${{ secrets.AKS_CLUSTER_NAME }}-${{ github.event.number || github.run_id }}' + - name: Create Namespace apk + shell: sh + run: | + kubectl create namespace apk + kubectl get ns + - name: Checkout apk-repo. + uses: actions/checkout@v3 + with: + fetch-depth: "0" + path: apk-repo + token: ${{ secrets.APK_BOT_TOKEN }} + - name: Set release username and email + shell: sh + run: | + git config --global user.name ${{ secrets.APK_BOT_USER }} + git config --global user.email ${{ secrets.APK_BOT_EMAIL }} + + - name: checkout pull request and merge. + shell: sh + if: github.event_name == 'pull_request_target' && contains(github.event.label.name, 'trigger-action') + run: | + cd apk-repo + gh pr checkout ${{ github.event.number }} -b pr-${{ github.event.number }} + git checkout pr-${{ github.event.number }} + git merge origin/main + + - name: Helm release deploy APIM CP + if: github.event_name == 'pull_request_target' && contains(github.event.label.name, 'trigger-action') + shell: sh + run: | + cd apk-repo/test/apim-apk-agent-test/apim-cp-helm-chart + helm repo add bitnami https://charts.bitnami.com/bitnami + helm repo add jetstack https://charts.jetstack.io + helm dependency build + helm install apim -n apk . --debug --wait --timeout 5m0s \ + --set deployment.image.respository=sampathrajapakse/wso2am \ + --set deployment.image.tag=4.3.0 \ + kubectl get pods -n apk + kubectl get svc -n apk + + - name: Helm release deploy APK DP + if: github.event_name == 'pull_request_target' && contains(github.event.label.name, 'trigger-action') + shell: sh + run: | + cd /helm-charts + helm dependency build + helm install apk-test-setup -n apk . --debug --wait --timeout 15m0s \ + kubectl get pods -n apk + kubectl get svc -n apk + + - name: Helm release deploy APIM APK Agent + if: github.event_name == 'pull_request_target' && contains(github.event.label.name, 'trigger-action') + shell: sh + run: | + cd apk-repo/test/apim-apk-agent-test/agent-helm-chart + helm dependency build + helm install apim-apk-agent -n apk . --debug --wait --timeout 2m0s \ + --set controlPlane.serviceURL=https://apim-wso2am-cp-1-service:9443/ \ + --set controlPlane.eventListeningEndpoints=amqp://admin:admin@apim-wso2am-cp-1-service:5673?retries='10'&connectdelay='30' \ + --set dataPlane.k8ResourceEndpoint=https://apk-wso2-apk-config-ds-service.apk.svc.cluster.local:9443/api/configurator/apis/generate-k8s-resources \ + kubectl get pods -n apk + kubectl get svc -n apk + - name: Run test cases + shell: sh + run: | + cd apk-repo/test/apim-apk-agent-test/cucumber-tests + sh ./scripts/setup-hosts.sh + ./gradlew runTests + - name: Helm release undeploy + if: always() + shell: sh + run: | + cd apk-repo/helm-charts + kubectl describe pods -n apk + kubectl get pods -n apk + kubectl get svc -n apk + helm uninstall apim-apk-agent -n apk + cd apk-repo/test/apim-apk-agent-test/apim-cp-helm-chart + helm uninstall apim -n apk + cd apk-repo/test/apim-apk-agent-test/agent-helm-chart + helm uninstall apim-apk-agent -n apk + - name: Delete AKS cluster + if: always() + uses: azure/CLI@v1 + with: + azcliversion: 2.44.1 + inlineScript: | + az aks delete --resource-group ${{ secrets.AZURE_RESOURCE_GROUP }} --name go-integ-${{ secrets.AKS_CLUSTER_NAME }}-${{ github.event.number || github.run_id }} --yes + - name: Logout from azure + if: always() + uses: azure/CLI@v1 + with: + azcliversion: 2.44.1 + inlineScript: | + az logout + - name: Publish Test Report + if: always() + uses: malinthaprasan/action-surefire-report@v1 + with: + report_paths: 'apk-agent-repo/test/postman-tests/build/*.xml' + fail_on_test_failures: true \ No newline at end of file diff --git a/test/apim-apk-agent-test/agent-helm-chart/Chart.lock b/test/apim-apk-agent-test/agent-helm-chart/Chart.lock new file mode 100644 index 000000000..028ab93b7 --- /dev/null +++ b/test/apim-apk-agent-test/agent-helm-chart/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: cert-manager + repository: https://charts.jetstack.io + version: v1.10.1 +digest: sha256:ec4c4f13caccd7a20912773027a411630546bcb6139744732d49806ce2514fc8 +generated: "2024-01-22T12:10:42.822012+05:30" diff --git a/test/apim-apk-agent-test/agent-helm-chart/Chart.yaml b/test/apim-apk-agent-test/agent-helm-chart/Chart.yaml new file mode 100644 index 000000000..23dbf0f64 --- /dev/null +++ b/test/apim-apk-agent-test/agent-helm-chart/Chart.yaml @@ -0,0 +1,24 @@ +# Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. +# +# WSO2 LLC. licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +apiVersion: v2 +name: apim-apk-agent +description: A Helm chart for deploying apim-apk-agent +version: 0.1.0 +dependencies: + - name: cert-manager + version: "v1.10.1" + repository: "https://charts.jetstack.io" \ No newline at end of file diff --git a/test/apim-apk-agent-test/agent-helm-chart/charts/cert-manager-v1.10.1.tgz b/test/apim-apk-agent-test/agent-helm-chart/charts/cert-manager-v1.10.1.tgz new file mode 100644 index 0000000000000000000000000000000000000000..ef78ba848f2d7f59fc0a69f8268a8f7676a04012 GIT binary patch literal 65091 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 \(.*\)$'` + if expr "$link" : '.*/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`/"$link" + fi +done + +# Get standard environment variables +PRGDIR=`dirname "$PRG"` + +# Only set CARBON_HOME if not already set +[ -z "$CARBON_HOME" ] && CARBON_HOME=`cd "$PRGDIR/.." ; pwd` + +# Set AXIS2_HOME. Needed for One Click JAR Download +AXIS2_HOME="$CARBON_HOME" + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CARBON_HOME" ] && CARBON_HOME=`cygpath --unix "$CARBON_HOME"` + [ -n "$AXIS2_HOME" ] && CARBON_HOME=`cygpath --unix "$CARBON_HOME"` +fi + +# For OS400 +if $os400; then + # Set job priority to standard for interactive (interactive - 6) by using + # the interactive priority - 6, the helper threads that respond to requests + # will be running at the same priority as interactive jobs. + COMMAND='chgjob job('$JOBNAME') runpty(6)' + system $COMMAND + + # Enable multi threading + QIBM_MULTI_THREADED=Y + export QIBM_MULTI_THREADED +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$CARBON_HOME" ] && + CARBON_HOME="`(cd "$CARBON_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + [ -n "$AXIS2_HOME" ] && + CARBON_HOME="`(cd "$CARBON_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD=java + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." + echo " CARBON cannot execute $JAVACMD" + exit 1 +fi + +# if JAVA_HOME is not set we're not happy +if [ -z "$JAVA_HOME" ]; then + echo "You must set the JAVA_HOME variable before running CARBON." + exit 1 +fi + +if [ -e "$CARBON_HOME/wso2carbon.pid" ]; then + PID=`cat "$CARBON_HOME"/wso2carbon.pid` +fi + +# ----- Process the input command ---------------------------------------------- +args="" +for c in $* +do + if [ "$c" = "--debug" ] || [ "$c" = "-debug" ] || [ "$c" = "debug" ]; then + CMD="--debug" + continue + elif [ "$CMD" = "--debug" ]; then + if [ -z "$PORT" ]; then + PORT=$c + fi + elif [ "$c" = "--stop" ] || [ "$c" = "-stop" ] || [ "$c" = "stop" ]; then + CMD="stop" + elif [ "$c" = "--start" ] || [ "$c" = "-start" ] || [ "$c" = "start" ]; then + CMD="start" + elif [ "$c" = "--version" ] || [ "$c" = "-version" ] || [ "$c" = "version" ]; then + CMD="version" + elif [ "$c" = "--restart" ] || [ "$c" = "-restart" ] || [ "$c" = "restart" ]; then + CMD="restart" + elif [ "$c" = "--test" ] || [ "$c" = "-test" ] || [ "$c" = "test" ]; then + CMD="test" + elif [ "$c" = "--optimize" ] || [ "$c" = "-optimize" ] || [ "$c" = "optimize" ]; then + for option in $*; do + if [ "$option" = "--skipConfigOptimization" ] || [ "$option" = "-skipConfigOptimization" ] || + [ "$option" = "skipConfigOptimization" ]; then + passedSkipConfigOptimizationOption=true + echo "Passed skipConfigOptimization Option: $passedSkipConfigOptimizationOption" + fi + done + + for profile in $*; do + case "$profile" in + *Dprofile=*) + cd $(dirname "$0") + if [ "$passedSkipConfigOptimizationOption" = true ]; then + sh profileSetup.sh $profile --skipConfigOptimization + else + sh profileSetup.sh $profile + fi + echo "Starting the server..." + ;; + esac + done + else + args="$args $c" + fi +done + +if [ "$CMD" = "--debug" ]; then + if [ "$PORT" = "" ]; then + echo " Please specify the debug port after the --debug option" + exit 1 + fi + if [ -n "$JAVA_OPTS" ]; then + echo "Warning !!!. User specified JAVA_OPTS will be ignored, once you give the --debug option." + fi + CMD="RUN" + JAVA_OPTS="-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=$PORT" + echo "Please start the remote debugging client to continue..." +elif [ "$CMD" = "start" ]; then + if [ -e "$CARBON_HOME/wso2carbon.pid" ]; then + if ps -p $PID > /dev/null ; then + echo "Process is already running" + exit 0 + fi + fi + export CARBON_HOME="$CARBON_HOME" +# using nohup sh to avoid erros in solaris OS.TODO + nohup sh "$CARBON_HOME"/bin/api-manager.sh $args > /dev/null 2>&1 & + exit 0 +elif [ "$CMD" = "stop" ]; then + export CARBON_HOME="$CARBON_HOME" + kill -term `cat "$CARBON_HOME"/wso2carbon.pid` + exit 0 +elif [ "$CMD" = "restart" ]; then + export CARBON_HOME="$CARBON_HOME" + kill -term `cat "$CARBON_HOME"/wso2carbon.pid` + process_status=0 + pid=`cat "$CARBON_HOME"/wso2carbon.pid` + while [ "$process_status" -eq "0" ] + do + sleep 1; + ps -p$pid 2>&1 > /dev/null + process_status=$? + done + +# using nohup sh to avoid erros in solaris OS.TODO + nohup sh "$CARBON_HOME"/bin/api-manager.sh $args > /dev/null 2>&1 & + exit 0 +elif [ "$CMD" = "test" ]; then + JAVACMD="exec "$JAVACMD"" +elif [ "$CMD" = "version" ]; then + cat "$CARBON_HOME"/bin/version.txt + cat "$CARBON_HOME"/bin/wso2carbon-version.txt + exit 0 +fi + +# ---------- Handle the SSL Issue with proper JDK version -------------------- +java_version=$("$JAVACMD" -version 2>&1 | awk -F '"' '/version/ {print $2}') +java_version_formatted=$(echo "$java_version" | awk -F. '{printf("%02d%02d",$1,$2);}') +if [ $java_version_formatted -lt 1100 ] || [ $java_version_formatted -gt 1700 ]; then + echo " Starting WSO2 Carbon (in unsupported JDK)" + echo " [ERROR] CARBON is supported only between JDK 11 and JDK 17" +fi + +CARBON_XBOOTCLASSPATH="" +for f in "$CARBON_HOME"/lib/xboot/*.jar +do + if [ "$f" != "$CARBON_HOME/lib/xboot/*.jar" ];then + CARBON_XBOOTCLASSPATH="$CARBON_XBOOTCLASSPATH":$f + fi +done + + +CARBON_CLASSPATH="" +if [ -e "$JAVA_HOME/lib/tools.jar" ]; then + CARBON_CLASSPATH="$JAVA_HOME/lib/tools.jar" +fi +for f in "$CARBON_HOME"/bin/*.jar +do + if [ "$f" != "$CARBON_HOME/bin/*.jar" ];then + CARBON_CLASSPATH="$CARBON_CLASSPATH":$f + fi +done +for t in "$CARBON_HOME"/lib/*.jar +do + CARBON_CLASSPATH="$CARBON_CLASSPATH":$t +done +for t in "$CARBON_HOME"/lib/endorsed/*.jar +do + CARBON_CLASSPATH="$CARBON_CLASSPATH":$t +done + + + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + JAVA_HOME=`cygpath --absolute --windows "$JAVA_HOME"` + CARBON_HOME=`cygpath --absolute --windows "$CARBON_HOME"` + AXIS2_HOME=`cygpath --absolute --windows "$CARBON_HOME"` + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + CARBON_CLASSPATH=`cygpath --path --windows "$CARBON_CLASSPATH"` + CARBON_XBOOTCLASSPATH=`cygpath --path --windows "$CARBON_XBOOTCLASSPATH"` +fi + +# ----- Execute The Requested Command ----------------------------------------- + +echo JAVA_HOME environment variable is set to $JAVA_HOME +echo CARBON_HOME environment variable is set to "$CARBON_HOME" + +cd "$CARBON_HOME" + +TMP_DIR="$CARBON_HOME"/tmp +if [ -d "$TMP_DIR" ]; then +rm -rf "$TMP_DIR"/* +fi + +START_EXIT_STATUS=121 +status=$START_EXIT_STATUS + +if [ -z "$JVM_MEM_OPTS" ]; then + java_version=$("$JAVACMD" -version 2>&1 | awk -F '"' '/version/ {print $2}') + JVM_MEM_OPTS="-Xms256m -Xmx1024m" + if [ "$java_version" \< "1.8" ]; then + JVM_MEM_OPTS="$JVM_MEM_OPTS -XX:MaxPermSize=256m" + fi +fi +echo "Using Java memory options: $JVM_MEM_OPTS" + +#To monitor a Carbon server in remote JMX mode on linux host machines, set the below system property. +# -Djava.rmi.server.hostname="your.IP.goes.here" + +JAVA_VER_BASED_OPTS="--add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens java.rmi/sun.rmi.transport=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED" + +if [ $java_version_formatted -ge 1700 ]; then + JAVA_VER_BASED_OPTS="$JAVA_VER_BASED_OPTS --add-opens=java.naming/com.sun.jndi.ldap=ALL-UNNAMED --add-opens=java.base/sun.security.x509=ALL-UNNAMED" +fi + +while [ "$status" = "$START_EXIT_STATUS" ] +do + $JAVACMD \ + -Xbootclasspath/a:"$CARBON_XBOOTCLASSPATH" \ + $JVM_MEM_OPTS \ + -XX:+HeapDumpOnOutOfMemoryError \ + -XX:HeapDumpPath="$CARBON_HOME/repository/logs/heap-dump.hprof" \ + $JAVA_OPTS \ + -Dcom.sun.management.jmxremote \ + -classpath "$CARBON_CLASSPATH" \ + $JAVA_VER_BASED_OPTS \ + -Djava.io.tmpdir="$CARBON_HOME/tmp" \ + -Dcatalina.base="$CARBON_HOME/lib/tomcat" \ + -Dwso2.server.standalone=true \ + -Dcarbon.registry.root=/ \ + -Djava.command="$JAVACMD" \ + -Dcarbon.home="$CARBON_HOME" \ + -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager \ + -Dcarbon.config.dir.path="$CARBON_HOME/repository/conf" \ + -Djava.util.logging.config.file="$CARBON_HOME/repository/conf/etc/logging-bridge.properties" \ + -Dcomponents.repo="$CARBON_HOME/repository/components/plugins" \ + -Dconf.location="$CARBON_HOME/repository/conf"\ + -Dcom.atomikos.icatch.file="$CARBON_HOME/lib/transactions.properties" \ + -Dcom.atomikos.icatch.hide_init_file_path=true \ + -Dorg.apache.jasper.compiler.Parser.STRICT_QUOTE_ESCAPING=false \ + -Dorg.apache.jasper.runtime.BodyContentImpl.LIMIT_BUFFER=true \ + -Dcom.sun.jndi.ldap.connect.pool.authentication=simple \ + -Dcom.sun.jndi.ldap.connect.pool.timeout=3000 \ + -Dorg.terracotta.quartz.skipUpdateCheck=true \ + -Djava.security.egd=file:/dev/./urandom \ + -Dfile.encoding=UTF8 \ + -Djava.net.preferIPv4Stack=true \ + -Dcom.ibm.cacheLocalHost=true \ + -Dorg.opensaml.httpclient.https.disableHostnameVerification=true \ + -Dhttpclient.hostnameVerifier=AllowAll \ + -DworkerNode=false \ + -DenableCorrelationLogs=false \ + -Dcarbon.new.config.dir.path="$CARBON_HOME/repository/resources/conf" \ + -Djavax.xml.xpath.XPathFactory:http://java.sun.com/jaxp/xpath/dom=net.sf.saxon.xpath.XPathFactoryImpl \ + -Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector \ + -Dorg.ops4j.pax.logging.logReaderEnabled=false \ + -Dorg.ops4j.pax.logging.eventAdminEnabled=false \ + -Djdk.nio.zipfs.allowDotZipEntry=true \ + -Djdk.util.zip.disableZip64ExtraFieldValidation=true \ + org.wso2.carbon.bootstrap.Bootstrap $* + status=$? +done diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/confs/instance-1/deployment.toml b/test/apim-apk-agent-test/apim-cp-helm-chart/confs/instance-1/deployment.toml new file mode 100644 index 000000000..8c26d8543 --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/confs/instance-1/deployment.toml @@ -0,0 +1,283 @@ +[server] +hostname = "{{ .Values.kubernetes.ingress.controlPlane.hostname }}" +#node_ip = "$env{NODE_IP}" +offset = {{ .Values.wso2.apim.portOffset }} +base_path = "${carbon.protocol}://${carbon.host}:${carbon.management.port}" +#discard_empty_caches = false +server_role = "control-plane" + +[user_store] +type = {{ .Values.wso2.apim.configurations.userStore.type | quote }} +{{- range $key, $value := .Values.wso2.apim.configurations.userStore.properties }} +{{ $key }} = {{ $value | quote }} +{{- end }} + +[super_admin] +username = {{ .Values.wso2.apim.configurations.adminUsername | quote }} +{{- if .Values.wso2.apim.secureVaultEnabled }} +password = "$secret{admin_password}" +{{- else }} +password = {{ .Values.wso2.apim.configurations.adminPassword | quote }} +{{- end }} +create_admin_account = true + +[database.apim_db] +type = "{{ .Values.wso2.apim.configurations.databases.type }}" +url = "{{ .Values.wso2.apim.configurations.databases.apim_db.url}}" +username = "{{ .Values.wso2.apim.configurations.databases.apim_db.username }}" +{{- if .Values.wso2.apim.secureVaultEnabled }} +password = "$secret{apim_db_password}" +{{- else }} +password = {{ .Values.wso2.apim.configurations.databases.apim_db.password | quote }} +{{- end }} + +[database.apim_db.pool_options] +{{- range $key, $value := .Values.wso2.apim.configurations.databases.apim_db.poolParameters }} +{{ $key }} = "{{ $value }}" +{{- end }} + +[database.shared_db] +type = "{{ .Values.wso2.apim.configurations.databases.type }}" +url = "{{ .Values.wso2.apim.configurations.databases.shared_db.url}}" +username = "{{ .Values.wso2.apim.configurations.databases.shared_db.username }}" +{{- if .Values.wso2.apim.secureVaultEnabled }} +password = "$secret{shared_db_password}" +{{- else }} +password = {{ .Values.wso2.apim.configurations.databases.shared_db.password | quote }} +{{- end }} + +[database.shared_db.pool_options] +{{- range $key, $value := .Values.wso2.apim.configurations.databases.shared_db.poolParameters }} +{{ $key }} = "{{ $value }}" +{{- end }} + +{{- if .Values.wso2.apim.configurations.security.keystores.primary.enabled }} +[keystore.primary] +type = "JKS" +file_name = "{{ .Values.wso2.apim.configurations.security.keystores.primary.name }}" +alias = "{{ .Values.wso2.apim.configurations.security.keystores.primary.alias }}" +{{- if .Values.wso2.apim.secureVaultEnabled }} +password = "$secret{keystore_password}" +key_password = "$secret{keystore_key_password}" +{{- else }} +password = {{ .Values.wso2.apim.configurations.security.keystores.primary.password | quote }} +key_password = {{ .Values.wso2.apim.configurations.security.keystores.primary.keyPassword | quote }} +{{- end }} +{{- end }} + +{{- if .Values.wso2.apim.configurations.security.keystores.tls.enabled }} +[keystore.tls] +type = "JKS" +file_name = "{{ .Values.wso2.apim.configurations.security.keystores.tls.name }}" +alias = "{{ .Values.wso2.apim.configurations.security.keystores.tls.alias }}" +{{- if .Values.wso2.apim.secureVaultEnabled }} +password = "$secret{ssl_keystore_password}" +key_password = "$secret{ssl_key_password}" +{{- else }} +password = {{ .Values.wso2.apim.configurations.security.keystores.tls.password | quote }} +key_password = {{ .Values.wso2.apim.configurations.security.keystores.tls.keyPassword | quote }} +{{- end }} +{{- end }} + +{{- if .Values.wso2.apim.configurations.security.keystores.internal.enabled }} +[keystore.internal] +type = "JKS" +file_name = "{{ .Values.wso2.apim.configurations.security.keystores.internal.name }}" +alias = "{{ .Values.wso2.apim.configurations.security.keystores.internal.alias }}" +{{- if .Values.wso2.apim.secureVaultEnabled }} +password = "$secret{internal_keystore_password}" +key_password = "$secret{internal_keystore_key_password}" +{{- else }} +password = {{ .Values.wso2.apim.configurations.security.keystores.internal.password | quote }} +key_password = {{ .Values.wso2.apim.configurations.security.keystores.internal.keyPassword | quote }} +{{- end }} +{{- end }} + +[truststore] +type = "JKS" +file_name = "{{ .Values.wso2.apim.configurations.security.truststore.name }}" +{{- if .Values.wso2.apim.secureVaultEnabled }} +password = "$secret{truststore_password}" +{{- else }} +password = {{ .Values.wso2.apim.configurations.security.truststore.password | quote }} +{{- end }} + +{{- range $i, $env := .Values.wso2.apim.configurations.gateway.environments }} +[[apim.gateway.environment]] +name = {{ $env.name | quote }} +type = {{ $env.type | quote }} +gateway_type = "APK" +provider = {{ $env.provider | quote }} +display_in_api_console = {{ $env.displayInApiConsole }} +description = {{ $env.description | quote }} +show_as_token_endpoint_url = {{ $env.showAsTokenEndpointUrl }} +service_url = "https://{{ $env.serviceName }}:{{ $env.servicePort }}/services/" +username= "${admin.username}" +password= "${admin.password}" +ws_endpoint = "ws://{{ $env.wsHostname }}" +wss_endpoint = "wss://{{ $env.wsHostname }}" +http_endpoint = "http://{{ $env.httpHostname }}" +https_endpoint = "https://{{ $env.httpHostname }}" +websub_event_receiver_http_endpoint = "http://{{ $env.websubHostname }}" +websub_event_receiver_https_endpoint = "https://{{ $env.websubHostname }}" + +{{- end }} + +[apim.key_manager] +{{- if .Values.wso2.apim.configurations.iskm.enabled }} +type = "WSO2-IS" +service_url = "https://{{ .Values.wso2.apim.configurations.iskm.serviceName }}:{{ .Values.wso2.apim.configurations.iskm.servicePort }}/services/" +{{- else }} +service_url = "https://{{ template "apim-helm-cp.fullname" . }}-service:{{ add 9443 .Values.wso2.apim.portOffset }}/services/" +{{- end }} +username= "$ref{super_admin.username}" +password= "$ref{super_admin.password}" + +{{ if .Values.wso2.apim.configurations.iskm.enabled }} +[oauth.grant_type.token_exchange] +enable = false +allow_refresh_tokens = true +iat_validity_period = "1h" +{{- end }} + +#[apim.publisher] +#{{- if .Values.wso2.apim.configurations.publisher.supportedDocumentTypes }} +#supported_document_types = {{ toJson .Values.wso2.apim.configurations.publisher.supportedDocumentTypes }} +#{{- end }} + +[apim.devportal] +url = "https://{{ .Values.kubernetes.ingress.controlPlane.hostname }}/devportal" +{{- if .Values.wso2.apim.configurations.devportal.enableApplicationSharing }} +enable_application_sharing = {{ .Values.wso2.apim.configurations.devportal.enableApplicationSharing }} +{{- end }} +{{- if .Values.wso2.apim.configurations.devportal.applicationSharingType }} +application_sharing_type = {{ .Values.wso2.apim.configurations.devportal.applicationSharingType | quote }} +{{- end }} +{{- if .Values.wso2.apim.configurations.devportal.applicationSharingImpl }} +application_sharing_impl = {{ .Values.wso2.apim.configurations.devportal.applicationSharingImpl | quote }} +{{- end }} +{{- if .Values.wso2.apim.configurations.devportal.displayMultipleVersions }} +display_multiple_versions = {{ .Values.wso2.apim.configurations.devportal.displayMultipleVersions }} +{{- end }} +{{- if .Values.wso2.apim.configurations.devportal.displayDeprecatedAPIs }} +display_deprecated_apis = {{ .Values.wso2.apim.configurations.devportal.displayDeprecatedAPIs }} +{{- end }} +{{- if .Values.wso2.apim.configurations.devportal.enableComments }} +enable_comments = {{ .Values.wso2.apim.configurations.devportal.enableComments }} +{{- end }} +{{- if .Values.wso2.apim.configurations.devportal.enableRatings }} +enable_ratings = {{ .Values.wso2.apim.configurations.devportal.enableRatings }} +{{- end }} +{{- if .Values.wso2.apim.configurations.devportal.enableForum }} +enable_forum = {{ .Values.wso2.apim.configurations.devportal.enableForum }} +{{- end }} +{{- if .Values.wso2.apim.configurations.devportal.enableAnonymousMode }} +enable_anonymous_mode = {{ .Values.wso2.apim.configurations.devportal.enableAnonymousMode }} +{{- end }} +{{- if .Values.wso2.apim.configurations.devportal.enableCrossTenantSubscriptions }} +enable_cross_tenant_subscriptions = {{ .Values.wso2.apim.configurations.devportal.enableCrossTenantSubscriptions }} +{{- end }} +{{- if .Values.wso2.apim.configurations.devportal.defaultReservedUsername }} +default_reserved_username = {{ .Values.wso2.apim.configurations.devportal.defaultReservedUsername | quote }} +{{- end }} +{{- if .Values.wso2.apim.configurations.devportal.loginUsernameCaseInsensitive }} +login_username_case_insensitive = {{ .Values.wso2.apim.configurations.devportal.loginUsernameCaseInsensitive }} +{{- end }} +{{- if .Values.wso2.apim.configurations.devportal.enableKeyProvisioning }} +enable_key_provisioning = {{ .Values.wso2.apim.configurations.devportal.enableKeyProvisioning }} +{{- end }} + +[apim.event_hub] +enable = true +username = "$ref{super_admin.username}" +password = "$ref{super_admin.password}" +service_url = "https://{{ template "apim-helm-cp.fullname" . }}-service:{{ add 9443 .Values.wso2.apim.portOffset }}/services/" +event_listening_endpoints = ["tcp://localhost:{{ add 5672 .Values.wso2.apim.portOffset }}"] +{{- if .Values.wso2.deployment.highAvailability }} +event_duplicate_url = ["tcp://{{ template "apim-helm-cp.fullname" . }}-2-service:{{ add 5672 .Values.wso2.apim.portOffset }}"] +{{- end }} + +[[apim.event_hub.publish.url_group]] +urls = ["tcp://{{ template "apim-helm-cp.fullname" . }}-1-service:{{ add 9611 .Values.wso2.apim.portOffset }}"] +auth_urls = ["ssl://{{ template "apim-helm-cp.fullname" . }}-1-service:{{ add 9711 .Values.wso2.apim.portOffset }}"] + +{{- if .Values.wso2.deployment.highAvailability }} +[[apim.event_hub.publish.url_group]] +urls = ["tcp://{{ template "apim-helm-cp.fullname" . }}-2-service:{{ add 9611 .Values.wso2.apim.portOffset }}"] +auth_urls = ["ssl://{{ template "apim-helm-cp.fullname" . }}-2-service:{{ add 9711 .Values.wso2.apim.portOffset }}"] +{{- end }} + +[apim.oauth_config] +{{- if .Values.wso2.apim.configurations.iskm.enabled }} +revoke_endpoint = "https://{{ .Values.wso2.apim.configurations.iskm.serviceName }}:{{ .Values.wso2.apim.configurations.iskm.servicePort }}/oauth2/revoke" +{{- else }} +revoke_endpoint = "https://{{ template "apim-helm-cp.fullname" . }}-service:{{ add 9443 .Values.wso2.apim.portOffset }}/oauth2/revoke" +{{- end }} +enable_token_encryption = {{ .Values.wso2.apim.configurations.oauth_config.enableTokenEncryption }} +enable_token_hashing = {{ .Values.wso2.apim.configurations.oauth_config.enableTokenHashing }} +allowed_scopes = {{ toJson .Values.wso2.apim.configurations.oauth_config.allowedScopes }} + +{{- if .Values.wso2.apim.configurations.openTracer.enabled }} +[apim.open_tracer] +remote_tracer.enable = {{ .Values.wso2.apim.configurations.openTracer.enabled }} +remote_tracer.name = {{ .Values.wso2.apim.configurations.openTracer.name | quote }} +remote_tracer.properties.hostname = {{ .Values.wso2.apim.configurations.openTracer.properties.hostname | quote }} +remote_tracer.properties.port = {{ .Values.wso2.apim.configurations.openTracer.properties.port | quote }} +{{- end }} + +{{- if .Values.wso2.apim.configurations.openTelemetry.enabled }} +[apim.open_telemetry] +remote_tracer.enable = {{ .Values.wso2.apim.configurations.openTelemetry.enabled }} +remote_tracer.name = {{ .Values.wso2.apim.configurations.openTelemetry.name | quote }} +remote_tracer.hostname = {{ .Values.wso2.apim.configurations.openTelemetry.hostname | quote }} +remote_tracer.port = {{ .Values.wso2.apim.configurations.openTelemetry.port | quote }} +{{- end }} + +[transport.https.properties] +proxyPort = 443 + +[[event_handler]] +name="userPostSelfRegistration" +subscriptions=["POST_ADD_USER"] + +[service_provider] +sp_name_regex = "^[\\sa-zA-Z0-9._-]*$" + +{{- if not .Values.wso2.apim.configurations.iskm.enabled }} +[[event_listener]] +id = "token_revocation" +type = "org.wso2.carbon.identity.core.handler.AbstractIdentityHandler" +name = "org.wso2.is.notification.ApimOauthEventInterceptor" +order = 1 + +[event_listener.properties] +notification_endpoint = "https://localhost:${mgt.transport.https.port}/internal/data/v1/notify" +username = "${admin.username}" +password = "${admin.password}" +'header.X-WSO2-KEY-MANAGER' = "default" +{{- end }} + +{{- if .Values.wso2.deployment.persistence.solrIndexing.enabled }} +[database.local] +url = "jdbc:h2:/home/wso2carbon/solr/database/WSO2CARBON_DB;DB_CLOSE_ON_EXIT=FALSE" + +[indexing] +location = "/home/wso2carbon/solr/indexed-data" +{{- else }} +[database.local] +url = "jdbc:h2:./repository/database/WSO2CARBON_DB;DB_CLOSE_ON_EXIT=FALSE" +{{- end }} + +{{- if .Values.wso2.apim.secureVaultEnabled }} +[secrets] +admin_password = {{ .Values.wso2.apim.configurations.adminPassword | quote }} +keystore_password = {{ .Values.wso2.apim.configurations.security.keystores.primary.password | quote }} +keystore_key_password = {{ .Values.wso2.apim.configurations.security.keystores.primary.keyPassword | quote }} +ssl_keystore_password = {{ .Values.wso2.apim.configurations.security.keystores.tls.password | quote }} +ssl_key_password = {{ .Values.wso2.apim.configurations.security.keystores.tls.keyPassword | quote }} +internal_keystore_password = {{ .Values.wso2.apim.configurations.security.keystores.internal.password | quote }} +internal_keystore_key_password = {{ .Values.wso2.apim.configurations.security.keystores.internal.keyPassword | quote }} +truststore_password = {{ .Values.wso2.apim.configurations.security.truststore.password | quote }} +apim_db_password = {{ .Values.wso2.apim.configurations.databases.apim_db.password | quote}} +shared_db_password = {{ .Values.wso2.apim.configurations.databases.shared_db.password | quote}} +{{- end}} \ No newline at end of file diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/confs/instance-2/deployment.toml b/test/apim-apk-agent-test/apim-cp-helm-chart/confs/instance-2/deployment.toml new file mode 100644 index 000000000..91c7c77b4 --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/confs/instance-2/deployment.toml @@ -0,0 +1,270 @@ +[server] +hostname = "{{ .Values.kubernetes.ingress.controlPlane.hostname }}" +node_ip = "$env{NODE_IP}" +offset = {{ .Values.wso2.apim.portOffset }} +base_path = "${carbon.protocol}://${carbon.host}:${carbon.management.port}" +#discard_empty_caches = false +server_role = "control-plane" + +[user_store] +type = {{ .Values.wso2.apim.configurations.userStore.type | quote }} +{{- range $key, $value := .Values.wso2.apim.configurations.userStore.properties }} +{{ $key }} = {{ $value | quote }} +{{- end }} + +[super_admin] +username = {{ .Values.wso2.apim.configurations.adminUsername | quote }} +{{- if .Values.wso2.apim.secureVaultEnabled }} +password = "$secret{admin_password}" +{{- else }} +password = {{ .Values.wso2.apim.configurations.adminPassword | quote }} +{{- end }} +create_admin_account = true + +[database.apim_db] +type = "{{ .Values.wso2.apim.configurations.databases.type }}" +url = "{{ .Values.wso2.apim.configurations.databases.apim_db.url}}" +username = "{{ .Values.wso2.apim.configurations.databases.apim_db.username }}" +{{- if .Values.wso2.apim.secureVaultEnabled }} +password = "$secret{apim_db_password}" +{{- else }} +password = {{ .Values.wso2.apim.configurations.databases.apim_db.password | quote }} +{{- end }} +driver = "{{ .Values.wso2.apim.configurations.databases.jdbc.driver }}" + +[database.apim_db.pool_options] +{{- range $key, $value := .Values.wso2.apim.configurations.databases.apim_db.poolParameters }} +{{ $key }} = "{{ $value }}" +{{- end }} + +[database.shared_db] +type = "{{ .Values.wso2.apim.configurations.databases.type }}" +url = "{{ .Values.wso2.apim.configurations.databases.shared_db.url}}" +username = "{{ .Values.wso2.apim.configurations.databases.shared_db.username }}" +{{- if .Values.wso2.apim.secureVaultEnabled }} +password = "$secret{shared_db_password}" +{{- else }} +password = {{ .Values.wso2.apim.configurations.databases.shared_db.password | quote }} +{{- end }} +driver = "{{ .Values.wso2.apim.configurations.databases.jdbc.driver }}" + +[database.shared_db.pool_options] +{{- range $key, $value := .Values.wso2.apim.configurations.databases.shared_db.poolParameters }} +{{ $key }} = "{{ $value }}" +{{- end }} + +{{- if .Values.wso2.apim.configurations.security.keystores.primary.enabled }} +[keystore.primary] +type = "JKS" +file_name = "{{ .Values.wso2.apim.configurations.security.keystores.primary.name }}" +alias = "{{ .Values.wso2.apim.configurations.security.keystores.primary.alias }}" +{{- if .Values.wso2.apim.secureVaultEnabled }} +password = "$secret{keystore_password}" +key_password = "$secret{keystore_key_password}" +{{- else }} +password = {{ .Values.wso2.apim.configurations.security.keystores.primary.password | quote }} +key_password = {{ .Values.wso2.apim.configurations.security.keystores.primary.keyPassword | quote }} +{{- end }} +{{- end }} + +{{- if .Values.wso2.apim.configurations.security.keystores.tls.enabled }} +[keystore.tls] +type = "JKS" +file_name = "{{ .Values.wso2.apim.configurations.security.keystores.tls.name }}" +alias = "{{ .Values.wso2.apim.configurations.security.keystores.tls.alias }}" +{{- if .Values.wso2.apim.secureVaultEnabled }} +password = "$secret{ssl_keystore_password}" +key_password = "$secret{ssl_key_password}" +{{- else }} +password = {{ .Values.wso2.apim.configurations.security.keystores.tls.password | quote }} +key_password = {{ .Values.wso2.apim.configurations.security.keystores.tls.keyPassword | quote }} +{{- end }} +{{- end }} + +{{- if .Values.wso2.apim.configurations.security.keystores.internal.enabled }} +[keystore.internal] +type = "JKS" +file_name = "{{ .Values.wso2.apim.configurations.security.keystores.internal.name }}" +alias = "{{ .Values.wso2.apim.configurations.security.keystores.internal.alias }}" +{{- if .Values.wso2.apim.secureVaultEnabled }} +password = "$secret{internal_keystore_password}" +key_password = "$secret{internal_keystore_key_password}" +{{- else }} +password = {{ .Values.wso2.apim.configurations.security.keystores.internal.password | quote }} +key_password = {{ .Values.wso2.apim.configurations.security.keystores.internal.keyPassword | quote }} +{{- end }} +{{- end }} + +{{- range $i, $env := .Values.wso2.apim.configurations.gateway.environments }} +[[apim.gateway.environment]] +name = {{ $env.name | quote }} +type = {{ $env.type | quote }} +provider = {{ $env.provider | quote }} +display_in_api_console = {{ $env.displayInApiConsole }} +description = {{ $env.description | quote }} +show_as_token_endpoint_url = {{ $env.showAsTokenEndpointUrl }} +service_url = "https://{{ $env.serviceName }}:{{ $env.servicePort }}/services/" +username= "${admin.username}" +password= "${admin.password}" +ws_endpoint = "ws://{{ $env.wsHostname }}" +wss_endpoint = "wss://{{ $env.wsHostname }}" +http_endpoint = "http://{{ $env.httpHostname }}" +https_endpoint = "https://{{ $env.httpHostname }}" +websub_event_receiver_http_endpoint = "http://{{ $env.websubHostname }}" +websub_event_receiver_https_endpoint = "https://{{ $env.websubHostname }}" + +{{- end }} + +[apim.key_manager] +{{- if .Values.wso2.apim.configurations.iskm.enabled }} +type = "WSO2-IS" +service_url = "https://{{ .Values.wso2.apim.configurations.iskm.serviceName }}:{{ .Values.wso2.apim.configurations.iskm.servicePort }}/services/" +{{- else }} +service_url = "https://{{ template "apim-helm-cp.fullname" . }}-service:{{ add 9443 .Values.wso2.apim.portOffset }}/services/" +{{- end }} +username= "$ref{super_admin.username}" +password= "$ref{super_admin.password}" + +{{ if .Values.wso2.apim.configurations.iskm.enabled }} +[oauth.grant_type.token_exchange] +enable = false +allow_refresh_tokens = true +iat_validity_period = "1h" +{{- end }} + +[apim.devportal] +url = "https://{{ .Values.kubernetes.ingress.controlPlane.hostname }}/devportal" +{{- if .Values.wso2.apim.configurations.devportal.enableApplicationSharing }} +enable_application_sharing = {{ .Values.wso2.apim.configurations.devportal.enableApplicationSharing }} +{{- end }} +{{- if .Values.wso2.apim.configurations.devportal.applicationSharingType }} +application_sharing_type = {{ .Values.wso2.apim.configurations.devportal.applicationSharingType | quote }} +{{- end }} +{{- if .Values.wso2.apim.configurations.devportal.applicationSharingImpl }} +application_sharing_impl = {{ .Values.wso2.apim.configurations.devportal.applicationSharingImpl | quote }} +{{- end }} +{{- if .Values.wso2.apim.configurations.devportal.displayMultipleVersions }} +display_multiple_versions = {{ .Values.wso2.apim.configurations.devportal.displayMultipleVersions }} +{{- end }} +{{- if .Values.wso2.apim.configurations.devportal.displayDeprecatedAPIs }} +display_deprecated_apis = {{ .Values.wso2.apim.configurations.devportal.displayDeprecatedAPIs }} +{{- end }} +{{- if .Values.wso2.apim.configurations.devportal.enableComments }} +enable_comments = {{ .Values.wso2.apim.configurations.devportal.enableComments }} +{{- end }} +{{- if .Values.wso2.apim.configurations.devportal.enableRatings }} +enable_ratings = {{ .Values.wso2.apim.configurations.devportal.enableRatings }} +{{- end }} +{{- if .Values.wso2.apim.configurations.devportal.enableForum }} +enable_forum = {{ .Values.wso2.apim.configurations.devportal.enableForum }} +{{- end }} +{{- if .Values.wso2.apim.configurations.devportal.enableAnonymousMode }} +enable_anonymous_mode = {{ .Values.wso2.apim.configurations.devportal.enableAnonymousMode }} +{{- end }} +{{- if .Values.wso2.apim.configurations.devportal.enableCrossTenantSubscriptions }} +enable_cross_tenant_subscriptions = {{ .Values.wso2.apim.configurations.devportal.enableCrossTenantSubscriptions }} +{{- end }} +{{- if .Values.wso2.apim.configurations.devportal.defaultReservedUsername }} +default_reserved_username = {{ .Values.wso2.apim.configurations.devportal.defaultReservedUsername | quote }} +{{- end }} +{{- if .Values.wso2.apim.configurations.devportal.loginUsernameCaseInsensitive }} +login_username_case_insensitive = {{ .Values.wso2.apim.configurations.devportal.loginUsernameCaseInsensitive }} +{{- end }} +{{- if .Values.wso2.apim.configurations.devportal.enableKeyProvisioning }} +enable_key_provisioning = {{ .Values.wso2.apim.configurations.devportal.enableKeyProvisioning }} +{{- end }} + +[apim.event_hub] +enable = true +username = "$ref{super_admin.username}" +password = "$ref{super_admin.password}" +service_url = "https://{{ template "apim-helm-cp.fullname" . }}-service:{{ add 9443 .Values.wso2.apim.portOffset }}/services/" +event_listening_endpoints = ["tcp://localhost:{{ add 5672 .Values.wso2.apim.portOffset }}"] +{{- if .Values.wso2.deployment.highAvailability }} +event_duplicate_url = ["tcp://{{ template "apim-helm-cp.fullname" . }}-1-service:{{ add 5672 .Values.wso2.apim.portOffset }}"] +{{- end }} + +[[apim.event_hub.publish.url_group]] +urls = ["tcp://{{ template "apim-helm-cp.fullname" . }}-1-service:{{ add 9611 .Values.wso2.apim.portOffset }}"] +auth_urls = ["ssl://{{ template "apim-helm-cp.fullname" . }}-1-service:{{ add 9711 .Values.wso2.apim.portOffset }}"] + +{{- if .Values.wso2.deployment.highAvailability }} +[[apim.event_hub.publish.url_group]] +urls = ["tcp://{{ template "apim-helm-cp.fullname" . }}-2-service:{{ add 9611 .Values.wso2.apim.portOffset }}"] +auth_urls = ["ssl://{{ template "apim-helm-cp.fullname" . }}-2-service:{{ add 9711 .Values.wso2.apim.portOffset }}"] +{{- end }} + +[apim.oauth_config] +{{- if .Values.wso2.apim.configurations.iskm.enabled }} +revoke_endpoint = "https://{{ .Values.wso2.apim.configurations.iskm.serviceName }}:{{ .Values.wso2.apim.configurations.iskm.servicePort }}/oauth2/revoke" +{{- else }} +revoke_endpoint = "https://{{ template "apim-helm-cp.fullname" . }}-service:{{ add 9443 .Values.wso2.apim.portOffset }}/oauth2/revoke" +{{- end }} +enable_token_encryption = {{ .Values.wso2.apim.configurations.oauth_config.enableTokenEncryption }} +enable_token_hashing = {{ .Values.wso2.apim.configurations.oauth_config.enableTokenHashing }} +allowed_scopes = {{ toJson .Values.wso2.apim.configurations.oauth_config.allowedScopes }} + +{{- if .Values.wso2.apim.configurations.openTracer.enabled }} +[apim.open_tracer] +remote_tracer.enable = {{ .Values.wso2.apim.configurations.openTracer.enabled }} +remote_tracer.name = {{ .Values.wso2.apim.configurations.openTracer.name | quote }} +remote_tracer.properties.hostname = {{ .Values.wso2.apim.configurations.openTracer.properties.hostname | quote }} +remote_tracer.properties.port = {{ .Values.wso2.apim.configurations.openTracer.properties.port | quote }} +{{- end }} + +{{- if .Values.wso2.apim.configurations.openTelemetry.enabled }} +[apim.open_telemetry] +remote_tracer.enable = {{ .Values.wso2.apim.configurations.openTelemetry.enabled }} +remote_tracer.name = {{ .Values.wso2.apim.configurations.openTelemetry.name | quote }} +remote_tracer.hostname = {{ .Values.wso2.apim.configurations.openTelemetry.hostname | quote }} +remote_tracer.port = {{ .Values.wso2.apim.configurations.openTelemetry.port | quote }} +{{- end }} + +[transport.https.properties] +proxyPort = 443 + +[[event_handler]] +name="userPostSelfRegistration" +subscriptions=["POST_ADD_USER"] + +[service_provider] +sp_name_regex = "^[\\sa-zA-Z0-9._-]*$" + +{{- if not .Values.wso2.apim.configurations.iskm.enabled }} +[[event_listener]] +id = "token_revocation" +type = "org.wso2.carbon.identity.core.handler.AbstractIdentityHandler" +name = "org.wso2.is.notification.ApimOauthEventInterceptor" +order = 1 + +[event_listener.properties] +notification_endpoint = "https://localhost:${mgt.transport.https.port}/internal/data/v1/notify" +username = "${admin.username}" +password = "${admin.password}" +'header.X-WSO2-KEY-MANAGER' = "default" +{{- end }} + +{{- if .Values.wso2.deployment.persistence.solrIndexing.enabled }} +[database.local] +url = "jdbc:h2:/home/wso2carbon/solr/database/WSO2CARBON_DB;DB_CLOSE_ON_EXIT=FALSE" + +[indexing] +location = "/home/wso2carbon/solr/indexed-data" +{{- else }} +[database.local] +url = "jdbc:h2:./repository/database/WSO2CARBON_DB;DB_CLOSE_ON_EXIT=FALSE" +{{- end }} + +{{- if .Values.wso2.apim.secureVaultEnabled }} +[secrets] +admin_password = {{ .Values.wso2.apim.configurations.adminPassword | quote }} +keystore_password = {{ .Values.wso2.apim.configurations.security.keystores.primary.password | quote }} +keystore_key_password = {{ .Values.wso2.apim.configurations.security.keystores.primary.keyPassword | quote }} +ssl_keystore_password = {{ .Values.wso2.apim.configurations.security.keystores.tls.password | quote }} +ssl_key_password = {{ .Values.wso2.apim.configurations.security.keystores.tls.keyPassword | quote }} +internal_keystore_password = {{ .Values.wso2.apim.configurations.security.keystores.internal.password | quote }} +internal_keystore_key_password = {{ .Values.wso2.apim.configurations.security.keystores.internal.keyPassword | quote }} +truststore_password = {{ .Values.wso2.apim.configurations.security.truststore.password | quote }} +apim_db_password = {{ .Values.wso2.apim.configurations.databases.apim_db.password | quote}} +shared_db_password = {{ .Values.wso2.apim.configurations.databases.shared_db.password | quote}} +{{- end}} diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/confs/log4j2.properties b/test/apim-apk-agent-test/apim-cp-helm-chart/confs/log4j2.properties new file mode 100644 index 000000000..f3f40b696 --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/confs/log4j2.properties @@ -0,0 +1,525 @@ +# list of all appenders +#add entry "syslog" to use the syslog appender +{{- if .Values.wso2.apim.log4j2.appenders }} +appenders = {{ .Values.wso2.apim.log4j2.appenders }}, CARBON_CONSOLE,HTTP_ACCESS_CONSOLE,AUDIT_CONSOLE,TRANSACTION_CONSOLE,CORRELATION_CONSOLE,CARBON_LOGFILE,AUDIT_LOGFILE,ATOMIKOS_LOGFILE,CARBON_TRACE_LOGFILE,ERROR_LOGFILE,OPEN_TRACING,SERVICE_APPENDER,TRACE_APPENDER,osgi,CORRELATION,BOTDATA_APPENDER,API_LOGFILE +{{- else }} +appenders = CARBON_CONSOLE,HTTP_ACCESS_CONSOLE,AUDIT_CONSOLE,TRANSACTION_CONSOLE,CORRELATION_CONSOLE,CARBON_LOGFILE,AUDIT_LOGFILE,ATOMIKOS_LOGFILE,CARBON_TRACE_LOGFILE,ERROR_LOGFILE,OPEN_TRACING,SERVICE_APPENDER,TRACE_APPENDER,osgi,CORRELATION,BOTDATA_APPENDER,API_LOGFILE +{{- end }} + +# CARBON_CONSOLE is set to be a ConsoleAppender using a PatternLayout. +appender.CARBON_CONSOLE.type = Console +appender.CARBON_CONSOLE.name = CARBON_CONSOLE +appender.CARBON_CONSOLE.layout.type = PatternLayout +appender.CARBON_CONSOLE.layout.pattern = TID: [%X{tenantId}] Tenant: [%X{tenantDomain}] [%d] [%X{Correlation-ID}] : apim-cp : %5p {%c} - %replace{%mm}{\n}{|}%xThrowable{separator(|)}%n +appender.CARBON_CONSOLE.filter.threshold.type = ThresholdFilter +appender.CARBON_CONSOLE.filter.threshold.level = DEBUG + +appender.HTTP_ACCESS_CONSOLE.type = Console +appender.HTTP_ACCESS_CONSOLE.name = HTTP_ACCESS_CONSOLE +appender.HTTP_ACCESS_CONSOLE.layout.type = PatternLayout +appender.HTTP_ACCESS_CONSOLE.layout.pattern = apim-access [%X{Correlation-ID}] %mm%n +appender.HTTP_ACCESS_CONSOLE.filter.threshold.type = ThresholdFilter +appender.HTTP_ACCESS_CONSOLE.filter.threshold.level = INFO + +# AUDIT_CONSOLE is set to be a ConsoleAppender using a PatternLayout. +appender.AUDIT_CONSOLE.type = Console +appender.AUDIT_CONSOLE.name = AUDIT_CONSOLE +appender.AUDIT_CONSOLE.layout.type = PatternLayout +appender.AUDIT_CONSOLE.layout.pattern = TID: [%X{tenantId}] Tenant: [%X{tenantDomain}] [%d] [%X{Correlation-ID}] : apim-cp-audit : %5p {%c} - %replace{%mm}{\n}{|}%xThrowable{separator(|)}%n +appender.AUDIT_CONSOLE.filter.threshold.type = ThresholdFilter +appender.AUDIT_CONSOLE.filter.threshold.level = DEBUG + +# TRANSACTION_CONSOLE is set to be a ConsoleAppender using a PatternLayout. +appender.TRANSACTION_CONSOLE.type = Console +appender.TRANSACTION_CONSOLE.name = TRANSACTION_CONSOLE +appender.TRANSACTION_CONSOLE.layout.type = PatternLayout +appender.TRANSACTION_CONSOLE.layout.pattern = TID: [%X{tenantId}] [%d] [%X{Correlation-ID}] : apim-cp-transaction : {%c} - %mm %n +appender.TRANSACTION_CONSOLE.filter.threshold.type = ThresholdFilter +appender.TRANSACTION_CONSOLE.filter.threshold.level = DEBUG + +# CORRELATION_CONSOLE is set to be a ConsoleAppender using a PatternLayout. +appender.CORRELATION_CONSOLE.type = Console +appender.CORRELATION_CONSOLE.name = CORRELATION_CONSOLE +appender.CORRELATION_CONSOLE.layout.type = PatternLayout +appender.CORRELATION_CONSOLE.layout.pattern = %d{yyyy-MM-dd HH:mm:ss,SSS}|%X{Correlation-ID} : apim-cp-correlation : |%t|%mm%n +appender.CORRELATION_CONSOLE.filter.threshold.type = ThresholdFilter +appender.CORRELATION_CONSOLE.filter.threshold.level = DEBUG + +# CARBON_LOGFILE is set to be a DailyRollingFileAppender using a PatternLayout. +appender.CARBON_LOGFILE.type = RollingFile +appender.CARBON_LOGFILE.name = CARBON_LOGFILE +appender.CARBON_LOGFILE.fileName = ${sys:carbon.home}/repository/logs/wso2carbon.log +appender.CARBON_LOGFILE.filePattern = ${sys:carbon.home}/repository/logs/wso2carbon-%d{MM-dd-yyyy}-%i.log.gz +appender.CARBON_LOGFILE.layout.type = PatternLayout +appender.CARBON_LOGFILE.layout.pattern = TID: [%tenantId] [%appName] [%d] %5p {%c} - %m%ex%n +appender.CARBON_LOGFILE.policies.type = Policies +appender.CARBON_LOGFILE.policies.time.type = TimeBasedTriggeringPolicy +appender.CARBON_LOGFILE.policies.time.interval = 1 +appender.CARBON_LOGFILE.policies.time.modulate = true +appender.CARBON_LOGFILE.policies.size.type = SizeBasedTriggeringPolicy +appender.CARBON_LOGFILE.policies.size.size=1GB +appender.CARBON_LOGFILE.strategy.type = DefaultRolloverStrategy +appender.CARBON_LOGFILE.strategy.max = 20 +appender.CARBON_LOGFILE.filter.threshold.type = ThresholdFilter +appender.CARBON_LOGFILE.filter.threshold.level = DEBUG + +appender.CARBON_LOGFILE.strategy.action.type = Delete +appender.CARBON_LOGFILE.strategy.action.basepath = ${sys:carbon.home}/repository/logs/ +appender.CARBON_LOGFILE.strategy.action.maxdepth = 1 +appender.CARBON_LOGFILE.strategy.action.condition.type = IfLastModified +appender.CARBON_LOGFILE.strategy.action.condition.age = 14D +appender.CARBON_LOGFILE.strategy.action.PathConditions.type = IfFileName +appender.CARBON_LOGFILE.strategy.action.PathConditions.glob = wso2carbon-* + +# Appender config to AUDIT_LOGFILE +appender.AUDIT_LOGFILE.type = RollingFile +appender.AUDIT_LOGFILE.name = AUDIT_LOGFILE +appender.AUDIT_LOGFILE.fileName = ${sys:carbon.home}/repository/logs/audit.log +appender.AUDIT_LOGFILE.filePattern = ${sys:carbon.home}/repository/logs/audit-%d{MM-dd-yyyy}-%i.log +appender.AUDIT_LOGFILE.layout.type = PatternLayout +appender.AUDIT_LOGFILE.layout.pattern = TID: [%tenantId] [%d] %5p {%c} - %m%ex%n +appender.AUDIT_LOGFILE.policies.type = Policies +appender.AUDIT_LOGFILE.policies.time.type = TimeBasedTriggeringPolicy +appender.AUDIT_LOGFILE.policies.time.interval = 1 +appender.AUDIT_LOGFILE.policies.time.modulate = true +appender.AUDIT_LOGFILE.policies.size.type = SizeBasedTriggeringPolicy +appender.AUDIT_LOGFILE.policies.size.size = 10MB +appender.AUDIT_LOGFILE.strategy.type = DefaultRolloverStrategy +appender.AUDIT_LOGFILE.strategy.max = 20 +appender.AUDIT_LOGFILE.filter.threshold.type = ThresholdFilter +appender.AUDIT_LOGFILE.filter.threshold.level = INFO + +# Appender config API logging +appender.API_LOGFILE.type = RollingFile +appender.API_LOGFILE.name = API_LOGFILE +appender.API_LOGFILE.fileName = ${sys:carbon.home}/repository/logs/api.log +appender.API_LOGFILE.filePattern = ${sys:carbon.home}/repository/logs/api-%d{MM-dd-yyyy}-%i.log +appender.API_LOGFILE.layout.type = PatternLayout +appender.API_LOGFILE.layout.pattern = [%d] %5p {%c} %X{apiName} - %m%ex%n +appender.API_LOGFILE.policies.type = Policies +appender.API_LOGFILE.policies.time.type = TimeBasedTriggeringPolicy +appender.API_LOGFILE.policies.time.interval = 1 +appender.API_LOGFILE.policies.time.modulate = true +appender.API_LOGFILE.policies.size.type = SizeBasedTriggeringPolicy +appender.API_LOGFILE.policies.size.size = 10MB +appender.API_LOGFILE.strategy.type = DefaultRolloverStrategy +appender.API_LOGFILE.strategy.max = 20 +appender.API_LOGFILE.filter.threshold.type = ThresholdFilter +appender.API_LOGFILE.filter.threshold.level = DEBUG + +# Appender config to send Atomikos transaction logs to new log file tm.out. +appender.ATOMIKOS_LOGFILE.type = RollingFile +appender.ATOMIKOS_LOGFILE.name = ATOMIKOS_LOGFILE +appender.ATOMIKOS_LOGFILE.fileName = ${sys:carbon.home}/repository/logs/tm.out +appender.ATOMIKOS_LOGFILE.filePattern = ${sys:carbon.home}/repository/logs/tm-%d{MM-dd-yyyy}.out +appender.ATOMIKOS_LOGFILE.layout.type = PatternLayout +appender.ATOMIKOS_LOGFILE.layout.pattern = [%d] [%tenantId] %5p {%c} - %m%ex%n +appender.ATOMIKOS_LOGFILE.policies.type = Policies +appender.ATOMIKOS_LOGFILE.policies.time.type = TimeBasedTriggeringPolicy +appender.ATOMIKOS_LOGFILE.policies.time.interval = 1 +appender.ATOMIKOS_LOGFILE.policies.time.modulate = true +appender.ATOMIKOS_LOGFILE.strategy.type = DefaultRolloverStrategy +appender.ATOMIKOS_LOGFILE.strategy.max = 20 + +# Appender config to CARBON_TRACE_LOGFILE +appender.CARBON_TRACE_LOGFILE.type = RollingFile +appender.CARBON_TRACE_LOGFILE.name = CARBON_TRACE_LOGFILE +appender.CARBON_TRACE_LOGFILE.fileName = ${sys:carbon.home}/repository/logs/wso2carbon-trace-messages.log +appender.CARBON_TRACE_LOGFILE.filePattern = ${sys:carbon.home}/repository/logs/wso2carbon-trace-messages-%d{MM-dd-yyyy}.log +appender.CARBON_TRACE_LOGFILE.layout.type = PatternLayout +appender.CARBON_TRACE_LOGFILE.layout.pattern = [%d] [%tenantId] %5p {%c} - %m%ex%n +appender.CARBON_TRACE_LOGFILE.policies.type = Policies +appender.CARBON_TRACE_LOGFILE.policies.time.type = TimeBasedTriggeringPolicy +appender.CARBON_TRACE_LOGFILE.policies.time.interval = 1 +appender.CARBON_TRACE_LOGFILE.policies.time.modulate = true +appender.CARBON_TRACE_LOGFILE.policies.size.type = SizeBasedTriggeringPolicy +appender.CARBON_TRACE_LOGFILE.policies.size.size = 10MB +appender.CARBON_TRACE_LOGFILE.strategy.type = DefaultRolloverStrategy +appender.CARBON_TRACE_LOGFILE.strategy.max = 20 + +# Appender config to put correlation Log. +appender.CORRELATION.type = RollingFile +appender.CORRELATION.name = CORRELATION +appender.CORRELATION.fileName = ${sys:carbon.home}/repository/logs/correlation.log +appender.CORRELATION.filePattern =${sys:carbon.home}/repository/logs/correlation-%d{MM-dd-yyyy}-%i.log.gz +appender.CORRELATION.layout.type = PatternLayout +appender.CORRELATION.layout.pattern = %d{yyyy-MM-dd HH:mm:ss,SSS}|%X{Correlation-ID}|%t|%m%n +appender.CORRELATION.policies.type = Policies +appender.CORRELATION.policies.time.type = TimeBasedTriggeringPolicy +appender.CORRELATION.policies.time.interval = 1 +appender.CORRELATION.policies.time.modulate = true +appender.CORRELATION.policies.size.type = SizeBasedTriggeringPolicy +appender.CORRELATION.policies.size.size = 10MB +appender.CORRELATION.strategy.type = DefaultRolloverStrategy +appender.CORRELATION.strategy.max = 20 +appender.CORRELATION.filter.threshold.type = ThresholdFilter +appender.CORRELATION.filter.threshold.level = INFO + +appender.ERROR_LOGFILE.type = RollingFile +appender.ERROR_LOGFILE.name = ERROR_LOGFILE +appender.ERROR_LOGFILE.fileName = ${sys:carbon.home}/repository/logs/wso2-apigw-errors.log +appender.ERROR_LOGFILE.filePattern = ${sys:carbon.home}/repository/logs/wso2-apigw-errors-%d{MM-dd-yyyy}-%i.log.gz +appender.ERROR_LOGFILE.layout.type = PatternLayout +appender.ERROR_LOGFILE.layout.pattern = %d{ISO8601} [%X{ip}-%X{host}] [%t] %5p %c{1} %m%n +appender.ERROR_LOGFILE.policies.type = Policies +appender.ERROR_LOGFILE.policies.time.type = TimeBasedTriggeringPolicy +appender.ERROR_LOGFILE.policies.time.interval = 1 +appender.ERROR_LOGFILE.policies.time.modulate = true +appender.ERROR_LOGFILE.policies.size.type = SizeBasedTriggeringPolicy +appender.ERROR_LOGFILE.policies.size.size = 10MB +appender.ERROR_LOGFILE.strategy.type = DefaultRolloverStrategy +appender.ERROR_LOGFILE.strategy.max = 20 +appender.ERROR_LOGFILE.filter.threshold.type = ThresholdFilter +appender.ERROR_LOGFILE.filter.threshold.level = WARN + +appender.CARBON_SYS_LOG.type = Syslog +appender.CARBON_SYS_LOG.name = CARBON_SYS_LOG +appender.CARBON_SYS_LOG.host = localhost +appender.CARBON_SYS_LOG.facility = USER +appender.CARBON_SYS_LOG.layout.type = PatternLayout +appender.CARBON_SYS_LOG.layout.pattern = [%d] %5p - %x %m {%c}%n +appender.CARBON_SYS_LOG.filter.threshold.type = ThresholdFilter +appender.CARBON_SYS_LOG.filter.threshold.level = DEBUG + +appender.OPEN_TRACING.type = RollingFile +appender.OPEN_TRACING.name = OPEN_TRACING +appender.OPEN_TRACING.fileName = ${sys:carbon.home}/repository/logs/wso2-apimgt-open-tracing.log +appender.OPEN_TRACING.filePattern = ${sys:carbon.home}/repository/logs/wso2-apimgt-open-tracing-%d{MM-dd-yyyy}-%i.log.gz +appender.OPEN_TRACING.layout.type = PatternLayout +appender.OPEN_TRACING.layout.pattern = %d{HH:mm:ss,SSS} [%X{ip}-%X{host}] [%t] %5p %m%nn +appender.OPEN_TRACING.policies.type = Policies +appender.OPEN_TRACING.policies.time.type = TimeBasedTriggeringPolicy +appender.OPEN_TRACING.policies.time.interval = 1 +appender.OPEN_TRACING.policies.time.modulate = true +appender.OPEN_TRACING.policies.size.type = SizeBasedTriggeringPolicy +appender.OPEN_TRACING.policies.size.size = 10MB +appender.OPEN_TRACING.strategy.type = DefaultRolloverStrategy +appender.OPEN_TRACING.strategy.max = 20 +appender.OPEN_TRACING.filter.threshold.type = ThresholdFilter +appender.OPEN_TRACING.filter.threshold.level = TRACE + +appender.TRACE_APPENDER.type = RollingFile +appender.TRACE_APPENDER.name = TRACE_APPENDER +appender.TRACE_APPENDER.fileName = ${sys:carbon.home}/repository/logs/wso2-apigw-trace.log +appender.TRACE_APPENDER.filePattern = ${sys:carbon.home}/repository/logs/wso2-apigw-trace-%d{MM-dd-yyyy}.log +appender.TRACE_APPENDER.layout.type = PatternLayout +appender.TRACE_APPENDER.layout.pattern = %d{HH:mm:ss,SSS} [%X{ip}-%X{host}] [%t] %5p %c{1} %m%n +appender.TRACE_APPENDER.policies.type = Policies +appender.TRACE_APPENDER.policies.time.type = TimeBasedTriggeringPolicy +appender.TRACE_APPENDER.policies.time.interval = 1 +appender.TRACE_APPENDER.policies.time.modulate = true +appender.TRACE_APPENDER.strategy.type = DefaultRolloverStrategy +appender.TRACE_APPENDER.strategy.max = 20 + +appender.SERVICE_APPENDER.type = RollingFile +appender.SERVICE_APPENDER.name = SERVICE_APPENDER +appender.SERVICE_APPENDER.fileName = ${sys:carbon.home}/repository/logs/wso2-apigw-service.log +appender.SERVICE_APPENDER.filePattern = ${sys:carbon.home}/repository/logs/wso2-apigw-service-%i.log +appender.SERVICE_APPENDER.layout.type = PatternLayout +appender.SERVICE_APPENDER.layout.pattern = %d{ISO8601} [%X{ip}-%X{host}] [%t] %5p %c{1} %m%n +appender.SERVICE_APPENDER.policies.type = Policies +appender.SERVICE_APPENDER.policies.size.type = SizeBasedTriggeringPolicy +appender.SERVICE_APPENDER.policies.size.size=1000KB +appender.SERVICE_APPENDER.strategy.type = DefaultRolloverStrategy +appender.SERVICE_APPENDER.strategy.max = 10 + +appender.osgi.type = PaxOsgi +appender.osgi.name = PaxOsgi +appender.osgi.filter = * + +{{- println }} +{{- if .Values.wso2.apim.log4j2.loggers }} +loggers = {{ .Values.wso2.apim.log4j2.loggers }}, AUDIT_LOG, trace-messages, org-apache-coyote, com-hazelcast, Owasp-CsrfGuard, org-apache-axis2-wsdl-codegen-writer-PrettyPrinter, org-apache-axis2-clustering, org-apache-catalina, org-apache-tomcat, org-wso2-carbon-apacheds, org-apache-directory-server-ldap, org-apache-directory-server-core-event, com-atomikos, org-quartz, org-apache-jackrabbit-webdav, org-apache-juddi, org-apache-commons-digester-Digester, org-apache-jasper-compiler-TldLocationsCache, org-apache-qpid, org-apache-qpid-server-Main, qpid-message, qpid-message-broker-listening, org-apache-tiles, org-apache-commons-httpclient, org-apache-solr, me-prettyprint-cassandra-hector-TimingLogger, org-apache-axis-enterprise, org-apache-directory-shared-ldap, org-apache-directory-server-ldap-handlers, org-apache-directory-shared-ldap-entry-DefaultServerAttribute, org-apache-directory-server-core-DefaultDirectoryService, org-apache-directory-shared-ldap-ldif-LdifReader, org-apache-directory-server-ldap-LdapProtocolHandler, org-apache-directory-server-core, org-apache-directory-server-ldap-LdapSession, DataNucleus, Datastore, Datastore-Schema, JPOX-Datastore, JPOX-Plugin, JPOX-MetaData, JPOX-Query, JPOX-General, JPOX-Enhancer, org-apache-hadoop-hive, hive, ExecMapper, ExecReducer, net-sf-ehcache-config-ConfigurationFactory, axis2Deployment, equinox, tomcat2, StAXDialectDetector, trace, synapse, synapse_transport, axis2, axis2_transport, org-wso2-carbon, hunsicker, thrift-publisher, service_logger, trace_logger, org-wso2-carbon-apimgt-gateway-mediators-BotDetectionMediator, wso2-callhome, correlation +{{- else }} +loggers = AUDIT_LOG, trace-messages, org-apache-coyote, com-hazelcast, Owasp-CsrfGuard, org-apache-axis2-wsdl-codegen-writer-PrettyPrinter, org-apache-axis2-clustering, org-apache-catalina, org-apache-tomcat, org-wso2-carbon-apacheds, org-apache-directory-server-ldap, org-apache-directory-server-core-event, com-atomikos, org-quartz, org-apache-jackrabbit-webdav, org-apache-juddi, org-apache-commons-digester-Digester, org-apache-jasper-compiler-TldLocationsCache, org-apache-qpid, org-apache-qpid-server-Main, qpid-message, qpid-message-broker-listening, org-apache-tiles, org-apache-commons-httpclient, org-apache-solr, me-prettyprint-cassandra-hector-TimingLogger, org-apache-axis-enterprise, org-apache-directory-shared-ldap, org-apache-directory-server-ldap-handlers, org-apache-directory-shared-ldap-entry-DefaultServerAttribute, org-apache-directory-server-core-DefaultDirectoryService, org-apache-directory-shared-ldap-ldif-LdifReader, org-apache-directory-server-ldap-LdapProtocolHandler, org-apache-directory-server-core, org-apache-directory-server-ldap-LdapSession, DataNucleus, Datastore, Datastore-Schema, JPOX-Datastore, JPOX-Plugin, JPOX-MetaData, JPOX-Query, JPOX-General, JPOX-Enhancer, org-apache-hadoop-hive, hive, ExecMapper, ExecReducer, net-sf-ehcache-config-ConfigurationFactory, axis2Deployment, equinox, tomcat2, StAXDialectDetector, trace, synapse, synapse_transport, axis2, axis2_transport, org-wso2-carbon, hunsicker, thrift-publisher, service_logger, trace_logger, org-wso2-carbon-apimgt-gateway-mediators-BotDetectionMediator, wso2-callhome, correlation +{{- end }} + +logger.API_LOG.name = API_LOG +logger.API_LOG.level = INFO +logger.API_LOG.appenderRef.API_LOGFILE.ref = API_LOGFILE +logger.API_LOG.additivity = false + +logger.AUDIT_LOG.name = AUDIT_LOG +logger.AUDIT_LOG.level = INFO +logger.AUDIT_LOG.appenderRef.AUDIT_LOGFILE.ref = AUDIT_LOGFILE +logger.AUDIT_LOG.additivity = false + +logger.AUDIT_LOG_CONSOLE.name = AUDIT_LOG_CONSOLE +logger.AUDIT_LOG_CONSOLE.level = INFO +logger.AUDIT_LOG_CONSOLE.appenderRef.AUDIT_CONSOLE.ref = AUDIT_CONSOLE +logger.AUDIT_LOG_CONSOLE.additivity = false + +logger.HTTP_ACCESS_CONSOLE.name = HTTP_ACCESS_CONSOLE +logger.HTTP_ACCESS_CONSOLE.level = INFO +logger.HTTP_ACCESS_CONSOLE.appenderRef.HTTP_ACCESS_CONSOLE.ref = HTTP_ACCESS_CONSOLE +logger.HTTP_ACCESS_CONSOLE.additivity = false + +logger.TRANSACTION_CONSOLE.name = TRANSACTION_CONSOLE +logger.TRANSACTION_CONSOLE.level = INFO +logger.TRANSACTION_CONSOLE.appenderRef.TRANSACTION_CONSOLE.ref = TRANSACTION_CONSOLE +logger.TRANSACTION_CONSOLE.additivity = false + +logger.CORRELATION_CONSOLE.name = CORRELATION_CONSOLE +logger.CORRELATION_CONSOLE.level = INFO +logger.CORRELATION_CONSOLE.appenderRef.CORRELATION_CONSOLE.ref = CORRELATION_CONSOLE +logger.CORRELATION_CONSOLE.additivity = false + +logger.trace-messages.name = trace.messages +logger.trace-messages.level = TRACE +logger.trace-messages.appenderRef.CARBON_TRACE_LOGFILE.ref = CARBON_TRACE_LOGFILE + +logger.org-apache-coyote.name = org.apache.coyote +logger.org-apache-coyote.level = WARN + +logger.com-hazelcast.name = com.hazelcast +logger.com-hazelcast.level = ERROR + +logger.Owasp-CsrfGuard.name = Owasp.CsrfGuard +logger.Owasp-CsrfGuard.level = WARN + +logger.org-apache-axis2-wsdl-codegen-writer-PrettyPrinter.name = org.apache.axis2.wsdl.codegen.writer.PrettyPrinter +logger.org-apache-axis2-wsdl-codegen-writer-PrettyPrinter.level = ERROR +logger.org-apache-axis2-wsdl-codegen-writer-PrettyPrinter.appenderRef.CARBON_LOGFILE.ref = CARBON_LOGFILE + +logger.org-apache-axis2-clustering.name = org.apache.axis2.clustering +logger.org-apache-axis2-clustering.level = INFO +logger.org-apache-axis2-clustering.additivity = false + +logger.org-apache.name = org.apache +logger.org-apache.level = INFO +logger.org-apache.additivity = false +logger.org-apache.appenderRef.CARBON_LOGFILE.ref = CARBON_LOGFILE + +logger.org-apache-catalina.name = org.apache.catalina +logger.org-apache-catalina.level = ERROR + +logger.org-apache-tomcat.name = org.apache.tomcat +logger.org-apache-tomcat.level = INFO + +logger.org-wso2-carbon-apacheds.name = org.wso2.carbon.apacheds +logger.org-wso2-carbon-apacheds.level = WARN + +logger.org-apache-directory-server-ldap.name = org.apache.directory.server.ldap +logger.org-apache-directory-server-ldap.level = ERROR + +logger.org-apache-directory-server-core-event.name = org.apache.directory.server.core.event +logger.org-apache-directory-server-core-event.level = WARN + +logger.com-atomikos.name = com.atomikos +logger.com-atomikos.level = INFO +logger.com-atomikos.additivity = false +logger.com-atomikos.appenderRef.ATOMIKOS_LOGFILE.ref = ATOMIKOS_LOGFILE + +logger.org-quartz.name = org.quartz +logger.org-quartz.level = WARN + +logger.org-apache-jackrabbit-webdav.name = org.apache.jackrabbit.webdav +logger.org-apache-jackrabbit-webdav.level = WARN + +logger.org-apache-juddi.name = org.apache.juddi +logger.org-apache-juddi.level = ERROR + +logger.org-apache-commons-digester-Digester.name = org.apache.commons.digester.Digester +logger.org-apache-commons-digester-Digester.level = WARN + +logger.org-apache-jasper-compiler-TldLocationsCache.name = org.apache.jasper.compiler.TldLocationsCache +logger.org-apache-jasper-compiler-TldLocationsCache.level = WARN + +logger.org-apache-qpid.name = org.apache.qpid +logger.org-apache-qpid.level = WARN + +logger.org-apache-qpid-server-Main.name = org.apache.qpid.server.Main +logger.org-apache-qpid-server-Main.level = INFO + +logger.qpid-message.name = qpid.message +logger.qpid-message.level = WARN + +logger.qpid-message-broker-listening.name = qpid.message.broker.listening +logger.qpid-message-broker-listening.level = INFO + +logger.org-apache-tiles.name = org.apache.tiles +logger.org-apache-tiles.level = WARN + +logger.org-apache-commons-httpclient.name = org.apache.commons.httpclient +logger.org-apache-commons-httpclient.level = ERROR + +logger.org-apache-solr.name = org.apache.solr +logger.org-apache-solr.level = ERROR + +logger.me-prettyprint-cassandra-hector-TimingLogger.name = me.prettyprint.cassandra.hector.TimingLogger +logger.me-prettyprint-cassandra-hector-TimingLogger.level = ERROR + +logger.org-wso2.name = org.wso2 +logger.org-wso2.level = INFO + +logger.org-wso2-carbon.name = org.wso2.carbon +logger.org-wso2-carbon.level = INFO + +logger.org-apache-axis-enterprise.name = org.apache.axis2.enterprise +logger.org-apache-axis-enterprise.level = FATAL +logger.org-apache-axis-enterprise.appenderRef.CARBON_LOGFILE.ref = CARBON_LOGFILE + +logger.org-apache-directory-shared-ldap.name = org.apache.directory.shared.ldap +logger.org-apache-directory-shared-ldap.level = WARN +logger.org-apache-directory-shared-ldap.appenderRef.CARBON_LOGFILE.ref = CARBON_LOGFILE + +logger.org-apache-directory-server-ldap-handlers.name = org.apache.directory.server.ldap.handlers +logger.org-apache-directory-server-ldap-handlers.level = WARN +logger.org-apache-directory-server-ldap-handlers.appenderRef.CARBON_LOGFILE.ref = CARBON_LOGFILE + +# Following are to remove false error messages from startup (IS) +logger.org-apache-directory-shared-ldap-entry-DefaultServerAttribute.name = org.apache.directory.shared.ldap.entry.DefaultServerAttribute +logger.org-apache-directory-shared-ldap-entry-DefaultServerAttribute.level = FATAL +logger.org-apache-directory-shared-ldap-entry-DefaultServerAttribute.appenderRef.CARBON_LOGFILE.ref = CARBON_LOGFILE + +logger.org-apache-directory-server-core-DefaultDirectoryService.name = org.apache.directory.server.core.DefaultDirectoryService +logger.org-apache-directory-server-core-DefaultDirectoryService.level = ERROR +logger.org-apache-directory-server-core-DefaultDirectoryService.appenderRef.CARBON_LOGFILE.ref = CARBON_LOGFILE + +logger.org-apache-directory-shared-ldap-ldif-LdifReader.name = org.apache.directory.shared.ldap.ldif.LdifReader +logger.org-apache-directory-shared-ldap-ldif-LdifReader.level = ERROR +logger.org-apache-directory-shared-ldap-ldif-LdifReader.appenderRef.CARBON_LOGFILE.ref = CARBON_LOGFILE + +logger.org-apache-directory-server-ldap-LdapProtocolHandler.name = org.apache.directory.server.ldap.LdapProtocolHandler +logger.org-apache-directory-server-ldap-LdapProtocolHandler.level = ERROR +logger.org-apache-directory-server-ldap-LdapProtocolHandler.appenderRef.CARBON_LOGFILE.ref = CARBON_LOGFILE + +logger.org-apache-directory-server-core.name = org.apache.directory.server.core +logger.org-apache-directory-server-core.level = ERROR +logger.org-apache-directory-server-core.appenderRef.CARBON_LOGFILE.ref = CARBON_LOGFILE + +logger.org-apache-directory-server-ldap-LdapSession.name = org.apache.directory.server.ldap.LdapSession +logger.org-apache-directory-server-ldap-LdapSession.level = Error +logger.org-apache-directory-server-ldap-LdapSession.appenderRef.CARBON_LOGFILE.ref = CARBON_LOGFILE + +logger.correlation.name = correlation +logger.correlation.level = INFO +logger.correlation.appenderRef.CORRELATION.ref = CORRELATION +logger.correlation.additivity = false + +# Hive Related Log configurations +logger.DataNucleus.name = DataNucleus +logger.DataNucleus.level = ERROR + +logger.Datastore.name = Datastore +logger.Datastore.level = ERROR + +logger.Datastore-Schema.name = Datastore.Schema +logger.Datastore-Schema.level = ERROR + +logger.JPOX-Datastore.name = JPOX.Datastore +logger.JPOX-Datastore.level = ERROR + +logger.JPOX-Plugin.name = JPOX.Plugin +logger.JPOX-Plugin.level = ERROR + +logger.JPOX-MetaData.name = JPOX.MetaData +logger.JPOX-MetaData.level = ERROR + +logger.JPOX-Query.name = JPOX.Query +logger.JPOX-Query.level = ERROR + +logger.JPOX-General.name = JPOX.General +logger.JPOX-General.level = ERROR + +logger.JPOX-Enhancer.name = JPOX.Enhancer +logger.JPOX-Enhancer.level = ERROR + +logger.org-apache-hadoop-hive.name = org.apache.hadoop.hive +logger.org-apache-hadoop-hive.level = WARN + +logger.hive.name = hive +logger.hive.level = WARN + +logger.ExecMapper.name = ExecMapper +logger.ExecMapper.level = WARN + +logger.ExecReducer.name = ExecReducer +logger.ExecReducer.level = WARN + +logger.net-sf-ehcache-config-ConfigurationFactory.name = net.sf.ehcache.config.ConfigurationFactory +logger.net-sf-ehcache-config-ConfigurationFactory.level = ERROR + +logger.axis2Deployment.name = org.apache.axis2.deployment +logger.axis2Deployment.level = WARN + +logger.equinox.name = org.eclipse.equinox +logger.equinox.level = FATAL + +logger.tomcat2.name = tomcat +logger.tomcat2.level = FATAL + +logger.StAXDialectDetector.name = org.apache.axiom.util.stax.dialect.StAXDialectDetector +logger.StAXDialectDetector.level = ERROR + +logger.trace.name = tracer +logger.trace.level = TRACE +logger.trace.appenderRef.OPEN_TRACING.ref = OPEN_TRACING + +logger.synapse.name = org.apache.synapse +logger.synapse.level = INFO + +logger.synapse_transport.name = org.apache.synapse.transport +logger.synapse_transport.level = INFO + +logger.axis2.name = org.apache.axis2 +logger.axis2.level = INFO + +logger.axis2_transport.name = org.apache.axis2.transport +logger.axis2_transport.level = INFO + +logger.hunsicker.name = de.hunsicker.jalopy.io +logger.hunsicker.level = FATAL + +logger.synapse-headers.name = org.apache.synapse.transport.http.headers +logger.synapse-headers.level = DEBUG + +logger.synapse-wire.name = org.apache.synapse.transport.http.wire +logger.synapse-wire.level = DEBUG + +logger.thrift-publisher.name = org.wso2.carbon.databridge.agent.thrift.AsyncDataPublisher +logger.thrift-publisher.level = WARN + +logger.service_logger.name = SERVICE_LOGGER +logger.service_logger.level = INFO +logger.service_logger.additivity = false +logger.service_logger.appenderRef.SERVICE_APPENDER.ref = SERVICE_APPENDER + +logger.wso2-callhome.name = org.wso2.callhome +logger.wso2-callhome.level = INFO + +logger.trace_logger.name = TRACE_LOGGER +logger.trace_logger.level = INFO +logger.trace_logger.appenderRef.TRACE_APPENDER.ref = TRACE_APPENDER + +# root loggers +rootLogger.level = ERROR +rootLogger.appenderRef.CARBON_CONSOLE.ref = CARBON_CONSOLE +rootLogger.appenderRef.CARBON_LOGFILE.ref = CARBON_LOGFILE +rootLogger.appenderRef.ERROR_LOGFILE.ref = ERROR_LOGFILE +rootLogger.appenderRef.PaxOsgi.ref = PaxOsgi +#rootLogger.appenderReg.CARBON_SYS_LOG.ref = CARBON_SYS_LOG +#rootLogger.appenderRef.syslog.ref = syslog + +# bot detection feature appender +appender.BOTDATA_APPENDER.type = RollingFile +appender.BOTDATA_APPENDER.name = BOTDATA_APPENDER +appender.BOTDATA_APPENDER.fileName = ${sys:carbon.home}/repository/logs/wso2-BotDetectedData.log +appender.BOTDATA_APPENDER.filePattern = ${sys:carbon.home}/repository/logs/wso2-BotDetectedData-%d{MM-dd-yyyy}.log +appender.BOTDATA_APPENDER.layout.type = PatternLayout +appender.BOTDATA_APPENDER.layout.pattern = [%d] [%tenantId] %5p {%c} - %m%ex%n +appender.BOTDATA_APPENDER.policies.type = Policies +appender.BOTDATA_APPENDER.policies.time.type = TimeBasedTriggeringPolicy +appender.BOTDATA_APPENDER.policies.time.interval = 1 +appender.BOTDATA_APPENDER.policies.time.modulate = true +appender.BOTDATA_APPENDER.policies.size.type = SizeBasedTriggeringPolicy +appender.BOTDATA_APPENDER.policies.size.size = 10MB +appender.BOTDATA_APPENDER.strategy.type = DefaultRolloverStrategy +appender.BOTDATA_APPENDER.strategy.max = 20 + +logger.org-wso2-carbon-apimgt-gateway-mediators-BotDetectionMediator.name = org.wso2.carbon.apimgt.gateway.mediators.BotDetectionMediator +logger.org-wso2-carbon-apimgt-gateway-mediators-BotDetectionMediator.level = INFO +logger.org-wso2-carbon-apimgt-gateway-mediators-BotDetectionMediator.appenderRef.BOTDATA_APPENDER.ref = BOTDATA_APPENDER +logger.org-wso2-carbon-apimgt-gateway-mediators-BotDetectionMediator.additivity = false + +category.SERVICE_APPENDER._OpenService_ = TRACE_APPENDER, BOTDATA_APPENDER diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/confs/secret-conf.properties b/test/apim-apk-agent-test/apim-cp-helm-chart/confs/secret-conf.properties new file mode 100644 index 000000000..645e5539b --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/confs/secret-conf.properties @@ -0,0 +1,12 @@ +keystore.identity.location=/home/wso2carbon/wso2am-{{ .Values.wso2.apim.version }}/repository/resources/security/{{ .Values.wso2.apim.configurations.security.keystores.internal.name }} +keystore.identity.type=JKS +keystore.identity.store.password=identity.store.password +keystore.identity.store.secretProvider=org.wso2.carbon.securevault.DefaultSecretCallbackHandler +secretRepositories.file.provider=org.wso2.securevault.secret.repository.FileBaseSecretRepositoryProvider +secretRepositories.file.location=repository/conf/security/cipher-text.properties +secretRepositories=file +secVault.enabled=true +keystore.identity.key.password=identity.key.password +carbon.secretProvider=org.wso2.securevault.secret.handler.SecretManagerSecretCallbackHandler +keystore.identity.key.secretProvider=org.wso2.carbon.securevault.DefaultSecretCallbackHandler +keystore.identity.alias={{ .Values.wso2.apim.configurations.security.keystores.internal.alias }} diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/_helpers.tpl b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/_helpers.tpl new file mode 100644 index 000000000..704ae2467 --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/_helpers.tpl @@ -0,0 +1,69 @@ +{{/* +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# --------------------------------------------------------------------------------------s +*/}} + +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "apim-helm-cp.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "apim-helm-cp.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "apim-helm-cp.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "apim-helm-cp.labels" -}} +app.kubernetes.io/name: {{ include "apim-helm-cp.name" . }} +helm.sh/chart: {{ include "apim-helm-cp.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Common prefix prepended to Kubernetes resources of this chart +*/}} +{{- define "apim-helm-cp.resource.prefix" -}} +{{- if eq .Values.kubernetes.resourceSuffix "${RESOURCE_SUFFIX}" }} +{{- "wso2am-cp" }} +{{- else }} +{{- "wso2am-cp-" }}{{ .Values.kubernetes.resourceSuffix }} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/instance-1/wso2am-cp-conf.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/instance-1/wso2am-cp-conf.yaml new file mode 100644 index 000000000..76fbcebb4 --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/instance-1/wso2am-cp-conf.yaml @@ -0,0 +1,18 @@ +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# -------------------------------------------------------------------------------------- + +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-conf-1 + namespace : {{ .Release.Namespace }} +data: + deployment.toml: {{ tpl (.Files.Get "confs/instance-1/deployment.toml") . | quote }} diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/instance-1/wso2am-cp-deployment.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/instance-1/wso2am-cp-deployment.yaml new file mode 100644 index 000000000..50674373c --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/instance-1/wso2am-cp-deployment.yaml @@ -0,0 +1,233 @@ +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# -------------------------------------------------------------------------------------- + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-deployment-1 + namespace: {{ .Release.Namespace }} +spec: + replicas: {{ .Values.wso2.deployment.replicas }} + strategy: + type: Recreate + selector: + matchLabels: + deployment: {{ template "apim-helm-cp.fullname" . }} + node: {{ template "apim-helm-cp.fullname" . }}-1 + template: + metadata: + annotations: + {{- if .Values.kubernetes.enableAppArmor }} + container.apparmor.security.beta.kubernetes.io/wso2am-control-plane: runtime/default + {{- end }} + checksum.am.cp.conf: {{ include (print $.Template.BasePath "/control-plane/instance-1/wso2am-cp-conf.yaml") . | sha256sum }} + checksum.am.cp.conf.log4j2: {{ include (print $.Template.BasePath "/control-plane/wso2am-cp-conf-log4j2.yaml") . | sha256sum }} + checksum.am.cp.conf.secret: {{ include (print $.Template.BasePath "/control-plane/wso2am-cp-conf-secret-conf.yaml") . | sha256sum }} + labels: + deployment: {{ template "apim-helm-cp.fullname" . }} + node: {{ template "apim-helm-cp.fullname" . }}-1 + product: apim + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: deployment + operator: In + values: + - {{ template "apim-helm-cp.fullname" . }} + topologyKey: "topology.kubernetes.io/zone" + weight: 100 + {{- if .Values.wso2.apim.secureVaultEnabled }} + {{- if .Values.aws.enabled }} + serviceAccount: {{ .Values.aws.serviceAccountName }} + {{- else if .Values.gcp.enabled }} + serviceAccount: {{ .Values.gcp.serviceAccountName }} + {{- else if .Values.azure.enabled }} + serviceAccount: {{ .Values.azure.serviceAccountName }} + {{- end }} + {{- end }} + securityContext: + seccompProfile: + type: RuntimeDefault + containers: + - name: wso2am-control-plane + image: sampathrajapakse/wso2am:4.3.0 + imagePullPolicy: {{ .Values.wso2.deployment.imagePullPolicy }} + env: + - name: PROFILE_NAME + value: control-plane + - name: NODE_IP + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: status.podIP + - name: JVM_MEM_OPTS + value: "-Xms{{ .Values.wso2.deployment.resources.jvm.memory.xms }} -Xmx{{ .Values.wso2.deployment.resources.jvm.memory.xmx }}" + startupProbe: + exec: + command: + - /bin/sh + - -c + - nc -z localhost {{ add 9443 .Values.wso2.apim.portOffset }} + initialDelaySeconds: {{ .Values.wso2.deployment.startupProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.wso2.deployment.startupProbe.periodSeconds }} + failureThreshold: {{ .Values.wso2.deployment.startupProbe.failureThreshold }} + livenessProbe: + httpGet: + path: /services/Version + port: {{ add 9763 .Values.wso2.apim.portOffset }} + initialDelaySeconds: {{ .Values.wso2.deployment.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.wso2.deployment.livenessProbe.periodSeconds }} + failureThreshold: {{ .Values.wso2.deployment.livenessProbe.failureThreshold }} + readinessProbe: + httpGet: + path: /services/Version + port: {{ add 9763 .Values.wso2.apim.portOffset }} + initialDelaySeconds: {{ .Values.wso2.deployment.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.wso2.deployment.readinessProbe.periodSeconds }} + failureThreshold: {{ .Values.wso2.deployment.readinessProbe.failureThreshold }} + lifecycle: + preStop: + exec: + command: + - "sh" + - "-c" + - > + echo "Pre stop hook triggered"; + sleep {{ .Values.wso2.deployment.lifecycle.preStopHook.sleepSeconds }}; + echo "Shutdown APIM Server"; + ${WSO2_SERVER_HOME}/bin/api-manager.sh stop + resources: + requests: + memory: {{ .Values.wso2.deployment.resources.requests.memory }} + cpu: {{ .Values.wso2.deployment.resources.requests.cpu }} + limits: + memory: {{ .Values.wso2.deployment.resources.limits.memory }} + cpu: {{ .Values.wso2.deployment.resources.limits.cpu }} + securityContext: + runAsUser: {{ .Values.kubernetes.securityContext.runAsUser }} + allowPrivilegeEscalation: false + runAsNonRoot: true + capabilities: + drop: + - all + ports: + - containerPort: {{ add 9763 .Values.wso2.apim.portOffset }} + protocol: "TCP" + - containerPort: {{ add 9443 .Values.wso2.apim.portOffset }} + protocol: "TCP" + - containerPort: {{ add 9711 .Values.wso2.apim.portOffset }} + protocol: "TCP" + - containerPort: {{ add 9611 .Values.wso2.apim.portOffset }} + protocol: "TCP" + - containerPort: {{ add 5672 .Values.wso2.apim.portOffset }} + protocol: "TCP" + volumeMounts: + - name: wso2am-control-plane-conf + mountPath: /home/wso2carbon/wso2-config-volume/repository/conf/deployment.toml + subPath: deployment.toml + - name: wso2am-control-plane-entrypoint + mountPath: /home/wso2carbon/docker-entrypoint.sh + subPath: docker-entrypoint.sh + - name: wso2am-control-plane-log4j2 + mountPath: /home/wso2carbon/wso2-config-volume/repository/conf/log4j2.properties + subPath: log4j2.properties + - name: wso2am-sh-conf + mountPath: /home/wso2carbon/wso2-config-volume/bin/api-manager.sh + subPath: api-manager.sh + {{- if .Values.wso2.apim.secureVaultEnabled }} + - name: wso2am-control-plane-secret-conf + mountPath: /home/wso2carbon/wso2-config-volume/repository/conf/security/secret-conf.properties + subPath: secret-conf.properties + - name: wso2am-control-plane-secret-store-csi + mountPath: /mnt/secrets-store + readOnly: true + {{- end }} +# - name: wso2am-control-plane-keystores +# mountPath: /home/wso2carbon/wso2-config-volume/repository/resources/security/{{ .Values.wso2.apim.configurations.security.truststore.name }} +# subPath: {{ .Values.wso2.apim.configurations.security.truststore.name }} +# {{- if .Values.wso2.apim.configurations.security.keystores.primary.enabled }} +# - name: wso2am-control-plane-keystores +# mountPath: /home/wso2carbon/wso2-config-volume/repository/resources/security/{{ .Values.wso2.apim.configurations.security.keystores.primary.name }} +# subPath: {{ .Values.wso2.apim.configurations.security.keystores.primary.name }} +# {{- end }} +# {{- if .Values.wso2.apim.configurations.security.keystores.tls.enabled }} +# - name: wso2am-control-plane-keystores +# mountPath: /home/wso2carbon/wso2-config-volume/repository/resources/security/{{ .Values.wso2.apim.configurations.security.keystores.tls.name }} +# subPath: {{ .Values.wso2.apim.configurations.security.keystores.tls.name }} +# {{- end }} +# {{- if .Values.wso2.apim.configurations.security.keystores.internal.enabled }} +# - name: wso2am-control-plane-keystores +# mountPath: /home/wso2carbon/wso2-config-volume/repository/resources/security/{{ .Values.wso2.apim.configurations.security.keystores.internal.name }} +# subPath: {{ .Values.wso2.apim.configurations.security.keystores.internal.name }} +# {{- end }} + {{ if .Values.wso2.deployment.persistence.solrIndexing.enabled }} + - name: wso2am-control-plane-local-carbondb + mountPath: /home/wso2carbon/solr/database + - name: wso2am-control-plane-solr + mountPath: /home/wso2carbon/solr/indexed-data + {{ end }} + {{- if .Values.wso2.deployment.nodeSelector }} + nodeSelector: + {{- toYaml .Values.wso2.deployment.nodeSelector | nindent 8 }} + {{- end }} + volumes: + - name: wso2am-control-plane-conf + configMap: + name: {{ template "apim-helm-cp.fullname" . }}-conf-1 + defaultMode: 0407 + - name: wso2am-control-plane-entrypoint + configMap: + name: {{ template "apim-helm-cp.fullname" . }}-conf-entrypoint + defaultMode: 0407 + - name: wso2am-control-plane-log4j2 + configMap: + name: {{ template "apim-helm-cp.fullname" . }}-conf-log4j2 + defaultMode: 0407 + - name: wso2am-sh-conf + configMap: + name: {{ template "apim-helm-cp.fullname" . }}-conf-sh + defaultMode: 0407 + {{- if .Values.wso2.apim.secureVaultEnabled }} + - name: wso2am-control-plane-secret-conf + configMap: + name: {{ template "apim-helm-cp.fullname" . }}-conf-secret-conf + - name: wso2am-control-plane-secret-store-csi + csi: + driver: secrets-store.csi.k8s.io + readOnly: true + {{- if .Values.azure.enabled }} + volumeAttributes: + secretProviderClass: {{ .Values.azure.keyVault.secretProviderClass }} + nodePublishSecretRef: + name: {{ .Values.azure.keyVault.activeDirectory.servicePrincipal.credentialsSecretName }} + {{- else if .Values.aws.enabled }} + volumeAttributes: + secretProviderClass: {{ .Values.aws.secretsManager.secretProviderClass }} + {{- else if .Values.gcp.enabled }} + volumeAttributes: + secretProviderClass: {{ .Values.gcp.secretsManager.secretProviderClass }} + {{- end }} + {{- end }} +# - name: wso2am-control-plane-keystores +# secret: +# secretName: {{ .Values.wso2.apim.configurations.security.jksSecretName }} + {{ if .Values.wso2.deployment.persistence.solrIndexing.enabled }} + - name: wso2am-control-plane-local-carbondb + persistentVolumeClaim: + claimName: {{ template "apim-helm-cp.fullname" . }}-local-carbon-database-1 + - name: wso2am-control-plane-solr + persistentVolumeClaim: + claimName: {{ template "apim-helm-cp.fullname" . }}-solr-indexed-data-1 + {{ end }} diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/instance-1/wso2am-cp-service.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/instance-1/wso2am-cp-service.yaml new file mode 100644 index 000000000..f35478553 --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/instance-1/wso2am-cp-service.yaml @@ -0,0 +1,35 @@ +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# -------------------------------------------------------------------------------------- + +apiVersion: v1 +kind: Service +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-1-service + namespace : {{ .Release.Namespace }} +spec: + # label keys and values that must match in order to receive traffic for this service + selector: + deployment: {{ template "apim-helm-cp.fullname" . }} + node: {{ template "apim-helm-cp.fullname" . }}-1 + ports: + # ports that this service should serve on + - name: binary + protocol: TCP + port: {{ add 9611 .Values.wso2.apim.portOffset }} + - name: binary-secure + protocol: TCP + port: {{ add 9711 .Values.wso2.apim.portOffset }} + - name: jms-tcp + protocol: TCP + port: {{ add 5672 .Values.wso2.apim.portOffset }} + - name: servlet-https + protocol: TCP + port: {{ add 9443 .Values.wso2.apim.portOffset }} diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/instance-2/wso2am-cp-conf.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/instance-2/wso2am-cp-conf.yaml new file mode 100644 index 000000000..368f29442 --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/instance-2/wso2am-cp-conf.yaml @@ -0,0 +1,20 @@ +{{- if .Values.wso2.deployment.highAvailability}} +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# -------------------------------------------------------------------------------------- + +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-conf-2 + namespace : {{ .Release.Namespace }} +data: + deployment.toml: {{ tpl (.Files.Get "confs/instance-2/deployment.toml") . | quote }} +{{- end }} diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/instance-2/wso2am-cp-deployment.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/instance-2/wso2am-cp-deployment.yaml new file mode 100644 index 000000000..a3a3de888 --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/instance-2/wso2am-cp-deployment.yaml @@ -0,0 +1,235 @@ +{{- if .Values.wso2.deployment.highAvailability}} +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# -------------------------------------------------------------------------------------- + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-deployment-2 + namespace: {{ .Release.Namespace }} +spec: + replicas: {{ .Values.wso2.deployment.replicas }} + strategy: + type: Recreate + selector: + matchLabels: + deployment: {{ template "apim-helm-cp.fullname" . }} + node: {{ template "apim-helm-cp.fullname" . }}-2 + template: + metadata: + annotations: + {{- if .Values.kubernetes.enableAppArmor }} + container.apparmor.security.beta.kubernetes.io/wso2am-control-plane: runtime/default + {{- end }} + checksum.am.cp.conf: {{ include (print $.Template.BasePath "/control-plane/instance-2/wso2am-cp-conf.yaml") . | sha256sum }} + checksum.am.cp.conf.log4j2: {{ include (print $.Template.BasePath "/control-plane/wso2am-cp-conf-log4j2.yaml") . | sha256sum }} + checksum.am.cp.conf.secret: {{ include (print $.Template.BasePath "/control-plane/wso2am-cp-conf-secret-conf.yaml") . | sha256sum }} + labels: + deployment: {{ template "apim-helm-cp.fullname" . }} + node: {{ template "apim-helm-cp.fullname" . }}-2 + product: apim + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: deployment + operator: In + values: + - {{ template "apim-helm-cp.fullname" . }} + topologyKey: "topology.kubernetes.io/zone" + weight: 100 + {{- if .Values.wso2.apim.secureVaultEnabled }} + {{- if .Values.aws.enabled }} + serviceAccount: {{ .Values.aws.serviceAccountName }} + {{- else if .Values.gcp.enabled }} + serviceAccount: {{ .Values.gcp.serviceAccountName }} + {{- else if .Values.azure.enabled }} + serviceAccount: {{ .Values.azure.serviceAccountName }} + {{- end }} + {{- end }} + securityContext: + seccompProfile: + type: RuntimeDefault + containers: + - name: wso2am-control-plane + image: {{ .Values.wso2.deployment.image.registry }}/{{ .Values.wso2.deployment.image.repository }}@{{ .Values.wso2.deployment.image.digest }} + imagePullPolicy: {{ .Values.wso2.deployment.imagePullPolicy }} + env: + - name: PROFILE_NAME + value: control-plane + - name: NODE_IP + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: status.podIP + - name: JVM_MEM_OPTS + value: "-Xms{{ .Values.wso2.deployment.resources.jvm.memory.xms }} -Xmx{{ .Values.wso2.deployment.resources.jvm.memory.xmx }}" + startupProbe: + exec: + command: + - /bin/sh + - -c + - nc -z localhost {{ add 9443 .Values.wso2.apim.portOffset }} + initialDelaySeconds: {{ .Values.wso2.deployment.startupProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.wso2.deployment.startupProbe.periodSeconds }} + failureThreshold: {{ .Values.wso2.deployment.startupProbe.failureThreshold }} + livenessProbe: + httpGet: + path: /services/Version + port: {{ add 9763 .Values.wso2.apim.portOffset }} + initialDelaySeconds: {{ .Values.wso2.deployment.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.wso2.deployment.livenessProbe.periodSeconds }} + failureThreshold: {{ .Values.wso2.deployment.livenessProbe.failureThreshold }} + readinessProbe: + httpGet: + path: /services/Version + port: {{ add 9763 .Values.wso2.apim.portOffset }} + initialDelaySeconds: {{ .Values.wso2.deployment.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.wso2.deployment.readinessProbe.periodSeconds }} + failureThreshold: {{ .Values.wso2.deployment.readinessProbe.failureThreshold }} + lifecycle: + preStop: + exec: + command: + - "sh" + - "-c" + - > + echo "Pre stop hook triggered"; + sleep {{ .Values.wso2.deployment.lifecycle.preStopHook.sleepSeconds }}; + echo "Shutdown APIM Server"; + ${WSO2_SERVER_HOME}/bin/api-manager.sh stop + resources: + requests: + memory: {{ .Values.wso2.deployment.resources.requests.memory }} + cpu: {{ .Values.wso2.deployment.resources.requests.cpu }} + limits: + memory: {{ .Values.wso2.deployment.resources.limits.memory }} + cpu: {{ .Values.wso2.deployment.resources.limits.cpu }} + securityContext: + runAsUser: {{ .Values.kubernetes.securityContext.runAsUser }} + allowPrivilegeEscalation: false + runAsNonRoot: true + capabilities: + drop: + - all + ports: + - containerPort: {{ add 9763 .Values.wso2.apim.portOffset }} + protocol: "TCP" + - containerPort: {{ add 9443 .Values.wso2.apim.portOffset }} + protocol: "TCP" + - containerPort: {{ add 9711 .Values.wso2.apim.portOffset }} + protocol: "TCP" + - containerPort: {{ add 9611 .Values.wso2.apim.portOffset }} + protocol: "TCP" + - containerPort: {{ add 5672 .Values.wso2.apim.portOffset }} + protocol: "TCP" + volumeMounts: + - name: wso2am-control-plane-conf + mountPath: /home/wso2carbon/wso2-config-volume/repository/conf/deployment.toml + subPath: deployment.toml + - name: wso2am-control-plane-entrypoint + mountPath: /home/wso2carbon/docker-entrypoint.sh + subPath: docker-entrypoint.sh + - name: wso2am-control-plane-log4j2 + mountPath: /home/wso2carbon/wso2-config-volume/repository/conf/log4j2.properties + subPath: log4j2.properties + - name: wso2am-sh-conf + mountPath: /home/wso2carbon/wso2-config-volume/bin/api-manager.sh + subPath: api-manager.sh + {{- if .Values.wso2.apim.secureVaultEnabled }} + - name: wso2am-control-plane-secret-conf + mountPath: /home/wso2carbon/wso2-config-volume/repository/conf/security/secret-conf.properties + subPath: secret-conf.properties + - name: wso2am-control-plane-secret-store-csi + mountPath: /mnt/secrets-store + readOnly: true + {{- end }} + - name: wso2am-control-plane-keystores + mountPath: /home/wso2carbon/wso2-config-volume/repository/resources/security/{{ .Values.wso2.apim.configurations.security.truststore.name }} + subPath: {{ .Values.wso2.apim.configurations.security.truststore.name }} + {{- if .Values.wso2.apim.configurations.security.keystores.primary.enabled }} + - name: wso2am-control-plane-keystores + mountPath: /home/wso2carbon/wso2-config-volume/repository/resources/security/{{ .Values.wso2.apim.configurations.security.keystores.primary.name }} + subPath: {{ .Values.wso2.apim.configurations.security.keystores.primary.name }} + {{- end }} + {{- if .Values.wso2.apim.configurations.security.keystores.tls.enabled }} + - name: wso2am-control-plane-keystores + mountPath: /home/wso2carbon/wso2-config-volume/repository/resources/security/{{ .Values.wso2.apim.configurations.security.keystores.tls.name }} + subPath: {{ .Values.wso2.apim.configurations.security.keystores.tls.name }} + {{- end }} + {{- if .Values.wso2.apim.configurations.security.keystores.internal.enabled }} + - name: wso2am-control-plane-keystores + mountPath: /home/wso2carbon/wso2-config-volume/repository/resources/security/{{ .Values.wso2.apim.configurations.security.keystores.internal.name }} + subPath: {{ .Values.wso2.apim.configurations.security.keystores.internal.name }} + {{- end }} + {{ if .Values.wso2.deployment.persistence.solrIndexing.enabled }} + - name: wso2am-control-plane-local-carbondb + mountPath: /home/wso2carbon/solr/database + - name: wso2am-control-plane-solr + mountPath: /home/wso2carbon/solr/indexed-data + {{ end }} + {{- if .Values.wso2.deployment.nodeSelector }} + nodeSelector: + {{- toYaml .Values.wso2.deployment.nodeSelector | nindent 8 }} + {{- end }} + volumes: + - name: wso2am-control-plane-conf + configMap: + name: {{ template "apim-helm-cp.fullname" . }}-conf-2 + defaultMode: 0407 + - name: wso2am-control-plane-entrypoint + configMap: + name: {{ template "apim-helm-cp.fullname" . }}-conf-entrypoint + defaultMode: 0407 + - name: wso2am-control-plane-log4j2 + configMap: + name: {{ template "apim-helm-cp.fullname" . }}-conf-log4j2 + defaultMode: 0407 + - name: wso2am-sh-conf + configMap: + name: {{ template "apim-helm-cp.fullname" . }}-conf-sh + defaultMode: 0407 + {{- if .Values.wso2.apim.secureVaultEnabled }} + - name: wso2am-control-plane-secret-conf + configMap: + name: {{ template "apim-helm-cp.fullname" . }}-conf-secret-conf + - name: wso2am-control-plane-secret-store-csi + csi: + driver: secrets-store.csi.k8s.io + readOnly: true + {{- if .Values.azure.enabled }} + volumeAttributes: + secretProviderClass: {{ .Values.azure.keyVault.secretProviderClass }} + nodePublishSecretRef: + name: {{ .Values.azure.keyVault.activeDirectory.servicePrincipal.credentialsSecretName }} + {{- else if .Values.aws.enabled }} + volumeAttributes: + secretProviderClass: {{ .Values.aws.secretsManager.secretProviderClass }} + {{- else if .Values.gcp.enabled }} + volumeAttributes: + secretProviderClass: {{ .Values.gcp.secretsManager.secretProviderClass }} + {{- end }} + {{- end }} + - name: wso2am-control-plane-keystores + secret: + secretName: {{ .Values.wso2.apim.configurations.security.jksSecretName }} + {{ if .Values.wso2.deployment.persistence.solrIndexing.enabled }} + - name: wso2am-control-plane-local-carbondb + persistentVolumeClaim: + claimName: {{ template "apim-helm-cp.fullname" . }}-local-carbon-database-2 + - name: wso2am-control-plane-solr + persistentVolumeClaim: + claimName: {{ template "apim-helm-cp.fullname" . }}-solr-indexed-data-2 + {{ end }} +{{- end }} \ No newline at end of file diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/instance-2/wso2am-cp-service.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/instance-2/wso2am-cp-service.yaml new file mode 100644 index 000000000..8a9e8e3a5 --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/instance-2/wso2am-cp-service.yaml @@ -0,0 +1,37 @@ +{{- if .Values.wso2.deployment.highAvailability}} +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# -------------------------------------------------------------------------------------- + +apiVersion: v1 +kind: Service +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-2-service + namespace : {{ .Release.Namespace }} +spec: + # label keys and values that must match in order to receive traffic for this service + selector: + deployment: {{ template "apim-helm-cp.fullname" . }} + node: {{ template "apim-helm-cp.fullname" . }}-2 + ports: + # ports that this service should serve on + - name: binary + protocol: TCP + port: {{ add 9611 .Values.wso2.apim.portOffset }} + - name: binary-secure + protocol: TCP + port: {{ add 9711 .Values.wso2.apim.portOffset }} + - name: jms-tcp + protocol: TCP + port: {{ add 5672 .Values.wso2.apim.portOffset }} + - name: servlet-https + protocol: TCP + port: {{ add 9443 .Values.wso2.apim.portOffset }} +{{- end }} \ No newline at end of file diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-conf-script.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-conf-script.yaml new file mode 100644 index 000000000..3c6ea7125 --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-conf-script.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-conf-sh + namespace : {{ .Release.Namespace }} +data: + api-manager.sh: {{ tpl (.Files.Get "confs/api-manager.sh") . | quote}} diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-conf-entrypoint.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-conf-entrypoint.yaml new file mode 100644 index 000000000..e40243972 --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-conf-entrypoint.yaml @@ -0,0 +1,71 @@ +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# -------------------------------------------------------------------------------------- + +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-conf-entrypoint + namespace: {{ .Release.Namespace }} +data: + docker-entrypoint.sh: | + #!/bin/bash + set -e + + # volume mounts + config_volume=${WORKING_DIRECTORY}/wso2-config-volume + artifact_volume=${WORKING_DIRECTORY}/wso2-artifact-volume + + # check if the WSO2 non-root user home exists + test ! -d ${WORKING_DIRECTORY} && echo "WSO2 Docker non-root user home does not exist" && exit 1 + + # check if the WSO2 product home exists + test ! -d ${WSO2_SERVER_HOME} && echo "WSO2 Docker product home does not exist" && exit 1 + + # Copying carbon_db + if ! test -f /home/wso2carbon/solr/database/WSO2CARBON_DB.mv.db + then + echo "Copying WSO2CARBON_DB.mv.db" >&2 + cp ${WSO2_SERVER_HOME}/repository/database/WSO2CARBON_DB.mv.db /home/wso2carbon/solr/database/ + fi + + # optimize WSO2 Carbon Server, if the profile name is defined as an environment variable + if [[ ! -z "${PROFILE_NAME}" ]] + then + echo "Optimizing WSO2 Carbon Server" >&2 + sh ${WSO2_SERVER_HOME}/bin/profileSetup.sh -Dprofile=${PROFILE_NAME} + fi + + # copy any configuration changes mounted to config_volume + test -d ${config_volume} && [[ "$(ls -A ${config_volume})" ]] && cp -RL ${config_volume}/* ${WSO2_SERVER_HOME}/ + # copy any artifact changes mounted to artifact_volume + test -d ${artifact_volume} && [[ "$(ls -A ${artifact_volume})" ]] && cp -RL ${artifact_volume}/* ${WSO2_SERVER_HOME}/ + + {{- if .Values.wso2.apim.secureVaultEnabled }} + # copy internal keystore credentials to password-tmp file for cipher-tool usage + {{- if .Values.azure.enabled }} + cp /mnt/secrets-store/{{ .Values.azure.keyVault.secretIdentifiers.internalKeystorePassword }} ${WSO2_SERVER_HOME}/password-tmp + {{- else if .Values.aws.enabled }} + cp /mnt/secrets-store/{{ .Values.aws.secretsManager.secretIdentifiers.internalKeystorePassword.secretKey }} ${WSO2_SERVER_HOME}/password-tmp + {{- else if .Values.gcp.enabled }} + cp /mnt/secrets-store/{{ .Values.gcp.secretsManager.secret.secretName }} ${WSO2_SERVER_HOME}/password-tmp + {{- end }} + {{- end }} + + # start WSO2 Carbon server + echo "Start WSO2 Carbon server" >&2 + if [[ -z "${PROFILE_NAME}" ]] + then + # start the server with the provided startup arguments + sh ${WSO2_SERVER_HOME}/bin/api-manager.sh "$@" {{ .Values.wso2.apim.startupArgs }} + else + # start the server with the specified profile and provided startup arguments + sh ${WSO2_SERVER_HOME}/bin/api-manager.sh -Dprofile=${PROFILE_NAME} "$@" {{ .Values.wso2.apim.startupArgs }} + fi diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-conf-log4j2.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-conf-log4j2.yaml new file mode 100644 index 000000000..c86cd0433 --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-conf-log4j2.yaml @@ -0,0 +1,18 @@ +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# -------------------------------------------------------------------------------------- + +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-conf-log4j2 + namespace : {{ .Release.Namespace }} +data: + log4j2.properties: {{ tpl (.Files.Get "confs/log4j2.properties") . | quote }} diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-conf-secret-conf.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-conf-secret-conf.yaml new file mode 100644 index 000000000..10e9fffdb --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-conf-secret-conf.yaml @@ -0,0 +1,19 @@ +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# -------------------------------------------------------------------------------------- +{{- if .Values.wso2.apim.secureVaultEnabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-conf-secret-conf + namespace : {{ .Release.Namespace }} +data: + secret-conf.properties: {{ tpl (.Files.Get "confs/secret-conf.properties") . | quote}} +{{- end }} diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-ingress.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-ingress.yaml new file mode 100644 index 000000000..6e60304d4 --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-ingress.yaml @@ -0,0 +1,47 @@ +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# -------------------------------------------------------------------------------------- + +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-ingress + namespace : {{ .Release.Namespace }} +{{- if .Values.kubernetes.ingress.controlPlane.annotations }} + annotations: +{{ toYaml .Values.kubernetes.ingress.controlPlane.annotations | indent 4 }} + {{- if .Values.kubernetes.ingress.ratelimit.enabled }} + nginx.ingress.kubernetes.io/configuration-snippet: | + limit_req zone={{ .Values.kubernetes.ingress.ratelimit.zoneName }} burst={{ .Values.kubernetes.ingress.ratelimit.burstLimit }} nodelay; + limit_req_status 429; + set $rangeheadervalue $http_range; + if ($http_accept_encoding = "gzip") { + set $rangeheadervalue ""; + } + proxy_set_header Range $rangeheadervalue; + {{- end }} +{{- end }} +spec: + ingressClassName: {{ .Values.kubernetes.ingressClass }} + tls: + - hosts: + - {{ .Values.kubernetes.ingress.controlPlane.hostname }} + secretName: {{ .Values.kubernetes.ingress.tlsSecret }} + rules: + - host: {{ .Values.kubernetes.ingress.controlPlane.hostname }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ template "apim-helm-cp.fullname" . }}-service + port: + number: {{ add 9443 .Values.wso2.apim.portOffset }} diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-pdb.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-pdb.yaml new file mode 100644 index 000000000..8f4e9613e --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-pdb.yaml @@ -0,0 +1,20 @@ +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# -------------------------------------------------------------------------------------- + +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-pdb +spec: + minAvailable: {{ .Values.wso2.deployment.minAvailable | quote }} + selector: + matchLabels: + deployment: {{ template "apim-helm-cp.fullname" . }}-deployment diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-secret-store-provider-gcp.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-secret-store-provider-gcp.yaml new file mode 100644 index 000000000..27fa49ecb --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-secret-store-provider-gcp.yaml @@ -0,0 +1,24 @@ +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# -------------------------------------------------------------------------------------- + +{{- if and .Values.wso2.apim.secureVaultEnabled .Values.gcp.enabled }} +apiVersion: secrets-store.csi.x-k8s.io/v1 +kind: SecretProviderClass +metadata: + name: {{ .Values.gcp.secretsManager.secretProviderClass }} + namespace: {{ .Release.Namespace }} +spec: + provider: gcp + parameters: + secrets: | + - resourceName: "projects/{{ .Values.gcp.secretsManager.projectId }}/secrets/{{ .Values.gcp.secretsManager.secret.secretName }}/versions/{{ .Values.gcp.secretsManager.secret.secretVersion }}" + path: {{ .Values.gcp.secretsManager.secret.secretName }} +{{- end }} diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-service.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-service.yaml new file mode 100644 index 000000000..6a6bb2637 --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-service.yaml @@ -0,0 +1,28 @@ +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# -------------------------------------------------------------------------------------- + +apiVersion: v1 +kind: Service +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-service + namespace : {{ .Release.Namespace }} +spec: + # label keys and values that must match in order to receive traffic for this service + selector: + deployment: {{ template "apim-helm-cp.fullname" . }} + ports: + # ports that this service should serve on + - name: servlet-http + protocol: TCP + port: {{ add 9763 .Values.wso2.apim.portOffset }} + - name: servlet-https + protocol: TCP + port: {{ add 9443 .Values.wso2.apim.portOffset }} diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-storageclass-aws.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-storageclass-aws.yaml new file mode 100644 index 000000000..d70d2bc32 --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-storageclass-aws.yaml @@ -0,0 +1,24 @@ +{{ if and .Values.wso2.deployment.persistence.solrIndexing.enabled .Values.aws.enabled }} +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# -------------------------------------------------------------------------------------- + +kind: StorageClass +apiVersion: storage.k8s.io/v1 +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-sc +provisioner: efs.csi.aws.com +parameters: + provisioningMode: efs-ap + fileSystemId: {{ .Values.aws.efs.fileSystemId | quote }} + directoryPerms: {{ .Values.aws.efs.directoryPerms | quote }} + uid: {{ .Values.kubernetes.securityContext.runAsUser | quote }} + gid: {{ .Values.kubernetes.securityContext.runAsUser | quote }} +{{- end }} diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-storageclass-gcp.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-storageclass-gcp.yaml new file mode 100644 index 000000000..8c86b4ad2 --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-storageclass-gcp.yaml @@ -0,0 +1,23 @@ +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# -------------------------------------------------------------------------------------- + +{{ if and .Values.wso2.deployment.persistence.solrIndexing.enabled .Values.gcp.enabled }} +kind: StorageClass +apiVersion: storage.k8s.io/v1 +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-sc +provisioner: filestore.csi.storage.gke.io +parameters: + tier: {{ .Values.gcp.fs.tier | quote }} + network: {{ .Values.gcp.fs.network | quote }} + uid: {{ .Values.kubernetes.securityContext.runAsUser | quote }} + gid: {{ .Values.kubernetes.securityContext.runAsUser | quote }} +{{- end }} diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-volume-aws.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-volume-aws.yaml new file mode 100644 index 000000000..60e79f04c --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-volume-aws.yaml @@ -0,0 +1,92 @@ +{{ if and .Values.wso2.deployment.persistence.solrIndexing.enabled .Values.aws.enabled }} +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# -------------------------------------------------------------------------------------- + +apiVersion: v1 +kind: PersistentVolume +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-local-carbon-database-1 + labels: + purpose: cp-carbondb-1 +spec: + capacity: + storage: {{ .Values.aws.efs.capacity }} + volumeMode: Filesystem + accessModes: + - ReadWriteMany + storageClassName: {{ template "apim-helm-cp.fullname" . }}-sc + persistentVolumeReclaimPolicy: Retain + csi: + driver: efs.csi.aws.com + volumeHandle: {{ .Values.aws.efs.fileSystemId }}::{{ .Values.aws.efs.accessPoints.carbonDb1 }} + +--- + +apiVersion: v1 +kind: PersistentVolume +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-solr-indexed-data-1 + labels: + purpose: cp-solr-1 +spec: + capacity: + storage: {{ .Values.aws.efs.capacity }} + volumeMode: Filesystem + accessModes: + - ReadWriteMany + storageClassName: {{ template "apim-helm-cp.fullname" . }}-sc + persistentVolumeReclaimPolicy: Retain + csi: + driver: efs.csi.aws.com + volumeHandle: {{ .Values.aws.efs.fileSystemId }}::{{ .Values.aws.efs.accessPoints.solr1 }} + +--- +{{ if .Values.wso2.deployment.highAvailability }} + +apiVersion: v1 +kind: PersistentVolume +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-local-carbon-database-2 + labels: + purpose: cp-carbondb-2 +spec: + capacity: + storage: {{ .Values.aws.efs.capacity }} + volumeMode: Filesystem + accessModes: + - ReadWriteMany + storageClassName: {{ template "apim-helm-cp.fullname" . }}-sc + persistentVolumeReclaimPolicy: Retain + csi: + driver: efs.csi.aws.com + volumeHandle: {{ .Values.aws.efs.fileSystemId }}::{{ .Values.aws.efs.accessPoints.carbonDb2 }} + +--- + +apiVersion: v1 +kind: PersistentVolume +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-solr-indexed-data-2 + labels: + purpose: cp-solr-2 +spec: + capacity: + storage: {{ .Values.aws.efs.capacity }} + volumeMode: Filesystem + accessModes: + - ReadWriteMany + storageClassName: {{ template "apim-helm-cp.fullname" . }}-sc + persistentVolumeReclaimPolicy: Retain + csi: + driver: efs.csi.aws.com + volumeHandle: {{ .Values.aws.efs.fileSystemId }}::{{ .Values.aws.efs.accessPoints.solr2 }} +{{- end }} +{{- end }} diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-volume-azure.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-volume-azure.yaml new file mode 100644 index 000000000..f5da5e681 --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-volume-azure.yaml @@ -0,0 +1,36 @@ +{{ if and .Values.wso2.deployment.persistence.solrIndexing.enabled .Values.azure.enabled }} +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# -------------------------------------------------------------------------------------- + +apiVersion: v1 +kind: PersistentVolume +metadata: + name: {{ template "apim-helm-cp.fullname" . }} + labels: + purpose: cp-volume +spec: + accessModes: + - ReadWriteMany + capacity: + storage: {{ .Values.azure.persistence.capacity }} + persistentVolumeReclaimPolicy: Retain + volumeMode: Filesystem + azureFile: + secretName: {{ .Values.azure.persistence.secretName }} + secretNamespace: {{ .Release.Namespace }} + shareName: {{ .Values.azure.persistence.fileShare }} + mountOptions: + - dir_mode=0777 + - file_mode=0777 + - uid=10001 + - gid=10001 + - cache=strict +{{- end }} diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-volume-claims-aws.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-volume-claims-aws.yaml new file mode 100644 index 000000000..af8615766 --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-volume-claims-aws.yaml @@ -0,0 +1,87 @@ +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# -------------------------------------------------------------------------------------- + +{{ if and .Values.wso2.deployment.persistence.solrIndexing.enabled .Values.aws.enabled }} + +--- + +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-local-carbon-database-1 + namespace : {{ .Release.Namespace }} +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: {{ .Values.wso2.deployment.persistence.solrIndexing.capacity.carbonDatabase }} + selector: + matchLabels: + purpose: cp-carbondb-1 + storageClassName: {{ template "apim-helm-cp.fullname" . }}-sc + +--- + +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-solr-indexed-data-1 + namespace : {{ .Release.Namespace }} +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: {{ .Values.wso2.deployment.persistence.solrIndexing.capacity.solrIndexedData }} + selector: + matchLabels: + purpose: cp-solr-1 + storageClassName: {{ template "apim-helm-cp.fullname" . }}-sc + +--- +{{ if .Values.wso2.deployment.highAvailability }} + +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-local-carbon-database-2 + namespace : {{ .Release.Namespace }} +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: {{ .Values.wso2.deployment.persistence.solrIndexing.capacity.carbonDatabase }} + selector: + matchLabels: + purpose: cp-carbondb-2 + storageClassName: {{ template "apim-helm-cp.fullname" . }}-sc + +--- + +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-solr-indexed-data-2 + namespace : {{ .Release.Namespace }} +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: {{ .Values.wso2.deployment.persistence.solrIndexing.capacity.solrIndexedData }} + selector: + matchLabels: + purpose: cp-solr-2 + storageClassName: {{ template "apim-helm-cp.fullname" . }}-sc + {{ end }} +{{- end }} diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-volume-claims-azure.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-volume-claims-azure.yaml new file mode 100644 index 000000000..d8595e8a5 --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-volume-claims-azure.yaml @@ -0,0 +1,87 @@ +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# -------------------------------------------------------------------------------------- + +{{ if and .Values.wso2.deployment.persistence.solrIndexing.enabled .Values.azure.enabled }} + +--- + +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-local-carbon-database-1 + namespace : {{ .Release.Namespace }} +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: {{ .Values.wso2.deployment.persistence.solrIndexing.capacity.carbonDatabase }} + selector: + matchLabels: + purpose: cp-volume + storageClassName: {{ .Values.azure.persistence.storageClass | default "" }} + +--- + +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-solr-indexed-data-1 + namespace : {{ .Release.Namespace }} +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: {{ .Values.wso2.deployment.persistence.solrIndexing.capacity.solrIndexedData }} + selector: + matchLabels: + purpose: cp-volume + storageClassName: {{ .Values.azure.persistence.storageClass | default "" }} + +--- +{{ if .Values.wso2.deployment.highAvailability }} + +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-local-carbon-database-2 + namespace : {{ .Release.Namespace }} +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: {{ .Values.wso2.deployment.persistence.solrIndexing.capacity.carbonDatabase }} + selector: + matchLabels: + purpose: cp-volume + storageClassName: {{ .Values.azure.persistence.storageClass | default "" }} + +--- + +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-solr-indexed-data-2 + namespace : {{ .Release.Namespace }} +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: {{ .Values.wso2.deployment.persistence.solrIndexing.capacity.solrIndexedData }} + selector: + matchLabels: + purpose: cp-volume + storageClassName: {{ .Values.azure.persistence.storageClass | default "" }} + {{ end }} +{{- end }} diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-volume-gcp.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-volume-gcp.yaml new file mode 100644 index 000000000..742229bd8 --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-cp-volume-gcp.yaml @@ -0,0 +1,105 @@ +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# -------------------------------------------------------------------------------------- + +{{ if and .Values.wso2.deployment.persistence.solrIndexing.enabled .Values.gcp.enabled }} +apiVersion: v1 +kind: PersistentVolume +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-local-carbon-database-1 + labels: + purpose: am-carbondb-1 +spec: + capacity: + storage: {{ .Values.gcp.fs.capacity }} + volumeMode: Filesystem + accessModes: + - ReadWriteMany + storageClassName: {{ template "apim-helm-cp.fullname" . }}-sc + persistentVolumeReclaimPolicy: Retain + csi: + driver: filestore.csi.storage.gke.io + volumeHandle: "modeInstance/{{ .Values.gcp.fs.location }}/{{ .Values.gcp.fs.fileshares.carbonDB1.fileStoreName }}/{{ .Values.gcp.fs.fileshares.carbonDB1.fileShareName }}" + volumeAttributes: + ip: {{ .Values.gcp.fs.fileshares.carbonDB1.ip }} + volume: {{ .Values.gcp.fs.fileshares.carbonDB1.fileShareName }} + +--- + +apiVersion: v1 +kind: PersistentVolume +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-solr-indexed-data-1 + labels: + purpose: am-solr-1 +spec: + capacity: + storage: {{ .Values.gcp.fs.capacity }} + volumeMode: Filesystem + accessModes: + - ReadWriteMany + storageClassName: {{ template "apim-helm-cp.fullname" . }}-sc + persistentVolumeReclaimPolicy: Retain + csi: + driver: filestore.csi.storage.gke.io + volumeHandle: "modeInstance/{{ .Values.gcp.fs.location }}/{{ .Values.gcp.fs.fileshares.solr1.fileStoreName }}/{{ .Values.gcp.fs.fileshares.solr1.fileShareName }}" + volumeAttributes: + ip: {{ .Values.gcp.fs.fileshares.solr1.ip }} + volume: {{ .Values.gcp.fs.fileshares.solr1.fileShareName }} + +--- +{{ if .Values.wso2.deployment.highAvailability }} + +apiVersion: v1 +kind: PersistentVolume +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-local-carbon-database-2 + labels: + purpose: am-carbondb-2 +spec: + capacity: + storage: {{ .Values.gcp.fs.capacity }} + volumeMode: Filesystem + accessModes: + - ReadWriteMany + storageClassName: {{ template "apim-helm-cp.fullname" . }}-sc + persistentVolumeReclaimPolicy: Retain + csi: + driver: filestore.csi.storage.gke.io + volumeHandle: "modeInstance/{{ .Values.gcp.fs.location }}/{{ .Values.gcp.fs.fileshares.carbonDB2.fileStoreName }}/{{ .Values.gcp.fs.fileshares.carbonDB2.fileShareName }}" + volumeAttributes: + ip: {{ .Values.gcp.fs.fileshares.carbonDB2.ip }} + volume: {{ .Values.gcp.fs.fileshares.carbonDB2.fileShareName }} + +--- + +apiVersion: v1 +kind: PersistentVolume +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-solr-indexed-data-2 + labels: + purpose: am-solr-2 +spec: + capacity: + storage: {{ .Values.gcp.fs.capacity }} + volumeMode: Filesystem + accessModes: + - ReadWriteMany + storageClassName: {{ template "apim-helm-cp.fullname" . }}-sc + persistentVolumeReclaimPolicy: Retain + csi: + driver: filestore.csi.storage.gke.io + volumeHandle: "modeInstance/{{ .Values.gcp.fs.location }}/{{ .Values.gcp.fs.fileshares.solr2.fileStoreName }}/{{ .Values.gcp.fs.fileshares.solr2.fileShareName }}" + volumeAttributes: + ip: {{ .Values.gcp.fs.fileshares.solr2.ip }} + volume: {{ .Values.gcp.fs.fileshares.solr2.fileShareName }} + +{{- end }} +{{- end }} diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-volume-claims-gcp.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-volume-claims-gcp.yaml new file mode 100644 index 000000000..a9e9f589e --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/control-plane/wso2am-volume-claims-gcp.yaml @@ -0,0 +1,85 @@ +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# -------------------------------------------------------------------------------------- + +{{ if and .Values.wso2.deployment.persistence.solrIndexing.enabled .Values.gcp.enabled }} + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-local-carbon-database-1 + namespace : {{ .Release.Namespace }} +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: {{ .Values.wso2.deployment.persistence.solrIndexing.capacity.carbonDatabase }} + selector: + matchLabels: + purpose: am-carbondb-1 + storageClassName: {{ template "apim-helm-cp.fullname" . }}-sc + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-solr-indexed-data-1 + namespace : {{ .Release.Namespace }} +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: {{ .Values.wso2.deployment.persistence.solrIndexing.capacity.solrIndexedData }} + selector: + matchLabels: + purpose: am-solr-1 + storageClassName: {{ template "apim-helm-cp.fullname" . }}-sc + +--- +{{ if .Values.wso2.deployment.highAvailability }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-local-carbon-database-2 + namespace : {{ .Release.Namespace }} +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: {{ .Values.wso2.deployment.persistence.solrIndexing.capacity.carbonDatabase }} + selector: + matchLabels: + purpose: am-carbondb-2 + storageClassName: {{ template "apim-helm-cp.fullname" . }}-sc + +--- + +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-solr-indexed-data-2 + namespace : {{ .Release.Namespace }} +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: {{ .Values.wso2.deployment.persistence.solrIndexing.capacity.solrIndexedData }} + selector: + matchLabels: + purpose: am-solr-2 + storageClassName: {{ template "apim-helm-cp.fullname" . }}-sc + +{{- end }} +{{- end }} diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/secrets/wso2am-cp-secret-store-csi.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/secrets/wso2am-cp-secret-store-csi.yaml new file mode 100644 index 000000000..d9b875d31 --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/secrets/wso2am-cp-secret-store-csi.yaml @@ -0,0 +1,22 @@ +{{- if and .Values.wso2.apim.secureVaultEnabled .Values.azure.enabled }} +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# -------------------------------------------------------------------------------------- + +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "apim-helm-cp.fullname" . }}-secret-store-csi + namespace: {{ .Release.Namespace }} +type: Opaque +data: + clientid: {{ .Values.azure.keyVault.activeDirectory.servicePrincipal.appId | b64enc }} + clientsecret: {{ .Values.azure.keyVault.activeDirectory.servicePrincipal.clientSecretName | b64enc }} +{{- end }} diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/secrets/wso2am-cp-secret-store-provider-aws.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/secrets/wso2am-cp-secret-store-provider-aws.yaml new file mode 100644 index 000000000..019c9c95d --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/secrets/wso2am-cp-secret-store-provider-aws.yaml @@ -0,0 +1,29 @@ +{{- if and .Values.wso2.apim.secureVaultEnabled .Values.aws.enabled }} + +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# -------------------------------------------------------------------------------------- + +apiVersion: secrets-store.csi.x-k8s.io/v1 +kind: SecretProviderClass +metadata: + name: {{ .Values.aws.secretsManager.secretProviderClass }} + namespace: {{ .Release.Namespace }} +spec: + provider: aws + parameters: + region: {{ .Values.aws.region }} + objects: | + - objectName: {{ .Values.aws.secretsManager.secretIdentifiers.internalKeystorePassword.secretName | quote }} + objectType: "secretsmanager" + jmesPath: + - path: {{ .Values.aws.secretsManager.secretIdentifiers.internalKeystorePassword.secretKey }} + objectAlias: {{ .Values.aws.secretsManager.secretIdentifiers.internalKeystorePassword.secretKey }} +{{- end }} diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/templates/secrets/wso2am-cp-secret-store-provider-azure.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/secrets/wso2am-cp-secret-store-provider-azure.yaml new file mode 100644 index 000000000..987d79485 --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/templates/secrets/wso2am-cp-secret-store-provider-azure.yaml @@ -0,0 +1,35 @@ +{{- if and .Values.wso2.apim.secureVaultEnabled .Values.azure.enabled }} +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# -------------------------------------------------------------------------------------- + +apiVersion: secrets-store.csi.x-k8s.io/v1 +kind: SecretProviderClass +metadata: + name: {{ .Values.azure.keyVault.secretProviderClass }} +spec: + provider: azure + parameters: + keyvaultName: {{ .Values.azure.keyVault.name }} + userAssignedIdentityID: "{{ .Values.azure.keyVault.activeDirectory.servicePrincipal.appId }}" + objects: | + array: + - | + objectName: {{ .Values.azure.keyVault.secretIdentifiers.internalKeystorePassword }} + objectType: secret + objectVersion: "" + - | + objectName: {{ .Values.azure.keyVault.secretIdentifiers.internalKeystoreKeyPassword }} + objectType: secret + objectVersion: "" + tenantId: {{ .Values.azure.keyVault.activeDirectory.tenantId }} + resourceGroup: {{ .Values.azure.keyVault.resourceManager.resourceGroup }} + subscriptionId: {{ .Values.azure.keyVault.resourceManager.subscriptionId }} +{{- end }} diff --git a/test/apim-apk-agent-test/apim-cp-helm-chart/values.yaml b/test/apim-apk-agent-test/apim-cp-helm-chart/values.yaml new file mode 100644 index 000000000..a23bebd2e --- /dev/null +++ b/test/apim-apk-agent-test/apim-cp-helm-chart/values.yaml @@ -0,0 +1,448 @@ +# ------------------------------------------------------------------------------------- +# +# Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). All Rights Reserved. +# +# This software is the property of WSO2 LLC. and its suppliers, if any. +# Dissemination of any information or reproduction of any material contained +# herein is strictly forbidden, unless permitted by WSO2 in accordance with the +# WSO2 Commercial License available at https://wso2.com/licenses/eula/3.2 +# +# -------------------------------------------------------------------------------------- + +aws: + # -- If AWS is used as the cloud provider + enabled: false + efs: + # -- EFS capacity + capacity: "" + # -- EFS directory permissions + directoryPerms: "0777" + # -- EFS file system ID for mounting the persistent volume + fileSystemId: "" + # -- EFS Access Points for static provisioning + accessPoints: + carbonDb1: "" + solr1: "" + carbonDb2: "" + solr2: "" + # -- AWS region + region: "" + secretsManager: + # -- AWS Secrets Manager secret provider class name + secretProviderClass: "wso2am-cp-secret-provider-class" + secretIdentifiers: + # -- Internal keystore password identifier in secrets manager + internalKeystorePassword: + # -- AWS Secrets Manager secret name + secretName: "" + # -- AWS Secrets Manager secret key + secretKey: "" + serviceAccountName: "" +azure: + # -- If Azure is used as the cloud provider + enabled: false + keyVault: + # -- Azure Key vault used for credential management + name: "" + # -- Azure Key vault secret provider class name + secretProviderClass: "wso2am-cp-secret-provider-class" + secretIdentifiers: + # -- Internal keystore password identifier in keyvault + internalKeystorePassword: "" + # -- Internal keystore key password identifier in keyvault + internalKeystoreKeyPassword: "" + activeDirectory: + # -- Service Principal created for transacting with the target Azure Key Vault + # For advanced details refer to official documentation (https://github.com/Azure/secrets-store-csi-driver-provider-azure/blob/master/docs/service-principal-mode.md) + servicePrincipal: + # -- Application ID of the service principal used in secret-store-csi + appId: "" + # -- Client secret name of the service principal used in secret-store-csi + clientSecretName: "" + # -- Credentials secret name of the service principal used as nodePublisherRef + credentialsSecretName: "" + # -- Azure Active Directory tenant ID of the target Key Vault + tenantId: "" + resourceManager: + # -- Subscription ID of the target Azure Key Vault + subscriptionId: "" + # -- Name of the Azure Resource Group to which the target Azure Key Vault belongs + resourceGroup: "" + persistence: + # Needed for persisting indexing related data + # -- Persistent volume capacity + capacity: "" + # -- Persistent volume storage class + storageClass: "" + # -- Azure file secret name + secretName: "" + # -- Azure fileshare name + fileShare: "" + +# Google Cloud Platform (GCP) integration status +gcp: + # -- If GCP is used as the cloud provider + enabled: false + # -- File Store configuration parameters + fs: + # -- Storage capacity of the file system (in GB or other appropriate units) + capacity: "" + # -- FileStore configuration for specific services + fileshares: + # -- FileShare configs for CarbonDB persistent storage for instance 1 + carbonDB1: + # -- FileStore of the CarbonDB persistent storage for instance 1 + fileStoreName: "" + # -- FileShare of the CarbonDB persistent storage for instance 1 + fileShareName: "" + # -- IP of the CarbonDB persistent storage for instance 1 + ip: "" + # -- FileShare configs for Solr persistent storage for instance 1 + solr1: + # -- FileStore of the Solr persistent storage for instance 1 + fileStoreName: "" + # -- FileShare of the Solr persistent storage for instance 1 + fileShareName: "" + # -- IP of the Solr persistent storage for instance 1 + ip: "" + # -- FileShare configs for CarbonDB2 persistent storage for instance 2 + carbonDB2: + # -- FileStore of the CarbonDB persistent storage for instance 2 + fileStoreName: "" + # -- FileShare of the CarbonDB persistent storage for instance 2 + fileShareName: "" + # -- IP of the CarbonDB persistent storage for instance 2 + ip: "" + # -- FileShare configs for Solr persistent storage for instance 2 + solr2: + # -- FileStore of the Solr persistent storage for instance 2 + fileStoreName: "" + # -- FileShare of the Solr persistent storage for instance 2 + fileShareName: "" + # -- IP of the Solr persistent storage for instance 2 + ip: "" + # -- Tier of the FileStore + tier: "" + # -- Network of the FileStore + network: "" + # -- Region of the FileStore + location: "" + + # -- Secrets Manager configuration parameters + secretsManager: + # -- Project ID + projectId: "" + # -- Secret provider class + secretProviderClass: "" + secret: + # -- Name of the secret + secretName: "" + # -- Version of the secret + secretVersion: "" + # -- Service Account with access to read secrets + serviceAccountName: "" + +kubernetes: + # -- Ingress class to be used for the ingress resource + ingressClass: "nginx" + ingress: + # -- Kubernetes secret created for Ingress TLS + tlsSecret: "" + ratelimit: + # -- Ingress rate limit + enabled: false + # -- Ingress ratelimit zone name + zoneName: "" + # -- Ingress ratelimit burst limit + burstLimit: "" + controlPlane: + # -- Ingress hostname + hostname: "am.wso2.com" + # -- Ingress annotations + annotations: + nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" + nginx.ingress.kubernetes.io/affinity: "cookie" + nginx.ingress.kubernetes.io/session-cookie-name: "route" + nginx.ingress.kubernetes.io/session-cookie-hash: "sha1" + + securityContext: + # -- User ID of the container + runAsUser: 802 + # -- Enable AppArmor profiles for the deployment + enableAppArmor: false + +wso2: + apim: + # -- APIM version + version: "4.3.0" + # -- Secure vauld enabled + secureVaultEnabled: false + # Logging related configurations + log4j2: + # -- Console loggers that can be enabled. Allowed values are AUDIT_LOG_CONSOLE, HTTP_ACCESS_CONSOLE, TRANSACTION_CONSOLE, CORRELATION_CONSOLE + loggers: "" + # -- Appenders + appenders: "" + # -- Startup arguments for APIM + startupArgs: "" + # -- Port Offset for APIM deployment + portOffset: 0 + # TOML configurations + configurations: + userStore: + # -- User store type. + # https://apim.docs.wso2.com/en/latest/administer/managing-users-and-roles/managing-user-stores/configure-primary-user-store/configuring-the-primary-user-store/ + type: "database_unique_id" + # -- User store properties + properties: + key: value + # -- Super admin username + adminUsername: "admin" + # -- Super admin password + adminPassword: "admin" + databases: + # -- Database type. eg: mysql, oracle, mssql, postgres + type: "h2" + jdbc: + # -- JDBC driver class name + driver: "" + # -- APIM AM_DB configurations. + apim_db: + # -- APIM AM_DB URL + url: "jdbc:h2:./repository/database/WSO2AM_DB;DB_CLOSE_ON_EXIT=FALSE" + # -- APIM AM_DB username + username: "wso2carbon" + # -- APIM AM_DB password + password: "wso2carbon" + # -- APIM database JDBC pool parameters + poolParameters: + defaultAutoCommit: false + testOnBorrow: true + testWhileIdle: true + validationInterval: 30000 + maxActive: 100 + maxWait: 60000 + minIdle: 5 + # -- APIM SharedDB configurations. + shared_db: + # -- APIM SharedDB URL + url: "jdbc:h2:./repository/database/WSO2SHARED_DB;DB_CLOSE_ON_EXIT=FALSE" + # -- APIM SharedDB username + username: "wso2carbon" + # -- APIM SharedDB password + password: "wso2carbon" + # -- APIM shared database JDBC pool parameters + poolParameters: + defaultAutoCommit: false + testOnBorrow: true + testWhileIdle: true + validationInterval: 30000 + maxActive: 100 + maxWait: 60000 + minIdle: 5 + + security: + # -- Kubernetes secret containing the keystores and truststore + #jksSecretName: "apim-keystore-secret" + keystores: + primary: + # -- Primary keystore enabled + enabled: false + # -- Primary keystore name + name: "wso2carbon.jks" + # -- Primary keystore alias + alias: "wso2carbon" + # -- Primary keystore password + password: "wso2carbon" + # -- Primary keystore key password + keyPassword: "wso2carbon" + tls: + # -- TLS keystore enabled + enabled: true + # -- TLS keystore name + name: "wso2carbon.jks" + # -- TLS keystore alias + alias: "wso2carbon" + # -- TLS keystore password + password: "wso2carbon" + # -- TLS keystore key password + keyPassword: "wso2carbon" + internal: + # -- Internal keystore enabled + enabled: false + # -- Internal keystore name + name: "wso2carbon.jks" + # -- Internal keystore alias + alias: "wso2carbon" + # -- Internal keystore password + password: "wso2carbon" + # -- Internal keystore key password + keyPassword: "wso2carbon" + truststore: + # -- Truststore name + name: "client-truststore.jks" + # -- Truststore password + password: "wso2carbon" + + gateway: + # -- APIM Gateway environments + environments: + - name: "Default" + type: "hybrid" + provider: "wso2" + displayInApiConsole: true + description: "This is a hybrid gateway that handles both production and sandbox token traffic." + showAsTokenEndpointUrl: true + serviceName: "wso2am-gateway-service" + servicePort: 9443 + wsHostname: "websocket.wso2.com" + httpHostname: "default.gw.wso2.com" + websubHostname: "websub.wso2.com" + + iskm: + # -- If Identity Server is used as the Resident KM + enabled: false + # -- Kubernetes service name exposing Identity Server + serviceName: "" + # -- Kubernetes service port exposing Identity Serve + servicePort: 9443 + + publisher: + # -- Supported document types in Publisher. + # This should be used only if there are additional document types to be supported. + supportedDocumentTypes: "" + + devportal: + enableApplicationSharing: + applicationSharingType: + applicationSharingImpl: + displayMutipleVersions: + displayDeprecatedAPIs: + enableComments: + enableRatings: + enableForum: + enableAnonymousMode: + enableCrossTenantSubscriptions: + defaultReservedUsername: + loginUsernameCaseInsensitive: + enableKeyProvisioning: + + # APIM OAuth configurations + oauth_config: + # -- Enable token encryption + enableTokenEncryption: false + # -- Enable token hashing + enableTokenHashing: false + # -- List of allow-listed scopes + allowedScopes: ["^device_.*,openid"] + + # APIM Open Tracing configurations + # https://apim.docs.wso2.com/en/latest/observe/api-manager/traces/monitoring-with-opentracing/ + openTracer: + # -- Open Tracing enabled + enabled: false + # -- Remote tracer name. e.g. jaeger, zipkin + name: "" + properties: + # -- Remote tracer hostname + hostname: "" + # -- Remote tracer port + port: "" + # APIM Open Telemetry configurations + openTelemetry: + # -- Open Telemetry enabled + enabled: false + # -- Remote tracer name. e.g. jaeger, zipkin, OTLP + name: "" + # -- Remote tracer hostname + hostname: "" + # -- Remote tracer port + port: "" + + deployment: + # Container image configurations + image: + # -- Container registry hostname + registry: "" + # -- Azure ACR repository name consisting the image + repository: "sampathrajapakse/wso2am" + # -- Docker image digest + digest: "" + # -- Refer to the Kubernetes documentation on updating images (https://kubernetes.io/docs/concepts/containers/images/#updating-images) + imagePullPolicy: Always + + resources: + # These are the resource recommendations for running WSO2 API Management product profiles with profile optimization + # Resource configurations defined here are applicable for all API Manager product profiles of this deployment + requests: + # -- Memory request for API Manager + memory: "2Gi" + # -- CPU request for API Manager + cpu: "2000m" + limits: + # -- Memory limit for API Manager + memory: "3Gi" + # -- CPU limit for API Manager + cpu: "3000m" + jvm: + memory: + # -- JVM heap memory Xms + xms: "2048m" + # -- JVM heap memory Xmx + xmx: "2048m" + + # Kubernetes Probes + # Indicates whether the container starting + startupProbe: + # -- Number of seconds after the container has started before startup probes are initiated + initialDelaySeconds: 60 + # -- How often (in seconds) to perform the probe + periodSeconds: 10 + # -- Minimum consecutive successes for the probe to be considered successful after having failed + failureThreshold: 3 + # Indicates whether the container is running + livenessProbe: + # -- Number of seconds after the container has started before liveness probes are initiated + initialDelaySeconds: 60 + # -- How often (in seconds) to perform the probe + periodSeconds: 10 + # -- Minimum consecutive successes for the probe to be considered successful after having failed + failureThreshold: 3 + # Indicates whether the container is ready to service requests + readinessProbe: + # -- Number of seconds after the container has started before readiness probes are initiated + initialDelaySeconds: 60 + # -- How often (in seconds) to perform the probe + periodSeconds: 10 + # -- Minimum consecutive successes for the probe to be considered successful after having failed + failureThreshold: 3 + + lifecycle: + preStopHook: + # -- Number of seconds to sleep before sending SIGTERM to the pod + sleepSeconds: 10 + + # Number of deployment replicas + replicas: 1 + # -- Minimum available pod counts for PDB + minAvailable: "50%" + + # -- Node selector to deploy pod in selected node. Add label to the node and specify the label here. + nodeSelector: + + # -- Enable high availability for traffic manager. If this is enabled, two traffic manager instances will be deployed. + # This is not relavant to HA in Kubernetes. Multiple replicas of the same instance will not count as HA for TM. + highAvailability: false + + persistence: + # -- Persistent runtime artifacts for Apache Solr-based indexing + solrIndexing: + # -- Indicates if persistence of the runtime artifacts for Apache Solr-based indexing is enabled + # By default, this is disabled + enabled: false + # Define capacities for persistent runtime artifact directories + capacity: + # -- For persisting the H2 based local Carbon database file + carbonDatabase: 50M + # -- For persisting the indexed solr data + solrIndexedData: 50M \ No newline at end of file diff --git a/test/apim-apk-agent-test/cucumber-tests/.gitattributes b/test/apim-apk-agent-test/cucumber-tests/.gitattributes new file mode 100644 index 000000000..097f9f98d --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/.gitattributes @@ -0,0 +1,9 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + diff --git a/test/apim-apk-agent-test/cucumber-tests/.gitignore b/test/apim-apk-agent-test/cucumber-tests/.gitignore new file mode 100644 index 000000000..1b6985c00 --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/.gitignore @@ -0,0 +1,5 @@ +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build diff --git a/test/apim-apk-agent-test/cucumber-tests/CRs/artifacts.yaml b/test/apim-apk-agent-test/cucumber-tests/CRs/artifacts.yaml new file mode 100644 index 000000000..139597f9c --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/CRs/artifacts.yaml @@ -0,0 +1,2 @@ + + diff --git a/test/apim-apk-agent-test/cucumber-tests/README.md b/test/apim-apk-agent-test/cucumber-tests/README.md new file mode 100644 index 000000000..4786daefd --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/README.md @@ -0,0 +1,91 @@ +# WSO2 APK Cucumber Based Integration Tests + +This folder contains APK cucumber integration tests that is used to test APK product capabilities + +## Pre-requisites + +1. [Helm](https://helm.sh/docs/intro/install/) +2. [Kubernetes Client](https://kubernetes.io/docs/tasks/tools/install-kubectl/) +3. [Kubernetes Cluster](https://kubernetes.io/docs/setup) + +## Overview + +This test module use the [Cucumber](https://cucumber.io/) framework and [Gherkin](https://cucumber.io/docs/gherkin/) Syntax to write the integration tests. + +## Writing Tests + +The tests are written using the Cucumber syntax in feature files located under the `src/test/resources/tests` directory. Each feature file represents a set of related test scenarios written in the Gherkin language. + +To create a new feature, follow these steps: + +1. Create a new feature file in the `src/test/resources/tests` directory with a `.feature` extension. + +2. Write your test scenarios in Gherkin syntax. + +3. Step definitions are written in Java and can be found under the `src/test/java/org/wso2/apk/integration` directory. You may need to add new step definitions to support your new scenarios. + +4. Add the new feature file to the `src/resources/testng.xml` file to run the tests. + +## Run or Debug the Integration Tests Locally + +1. Setup the deployment namespace. + + ```bash + kubectl create namespace apk + ``` + +2. Go to the `product-apim-tooling/apim-apk-agent-test/apk-helm-chart` directory and run following commands to install the APK components. + + ```bash + helm repo add bitnami https://charts.bitnami.com/bitnami + helm repo add jetstack https://charts.jetstack.io + helm dependency build + helm install apk-test-setup . -n apk + ``` + +3. Port forward router-service to use localhost. + + ```bash + kubectl port-forward svc/apk-test-setup-wso2-apk-router-service -n apk 9095:9095 + ``` + +4. Add the following DNS mappings to `/etc/hosts` file. + + ```bash + IP=127.0.0.1 + sudo echo "$IP idp.am.wso2.com" | sudo tee -a /etc/hosts + sudo echo "$IP api.am.wso2.com" | sudo tee -a /etc/hosts + sudo echo "$IP default.gw.wso2.com" | sudo tee -a /etc/hosts + sudo echo "$IP default.sandbox.gw.wso2.com" | sudo tee -a /etc/hosts + ``` + +5. Go to the `product-apim-tooling/apim-apk-agent-test/apim-cp-helm-chart` directory and run following commands to install the APIM CP component. + + ```bash + helm dependency build + helm install apim . -n apk + `` + +6. Port forward router-service to use localhost. + + ```bash + kubectl port-forward svc/apim-wso2am-cp-1-service -n apk 9443:9443 + ``` + +7. Go to the `product-apim-tooling/helm-charts` directory and run following commands to install the APIM APK Agent components. + + ```bash + helm dependency build + helm install apim-apk-agent . -n apk + ``` + +8. Run or debug integration tests. + + - Run following command from `product-apim-tooling/apim-apk-agent-test/cucumber-tests` directory to run the integration tests. + + ```bash + gradle runTests + ``` + - To run a single test, update the `product-apim-tooling/apim-apk-agent-test/cucumber-tests/src/resources/testng.xml` file with the required feature file and run the above command. + + - Run the `gradle runTests --debug-jvm` command and attach the debugger in the IDE to debug the integration tests. diff --git a/test/apim-apk-agent-test/cucumber-tests/build.gradle b/test/apim-apk-agent-test/cucumber-tests/build.gradle new file mode 100644 index 000000000..a681ff06a --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/build.gradle @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { + testImplementation 'io.cucumber:cucumber-picocontainer:7.2.3' + testImplementation 'io.cucumber:cucumber-core:7.2.3' + testImplementation 'com.google.guava:guava:32.1.1-jre' + testImplementation 'com.google.code.gson:gson:2.10.1' + testImplementation 'org.apache.httpcomponents:httpmime:4.5.13' + testImplementation 'org.apache.httpcomponents:httpclient:4.5.13' + testImplementation 'org.apache.httpcomponents:httpcore:4.4.14' + testImplementation 'io.cucumber:cucumber-java:7.13.0' + testImplementation 'io.cucumber:cucumber-testng:7.13.0' + testImplementation 'commons-io:commons-io:2.13.0' + testImplementation 'com.nimbusds:nimbus-jose-jwt:9.31' + testImplementation 'com.googlecode.json-simple:json-simple:1.1.1' +} + +test { + reports.junitXml.enabled = true + useTestNG() + testLogging { + events "passed", "skipped", "failed", "standardOut", "standardError" + } +} + +task runTests(type: JavaExec, dependsOn: 'classes') { + main = 'org.testng.TestNG' + classpath = files("./src/test/resources", + project.sourceSets.main.compileClasspath, + project.sourceSets.test.compileClasspath, + project.sourceSets.main.runtimeClasspath, + project.sourceSets.test.runtimeClasspath) + args = ["-d", "./build/test-output", "./src/test/resources/testng.xml"] +} diff --git a/test/apim-apk-agent-test/cucumber-tests/gradle/wrapper/gradle-wrapper.properties b/test/apim-apk-agent-test/cucumber-tests/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..070cb702f --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/test/apim-apk-agent-test/cucumber-tests/gradlew b/test/apim-apk-agent-test/cucumber-tests/gradlew new file mode 100755 index 000000000..a69d9cb6c --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/gradlew @@ -0,0 +1,240 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/test/apim-apk-agent-test/cucumber-tests/gradlew.bat b/test/apim-apk-agent-test/cucumber-tests/gradlew.bat new file mode 100644 index 000000000..f127cfd49 --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/gradlew.bat @@ -0,0 +1,91 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/test/apim-apk-agent-test/cucumber-tests/scripts/setup-hosts.sh b/test/apim-apk-agent-test/cucumber-tests/scripts/setup-hosts.sh new file mode 100644 index 000000000..de2d21ddf --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/scripts/setup-hosts.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +kubectl apply -f ./CRs/artifacts.yaml +kubectl wait deployment/wso2apim -n apk --for=condition=available --timeout=600s +kubectl wait --timeout=5m -n apk deployment/apk-test-setup-wso2-apk-adapter-deployment --for=condition=Available +kubectl wait --timeout=15m -n apk deployment/apk-test-setup-wso2-apk-gateway-runtime-deployment --for=condition=Available +kubectl wait --timeout=5m -n apk deployment/apim-apk-agent --for=condition=Available +IP=$(kubectl get svc apk-test-setup-wso2-apk-gateway-service -n apk-integration-test --output jsonpath='{.status.loadBalancer.ingress[0].ip}') +CC_IP=$(kubectl get svc apk-test-setup-wso2-apk-common-controller-web-server-service -n apk-integration-test --output jsonpath='{.status.loadBalancer.ingress[0].ip}') +sudo echo "$IP localhost" | sudo tee -a /etc/hosts +sudo echo "$IP idp.am.wso2.com" | sudo tee -a /etc/hosts +sudo echo "$CC_IP apk-test-setup-wso2-apk-common-controller-service.apk.svc" | sudo tee -a /etc/hosts +sudo echo "$IP am.wso2.com" | sudo tee -a /etc/hosts +sudo echo "$IP api.am.wso2.com" | sudo tee -a /etc/hosts +sudo echo "$IP default.gw.wso2.com" | sudo tee -a /etc/hosts +sudo echo "$IP default.sandbox.gw.wso2.com" | sudo tee -a /etc/hosts +sudo echo "255.255.255.255 broadcasthost" | sudo tee -a /etc/hosts +sudo echo "::1 localhost" | sudo tee -a /etc/hosts diff --git a/test/apim-apk-agent-test/cucumber-tests/settings.gradle b/test/apim-apk-agent-test/cucumber-tests/settings.gradle new file mode 100644 index 000000000..e44b1a136 --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/settings.gradle @@ -0,0 +1,10 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user manual at https://docs.gradle.org/7.5.1/userguide/multi_project_builds.html + */ + +rootProject.name = 'cucumber-tests' diff --git a/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/APKIntegrationTestSuite.java b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/APKIntegrationTestSuite.java new file mode 100644 index 000000000..5acc772cd --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/APKIntegrationTestSuite.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024, WSO2 LLC (http://www.wso2.com). + * + * WSO2 LLC licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wso2.apk.integration; + +import io.cucumber.testng.AbstractTestNGCucumberTests; + +public class APKIntegrationTestSuite extends AbstractTestNGCucumberTests { + +} diff --git a/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/APIDeploymentSteps.java b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/APIDeploymentSteps.java new file mode 100644 index 000000000..7f5c74dc6 --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/APIDeploymentSteps.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2024, WSO2 LLC (http://www.wso2.com). + * + * WSO2 LLC licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wso2.apk.integration.api; + +import com.google.common.io.Resources; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.entity.mime.content.FileBody; +import org.apache.http.message.BasicNameValuePair; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.testng.Assert; +import org.wso2.apk.integration.utils.Constants; +import org.wso2.apk.integration.utils.Utils; +import org.wso2.apk.integration.utils.clients.SimpleHTTPClient; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This class contains the step definitions for API Deployment. + */ +public class APIDeploymentSteps { + + private final SharedContext sharedContext; + private File payloadFile; + private File definitionFile; + + private String OASURL; + + private static final Log logger = LogFactory.getLog(APIDeploymentSteps.class); + + public APIDeploymentSteps(SharedContext sharedContext) { + + this.sharedContext = sharedContext; + } + + @When("I use the Payload file {string}") + public void iHaveTheAPKConf(String payloadFileName) throws IOException { + + URL url = Resources.getResource(payloadFileName); + payloadFile = new File(url.getPath()); + } + + @When("I use the OAS URL {string}") + public void iHaveTheOASURL(String pOASURL) throws IOException { + OASURL = pOASURL; + } + + @When("the definition file {string}") + public void iHaveTheDefinitionFile(String definitionFileName) throws IOException { + + URL url = Resources.getResource(definitionFileName); + definitionFile = new File(url.getPath()); + } + + @When("make the import API Creation request") + public void make_import_api_creation_request() throws Exception { + logger.info("OAS URL: " + OASURL); + + // Create a MultipartEntityBuilder to build the request entity + MultipartEntityBuilder builder = MultipartEntityBuilder.create() + .setMode(HttpMultipartMode.BROWSER_COMPATIBLE) + .addTextBody("url", OASURL, ContentType.TEXT_PLAIN) + .addPart("additionalProperties", new FileBody(payloadFile)); + + logger.info("Payload File: "+ new FileBody(payloadFile)); + + Map headers = new HashMap<>(); + headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, "Bearer " + sharedContext.getPublisherAccessToken()); + headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_API_HOST); + + HttpEntity multipartEntity = builder.build(); + + HttpResponse response = sharedContext.getHttpClient().doPostWithMultipart(Utils.getImportAPIURL(), + multipartEntity, headers); + + sharedContext.setResponse(response); + sharedContext.setResponseBody(SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse())); + sharedContext.setApiUUID(Utils.extractID(sharedContext.getResponseBody())); + Thread.sleep(3000); + } + + @When("make the API Revision Deployment request") + public void make_a_api_revision_deployment_request() throws Exception { + String apiUUID = sharedContext.getApiUUID(); + String payload = "{\"description\":\"Initial Revision\"}"; + + Map headers = new HashMap<>(); + headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, "Bearer " + sharedContext.getPublisherAccessToken()); + headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_API_HOST); + + HttpResponse response = sharedContext.getHttpClient().doPost(Utils.getAPIRevisionURL(apiUUID), + headers, payload, Constants.CONTENT_TYPES.APPLICATION_JSON); + + sharedContext.setRevisionUUID(Utils.extractID(SimpleHTTPClient.responseEntityBodyToString(response))); + + Thread.sleep(3000); + + String payload2 = "[{\"name\": \"Default\", \"vhost\": \"default.gw.wso2.com\", \"displayOnDevportal\": true}]"; + + HttpResponse response2 = sharedContext.getHttpClient().doPost(Utils.getAPIRevisionDeploymentURL(apiUUID, sharedContext.getRevisionUUID()), + headers, payload2, Constants.CONTENT_TYPES.APPLICATION_JSON); + + logger.info("Response: "+ response2); + + sharedContext.setResponse(response2); + Thread.sleep(3000); + } + + @When("make the API Deployment request") + public void make_a_api_deployment_request() throws Exception { + + // Create a MultipartEntityBuilder to build the request entity + MultipartEntityBuilder builder = MultipartEntityBuilder.create() + .setMode(HttpMultipartMode.BROWSER_COMPATIBLE) + .addPart("url", new FileBody(definitionFile)) + .addPart("apkConfiguration", new FileBody(payloadFile)); + + Map headers = new HashMap<>(); + headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, "Bearer " + sharedContext.getPublisherAccessToken()); + headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_API_HOST); + + HttpEntity multipartEntity = builder.build(); + + HttpResponse response = sharedContext.getHttpClient().doPostWithMultipart(Utils.getAPIDeployerURL(), + multipartEntity, headers); + + sharedContext.setResponse(response); + sharedContext.setResponseBody(SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse())); + Thread.sleep(3000); + } + + @When("make the API deployment request for organization {string}") + public void makeAPIDeploymentFromOrganization(String organization) throws Exception { + + // Create a MultipartEntityBuilder to build the request entity + MultipartEntityBuilder builder = MultipartEntityBuilder.create() + .setMode(HttpMultipartMode.BROWSER_COMPATIBLE) + .addPart("definitionFile", new FileBody(definitionFile)) + .addPart("apkConfiguration", new FileBody(payloadFile)); + + Map headers = new HashMap<>(); + Object accessToken = sharedContext.getStoreValue(organization); + headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, "Bearer " + accessToken); + headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_API_HOST); + + HttpEntity multipartEntity = builder.build(); + + HttpResponse response = sharedContext.getHttpClient().doPostWithMultipart(Utils.getAPIDeployerURL(), + multipartEntity, headers); + + sharedContext.setResponse(response); + sharedContext.setResponseBody(SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse())); + Thread.sleep(3000); + } + + + + @When("I undeploy the API whose ID is {string}") + public void i_undeploy_the_api_whose_id_is(String apiID) throws Exception { + + // Create query parameters + List queryParams = new ArrayList<>(); + queryParams.add(new BasicNameValuePair("apiId", apiID)); + + URI uri = new URIBuilder(Utils.getAPIUnDeployerURL()).addParameters(queryParams).build(); + + Map headers = new HashMap<>(); + headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, "Bearer " + sharedContext.getPublisherAccessToken()); + headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_API_HOST); + + HttpResponse response = sharedContext.getHttpClient().doPost(uri.toString(), headers, "", + Constants.CONTENT_TYPES.APPLICATION_JSON); + + sharedContext.setResponse(response); + sharedContext.setResponseBody(SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse())); + } + + @When("I undeploy the API whose ID is {string} and organization {string}") + public void undeployAPIByIdAndOrganization(String apiID,String organization) throws Exception { + + // Create query parameters + List queryParams = new ArrayList<>(); + queryParams.add(new BasicNameValuePair("apiId", apiID)); + + URI uri = new URIBuilder(Utils.getAPIUnDeployerURL()).addParameters(queryParams).build(); + + Map headers = new HashMap<>(); + Object header = sharedContext.getStoreValue(organization); + headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, "Bearer " + header); + headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_API_HOST); + + HttpResponse response = sharedContext.getHttpClient().doPost(uri.toString(), headers, "", + Constants.CONTENT_TYPES.APPLICATION_JSON); + + sharedContext.setResponse(response); + sharedContext.setResponseBody(SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse())); + } +} diff --git a/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/APKGenerationSteps.java b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/APKGenerationSteps.java new file mode 100644 index 000000000..21f2b09a0 --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/APKGenerationSteps.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023, WSO2 LLC (http://www.wso2.com). + * + * WSO2 LLC licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wso2.apk.integration.api; + +import com.google.common.io.Resources; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.entity.mime.content.FileBody; +import org.testng.Assert; +import org.wso2.apk.integration.utils.Utils; + +import java.io.File; +import java.net.URL; +import java.nio.charset.StandardCharsets; + +/** + * This class contains the step definitions for APK generation. + */ +public class APKGenerationSteps { + + private final SharedContext sharedContext; + private File definitionFile; + + public APKGenerationSteps(SharedContext sharedContext) { + + this.sharedContext = sharedContext; + } + + @When("I use the definition file {string} in resources") + public void i_use_the_definition_file_in_resources(String definitionFilePath) { + + URL url = Resources.getResource(definitionFilePath); + definitionFile = new File(url.getPath()); + } + + @When("generate the APK conf file for a {string} API") + public void generate_the_apk_conf_file(String apiType) throws Exception { + + // Create a MultipartEntityBuilder to build the request entity + MultipartEntityBuilder builder = MultipartEntityBuilder.create() + .setMode(HttpMultipartMode.BROWSER_COMPATIBLE) + .addTextBody("apiType", apiType) + .addPart("definition", new FileBody(definitionFile)); + + HttpEntity multipartEntity = builder.build(); + HttpResponse httpResponse = sharedContext.getHttpClient().doPostWithMultipart(Utils.getConfigGeneratorURL(), + multipartEntity); + sharedContext.setResponse(httpResponse); + } + + @Then("the response body should be {string} in resources") + public void the_response_body_should_be_in_resources(String expectedAPKConfFilePath) throws Exception { + + URL url = Resources.getResource(expectedAPKConfFilePath); + String text = Resources.toString(url, StandardCharsets.UTF_8); + + Assert.assertEquals(sharedContext.getHttpClient().getResponsePayload(sharedContext.getResponse()), text); + } +} diff --git a/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BaseSteps.java b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BaseSteps.java new file mode 100644 index 000000000..04db3306a --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BaseSteps.java @@ -0,0 +1,418 @@ +/* + * Copyright (c) 2024, WSO2 LLC (http://www.wso2.com). + * + * WSO2 LLC licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wso2.apk.integration.api; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JOSEObjectType; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.jwk.source.JWKSource; +import com.nimbusds.jose.jwk.source.JWKSourceBuilder; +import com.nimbusds.jose.proc.BadJOSEException; +import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier; +import com.nimbusds.jose.proc.JWSKeySelector; +import com.nimbusds.jose.proc.JWSVerificationKeySelector; +import com.nimbusds.jose.proc.SecurityContext; +import com.nimbusds.jose.util.Resource; +import com.nimbusds.jose.util.ResourceRetriever; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.proc.ConfigurableJWTProcessor; +import com.nimbusds.jwt.proc.DefaultJWTProcessor; +import io.cucumber.core.options.CurlOption; +import io.cucumber.datatable.DataTable; +import io.cucumber.java.Before; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import org.apache.commons.io.IOUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.testng.Assert; +import org.wso2.apk.integration.utils.Constants; +import org.wso2.apk.integration.utils.Utils; +import org.wso2.apk.integration.utils.clients.SimpleHTTPClient; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.text.ParseException; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This class contains the common step definitions. + */ +public class BaseSteps { + + private static final Log logger = LogFactory.getLog(BaseSteps.class); + private final SharedContext sharedContext; + private SimpleHTTPClient httpClient; + private static final int MAX_WAIT_FOR_NEXT_MINUTE_IN_SECONDS = 10; + + public BaseSteps(SharedContext sharedContext) { + + this.sharedContext = sharedContext; + } + + @Before + public void setup() throws Exception { + + httpClient = sharedContext.getHttpClient(); + } + + @Given("The system is ready") + public void systemIsReady() { + + } + + @Then("the response body should contain {string}") + public void theResponseBodyShouldContain(String expectedText) throws IOException { + Assert.assertTrue(sharedContext.getResponseBody().contains(expectedText), "Actual response body: " + sharedContext.getResponseBody()); + } + @Then("the response body should not contain {string}") + public void theResponseBodyShouldNotContain(String expectedText) throws IOException { + Assert.assertFalse(sharedContext.getResponseBody().contains(expectedText), "Actual response body: " + sharedContext.getResponseBody()); + } + + @Then("the response body should contain") + public void theResponseBodyShouldContain(DataTable dataTable) throws IOException { + List responseBodyLines = dataTable.asList(String.class); + for (String line : responseBodyLines) { + Assert.assertTrue(sharedContext.getResponseBody().contains(line), "Actual response body: " + sharedContext.getResponseBody()); + } + } + + @Then("the response status code should be {int}") + public void theResponseStatusCodeShouldBe(int expectedStatusCode) throws IOException { + + int actualStatusCode = sharedContext.getResponse().getStatusLine().getStatusCode(); + Assert.assertEquals(actualStatusCode, expectedStatusCode); + } + + @Then("I send {string} request to {string} with body {string}") + public void sendHttpRequest(String httpMethod, String url, String body) throws IOException { + body = Utils.resolveVariables(body, sharedContext.getValueStore()); + if (sharedContext.getResponse() instanceof CloseableHttpResponse) { + ((CloseableHttpResponse) sharedContext.getResponse()).close(); + } + if (CurlOption.HttpMethod.GET.toString().toLowerCase().equals(httpMethod.toLowerCase())) { + sharedContext.setResponse(httpClient.doGet(url, sharedContext.getHeaders())); + sharedContext.setResponseBody(SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse())); + } else if (CurlOption.HttpMethod.POST.toString().toLowerCase().equals(httpMethod.toLowerCase())) { + sharedContext.setResponse(httpClient.doPost(url, sharedContext.getHeaders(), body, null)); + sharedContext.setResponseBody(SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse())); + } else if (CurlOption.HttpMethod.PUT.toString().toLowerCase().equals(httpMethod.toLowerCase())) { + sharedContext.setResponse(httpClient.doPut(url, sharedContext.getHeaders(), body, null)); + sharedContext.setResponseBody(SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse())); + } else if (CurlOption.HttpMethod.DELETE.toString().toLowerCase().equals(httpMethod.toLowerCase())) { + sharedContext.setResponse(httpClient.doDelete(url, sharedContext.getHeaders())); + sharedContext.setResponseBody(SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse())); + } else if (CurlOption.HttpMethod.OPTIONS.toString().toLowerCase().equals(httpMethod.toLowerCase())) { + sharedContext.setResponse(httpClient.doOptions(url, sharedContext.getHeaders(), null, null)); + } + } + + // It will send request using a new thread and forget about the response + @Then("I send {string} async request to {string} with body {string}") + public void sendAsyncHttpRequest(String httpMethod, String url, String body) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + String finalBody = Utils.resolveVariables(body, sharedContext.getValueStore()); + if (sharedContext.getResponse() instanceof CloseableHttpResponse) { + ((CloseableHttpResponse) sharedContext.getResponse()).close(); + } + SimpleHTTPClient simpleHTTPClient = new SimpleHTTPClient(); + Thread thread = new Thread(() -> { + try { + if (CurlOption.HttpMethod.GET.toString().toLowerCase().equals(httpMethod.toLowerCase())) { + simpleHTTPClient.doGet(url, sharedContext.getHeaders()); + } else if (CurlOption.HttpMethod.POST.toString().toLowerCase().equals(httpMethod.toLowerCase())) { + simpleHTTPClient.doPost(url, sharedContext.getHeaders(), finalBody, null); + } else if (CurlOption.HttpMethod.PUT.toString().toLowerCase().equals(httpMethod.toLowerCase())) { + simpleHTTPClient.doPut(url, sharedContext.getHeaders(), finalBody, null); + } else if (CurlOption.HttpMethod.DELETE.toString().toLowerCase().equals(httpMethod.toLowerCase())) { + simpleHTTPClient.doPut(url, sharedContext.getHeaders(), finalBody, null); + } else if (CurlOption.HttpMethod.OPTIONS.toString().toLowerCase().equals(httpMethod.toLowerCase())) { + simpleHTTPClient.doOptions(url, sharedContext.getHeaders(), null, null); + } + } catch (IOException e) { + logger.warn("An async http request sending thread experienced an error: " + e); + } + }); + thread.start(); + } + + @Then("I set headers") + public void setHeaders(DataTable dataTable) { + List> rows = dataTable.asLists(String.class); + for (List columns : rows) { + String key = columns.get(0); + String value = columns.get(1); + key = Utils.resolveVariables(key, sharedContext.getValueStore()); + value = Utils.resolveVariables(value, sharedContext.getValueStore()); + sharedContext.addHeader(key, value); + } + } + + @Then("the response headers should contain") + public void theResponseHeadersShouldContain(DataTable dataTable) { + List> rows = dataTable.asLists(String.class); + for (List columns : rows) { + String key = columns.get(0); + String value = columns.get(1); + Header header = sharedContext.getResponse().getFirstHeader(key); + Assert.assertNotNull(header); + Assert.assertEquals(header.getValue(), value); + } + } + + @Then("the response headers should not contain") + public void theResponseHeadersShouldNotContain(DataTable dataTable) { + List> rows = dataTable.asLists(String.class); + for (List columns : rows) { + String key = columns.get(0); + Header header = sharedContext.getResponse().getFirstHeader(key); + Assert.assertNull(header); + } + } + + @Then("I eventually receive {int} response code, not accepting") + public void eventualSuccess(int statusCode, DataTable dataTable) throws IOException, InterruptedException { + List nonAcceptableCodes = dataTable.asList(Integer.class); + if (sharedContext.getResponse().getStatusLine().getStatusCode() == statusCode) { + Assert.assertTrue(true); + } else { + HttpResponse httpResponse = httpClient.executeLastRequestForEventualConsistentResponse(statusCode, + nonAcceptableCodes); + sharedContext.setResponse(httpResponse); + Assert.assertEquals(httpResponse.getStatusLine().getStatusCode(), statusCode); + } + } + + @Then("I wait for next minute") + public void waitForNextMinute() throws InterruptedException { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime nextMinute = now.plusMinutes(1).withSecond(0).withNano(0); + long secondsToWait = now.until(nextMinute, ChronoUnit.SECONDS); + if (secondsToWait > MAX_WAIT_FOR_NEXT_MINUTE_IN_SECONDS) { + return; + } + Thread.sleep((secondsToWait+1) * 1000); + logger.info("Current time: " + LocalDateTime.now()); + } + + @Then("I wait for next minute strictly") + public void waitForNextMinuteStrictly() throws InterruptedException { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime nextMinute = now.plusMinutes(1).withSecond(0).withNano(0); + long secondsToWait = now.until(nextMinute, ChronoUnit.SECONDS); + Thread.sleep((secondsToWait+1) * 1000); + logger.info("Current time: " + LocalDateTime.now()); + } + + @Then("I wait for {int} minute") + public void waitForMinute(int minute) throws InterruptedException { + Thread.sleep(minute * 1000); + } + + @Then("I wait for {int} seconds") + public void waitForSeconds(int seconds) throws InterruptedException { + Thread.sleep(seconds * 1000); + } + + @Then("the response headers contains key {string} and value {string}") + public void containsHeader(String key, String value) { + key = Utils.resolveVariables(key, sharedContext.getValueStore()); + value = Utils.resolveVariables(value, sharedContext.getValueStore()); + HttpResponse response = sharedContext.getResponse(); + if (response == null) { + Assert.fail("Response is null."); + } + Header header = response.getFirstHeader(key); + if (header == null) { + Assert.fail("Could not find a header with the given key: " + key); + } + if ("*".equals(value)) { + return; // Any value is acceptable + } + String actualValue = header.getValue(); + Assert.assertEquals(value, actualValue,"Header with key found but value mismatched."); + } + @Then("the response headers not contains key {string}") + public void notContainsHeader(String key) { + key = Utils.resolveVariables(key, sharedContext.getValueStore()); + HttpResponse response = sharedContext.getResponse(); + if (response == null) { + Assert.fail("Response is null."); + } + Header header = response.getFirstHeader(key); + Assert.assertNull(header,"header contains in response headers"); + } + + @Then("the {string} jwt should validate from JWKS {string} and contain") + public void decode_header_and_validate(String header,String jwksEndpoint, DataTable dataTable) throws MalformedURLException { + List> claims = dataTable.asMaps(String.class, String.class); + JsonObject jsonResponse = (JsonObject) JsonParser.parseString(sharedContext.getResponseBody()); + String headerValue = jsonResponse.get("headers").getAsJsonObject().get(header).getAsString(); + ConfigurableJWTProcessor jwtProcessor = new DefaultJWTProcessor<>(); + jwtProcessor.setJWSTypeVerifier(new DefaultJOSEObjectTypeVerifier<>(JOSEObjectType.JWT)); + ResourceRetriever retriever = url -> { + try { + HttpResponse httpResponse = new SimpleHTTPClient().doGet(url.toString(), Collections.emptyMap()); + StatusLine statusLine = httpResponse.getStatusLine(); + if (statusLine.getStatusCode() == 200) { + Header header1 = httpResponse.getFirstHeader("Content-Type"); + try (InputStream content = httpResponse.getEntity().getContent()) { + return new Resource(IOUtils.toString(content), header1.getValue()); + } + } else { + throw new IOException("HTTP " + statusLine.getStatusCode() + ": " + statusLine.getReasonPhrase()); + } + } catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) { + throw new IOException(e); + } + }; + + JWKSource keySource = JWKSourceBuilder.create(new URL(jwksEndpoint), retriever).build(); + JWSAlgorithm expectedJWSAlg = JWSAlgorithm.RS256; + JWSKeySelector keySelector = new JWSVerificationKeySelector<>(expectedJWSAlg, keySource); + jwtProcessor.setJWSKeySelector(keySelector); + try { + JWTClaimsSet claimsSet = jwtProcessor.process(headerValue, null); + for (Map claim : claims) { + Object claim1 = claimsSet.getClaim(claim.get("claim")); + Assert.assertNotNull(claim1, "Actual decoded JWT body: " + claimsSet); + Assert.assertEquals(claim.get("value"), claim1.toString(), "Actual " + + "decoded JWT body: " + claimsSet); + } + } catch (BadJOSEException | JOSEException|ParseException e) { + logger.error("JWT Signature verification fail", e); + Assert.fail("JWT Signature verification fail"); + } + } + + @Given("I have a DCR application for Publisher") + public void iHaveADCRApplicationForPublisher() throws Exception { + + Map headers = new HashMap<>(); + headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_IDP_HOST); + headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, "Basic YWRtaW46YWRtaW4="); + + HttpResponse httpResponse = httpClient.doPost(Utils.getDCREndpointURL(), headers, "{\n" + + " \"callbackUrl\":\"www.google.lk\",\n" + + " \"clientName\":\"rest_api_publisher\",\n" + + " \"owner\":\"admin\",\n" + + " \"grantType\":\"client_credentials password refresh_token\",\n" + + " \"saasApp\":true\n" + + " }", + Constants.CONTENT_TYPES.APPLICATION_JSON); + sharedContext.setPublisherBasicAuthToken(Utils.extractBasicToken(httpResponse)); + sharedContext.addStoreValue("publisherBasicAuthToken", sharedContext.getPublisherBasicAuthToken()); + } + + @Given("I have a DCR application for Devportal") + public void iHaveADCRApplicationForDevportal() throws Exception { + + Map headers = new HashMap<>(); + headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_IDP_HOST); + headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, "Basic YWRtaW46YWRtaW4="); + + HttpResponse httpResponse = httpClient.doPost(Utils.getDCREndpointURL(), headers, "{\n" + + " \"callbackUrl\":\"www.google.lk\",\n" + + " \"clientName\":\"rest_api_publisher\",\n" + + " \"owner\":\"admin\",\n" + + " \"grantType\":\"client_credentials password refresh_token\",\n" + + " \"saasApp\":true\n" + + " }", + Constants.CONTENT_TYPES.APPLICATION_JSON); + sharedContext.setDevportalBasicAuthToken(Utils.extractBasicToken(httpResponse)); + sharedContext.addStoreValue("devportalBasicAuthToken", sharedContext.getDevportalBasicAuthToken()); + } + + @Given("I have a valid Publisher access token") + public void iHaveValidPublisherAccessToken() throws Exception { + + Map headers = new HashMap<>(); + String basicAuthHeader = "Basic " + sharedContext.getPublisherBasicAuthToken(); + logger.info("Basic Auth Header: " + basicAuthHeader); + headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_IDP_HOST); + headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, basicAuthHeader); + + HttpResponse httpResponse = httpClient.doPost(Utils.getTokenEndpointURL(), headers, "grant_type=password&username=admin&password=admin&scope=apim:api_view apim:api_create", + Constants.CONTENT_TYPES.APPLICATION_X_WWW_FORM_URLENCODED); + logger.info("Response: " + httpResponse); + sharedContext.setPublisherAccessToken(Utils.extractToken(httpResponse)); + sharedContext.addStoreValue("publisherAccessToken", sharedContext.getPublisherAccessToken()); + } + + @Given("I have a valid Devportal access token") + public void iHaveValidDevportalAccessToken() throws Exception { + + Map headers = new HashMap<>(); + String basicAuthHeader = "Basic " + sharedContext.getDevportalBasicAuthToken(); + headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_IDP_HOST); + headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, basicAuthHeader); + + HttpResponse httpResponse = httpClient.doPost(Utils.getTokenEndpointURL(), headers, "grant_type=password&username=admin&password=admin&scope=apim:api_view apim:api_create", + Constants.CONTENT_TYPES.APPLICATION_JSON); + sharedContext.setDevportalAccessToken(Utils.extractToken(httpResponse)); + sharedContext.addStoreValue("devportalAccessToken", sharedContext.getDevportalAccessToken()); + } + +// @Given("I have a valid subscription without api deploy permission") +// public void iHaveValidSubscriptionWithAPICreateScope() throws Exception { +// +// Map headers = new HashMap<>(); +// headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_IDP_HOST); +// headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, "Basic NDVmMWM1YzgtYTkyZS0xMWVkLWFmYTEtMDI0MmFjMTIwMDAyOjRmYmQ2MmVjLWE5MmUtMTFlZC1hZmExLTAyNDJhYzEyMDAwMg=="); +// +// HttpResponse httpResponse = httpClient.doPost(Utils.getTokenEndpointURL(), headers, "grant_type=client_credentials", +// Constants.CONTENT_TYPES.APPLICATION_X_WWW_FORM_URLENCODED); +// sharedContext.setAccessToken(Utils.extractToken(httpResponse)); +// sharedContext.addStoreValue("accessToken", sharedContext.getAccessToken()); +// } +// +// @Given("I have a valid subscription with scopes") +// public void iHaveValidSubscriptionWithScope(DataTable dataTable) throws Exception { +// List> rows = dataTable.asLists(String.class); +// String scopes = Constants.EMPTY_STRING; +// for (List row : rows) { +// String scope = row.get(0); +// scopes += scope + Constants.SPACE_STRING; +// } +// Map headers = new HashMap<>(); +// headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_IDP_HOST); +// headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, Constants.SUBSCRIPTION_BASIC_AUTH_TOKEN); +// +// HttpResponse httpResponse = httpClient.doPost(Utils.getTokenEndpointURL(), headers, +// "grant_type=client_credentials&scope=" + scopes, +// Constants.CONTENT_TYPES.APPLICATION_X_WWW_FORM_URLENCODED); +// sharedContext.setAccessToken(Utils.extractToken(httpResponse)); +// sharedContext.addStoreValue(Constants.ACCESS_TOKEN, sharedContext.getAccessToken()); +// } +} diff --git a/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/JWTGeneratorSteps.java b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/JWTGeneratorSteps.java new file mode 100644 index 000000000..9b3d94028 --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/JWTGeneratorSteps.java @@ -0,0 +1,113 @@ +package org.wso2.apk.integration.api; + +import com.google.common.io.Resources; +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.JWSSigner; +import com.nimbusds.jose.crypto.RSASSASigner; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import io.cucumber.java.en.And; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.util.Date; +import java.util.UUID; + +import org.wso2.apk.integration.utils.Constants; + +public class JWTGeneratorSteps { + + private final SharedContext sharedContext; + + public JWTGeneratorSteps(SharedContext sharedContext) { + + this.sharedContext = sharedContext; + } + + @Then("I generate JWT token from idp1 with kid {string}") + public void generateTokenFromIdp1(String kid) throws IOException, CertificateException, KeyStoreException, + NoSuchAlgorithmException, JOSEException { + + URL url = Resources.getResource("artifacts/jwtcert/idp1.jks"); + File keyStoreFile = new File(url.getPath()); + KeyStore keyStore = KeyStore.getInstance(keyStoreFile, "wso2carbon".toCharArray()); + RSAKey rsaKey = RSAKey.load(keyStore, "idp1Key", "wso2carbon".toCharArray()); + JWSSigner signer = new RSASSASigner(rsaKey); + JWTClaimsSet claimsSet = new JWTClaimsSet.Builder() + .subject("alice") + .issuer("https://idp1.com") + .expirationTime(new Date(new Date().getTime() + 60 * 1000)) + .jwtID(UUID.randomUUID().toString()) + .claim("azp", UUID.randomUUID().toString()) + .claim("scope", Constants.API_CREATE_SCOPE) + .build(); + SignedJWT signedJWT = new SignedJWT( + new JWSHeader.Builder(JWSAlgorithm.RS256).keyID(kid).build(), + claimsSet); + signedJWT.sign(signer); + String jwtToken = signedJWT.serialize(); + sharedContext.addStoreValue("idp-1-token", jwtToken); + } + @Then("I generate JWT token from idp1 with kid {string} and consumer_key {string}") + public void generateTokenFromIdp1WithConsumerKey(String kid,String consumerKey) throws IOException, CertificateException, KeyStoreException, + NoSuchAlgorithmException, JOSEException { + + URL url = Resources.getResource("artifacts/jwtcert/idp1.jks"); + File keyStoreFile = new File(url.getPath()); + KeyStore keyStore = KeyStore.getInstance(keyStoreFile, "wso2carbon".toCharArray()); + RSAKey rsaKey = RSAKey.load(keyStore, "idp1Key", "wso2carbon".toCharArray()); + JWSSigner signer = new RSASSASigner(rsaKey); + JWTClaimsSet claimsSet = new JWTClaimsSet.Builder() + .subject("alice") + .issuer("https://idp1.com") + .expirationTime(new Date(new Date().getTime() + 60 * 1000)) + .jwtID(UUID.randomUUID().toString()) + .claim("azp", consumerKey) + .claim("scope", Constants.API_CREATE_SCOPE) + .build(); + SignedJWT signedJWT = new SignedJWT( + new JWSHeader.Builder(JWSAlgorithm.RS256).keyID(kid).build(), + claimsSet); + signedJWT.sign(signer); + String jwtToken = signedJWT.serialize(); + sharedContext.addStoreValue("idp-1-"+consumerKey+"-token", jwtToken); + } + + + @And("I have a valid token for organization {string}") + public void generateTokenFromIdp1WithOrganization(String organization) throws IOException, CertificateException, + KeyStoreException, + NoSuchAlgorithmException, JOSEException { + + URL url = Resources.getResource("artifacts/jwtcert/idp1.jks"); + File keyStoreFile = new File(url.getPath()); + KeyStore keyStore = KeyStore.getInstance(keyStoreFile, "wso2carbon".toCharArray()); + RSAKey rsaKey = RSAKey.load(keyStore, "idp1Key", "wso2carbon".toCharArray()); + JWSSigner signer = new RSASSASigner(rsaKey); + JWTClaimsSet claimsSet = new JWTClaimsSet.Builder() + .subject("alice") + .issuer("https://idp1.com") + .expirationTime(new Date(new Date().getTime() + 60 * 1000)) + .jwtID(UUID.randomUUID().toString()) + .claim("azp", UUID.randomUUID().toString()) + .claim("scope", Constants.API_CREATE_SCOPE) + .claim("organization", organization) + .build(); + SignedJWT signedJWT = new SignedJWT( + new JWSHeader.Builder(JWSAlgorithm.RS256).keyID("123-456").build(), + claimsSet); + signedJWT.sign(signer); + String jwtToken = signedJWT.serialize(); + sharedContext.addStoreValue(organization, jwtToken); + } +} diff --git a/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/MTLSClientCertSteps.java b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/MTLSClientCertSteps.java new file mode 100644 index 000000000..b3f66a865 --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/MTLSClientCertSteps.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2023, WSO2 LLC (http://www.wso2.com). + * + * WSO2 LLC licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wso2.apk.integration.api; + +import org.apache.http.HttpResponse; +import org.wso2.apk.integration.utils.Constants; +import org.wso2.apk.integration.utils.Utils; +import org.wso2.apk.integration.utils.clients.SimpleHTTPClient; + +import com.google.common.io.Resources; + +import io.cucumber.java.Before; +import io.cucumber.java.en.Then; + +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +/** + * This class contains the common step definitions. + */ +public class MTLSClientCertSteps { + + private final SharedContext sharedContext; + private SimpleHTTPClient httpClient; + + public MTLSClientCertSteps(SharedContext sharedContext) { + + this.sharedContext = sharedContext; + } + + @Before + public void setup() throws Exception { + + httpClient = sharedContext.getHttpClient(); + } + +// @Then("I have a valid token with a client certificate {string}") +// public void getValidClientCertificateForMTLS(String clientCertificatePath) throws Exception { +// +// Map headers = new HashMap<>(); +// headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_IDP_HOST); +// headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, +// "Basic NDVmMWM1YzgtYTkyZS0xMWVkLWFmYTEtMDI0MmFjMTIwMDAyOjRmYmQ2MmVjLWE5MmUtMTFlZC1hZmExLTAyNDJhYzEyMDAwMg=="); +// +// HttpResponse httpResponse = httpClient.doPost(Utils.getTokenEndpointURL(), headers, +// "grant_type=client_credentials&scope=" + Constants.API_CREATE_SCOPE, +// Constants.CONTENT_TYPES.APPLICATION_X_WWW_FORM_URLENCODED); +// sharedContext.setAccessToken(Utils.extractToken(httpResponse)); +// sharedContext.addStoreValue("accessToken", sharedContext.getAccessToken()); +// +// URL url = Resources.getResource("artifacts/certificates/" + clientCertificatePath); +// String clientCertificate = Resources.toString(url, StandardCharsets.UTF_8); +// sharedContext.addStoreValue("clientCertificate", clientCertificate); +// +// } +} \ No newline at end of file diff --git a/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java new file mode 100644 index 000000000..402a937a8 --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2023, WSO2 LLC (http://www.wso2.com). + * + * WSO2 LLC licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wso2.apk.integration.api; + +import org.apache.http.HttpResponse; +import org.wso2.apk.integration.utils.clients.SimpleHTTPClient; + +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class SharedContext { + + private SimpleHTTPClient httpClient; + private String publisherAccessToken; + private String devportalAccessToken; + private String publisherBasicAuthToken; + private String devportalBasicAuthToken; + private HttpResponse response; + private String responseBody; + private String apiUUID; + private String revisionUUID; + private HashMap valueStore = new HashMap<>(); + private HashMap headers = new HashMap<>(); + + public SimpleHTTPClient getHttpClient() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + if (httpClient == null) { + httpClient = new SimpleHTTPClient(); + } + return httpClient; + } + + public String getPublisherAccessToken() { + + return publisherAccessToken; + } + + public void setPublisherAccessToken(String accessToken) { + + this.publisherAccessToken = accessToken; + } + + public String getDevportalAccessToken() { + + return devportalAccessToken; + } + + public void setDevportalAccessToken(String accessToken) { + + this.devportalAccessToken = accessToken; + } + + public String getPublisherBasicAuthToken() { + + return publisherBasicAuthToken; + } + + public void setPublisherBasicAuthToken(String basicAuthToken) { + + this.publisherBasicAuthToken = basicAuthToken; + } + + public String getDevportalBasicAuthToken() { + + return devportalBasicAuthToken; + } + + public void setDevportalBasicAuthToken(String basicAuthToken) { + + this.devportalBasicAuthToken = basicAuthToken; + } + + public HttpResponse getResponse() { + + return response; + } + + public void setResponse(HttpResponse response) { + + this.response = response; + } + + public Object getStoreValue(String key) { + return valueStore.get(key); + } + + public void addStoreValue(String key, Object value) { + valueStore.put(key, value); + } + + public Map getValueStore() { + return Collections.unmodifiableMap(valueStore); + } + + public Map getHeaders() { + return Collections.unmodifiableMap(headers); + } + + public void addHeader(String key, String value) { + headers.put(key, value); + } + + public String getResponseBody() { + + return responseBody; + } + + public void setResponseBody(String responseBody) { + + this.responseBody = responseBody; + } + + public String getApiUUID() { + + return apiUUID; + } + + public void setApiUUID(String apiUUID) { + + this.apiUUID = apiUUID; + } + + public String getRevisionUUID() { + + return revisionUUID; + } + + public void setRevisionUUID(String revisionUUID) { + + this.revisionUUID = revisionUUID; + } +} diff --git a/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Constants.java b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Constants.java new file mode 100644 index 000000000..d2341bc3a --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Constants.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023, WSO2 LLC (http://www.wso2.com). + * + * WSO2 LLC licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wso2.apk.integration.utils; + +public class Constants { + + public static final String DEFAULT_IDP_HOST = "am.wso2.com"; + public static final String DEFAULT_API_HOST = "am.wso2.com"; + public static final String DEFAULT_GW_PORT = "9443"; + public static final String DEFAULT_TOKEN_EP = "oauth2/token"; + public static final String DEFAULT_DCR_EP = "client-registration/v0.17/register"; + public static final String DEFAULT_API_CONFIGURATOR = "api/configurator/1.0.0/"; + public static final String DEFAULT_API_DEPLOYER = "api/am/publisher/v4/"; + public static final String ACCESS_TOKEN = "accessToken"; + public static final String EMPTY_STRING = ""; + public static final String API_CREATE_SCOPE = "apk:api_create"; + public static final String SPACE_STRING = " "; + public static final String SUBSCRIPTION_BASIC_AUTH_TOKEN = + "Basic NDVmMWM1YzgtYTkyZS0xMWVkLWFmYTEtMDI0MmFjMTIwMDAyOjRmYmQ2MmVjLWE5MmUtMTFlZC1hZmExLTAyNDJhYzEyMDAwMg=="; + + public class REQUEST_HEADERS { + + public static final String HOST = "Host"; + public static final String AUTHORIZATION = "Authorization"; + public static final String CONTENT_TYPE = "Content-Type"; + } + + public class CONTENT_TYPES { + + public static final String APPLICATION_JSON = "application/json"; + public static final String APPLICATION_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded"; + + public static final String MULTIPART_FORM_DATA = "multipart/form-data"; + + public static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; + + public static final String APPLICATION_ZIP = "application/zip"; + + public static final String TEXT_PLAIN = "text/plain"; + } +} diff --git a/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/MultipartFilePart.java b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/MultipartFilePart.java new file mode 100644 index 000000000..808ecd83f --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/MultipartFilePart.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023, WSO2 LLC (http://www.wso2.com). + * + * WSO2 LLC licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wso2.apk.integration.utils; + +import java.io.File; + +public class MultipartFilePart { + + private String name; + private File file; + + public MultipartFilePart(String name, File file) { + + this.name = name; + this.file = file; + } + + public String getName() { + + return name; + } + + public File getFile() { + + return file; + } +} diff --git a/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Utils.java b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Utils.java new file mode 100644 index 000000000..0023c1ea1 --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Utils.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2023, WSO2 LLC (http://www.wso2.com). + * + * WSO2 LLC licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wso2.apk.integration.utils; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.entity.ContentType; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +public class Utils { + + public static String getConfigGeneratorURL() { + + return "https://" + Constants.DEFAULT_API_HOST + ":" + Constants.DEFAULT_GW_PORT + "/" + + Constants.DEFAULT_API_CONFIGURATOR + "apis/generate-configuration"; + } + + public static String getDCREndpointURL() { + + return "https://" + Constants.DEFAULT_IDP_HOST + ":" + Constants.DEFAULT_GW_PORT + "/" + + Constants.DEFAULT_DCR_EP; + } + + public static String getTokenEndpointURL() { + + return "https://" + Constants.DEFAULT_IDP_HOST + ":" + Constants.DEFAULT_GW_PORT + "/" + + Constants.DEFAULT_TOKEN_EP; + } + + public static String getAPIDeployerURL() { + + return "https://" + Constants.DEFAULT_API_HOST + ":" + Constants.DEFAULT_GW_PORT + "/" + + Constants.DEFAULT_API_DEPLOYER + "apis/deploy"; + } + + public static String getImportAPIURL() { + + return "https://" + Constants.DEFAULT_API_HOST + ":" + Constants.DEFAULT_GW_PORT + "/" + + Constants.DEFAULT_API_DEPLOYER + "apis/import-openapi"; + } + + public static String getAPIRevisionURL(String apiUUID) { + + return "https://" + Constants.DEFAULT_API_HOST + ":" + Constants.DEFAULT_GW_PORT + "/" + + Constants.DEFAULT_API_DEPLOYER + "apis/" + apiUUID + "/revisions"; + } + + public static String getAPIRevisionDeploymentURL(String apiUUID, String revisionId) { + + return "https://" + Constants.DEFAULT_API_HOST + ":" + Constants.DEFAULT_GW_PORT + "/" + + Constants.DEFAULT_API_DEPLOYER + "apis/" + apiUUID + "/deploy-revision?revisionId=" + revisionId; + } + + public static String getAPIUnDeployerURL() { + + return "https://" + Constants.DEFAULT_API_HOST + ":" + Constants.DEFAULT_GW_PORT + "/" + + Constants.DEFAULT_API_DEPLOYER + "apis/undeploy"; + } + + public static String extractID(String payload) throws IOException { + + JSONParser parser = new JSONParser(); + try { + // Parse the JSON string + JSONObject jsonObject = (JSONObject) parser.parse(payload); + + // Get the value of the "id" attribute + String idValue = (String) jsonObject.get("id"); + return idValue; + } catch (ParseException e) { + throw new IOException("Error while parsing the JSON payload: " + e.getMessage()); + } + } + + public static String extractToken(HttpResponse response) throws IOException { + + int responseCode = response.getStatusLine().getStatusCode(); + + HttpEntity entity = response.getEntity(); + Charset charset = ContentType.getOrDefault(entity).getCharset(); + if (charset == null) { + charset = StandardCharsets.UTF_8; + } + + BufferedReader reader = new BufferedReader(new InputStreamReader(entity.getContent(), charset)); + String inputLine; + StringBuilder stringBuilder = new StringBuilder(); + + while ((inputLine = reader.readLine()) != null) { + stringBuilder.append(inputLine); + } + + if (responseCode != HttpStatus.SC_OK) { + throw new IOException("Error while accessing the Token URL. " + + response.getStatusLine()); + } + + JsonParser parser = new JsonParser(); + JsonObject jsonResponse = (JsonObject) parser.parse(stringBuilder.toString()); + if (jsonResponse.has("access_token")) { + return jsonResponse.get("access_token").getAsString(); + } + throw new IOException("Missing key [access_token] in the response from the OAuth server"); + } + + public static String extractBasicToken(HttpResponse response) throws IOException { + + int responseCode = response.getStatusLine().getStatusCode(); + String clientId = null; + String clientSecret = null; + + HttpEntity entity = response.getEntity(); + Charset charset = ContentType.getOrDefault(entity).getCharset(); + if (charset == null) { + charset = StandardCharsets.UTF_8; + } + + BufferedReader reader = new BufferedReader(new InputStreamReader(entity.getContent(), charset)); + String inputLine; + StringBuilder stringBuilder = new StringBuilder(); + + while ((inputLine = reader.readLine()) != null) { + stringBuilder.append(inputLine); + } + + if (responseCode != HttpStatus.SC_OK) { + throw new IOException("Error while accessing the Token URL. " + + response.getStatusLine()); + } + + JsonParser parser = new JsonParser(); + JsonObject jsonResponse = (JsonObject) parser.parse(stringBuilder.toString()); + if (jsonResponse.has("clientId")) { + clientId = jsonResponse.get("clientId").getAsString(); + } + if (jsonResponse.has("clientSecret")) { + clientSecret = jsonResponse.get("clientSecret").getAsString(); + } + if (clientId != null && clientSecret != null) { + // base64 encode the clientId and clientSecret + return Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes()); + + } + throw new IOException("Missing key [access_token] in the response from the OAuth server"); + } + + public static String resolveVariables(String input, Map valueStore) { + // Define the pattern to match variables like ${variableName} + Pattern pattern = Pattern.compile("\\$\\{([^}]*)\\}"); + Matcher matcher = pattern.matcher(input); + StringBuffer resolvedString = new StringBuffer(); + + while (matcher.find()) { + String variableName = matcher.group(1); + String variableValue = valueStore.get(variableName).toString(); + + // Replace the variable with its value from the value store if it exists + // Otherwise, keep the variable placeholder as is in the string + String replacement = (variableValue != null) ? variableValue : matcher.group(); + matcher.appendReplacement(resolvedString, Matcher.quoteReplacement(replacement)); + } + + matcher.appendTail(resolvedString); + return resolvedString.toString(); + } +} diff --git a/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/SimpleHTTPClient.java b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/SimpleHTTPClient.java new file mode 100644 index 000000000..4a3595646 --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/SimpleHTTPClient.java @@ -0,0 +1,502 @@ +/* + * Copyright (c) 2023, WSO2 LLC (http://www.wso2.com). + * + * WSO2 LLC licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wso2.apk.integration.utils.clients; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpHeaders; +import javax.net.ssl.TrustManager; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpHead; +import org.apache.http.client.methods.HttpOptions; +import org.apache.http.client.methods.HttpPatch; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.TrustAllStrategy; +import org.apache.http.entity.ContentProducer; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.EntityTemplate; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.entity.mime.content.FileBody; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.ssl.SSLContexts; +import org.wso2.apk.integration.utils.MultipartFilePart; +import org.wso2.apk.integration.utils.exceptions.TimeoutException; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.X509Certificate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.GZIPOutputStream; +import javax.net.ssl.SSLContext; +import javax.net.ssl.X509TrustManager; + +public class SimpleHTTPClient { + + protected Log log = LogFactory.getLog(getClass()); + private CloseableHttpClient client; + private HttpUriRequest lastRequest; + private static final int EVENTUAL_SUCCESS_RESPONSE_TIMEOUT_IN_SECONDS = 10; + + public SimpleHTTPClient() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + +// final SSLContext sslcontext = SSLContexts.custom() +// .loadTrustMaterial(null, new TrustAllStrategy()) +// .build(); +// +// final SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslcontext); +// this.client = HttpClients.custom() +// .setSSLSocketFactory(csf) +// .evictExpiredConnections() +// .setMaxConnPerRoute(100) +// .setMaxConnTotal(1000) +// .build(); + + // Create SSL context that trusts all certificates + SSLContext sslContext = createAcceptAllSSLContext(); + + // Create a socket factory with custom SSL context and hostname verifier that accepts all hostnames + SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, + NoopHostnameVerifier.INSTANCE); + + // Create HttpClient with custom SSL socket factory + this.client = HttpClientBuilder.create().setSSLSocketFactory(sslSocketFactory).build(); + this.lastRequest = null; + } + + private SSLContext createAcceptAllSSLContext() throws NoSuchAlgorithmException, KeyManagementException { + // Create a TrustManager that trusts all certificates + TrustManager[] trustAllCerts = new TrustManager[]{ + new X509TrustManager() { + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return null; + } + + public void checkClientTrusted(X509Certificate[] certs, String authType) { + } + + public void checkServerTrusted(X509Certificate[] certs, String authType) { + } + } + }; + + // Create SSL context with the TrustManager that trusts all certificates + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); + return sslContext; + } + + /** + * Function to extract response body as a string + * + * @param response org.apache.http.HttpResponse object containing response entity body + * @return returns the response entity body as a string + * @throws IOException + */ + public static String responseEntityBodyToString(HttpResponse response) throws IOException { + + if (response != null && response.getEntity() != null) { + try (InputStream inputStreamContent = response.getEntity().getContent()) { + return IOUtils.toString(inputStreamContent); + } + } + return null; + } + + /** + * Send a HTTP GET request to the specified URL + * + * @param url Target endpoint URL + * @param headers Any HTTP headers that should be added to the request + * @return Returned HTTP response + * @throws IOException If an error occurs while making the invocation + */ + public HttpResponse doGet(String url, Map headers) throws IOException { + + HttpUriRequest request = new HttpGet(url); + setHeaders(headers, request); + this.lastRequest = request; + return client.execute(request); + } + + /** + * Send a HTTP POST request to the specified URL + * + * @param url Target endpoint URL + * @param headers Any HTTP headers that should be added to the request + * @param payload Content payload that should be sent + * @param contentType Content-type of the request + * @return Returned HTTP response + * @throws IOException If an error occurs while making the invocation + */ + public HttpResponse doPost(String url, final Map headers, final String payload, String contentType) + throws IOException { + + HttpUriRequest request = new HttpPost(url); + setHeaders(headers, request); + HttpEntityEnclosingRequest entityEncReq = (HttpEntityEnclosingRequest) request; + final boolean zip = headers != null && "gzip".equals(headers.get(HttpHeaders.CONTENT_ENCODING)); + + EntityTemplate ent = new EntityTemplate(new ContentProducer() { + public void writeTo(OutputStream outputStream) throws IOException { + + OutputStream out = outputStream; + if (zip) { + out = new GZIPOutputStream(outputStream); + } + out.write(payload.getBytes()); + out.flush(); + out.close(); + } + }); + if (contentType != null) { + ent.setContentType(contentType); + } else { + ent.setContentType(MediaType.JSON.getValue()); + } + if (zip) { + ent.setContentEncoding("gzip"); + } + entityEncReq.setEntity(ent); + this.lastRequest = request; + log.info("Request: " + request); + return client.execute(request); + } + + /** + * Send a HTTP POST with multipart request to the specified URL + * + * @param url Target endpoint URL + * @return Returned HTTP response + * @throws IOException If an error occurs while making the invocation + */ + public HttpResponse doPostWithMultipart(String url, HttpEntity httpEntity) + throws IOException { + + return doPostWithMultipart(url, httpEntity, new HashMap<>()); + } + + public HttpResponse doPostWithMultipart(String url, HttpEntity httpEntity, Map header) + throws IOException { + + HttpPost request = new HttpPost(url); + for (String headerKey : header.keySet()) { + request.addHeader(headerKey, header.get(headerKey)); + } + request.setEntity(httpEntity); + this.lastRequest = request; + return client.execute(request); + } + + public HttpResponse doPostWithMultipart(String url, List fileParts, Map header) + throws IOException { + + MultipartEntityBuilder entitybuilder = MultipartEntityBuilder.create(); + entitybuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); + for (MultipartFilePart filePart : fileParts) { + entitybuilder.addPart(filePart.getName(), new FileBody(filePart.getFile())); + } + HttpPost request = new HttpPost(url); + for (String headerKey : header.keySet()) { + request.addHeader(headerKey, header.get(headerKey)); + } + HttpEntity mutiPartHttpEntity = entitybuilder.build(); + request.setEntity(mutiPartHttpEntity); + this.lastRequest = request; + return client.execute(request); + } + + public HttpResponse doPutWithMultipart(String url, File file, Map header) + throws IOException { + + MultipartEntityBuilder entitybuilder = MultipartEntityBuilder.create(); + entitybuilder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); + entitybuilder.addBinaryBody("file", file, ContentType.APPLICATION_OCTET_STREAM, file.getName()); + HttpPut request = new HttpPut(url); + for (String headerKey : header.keySet()) { + request.addHeader(headerKey, header.get(headerKey)); + } + HttpEntity mutiPartHttpEntity = entitybuilder.build(); + request.setEntity(mutiPartHttpEntity); + this.lastRequest = request; + return client.execute(request); + } + + /** + * Extracts the payload from a HTTP response. For a given HttpResponse object, this + * method can be called only once. + * + * @param response HttpResponse instance to be extracted + * @return Content payload + * @throws IOException If an error occurs while reading from the response + */ + public String getResponsePayload(HttpResponse response) throws IOException { + + if (response.getEntity() != null) { + InputStream in = response.getEntity().getContent(); + int length; + byte[] tmp = new byte[2048]; + StringBuilder buffer = new StringBuilder(); + while ((length = in.read(tmp)) != -1) { + buffer.append(new String(tmp, 0, length)); + } + return buffer.toString(); + } + return null; + } + + /** + * Send a HTTP PATCH request to the specified URL + * + * @param url Target endpoint URL + * @param headers Any HTTP headers that should be added to the request + * @param payload Content payload that should be sent + * @param contentType Content-type of the request + * @return Returned HTTP response + * @throws IOException If an error occurs while making the invocation + */ + public HttpResponse doPatch(String url, final Map headers, final String payload, String contentType) + throws IOException { + + HttpUriRequest request = new HttpPatch(url); + setHeaders(headers, request); + HttpEntityEnclosingRequest entityEncReq = (HttpEntityEnclosingRequest) request; + final boolean zip = headers != null && "gzip".equals(headers.get(HttpHeaders.CONTENT_ENCODING)); + + EntityTemplate ent = new EntityTemplate(new ContentProducer() { + public void writeTo(OutputStream outputStream) throws IOException { + + OutputStream out = outputStream; + if (zip) { + out = new GZIPOutputStream(outputStream); + } + out.write(payload.getBytes()); + out.flush(); + out.close(); + } + }); + ent.setContentType(contentType); + if (zip) { + ent.setContentEncoding("gzip"); + } + entityEncReq.setEntity(ent); + return client.execute(request); + } + + /** + * Send a HTTP OPTIONS request to the specified URL + * + * @param url Target endpoint URL + * @param headers Any HTTP headers that should be added to the request + * @param payload Content payload that should be sent + * @param contentType Content-type of the request + * @return Returned HTTP response + * @throws IOException If an error occurs while making the invocation + */ + public HttpResponse doOptions(String url, final Map headers, final String payload, + String contentType) throws IOException { + + HttpUriRequest request = new HttpOptions(url); + setHeaders(headers, request); + if (payload != null) { + HttpEntityEnclosingRequest entityEncReq = (HttpEntityEnclosingRequest) request; + final boolean zip = headers != null && "gzip".equals(headers.get(HttpHeaders.CONTENT_ENCODING)); + + EntityTemplate ent = new EntityTemplate(new ContentProducer() { + public void writeTo(OutputStream outputStream) throws IOException { + + OutputStream out = outputStream; + if (zip) { + out = new GZIPOutputStream(outputStream); + } + out.write(payload.getBytes()); + out.flush(); + out.close(); + } + }); + ent.setContentType(contentType); + if (zip) { + ent.setContentEncoding("gzip"); + } + entityEncReq.setEntity(ent); + } + return client.execute(request); + } + + /** + * Send a HTTP Head request to the specified URL + * + * @param url Target endpoint URL + * @param headers Any HTTP headers that should be added to the request + * @return Returned HTTP response + * @throws IOException If an error occurs while making the invocation + */ + public HttpResponse doHead(String url, final Map headers) throws IOException { + + HttpUriRequest request = new HttpHead(url); + setHeaders(headers, request); + return client.execute(request); + } + + /** + * Send a HTTP DELETE request to the specified URL + * + * @param url Target endpoint URL + * @param headers Any HTTP headers that should be added to the request + * @return Returned HTTP response + * @throws IOException If an error occurs while making the invocation + */ + public HttpResponse doDelete(String url, final Map headers) throws IOException { + + HttpUriRequest request = new HttpDelete(url); + setHeaders(headers, request); + this.lastRequest = lastRequest; + return client.execute(request); + } + + /** + * Send a HTTP PUT request to the specified URL + * + * @param url Target endpoint URL + * @param headers Any HTTP headers that should be added to the request + * @param payload Content payload that should be sent + * @param contentType Content-type of the request + * @return Returned HTTP response + * @throws IOException If an error occurs while making the invocation + */ + public HttpResponse doPut(String url, final Map headers, final String payload, String contentType) + throws IOException { + + HttpUriRequest request = new HttpPut(url); + setHeaders(headers, request); + HttpEntityEnclosingRequest entityEncReq = (HttpEntityEnclosingRequest) request; + final boolean zip = headers != null && "gzip".equals(headers.get(HttpHeaders.CONTENT_ENCODING)); + + EntityTemplate ent = new EntityTemplate(new ContentProducer() { + public void writeTo(OutputStream outputStream) throws IOException { + + OutputStream out = outputStream; + if (zip) { + out = new GZIPOutputStream(outputStream); + } + out.write(payload.getBytes()); + out.flush(); + out.close(); + } + }); + ent.setContentType(contentType); + if (zip) { + ent.setContentEncoding("gzip"); + } + entityEncReq.setEntity(ent); + this.lastRequest = lastRequest; + return client.execute(request); + } + + private void setHeaders(Map headers, HttpUriRequest request) { + + if (headers != null && headers.size() > 0) { + for (Map.Entry header : headers.entrySet()) { + request.setHeader(header.getKey(), header.getValue()); + } + } + } + + public HttpResponse executeLastRequestForEventualConsistentResponse(int successResponseCode, + List nonAcceptableCodes) throws IOException, InterruptedException { + + int counter = 1; + int responseCode = -1; + String lastResponseBody = null; + while (counter < EVENTUAL_SUCCESS_RESPONSE_TIMEOUT_IN_SECONDS) { + counter++; + Thread.sleep(1000); + HttpResponse httpResponse = getClient().execute(lastRequest); + responseCode = httpResponse.getStatusLine().getStatusCode(); + if (responseCode == successResponseCode || nonAcceptableCodes.contains(responseCode)) { + return httpResponse; + } else { + if (counter == EVENTUAL_SUCCESS_RESPONSE_TIMEOUT_IN_SECONDS) { + lastResponseBody = responseEntityBodyToString(httpResponse); + } + ((CloseableHttpResponse) httpResponse).close(); + } + } + throw new TimeoutException("Could not receive expected response within time. Last received code: " + + responseCode + ", last response body: " + lastResponseBody); + } + + private HttpClient getClient() { + + final SSLContext sslcontext; + try { + sslcontext = SSLContexts.custom() + .loadTrustMaterial(null, new TrustAllStrategy()) + .build(); + } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) { + throw new RuntimeException(e); + } + final SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslcontext); + + return HttpClients.custom() + .setSSLSocketFactory(csf) + .evictExpiredConnections() + .build(); + } +} + +enum MediaType { + JSON("application/json"), + XML("application/xml"), + FORM("application/x-www-form-urlencoded"); + // Add more Content-Type values as needed + + private final String value; + + MediaType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/exceptions/TimeoutException.java b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/exceptions/TimeoutException.java new file mode 100644 index 000000000..3835962a6 --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/exceptions/TimeoutException.java @@ -0,0 +1,8 @@ +package org.wso2.apk.integration.utils.exceptions; + +public class TimeoutException extends RuntimeException { + + public TimeoutException(String s) { + super(s); + } +} diff --git a/test/apim-apk-agent-test/cucumber-tests/src/test/resources/artifacts/definitions/basic_auth_api.json b/test/apim-apk-agent-test/cucumber-tests/src/test/resources/artifacts/definitions/basic_auth_api.json new file mode 100644 index 000000000..f6a997e53 --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/src/test/resources/artifacts/definitions/basic_auth_api.json @@ -0,0 +1,225 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "EmployeeServiceAPI", + "version": "3.14" + }, + "servers": [ + { + "url": "http://backend:80/anything", + "description": "Server URL", + "variables": {} + } + ], + "paths": { + "/employee": { + "get": { + "tags": [ + "employee-controller" + ], + "operationId": "getEmployees", + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "default response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Employee" + } + } + } + } + } + } + }, + + "post": { + "tags": [ + "employee-controller" + ], + "operationId": "addEmployee", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Employee" + } + } + } + }, + "responses": { + "200": { + "description": "default response", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Employee" + } + } + } + } + } + } + }, + "/get": { + "get": { + "tags": [ + "employee-controller" + ], + "operationId": "getEmployees", + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "default response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Employee" + } + } + } + } + } + } + } + }, + "/post": { + "post": { + "tags": [ + "employee-controller" + ], + "operationId": "getEmployees", + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "default response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Employee" + } + } + } + } + } + } + } + }, + "/employee/{employeeId}": { + "put": { + "tags": [ + "employee-controller" + ], + "operationId": "editEmployee", + "parameters": [ + { + "name": "employeeId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "default response", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Employee" + } + } + } + } + } + }, + "delete": { + "tags": [ + "employee-controller" + ], + "operationId": "deleteEmployee", + "parameters": [ + { + "name": "employeeId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "default response", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Employee" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Employee": { + "type": "object", + "properties": { + "empId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "designation": { + "type": "string" + }, + "salary": { + "type": "number", + "format": "double" + } + } + } + } + } +} diff --git a/test/apim-apk-agent-test/cucumber-tests/src/test/resources/artifacts/jwtcert/idp1.jks b/test/apim-apk-agent-test/cucumber-tests/src/test/resources/artifacts/jwtcert/idp1.jks new file mode 100644 index 0000000000000000000000000000000000000000..8ce2101d89aafac1b84f3bc2bc3c786880131f9b GIT binary patch literal 2554 zcma)8XEYp&65h3q=)%e+vU)GO$|6x#Cx{3_l<2EnSx;FdELMpUooG?+YSDW{^d!n6 z5hVzM)kPxF6D6K|-aYr8_v^hMbLPzV%{OQM%y$M!q&cJlf{;X-LMV-Bv~KhfJrD*g zB+{gSi8Kipa4eDtsrxSq@&}j*$-98FFCqg<`j|CvZd0|6kppfq;Tx+yW*9ZyfX1Am z3OWxOXGMOSDi-*X&__IyF>OE2AJtF6!(&#zYBhiU@UdDyLDJUIsKxFlM{*IR+9*f0 z_Ubi6HO_o+?B(<)V;$@DeCS+!=$+!adPbTgx}8{t+FZt|PVcYCu?3*&I>WDj$_ zBw&>JWpO=8C_^<#)sBpG6mIQDH%vL#v=QZgmRim+SF796Z!u)-;Z5Ust$#y4$R`BN zF2JVZO6SFyW|p2?YQ7e#VQ)-L8@Msat0brI8mvqwYhNdIq^=FR#z| z^}%6$d@SxUdZyV#N_$o9Wol_zuna_ElUzPs-@vz6B!iU^5odLDM6#kUib){12dGL4$t=HJs zs^5we{krew8dAd_M+qRxH!K8aYrI(^ye&o8}2h-~Lrg z!Zu=~i5W`m+P2K(k};D2F&*VD356m5(R8*x0OrLtGm*6AHX*LKfwuF62*4thccio% zW&GYd&Mfzlm{>i{wpHP&3dMnM(^$3Ek5Gn%lki@|q~f_ovd`o<)yrKCYGPpx?CCP! z6)8$5Ax`M*R-g-8-JEU=~Nn!2w!5ds1H{`hB%T4Hz7HUT;KsuVkhP z2Tv5doePw@q?*eYvkovcy7Z!XMxsL9D~orCwt{>zDZ7~8vj7+llx!ZxuQ~3+Ej!G9 z=l1C|Zz(QmtuHM;^1c^2bY~(>V=V|bm?bf{iCajQ&3bKmQNFr$T?EYc8p)gM5RHxt%X{ZZ#Ij)q_zwY zHTu%O@}QSp?V9q7#dGyswiXCl`xT8#t9)yL`0^uoE!k_oAC$GBaUBpb(fuCOx5i!ILP-%drp!x_#- z$JOo!weIDlsy7MkqPPjw^Z>^qRK3taIX0qQwbtFLkR`xVzZT$#olB9aJ5ICGa zT3$5l8kO4J9Biuc2 ziq4*F0J?;hhpW+d<)Q0Pbt_ln4h`EIiVs68x!_XY8?OGb_>-kFv0Y7u)3tk7qKsMB zot(FVo;|vSmyRG^A5N<1_(1Aiaa3Kf= zMwX|QITQYf%W9EWcQPK76dPQ}iI#d#@x^Z$Lgy*^gUDOQkYL z+;MO*J1YfHrSD>9HIZ?B9`~7bIr%1I$xFEQ=%g;Alc&v1O0lz-4zI{;HozizGu~=Z z(>%j0#xPuVPtnbmUaZ(&mH_m)Cq8z)IsdD7qH?{xU-Bqx?i4gnI2NlIq0G2@3_NwQ z{9`%lQhQTnjB(Y~N3e~_h^B3i)OFp=wQPgi+W01&9W@;l@gGj49ww!?llx`BiJ~)n zJ3VC7^0wMB!We(L5lgYbHJp*VZCncnHyqkKnd5vRYH{!56Awe(7t{pCU6$JTKO~n- zRUT=6!sp~8PlI^ptsQ1L>!W$b>Y0&-NEDL(?|+C22mye(pGuHKxeVMDO7HGCH{wSg uiJS + + + + + + + + + + + + diff --git a/test/apim-apk-agent-test/cucumber-tests/src/test/resources/tests/api/Deployment.feature b/test/apim-apk-agent-test/cucumber-tests/src/test/resources/tests/api/Deployment.feature new file mode 100644 index 000000000..45002e28f --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/src/test/resources/tests/api/Deployment.feature @@ -0,0 +1,31 @@ +Feature: API Deployment + Scenario: Import an API + Given The system is ready + And I have a DCR application for Publisher + And I have a valid Publisher access token + When I use the Payload file "artifacts/payloads/api1.json" + And I use the OAS URL "https://petstore3.swagger.io/api/v3/openapi.json" + And make the import API Creation request + Then the response status code should be 201 + And the response body should contain "SwaggerPetstore" + And make the API Revision Deployment request + Then the response status code should be 201 + +# Scenario: Deploying an API +# Given The system is ready +# And I have a valid subscription +# When I use the APK Conf file "artifacts/apk-confs/cors_API.apk-conf" +# And the definition file "artifacts/definitions/cors_api.yaml" +# And make the API deployment request +# Then the response status code should be 200 +# And the response body should contain "cors-api-adff3dbc-2787-11ee-be56-0242ac120002" +# +# Scenario Outline: Undeploy an API +# Given The system is ready +# And I have a valid subscription +# When I undeploy the API whose ID is "" +# Then the response status code should be + +# Examples: +# | apiID | expectedStatusCode | +# | cors-api-adff3dbc-2787-11ee-be56-0242ac120002 | 202 |