diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index 7c8f78b4..88c80f3f 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -7,8 +7,9 @@ on: push: branches: - master - - v2.87 + - v2.122 - v2.110 + - v2.87 # Variables available to all jobs env: @@ -23,7 +24,7 @@ jobs: # This job will build and then push to docker hub build-push: # The type of runner the job will run on - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: # Ensure that the repo variables and secrets are set before running any other steps diff --git a/.github/workflows/dependency-graph.yml b/.github/workflows/dependency-graph.yml index ca13544e..17d31909 100644 --- a/.github/workflows/dependency-graph.yml +++ b/.github/workflows/dependency-graph.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - v2.122 - v2.110 - v2.87 jobs: diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index ef7c03b0..c7ba0cf0 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -4,8 +4,9 @@ on: pull_request: branches: - master - - v2.87 + - v2.122 - v2.110 + - v2.87 # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: @@ -19,6 +20,7 @@ jobs: DOCKER_NETWORK: exchange-api-network DOCKER_REGISTRY: openhorizon EXCHANGE_FE_HEADER: issuer + EXCHANGE_PEKKO_LOGLEVEL: debug EXCHANGE_ROOTPW: ci-password POSTGRES_DB_NAME: exchange POSTGRES_DB_PORT: 5432 diff --git a/.travis.yml b/.travis.yml index 57d08fd3..0c3e3600 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,9 @@ services: docker language: scala dist: focal -jdk: openjdk11 +jdk: openjdk17 scala: - - 2.13.5 + - 2.13.14 notifications: slack: secure: nPoYZ8FqRziV5+rQAc9GXFWtNsThBbOdxYOY8HpPwNWsWHtC2ZyPJBnNE6XIkmzR7+D5U8yOYqlk2CiqYLlSwa1+bqKuAyOBBhz51pDi8z+YXsjvgTpkxVmKv8N6jntAqo5eBFbVUW+/FPhKBD6qZIkbfRyThZSNZTSTv9oZ02Ynfb4NKuXgZeuinSotOaICiCvzfY4kYoe7EKss/XY6ON8qHUOcIQBsckDHrFEYwF270qNEIccZIkrOr3PKg3mXz2n+65T5i/UDNw3Z8RKDc32Y+TCfyAc3kyuQQYhKhL6/TLwAW/IPNGaFkUfR+2FH+C1VlLJpm1/mBj6uDvHBVRHSEL3ZofYFd5TTzUqkQRINATRQNpFNfjlMT3ifJrSyopKXyMsiea2y3EvM4/D3I5pHRvX2/BqidjJV3b5UIllirq/jk4PrCKshkKEZtC0CBNj4T8ewa9Qr3IxlKTFVHwnW1RWQmAXgFDFpbDzJ4vuLbUDhAKDXukWqoiwxTC3egMPQVnEFvxHVDAqdQUztsUjtg3LVXagLLl6+tYTwY53124aUXSkQMbANL+2ISZuRmg4dheTtaK/bE8L4dCQyyy7HFh03IbZGnGz3bhpDGxTBFAhnEQ4XWzYNpz8rEr0unvQTfBt2dUq4AH5bh1QseFh77lpBYWj9jxndZMbP9Pw= @@ -17,6 +17,7 @@ env: DOCKER_NETWORK=exchange-api-network DOCKER_REGISTRY=openhorizon EXCHANGE_FE_HEADER=issuer + EXCHANGE_PEKKO_LOGLEVEL=debug EXCHANGE_ROOTPW=ci-password POSTGRES_DB=exchange POSTGRES_PORT=5432 @@ -37,7 +38,6 @@ before_script: - 'sudo bash -c "echo ''{ \"api\": { \"db\": { \"jdbcUrl\": \"jdbc:postgresql://$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DB\", \"user\": \"$POSTGRES_USER\" }, \"root\": { \"password\": \"$EXCHANGE_ROOTPW\", \"frontEndHeader\": \"$EXCHANGE_FE_HEADER\" } } }'' > /etc/horizon/exchange/config.json"' -- cat /etc/horizon/exchange/config.json - unset SBT_OPTS - make travis-test diff --git a/CHANGELOG.md b/CHANGELOG.md index bce983fb..3463b880 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to this project will be documented in this file. +## [2.124.0] - 2024-09-14 +- Application configuration overhaul. + - Some database configuration changes are not backwards compatible. +- GET methods for Node resources no longer return passwords for admin user types, unless directly owned. +- Added new rest paths for deployment patterns and policies aligning and clarifying these resources. + - `.../v1/orgs//deployment/patterns/...` + - `.../v1/orgs//deployment/policies/...` + ## [2.123.0] - 2024-04-19 - pekko-http-xml 1.0.0 -> 1.0.1 - Reorganized class references in the Swagger documentation generator. diff --git a/Makefile b/Makefile index 3a5393a4..126f0ded 100644 --- a/Makefile +++ b/Makefile @@ -175,58 +175,6 @@ run-docker-db-postgres-https: target/docker/.run-docker-db-postgres-https /etc/horizon/exchange: sudo mkdir -p /etc/horizon/exchange -/etc/horizon/exchange/config-http.json: /etc/horizon/exchange - : $${EXCHANGE_ROOTPW:?} - sudo -- bash -c "printf \ -'{\n'\ -' \"api\": {\n'\ -' \"db\": {\n'\ -' \"jdbcUrl\": \"jdbc:postgresql://$(POSTGRES_CONTAINER_ADDRESS):$(POSTGRES_DB_PORT)/$(POSTGRES_DB_NAME)\",\n'\ -' \"user\": \"$(POSTGRES_DB_USER)\"\n'\ -' },\n'\ -' \"logging\": {\n'\ -' \"level\": \"$(EXCHANGE_LOG_LEVEL)\"\n'\ -' },\n'\ -' \"root\": {\n'\ -' \"password\": \"$(EXCHANGE_ROOTPW)\",\n'\ -' \"frontEndHeader\": \"$(EXCHANGE_FE_HEADER)\"\n'\ -' },\n'\ -' \"service\": {\n'\ -' \"port\": $(EXCHANGE_CONTAINER_PORT_HTTP),\n'\ -' \"portEncrypted\": null\n'\ -' }\n'\ -' }\n'\ -'}' > /etc/horizon/exchange/config-http.json" - sudo chmod o+r /etc/horizon/exchange/config-http.json - -/etc/horizon/exchange/config-https.json: /etc/horizon/exchange target/docker/.run-docker-db-postgres-https - : $${EXCHANGE_ROOTPW:?} - sudo -- bash -c "printf \ -'{\n'\ -' \"api\": {\n'\ -' \"db\": {\n'\ -' \"jdbcUrl\": \"jdbc:postgresql://$(POSTGRES_CONTAINER_ADDRESS):$(POSTGRES_DB_PORT)/$(POSTGRES_DB_NAME)\",\n'\ -' \"user\": \"$(POSTGRES_DB_USER)\"\n'\ -' },\n'\ -' \"logging\": {\n'\ -' \"level\": \"$(EXCHANGE_LOG_LEVEL)\"\n'\ -' },\n'\ -' \"root\": {\n'\ -' \"password\": \"$(EXCHANGE_ROOTPW)\",\n'\ -' \"frontEndHeader\": \"$(EXCHANGE_FE_HEADER)\"\n'\ -' },\n'\ -' \"service\": {\n'\ -' \"port\": $(EXCHANGE_CONTAINER_PORT_HTTP),\n'\ -' \"portEncrypted\": $(EXCHANGE_CONTAINER_PORT_HTTPS)\n'\ -' },\n'\ -' \"tls\": {\n'\ -' \"password\": \"$(EXCHANGE_TRUST_PW)\",\n'\ -' \"truststore\": \"/etc/horizon/exchange/localhost.p12\"\n'\ -' }\n'\ -' }\n'\ -'}' > /etc/horizon/exchange/config-https.json" - sudo chmod o+r /etc/horizon/exchange/config-https.json - ## Pre-Run - TLS Truststore ----------- ## Only do this once to create the exchange truststore for https (which includes the private key, and cert with multiple names). $(EXCHANGE_HOST_TRUST_DIR): /etc/horizon/exchange @@ -240,7 +188,7 @@ target/localhost.crt: target/docker/stage/Dockerfile printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth") -/etc/horizon/exchange/localhost.p12: target/localhost.crt +/etc/horizon/exchange/localhost.p12: /etc/horizon/exchange target/localhost.crt openssl pkcs12 -export -out target/localhost.p12 -in target/localhost.crt -inkey target/localhost.key -aes-256-cbc -passout pass:$(EXCHANGE_TRUST_PW) chmod o+r target/localhost.p12 sudo chown root:root target/localhost.p12 @@ -260,6 +208,13 @@ target/docker/.run-docker: /etc/horizon/exchange/config-http.json target/docker/ --network $(DOCKER_NETWORK) \ -d -t \ -p $(EXCHANGE_HOST_PORT_HTTP):$(EXCHANGE_CONTAINER_PORT_HTTP) \ + -e EXCHANGE_DB_HOST=$(POSTGRES_CONTAINER_ADDRESS) \ + -e EXCHANGE_DB_NAME=$(POSTGRES_DB_NAME) \ + -e EXCHANGE_DB_PORT=$(POSTGRES_DB_PORT) \ + -e EXCHANGE_DB_USER=$(POSTGRES_DB_USER) \ + -e EXCHANGE_PEKKO_HTTP_PORT=$(EXCHANGE_CONTAINER_PORT_HTTP) \ + -e EXCHANGE_PEKKO_LOGLEVEL=$(EXCHANGE_LOG_LEVEL) \ + -e "EXCHANGE_ROOT_PW=$$EXCHANGE_ROOT_PW" \ -v /etc/horizon/exchange/config.json:/etc/horizon/exchange/exchange-api.tmpl:ro \ $(IMAGE_STRING):$(DOCKER_TAG) @touch $@ @@ -268,7 +223,7 @@ target/docker/.run-docker: /etc/horizon/exchange/config-http.json target/docker/ run-docker: target/docker/.run-docker ## config.json is renamed to exchange-api.tmpl to overwrite the provided file of the same name in the Docker image. Prevents the container from attempting to overwrite a bind-mounted config.json with read-only permissions. -target/docker/.run-docker-icp-https: /etc/horizon/exchange/config-https.json target/docker/.docker-network /etc/horizon/exchange/localhost.p12 target/docker/.run-docker-db-postgres-https +target/docker/.run-docker-icp-https: target/docker/.docker-network /etc/horizon/exchange/localhost.p12 target/docker/.run-docker-db-postgres-https sudo -- bash -c "cp /etc/horizon/exchange/config-https.json /etc/horizon/exchange/config.json" docker run \ --name $(DOCKER_NAME) \ @@ -276,6 +231,16 @@ target/docker/.run-docker-icp-https: /etc/horizon/exchange/config-https.json tar -d -t \ -p $(EXCHANGE_HOST_PORT_HTTP):$(EXCHANGE_CONTAINER_PORT_HTTP) \ -p $(EXCHANGE_HOST_PORT_HTTPS):$(EXCHANGE_CONTAINER_PORT_HTTPS) \ + -e EXCHANGE_DB_HOST=$(POSTGRES_CONTAINER_ADDRESS) \ + -e EXCHANGE_DB_NAME=$(POSTGRES_DB_NAME) \ + -e EXCHANGE_DB_PORT=$(POSTGRES_DB_PORT) \ + -e EXCHANGE_DB_USER=$(POSTGRES_DB_USER) \ + -e EXCHANGE_PEKKO_HTTP_PORT=$(EXCHANGE_CONTAINER_PORT_HTTP) \ + -e EXCHANGE_PEKKO_HTTPS_PORT=$(EXCHANGE_CONTAINER_PORT_HTTPS) \ + -e EXCHANGE_PEKKO_LOGLEVEL=$(EXCHANGE_LOG_LEVEL) \ + -e "EXCHANGE_ROOT_PW=$$EXCHANGE_ROOT_PW" \ + -e "EXCHANGE_TLS_PASSWORD=$$EXCHANGE_TRUST_PW" \ + -e EXCHANGE_TLS_TRUSTSTORE=/etc/horizon/exchange/localhost.p12 \ -e "JAVA_OPTS=$(JAVA_OPTS)" \ -e "ICP_EXTERNAL_MGMT_INGRESS=$$ICP_EXTERNAL_MGMT_INGRESS" \ -v /etc/horizon/exchange/config.json:/etc/horizon/exchange/exchange-api.tmpl:ro \ @@ -290,13 +255,20 @@ run-docker-icp-https: target/docker/.run-docker-icp-https ## config.json is mounted into the container as exchange-api.tmpl to overwrite the provided file of the same name in the Docker image. Bind-mounting it with read-only permissions prevents the container from attempting to overwrite it. # -target/docker/.run-docker-icp: /etc/horizon/exchange/config-http.json target/docker/.docker-network +target/docker/.run-docker-icp: target/docker/.docker-network sudo -- bash -c "cp /etc/horizon/exchange/config-http.json /etc/horizon/exchange/config.json" docker run \ --name $(DOCKER_NAME) \ --network $(DOCKER_NETWORK) \ -d -t \ -p $(EXCHANGE_HOST_PORT_HTTP):$(EXCHANGE_CONTAINER_PORT_HTTP) \ + -e EXCHANGE_DB_HOST=$(POSTGRES_CONTAINER_ADDRESS) \ + -e EXCHANGE_DB_NAME=$(POSTGRES_DB_NAME) \ + -e EXCHANGE_DB_PORT=$(POSTGRES_DB_PORT) \ + -e EXCHANGE_DB_USER=$(POSTGRES_DB_USER) \ + -e EXCHANGE_PEKKO_HTTP_PORT=$(EXCHANGE_CONTAINER_PORT_HTTP) \ + -e EXCHANGE_PEKKO_LOGLEVEL=$(EXCHANGE_LOG_LEVEL) \ + -e "EXCHANGE_ROOT_PW=$$EXCHANGE_ROOT_PW" \ -e "JAVA_OPTS=$(JAVA_OPTS)" \ -e "ICP_EXTERNAL_MGMT_INGRESS=$$ICP_EXTERNAL_MGMT_INGRESS" \ -v /etc/horizon/exchange/config.json:/etc/horizon/exchange/exchange-api.tmpl:ro \ @@ -382,7 +354,6 @@ clean: clean-docker clean-truststore .PHONY: cleaner cleaner: clean cleaner-docker cleaner-truststore - sudo rm -fr /etc/horizon/exchange/config*.json .PHONY: cleanest cleanest: cleaner cleanest-docker cleanest-truststore diff --git a/build.sbt b/build.sbt index 27c48ac0..6a77ad37 100644 --- a/build.sbt +++ b/build.sbt @@ -26,6 +26,8 @@ val versionFunc = () => { Global / excludeLintKeys += daemonGroupGid // was getting unused error, even tho i think they are used Global / excludeLintKeys += dockerEnvVars +//Global / envVars := Map("HZN_ORG_ID" -> "mycluster") + lazy val root = (project in file(".")) .settings( description := "'Containerized exchange-api'", @@ -35,7 +37,7 @@ lazy val root = (project in file(".")) pekkoVersion := "[1.0.2]", release := sys.env.getOrElse("GIT_SHORT_SHA", versionFunc()), resolvers += Classpaths.typesafeReleases, - scalaVersion := "2.13.11", + scalaVersion := "2.13.14", summary := "'Open Horizon exchange-api image'", vendor := "'Open Horizon'", version := sys.env.getOrElse("IMAGE_VERSION", versionFunc()), @@ -51,6 +53,7 @@ lazy val root = (project in file(".")) // "org.apache.pekko" %% "pekko-http-spray-json" % "[10.2.1,)", "com.github.pjfanning" %% "pekko-http-jackson" % "[2.3.3,)", "org.apache.pekko" %% "pekko-http-cors" % "[1.0.0]", + "org.apache.pekko" %% "pekko-slf4j" % "[1.0.1]", "org.json4s" %% "json4s-native" % "4.0.6", "org.json4s" %% "json4s-jackson" % "4.0.6", @@ -62,6 +65,8 @@ lazy val root = (project in file(".")) //"io.swagger.core.v3" % "swagger-core-jakarta" % "[2.1.12]", // Version 2.1.13+ requires newer versions of slick and slick-hikaricp //"io.swagger.core.v3" % "swagger-jaxrs2-jakarta" % "[2.1.12]", // Version 2.1.13+ requires newer versions of slick and slick-hikaricp + "ch.qos.logback" % "logback-classic" % "1.5.6", + //"net.logstash.logback" % "logstash-logback-encoder" % "[7.4,)", // "com.typesafe.slick" %% "slick" % "[3.3.3]", // Version 3.4.1 depends on slick-pg and slick-pg_json4s v0.21.0 "com.typesafe.slick" %% "slick-hikaricp" % "[3.4.1]", // Version 3.4.1 depends on slick-pg and slick-pg_json4s v0.21.0 // "com.github.tminglei" %% "slick-pg" % "[0.20.4]", // Version 0.21.0 depends on version 3.4.0 of slick and slick-hikaricp @@ -70,7 +75,7 @@ lazy val root = (project in file(".")) // "com.zaxxer" % "HikariCP" % "[3.4.5,)", // "org.slf4j" % "slf4j-simple" % "[1.7.25]", // Version 1.7.35+ requires newer versions of slick and slick-hikaricp // "ch.qos.logback" % "logback-classic" % "1.3.0-alpha5", - "com.mchange" % "c3p0" % "[0.9.5.5,)", + //"com.mchange" % "c3p0" % "[0.9.5.5,)", "org.scalaj" %% "scalaj-http" % "[2.4.2]", // Deprecated as of April 2022, in v2.4.2 "com.typesafe" % "config" % "[1.4.3,)", "org.mindrot" % "jbcrypt" % "[0.4,)", // Last version (v0.4) release February 13, 2017 @@ -89,6 +94,7 @@ lazy val root = (project in file(".")) ), scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature"), javacOptions ++= Seq("-source", "17", "-target", "17", "-Xlint"), + //javaOptions ++= Seq("-Dconfig.file=/home/naphelps/git/exchange-api/target/config.json"), fork := true, Test / javaOptions ++= Seq("--add-opens", "java.base/java.net=ALL-UNNAMED"), // Used when running test suites with HTTPS. @@ -108,15 +114,12 @@ lazy val root = (project in file(".")) dockerBaseImage := "registry.access.redhat.com/ubi9-minimal:latest", dockerEnvVars := Map("JAVA_OPTS" -> ""), // this is here so JAVA_OPTS can be overridden on the docker run cmd with a value like: -Xmx1G // dockerEntrypoint ++= Seq("-Djava.security.auth.login.config=src/main/resources/jaas.config") // <- had trouble getting this to work - Docker / mappings ++= Seq((baseDirectory.value / "LICENSE.txt") -> "/1/licenses/LICENSE.txt", - (baseDirectory.value / "config" / "exchange-api.tmpl") -> "/2/etc/horizon/exchange/exchange-api.tmpl" - ), + Docker / mappings ++= Seq((baseDirectory.value / "LICENSE.txt") -> "/1/licenses/LICENSE.txt"), dockerCommands := Seq(Cmd("FROM", dockerBaseImage.value ++ " as stage0"), Cmd("LABEL", "snp-multi-stage='intermediate'"), Cmd("LABEL", "snp-multi-stage-id='6466ecf3-c305-40bb-909a-47e60bded33d'"), Cmd("WORKDIR", "/etc/horizon/exchange"), Cmd("COPY", "2/etc/horizon/exchange /2/etc/horizon/exchange"), - Cmd("RUN", "> /2/etc/horizon/exchange/config.json"), Cmd("WORKDIR", "/licenses"), Cmd("COPY", "1/licenses /1/licenses"), Cmd("WORKDIR", "/opt/docker"), @@ -149,12 +152,7 @@ lazy val root = (project in file(".")) Cmd("EXPOSE", "8080"), Cmd("EXPOSE", "8083"), Cmd("USER", "1001:1001"), - /* - * If bind-mounting your own config.json rename the configuration file in the container's filesystem to exchange-api.tmpl. This will overwrite the - * exchange-api.tmpl provided in this docker image and prevent cases where a bind-mount config.json is set with read-only permissions. - * Any mounted config.json can choose to use variables to take advantage of the substitution below. - */ - Cmd("ENTRYPOINT", "/usr/bin/envsubst $ENVSUBST_CONFIG < /etc/horizon/exchange/exchange-api.tmpl > /etc/horizon/exchange/config.json && /opt/docker/bin/" ++ name.value), + Cmd("ENTRYPOINT", "/opt/docker/bin/" ++ name.value), Cmd("CMD", "[]") ) ) diff --git a/config/exchange-api.tmpl b/config/exchange-api.tmpl deleted file mode 100644 index 77ce0452..00000000 --- a/config/exchange-api.tmpl +++ /dev/null @@ -1,12 +0,0 @@ -{ - "api": { - "db": { - "jdbcUrl": "$EXCHANGE_DB_URL", - "user": "$EXCHANGE_DB_USER", - "password": "$EXCHANGE_DB_PW" - }, - "root": { - "password": "$EXCHANGE_ROOT_PW" - } - } -} diff --git a/docs/README.md b/docs/README.md index d9afc454..30fedacf 100644 --- a/docs/README.md +++ b/docs/README.md @@ -57,37 +57,20 @@ services in the exchange. psql "host=$MY_IP dbname=postgres user= password=''" ``` -- Add a configuration file on your development system at `/etc/horizon/exchange/config.json` with at minimum the following content (this is needed for the automated tests. Defaults and the full list of configuration variables are in `src/main/resources/config.json`): - - ```json - { - "pekko": { - "loglevel": "DEBUG" - }, - "api": { - "db": { - "jdbcUrl": "jdbc:postgresql://localhost/postgres", // my local postgres db - "user": "myuser", - "password": "" - }, - "root": { - "password": "myrootpw" - } - } - } - ``` - -- If you want to run the `FrontEndSuite` test class `config.json` should also include `"frontEndHeader": "issuer"` directly after `email` under `root`. - -- Set the same exchange root password in your shell environment, for example: +- The following minimum set of environment variables are required to run an Exchange instance. Additional configuration options are available via TypeSafe Config. ```bash - export EXCHANGE_ROOTPW=myrootpw + # Example + export EXCHANGE_DB_NAME=openhorizon + export EXCHANGE_DB_PW=mydbuserpasswd + export EXCHANGE_DB_USER=mydbuser + export EXCHANGE_ROOT_PW=myrootpasswd ``` - If someone hasn't done it already, create the TLS private key and certificate: ```bash + # Example export EXCHANGE_KEY_PW= make gen-key ``` @@ -104,6 +87,7 @@ services in the exchange. - A convenience script `src/test/bash/primedb.sh` can be run to prime the DB with some exchange resources to use in manually testing: ```bash +# Example export EXCHANGE_USER= export EXCHANGE_PW= src/test/bash/primedb.sh @@ -113,6 +97,7 @@ src/test/bash/primedb.sh - To locally test the exchange against an existing ICP cluster: ```bash +# Example export ICP_EXTERNAL_MGMT_INGRESS=:8443 ``` @@ -131,6 +116,7 @@ When at the `sbt` sub-command prompt: - (Optional) To include tests for IBM IAM platform key authentication: ```bash +# Example export EXCHANGE_IAM_KEY=myiamplatformkey export EXCHANGE_IAM_EMAIL=myaccountemail@something.com export EXCHANGE_IAM_ACCOUNT=myibmcloudaccountid @@ -160,18 +146,15 @@ Project uses Scapegoat. To use: ## Building and Running the Docker Container in Local Sandbox - Update the version in `src/main/resources/version.txt` -- Add a second configuration file that is specific to running in the docker container: - - ```bash - sudo mkdir -p /etc/horizon/exchange/docker - sudo cp /etc/horizon/exchange/config.json /etc/horizon/exchange/docker/config.json - ``` - See [the Preconditions section](#preconditions) for the options for configuring postgresql to listen on an IP address that your exchange docker container will be able to reach. (Docker will not let it reach your host's `localhost` or `127.0.0.1` .) - - Set the `jdbcUrl` field in this `config.json` to use that IP address, for example: + - Set the `EXCHANGE_DB_...` environment variables to use that IP address, for example: - ```json - "jdbcUrl": "jdbc:postgresql://192.168.1.9/postgres", + ```bash + # Example + export EXCHANGE_DB_HOST=192.168.1.9 + export EXCHANGE_DB_NAME=postgres + export EXCHANGE_DB_PORT=5432 ``` - To compile your local code, build the exchange container, and run it locally, run: @@ -192,21 +175,6 @@ Project uses Scapegoat. To use: - **Note:** Swagger does not yet work in the local docker container. - At this point you probably want to run `docker rm -f amd64_exchange-api` to stop your local docker container so it stops listening on your 8080 port. Otherwise you may be very confused when you go back to running the exchange via `sbt`, but it doesn't seem to be executing your tests. -### Notes About `config/exchange-api.tmpl` - -- The `config/exchange-api.tmpl` is a application configuration template much like `/etc/horizon/exchange/config.json`. The template file itself is required for building a Docker image, but the content is not. It is recommend that the default content remain as-is when building a Docker image. -- The content layout of the template exactly matches that of `/etc/horizon/exchange/config.json`, and the content of the config.json can be directly copied-and-pasted into the template. This will set the default Exchange configuration to the hard-coded specifications defined in the config.json when a Docker container is created. -- Alternatively, instead of using hard-coded values the template accepts substitution variables (default content of the `config/exchange-api.tmpl`). At container creation the utility `envsubst` will make a value substitution with any corresponding environmental variables passed into the running container by Docker, Kubernetes, OpenShift, or etc. For example: - - `config/exchange-api.tmpl`: - - "jdbcUrl": "$EXCHANGE_DB_URL" - - Kubernetes config-map (environment variable passed to container at creation): - - "$EXCHANGE_DB_URL=192.168.0.123" - - Default `/etc/horizon/exchange/config.json` inside running container: - - "jdbcUrl": "192.168.0.123" -- It is possible to mix-and-match hard-coded values and substitution values in the template. -- ***WARNING:*** `envsubst` will attempt to substitute any value containing a `$`, which will include the value of `api.root.password` if it is a hashed password. To prevent this either pass the environmental variable `ENVSUBST_CONFIG` with a garbage value, e.g. `ENVSUBST_CONFIG='$donotsubstituteanything'` (this will effectively disable `envsubst`), or pass it with a value containing the exact substitution variables `envsubst` is to substitute (`ENVSUBST_CONFIG='${EXCHANGE_DB_URL} ${EXCHANGE_DB_USER} ${EXCHANGE_DB_PW} ${EXCHANGE_ROOT_PW} ...'`), and of course you have to pass those environment variables values into the container. - - By default `$ENVSUBST_CONFIG` is set to `$ENVSUBST_CONFIG=''` this causes `envsubst` to use its default opportunistic behavior and will attempt to make any/all substitutions where possible. -- It is also possible to directly pass a `/etc/horizon/exchange/config.json` to a container at creation using a bind/volume mount. This takes precedence over the content of the template `config/exchange-api.tmpl`. The directly passed config.json is still subject to the `envsubst` utility and the above warning still applies. ### Notes About the Docker Image Build Process @@ -276,13 +244,13 @@ To build an exchange container with code that is targeted for a git branch: ### Putting Hashed Password in config.json -The exchange root user password is set in the config file (`/etc/horizon/exchange/config.json`). But the password doesn't need to be clear text. You can hash the password with: +The exchange root user password is set via the environment variable `EXCHANGE_ROOT_PW`. But the password doesn't need to be clear text. You can hash the password with: ```bash curl -sS -X POST -H "Authorization:Basic $HZN_ORG_ID/$HZN_EXCHANGE_USER_AUTH" -H "Content-Type: application/json" -d '{ "password": "PUT-PW-HERE" }' $HZN_EXCHANGE_URL/admin/hashpw | jq ``` -And then put that hashed value in `/etc/horizon/exchange/config.json` in the `api.root.password` field. +And then set the environment variable `export EXCHANGE_ROOT_PW=hashedrootuserpassword` and restart the Exchange. ### Disabling Root User @@ -300,37 +268,36 @@ If you want to reduce the attack surface of the exchange, you can disable the ex hzn exchange user setadmin -u "root/root:PUT-ROOT-PW-HERE" -o PUT-IBM-CLOUD-ORG-HERE PUT-USER-HERE true ``` -Now you can disable root by setting `api.root.enabled` to `false` in `/etc/horizon/exchange/config.json`. +Now you can disable root by setting the environment variable `export EXCHANGE_ROOT_ENABLED=false` or unsetting the environment variable `unset EXCHANGE_ROOT_PW`. ## Using TLS With The Exchange - You need a PKCIS #12 cryptographic store (.pk12). https://en.wikipedia.org/wiki/PKCS_12 - See Makefile targets `target/localhost.crt` (line 236), `/etc/horizon/exchange/localhost.p12` (line 243), and `truststore` (line 250) for a skeleton to use with OpenSSL. - OpenSSL is used for the creation of (1) self-signed certificate stating the application server is who it says it is, (2) the server's private key, and (3) the PKCS #12 which is just a portable secure store for everything. - - The PKCS #12 is password protected. Set the environmental variable `EXCHANGE_TRUST_PW` when using the Makefile. + - The PKCS #12 is password protected. - Set `export EXCHANGE_TRUST_PW=` when wishing to not have a password on the PKCS #12 -- Set `api.tls.truststore` and `api.tls.password` in the Exchange's `/etc/horizon/echange/config.json` file. +- Set the environement variables `EXCHANGE_TLS_PASSWORD` and `EXCHANGE_TLS_TRUSTSTORE` + - ```bash + # Example + export EXCHANGE_TLS_PASSWORD=mytruststorepassword + export EXCHANGE_TLS_TRUSTSTORE=/etc/horizon/exchange/localhost.p12 + ``` - `truststore` expects the absolute (full) path to your intended PCKS #12 as a string. - Setting this is the mechanism by which the Exchange knows to attempt to set up TLS in the application server. - Use `"truststore": null` to disable. - `password` expects the PKCS #12's password. - The Exchange will throw an error and self terminate on start if this password is not set or set `null`. - - `api.tls.password` defaults to `null`. - - When using a PKCS #12 that does not have a set password, set `api.tls.password` to `"password": "",` in the `/etc/horizon/exchange/config.json`. - - See Makefile target `/etc/horizon/exchange/config-https.json` (line 201) for an idea. + - `EXCHANGE_TLS_PASSWORD` defaults to `null`. + - When using a PKCS #12 that does not have a set password, set the environment variable `export EXCHANGE_TLS_PASSWORD=""` to an empty string. - The default ports are `8080` for unencrypted traffic and `8083` for Encrypted. - - These can be adjusted in the Exchange's `/etc/horizon/echange/config.json` file. - - `api.service.portEncrypted` for changing the port listening for encrypted traffic. - - `api.service.port` for changing the port listening for unencrypted traffic. - - See Makefile target `/etc/horizon/exchange/config-https.json` (line 201) for an idea. + - These can be adjusted via the environment variables `EXCHANGE_PEKKO_HTTP_PORT` and `EXCHANGE_PEKKO_HTTPS_PORT`. - The Exchange is capable of hosting both HTTP and HTTPS traffic at the same time as well as mutually exclusively one. Freedom to mix-and-match. - HTTP and HTTPS are required to run on different ports. The Exchange always defaults to HTTP exclusively when in conflict. - - If ports are manually undefined in the Exchange's `/etc/horizon/echange/config.json` file then HTTP on port `8080` is defaulted. + - If ports are manually undefined in the Exchange's HTTP on port `8080` is defaulted. - The Exchange does not support mixing HTTP and HTTPS traffic on either port. -- Only `TLSv1.3` and `TLSv1.2` HTTPS traffic is supported by the Exchange with TLS enabled. - - `TLS_AES_256_GCM_SHA384` is the only supported TLSv1.3 cipher in the Exchange. - - The `TLSv1.3` cipher `TLS_CHACHA20_POLY1305_SHA256` is available starting in Java 14. - - The supported ciphers for `TLSv1.2` are `TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384` and `TLS_DHE_RSA_WITH_AES_256_GCM_SHA384`. +- Only `TLSv1.3` is supported by the Exchange with TLS enabled. + - `TLS_AES_256_GCM_SHA384` and `TLS_CHACHA20_POLY1305_SHA256` are the only supported TLSv1.3 ciphers in the Exchange. - [Optional] When using HTTPS with the Exchange the PostgreSQL database can also be configured with TLS turned on. - The Exchange does not require an SSL enabled PostgreSQL database to function with TLS enabled. - See https://www.postgresql.org/docs/13/runtime-config-connection.html#RUNTIME-CONFIG-CONNECTION-SSL for more information. @@ -343,178 +310,6 @@ Now you can disable root by setting `api.root.enabled` to `false` in `/etc/horiz - See the Makefile target chain `target/docker/.run-docker-icp-https` (line 272) for idea of a running Exchange and database in docker containers using TLS. - Do to technical limitations the Swagger page will only refer to the Exchange's HTTPS traffic port. -## Configuration Parameters - -`src/main/resources/config.json` is the default configuration file for the Exchange. This file is bundled in the Exchange jar. To run the exchange server with different values, copy this to `/etc/horizon/exchange/config.json`. In your version of the config file, you only have to set what you want to override. - -### pekko - -Pekko Actor: https://pekko.apache.org/docs/pekko/current/general/configuration-reference.html#pekko-actor -
-Pekko-Http: https://pekko.apache.org/docs/pekko-http/current/configuration.html -
-Log Level: http://logback.qos.ch/apidocs/ch/qos/logback/classic/Level.html - -| Parameter Name | Default Value | Description | -|----------------|----------------|-------------| -| loglevel | `"INFO"` | | - - - #### pekko.coordinated-shutdown - - | Parameter Name | Default Value | Description | - |-------------------------------|---------------|--------------------------------------------------------------------------------| - | phases.service-unbind.timeout | `"60s"` | Number of seconds to let in-flight requests complete before exiting the server | - - - #### pekko.http.parsing - - | Parameter Name | Default Value | Description | - |------------------------|---------------|-------------| - | max-header-name-length | `128` | | - - - #### pekko.http.server - - | Paramater Name | Default Value | Description | - |------------------|---------------|-------------| - | backlog | `100` | | - | bind-timeout | `"1s"` | | - | idle-timeout | `"60s"` | | - | linger-timeout | `"1m"` | | - | max-connections | `1024` | | - | pipelining-limit | `1` | | - | request-timeout | `"45s"` | | - | server-header | `""` | | - -### pekko-http-cors - -https://pekko.apache.org/docs/pekko-http/current/configuration.html - -| Parameter Name | Default Value | Description | -|-----------------------------|---------------------------------------------------|--------------------------------------------------------------------------| -| allow-credentials | `true` | | -| allow-generic-http-requests | `true` | Do not apply `Origin` header check to non-preflight (`OPTIONS`) requests | -| allowed-headers | `["*"]` | | -| allowed-methods | `["DELETE","GET","OPTIONS","PATCH","POST","PUT"]` | | -| allowed-origins | `["*"]` | | -| exposed-headers | `["*"]` | | -| max-age | `0s` | | - - -### api.acls - -| Parameter Name | Description | -|----------------|-------------------| -| AdminUser | | -| Agbot | | -| Anonymous | Not actually used | -| HubAdmin | | -| Node | | -| SuperUser | | -| User | | - -### api.akka [DEPRECATED] - -### api.cache - -| Parameter Name | Description | -|------------------------|-----------------------------------------------------------------| -| authDbTimeoutSeconds | Timeout for db access for critical auth info when cache missing | -| IAMusersMaxSize | The users that are backed by IAM users | -| IAMusersTtlSeconds | | -| idsMaxSize | Includes: local exchange users, nodes, agbots (all together) | -| idsTtlSeconds | | -| resourcesMaxSize | Each of: users, agbots, services, patterns, policies | -| resourcesTtlSeconds | | -| type | Currently guava is the only option | - -### api.db - -| Parameter Name | Description | -|------------------------------|----------------------------------------------------------------------| -| acquireIncrement | | -| driverClass | | -| idleConnectionTestPeriod | In seconds; 0 disables | -| initialPoolSize | | -| jdbcUrl | The back-end db the exchange uses | -| maxConnectionAge | In seconds; 0 is infinite | -| maxIdleTime | In seconds; 0 is infinite | -| maxIdleTimeExcessConnections | In seconds; 0 is infinite; culls connections down to the minPoolSize | -| maxPoolSize | | -| maxStatementsPerConnection | 0 disables; prepared statement caching per connection | -| minPoolSize | | -| numHelperThreads | | -| password | | -| queueSize | -1 for unlimited, 0 to disable | -| testConnectionOnCheckin | | -| upgradeTimeoutSeconds | | -| user | | - -#### api.defaults - -- ##### api.defaults.businessPolicy - - | Parameter Name | Description | - |----------------------------|---------------------------------------------------| - | check_agreement_status | | - | missing_heartbeat_interval | Used if the service.nodeHealth section is omitted | - -- ##### api.defaults.msgs - - | Parameter Name | Description | - |-------------------------------|------------------------------------------------------------------------| - | expired_msgs_removal_interval | Number of seconds between deletions of expired node and agbot messages | - -- ##### api.defaults.pattern - - | Parameter Name | Description | - |----------------------------|----------------------------------------------------| - | missing_heartbeat_interval | Used if the services.nodeHealth section is omitted | - | check_agreement_status | | - -#### api.limits - -| Parameter Name | Description | -|------------------------|-----------------------------------------------------------------------------------------------------------------------------| -| maxAgbots | Maximum number of agbots 1 user is allowed to create, 0 for unlimited | -| maxAgreements | Maximum number of agreements 1 node or agbot is allowed to create, 0 for unlimited | -| maxBusinessPolicies | Maximum number of business policies 1 user is allowed to create, 0 for unlimited | -| maxManagementPolicies | Maximum number of management policies 1 user is allowed to create, 0 for unlimited | -| maxMessagesInMailbox | Maximum number of msgs currently in 1 node or agbot mailbox (the sending side is handled by rate limiting), 0 for unlimited | -| maxNodes | Maximum number of nodes 1 user is allowed to create, 0 for unlimited | -| maxPatterns | Maximum number of patterns 1 user is allowed to create, 0 for unlimited | -| maxServices | Maximum number of services 1 user is allowed to create, 0 for unlimited | - -#### api.logging [DEPRECATED] - -#### api.resourceChanges - -| Parameter Name | Description | -|--------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| cleanupInterval | Number of seconds between pruning the resourcechanges table in the db of expired changes - 3600 is 1 hour | -| maxRecordsCap | Maximum number of records the notification framework route will return | -| ttl | Number of seconds to keep the history records of resource changes (14400 is 4 hours). When agents miss 1 or more heartbeats, they reset querying the /changes route, so they do not need very old entries | - -#### api.root - -| Parameter Name | Description | -|------------------|--------------------------------------------------------| -| enabled | If set to false it will not honor the root credentials | -| password | Set this in your own version of this config file | - -#### api.service - -| Parameter Name | Description | -|------------------------------------------------|------------------------| -| host | | -| port | Services HTTP traffic | -| portEncrypted | Services HTTPS traffic | -| shutdownWaitForRequestsToComplete [DEPRECATED] | [DEPRECATED] | - -#### api.tls - -| Parameter Name | Description | -|----------------|------------------------------------------------------------------------------------------------------------| -| password | Truststore's password | -| truststore | Absolute path and name of your pkcs12 (.p12) truststore that contains your tls certificate and private key | ## Todos that may be done in future versions diff --git a/docs/openapi-3-developer.json b/docs/openapi-3-developer.json index bdd0be16..a7186da1 100644 --- a/docs/openapi-3-developer.json +++ b/docs/openapi-3-developer.json @@ -8,7 +8,7 @@ "name" : "Apache License Version 2.0", "url" : "https://www.apache.org/licenses/LICENSE-2.0" }, - "version" : "2.123.0" + "version" : "2.124.0" }, "externalDocs" : { "description" : "Open-horizon ExchangeAPI", diff --git a/docs/openapi-3-user.json b/docs/openapi-3-user.json index 5adca481..900f7e37 100644 --- a/docs/openapi-3-user.json +++ b/docs/openapi-3-user.json @@ -8,7 +8,7 @@ "name" : "Apache License Version 2.0", "url" : "https://www.apache.org/licenses/LICENSE-2.0" }, - "version" : "2.123.0" + "version" : "2.124.0" }, "externalDocs" : { "description" : "Open-horizon ExchangeAPI", diff --git a/project/build.properties b/project/build.properties index abbbce5d..ee4c672c 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.8 +sbt.version=1.10.1 diff --git a/project/plugins.sbt b/project/plugins.sbt index 753bf0fd..1db24ebf 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,22 +1,14 @@ +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "[0.11.1,)") + // Builds docker image of our exchange svr addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "[1.9.13,)") -// addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "[5.2.4,)") - // Linter // addSbtPlugin("com.sksamuel.scapegoat" %% "sbt-scapegoat" % "[1.1.0,)") // A fast restart of our rest api svr in sbt. Does NOT require use of spray addSbtPlugin("io.spray" % "sbt-revolver" % "[0.9.1,)") -// To see the current versions being used, uncomment this line, then run: sbt dependencyTree -// addSbtPlugin("net.virtual-void" % "sbt-dependencpwdy-graph" % "[0.8.0,)") - -// Reformats the scala source code when compiling it - this was giving parser errors w/o giving line numbers -// addSbtPlugin("org.scalariform" % "sbt-scalariform" % "[1.8.2,)") - -// addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "latest.release") - // Code coverage report generation addSbtPlugin("org.scoverage" % "sbt-scoverage" % "[2.0.6,)") diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf new file mode 100644 index 00000000..01d3e648 --- /dev/null +++ b/src/main/resources/application.conf @@ -0,0 +1,103 @@ +# Scala SLICK application settings +exchange-db-connection { + # HikariCP https://github.com/brettwooldridge/HikariCP + connectionTimeout = ${?HIKARICP_CONNECTIONTIMEOUT} + keepaliveTime = ${?HIKARICP_KEEPALIVETIME} + maxLifetime = ${?HIKARICP_MAXLIFETIME} + + # Data Source Class + properties.databaseName = ${?EXCHANGE_DB_NAME} + properties.portNumber = ${?EXCHANGE_DB_PORT} + properties.serverName = ${?EXCHANGE_DB_HOST} +} + +ibm { + common-services { + external-management-ingress = ${?ICP_EXTERNAL_MGMT_INGRESS} + management-ingress-service-port = ${?ICP_MANAGEMENT_INGRESS_SERVICE_PORT} + identity-provider-service-port = ${?PLATFORM_IDENTITY_PROVIDER_SERVICE_PORT} + } +} + +# Apache Pekko setings +# https://pekko.apache.org/docs/pekko/current/general/configuration-reference.html#pekko-actor +# https://pekko.apache.org/docs/pekko-http/current/configuration.html +pekko { + coordinated-shutdown.phases.service-unbind.timeout = 60s # Number of seconds to let in-flight requests complete before exiting the server + http.parsing.max-header-name-length = 128 + http.server.backlog = 103 + http.server.default-http-port = 8080 # Services HTTP traffic + http.server.default-https-port = 8083 # Services HTTPS traffic + http.server.server-header = "" + loggers = ["org.apache.pekko.event.slf4j.Slf4jLogger"] + loglevel = "INFO" + logging-filter = "org.apache.pekko.event.slf4j.Slf4jLoggingFilter" + request-timeout = 45s +} + +# Apache Pekko - CORS settings https://pekko.apache.org/docs/pekko-http/current/configuration.html +pekko-http-cors { + allow-credentials = true + allow-generic-http-requests = true # Do not apply `Origin` header check to non-preflight (`OPTIONS`) requests + allowed-headers = ["*"] + allowed-methods = ["DELETE", "GET", "OPTIONS","PATCH", "POST", "PUT"] + allowed-origins = ["*"] + exposed-headers = ["*"] + max-age = 0s +} + +# Logback logger Settings +logback.core.model.processor.AppenderRefModelHandler = ${?EXCHANGE_LOG_LOGBACK_APPENDERREFMODELHANDLER} +logback.classic.model.processor.LoggerModelHandler = ${?EXCHANGE_LOG_LOGBACK_LOGGERMODELHANDLER} +logback.hikari.HikariConfig = ${?EXCHANGE_LOG_HIKARI_CONFIG} +logback.hikari.HikariDataSource = ${?EXCHANGE_LOG_HIKARI_DATASOURCE} +logback.hikari.pool.HikariPool = ${?EXCHANGE_LOG_HIKARI_POOL} +logback.hikari.pool.PoolBase = ${?EXCHANGE_LOG_HIKARI_POOL_BASE} +logback.scalacache.guava.GuavaCache = ${?EXCHANGE_LOG_SCALACACHE_GUAVA} +logback.slick.basic.BasicBackend.action = ${?EXCHANGE_LOG_SLICK_ACTION} +logback.slick.basic.BasicBackend.stream = ${?EXCHANGE_LOG_SLICK_STREAM} +logback.slick.compiler-log = ${?EXCHANGE_LOG_SLICK_COMPILER} +logback.slick.compiler.AssignUniqueSymbols = ${?EXCHANGE_LOG_SLICK_COMPILER_ASSIGNUNIQUESYMBOLS} +logback.slick.compiler.CodeGen = ${?EXCHANGE_LOG_SLICK_COMPILER_CODEGEN} +logback.slick.compiler.CreateAggregates = ${?EXCHANGE_LOG_SLICK_COMPILER_CREATEAGGREGATES} +logback.slick.compiler.CreateResultSetMapping = ${?EXCHANGE_LOG_SLICK_COMPILER_CREATERESULTSETMAPPING} +logback.slick.compiler.EmulateOuterJoins = ${?EXCHANGE_LOG_SLICK_COMPILER_EMULATEOUTERJOINS} +logback.slick.compiler.ExpandConditionals = ${?EXCHANGE_LOG_SLICK_COMPILER_EXPANDCONDITIONALS} +logback.slick.compiler.ExpandRecords = ${?EXCHANGE_LOG_SLICK_COMPILER_EXPANDRECORDS} +logback.slick.compiler.ExpandSums = ${?EXCHANGE_LOG_SLICK_COMPILER_EXPANDSUMS} +logback.slick.compiler.ExpandTables = ${?EXCHANGE_LOG_SLICK_COMPILER_EXPANDTABLES} +logback.slick.compiler.FixRowNumberOrdering = ${?EXCHANGE_LOG_SLICK_COMPILER_FIXROWNUMBERORDERING} +logback.slick.compiler.FlattenProjections = ${?EXCHANGE_LOG_SLICK_COMPILER_FLATTENPROJECTIONS} +logback.slick.compiler.ForceOuterBinds = ${?EXCHANGE_LOG_SLICK_COMPILER_FORCEOUTERBINDS} +logback.slick.compiler.HoistClientOps = ${?EXCHANGE_LOG_SLICK_COMPILER_HOISTCLIENTOPS} +logback.slick.compiler.Inline = ${?EXCHANGE_LOG_SLICK_COMPILER_INLINE} +logback.slick.compiler.InferTypes = ${?EXCHANGE_LOG_SLICK_COMPILER_INFERTYPES} +logback.slick.compiler.InsertCompiler = ${?EXCHANGE_LOG_SLICK_COMPILER_INSERTCOMPILER} +logback.slick.compiler.MergeToComprehensions = ${?EXCHANGE_LOG_SLICK_COMPILER_MERGETOCOMPREHENSIONS} +logback.slick.compiler.OptimizeScalar = ${?EXCHANGE_LOG_SLICK_COMPILER_OPTIMIZESCALAR} +logback.slick.compiler.PruneProjections = ${?EXCHANGE_LOG_SLICK_COMPILER_PRUNEPROJECTIONS} +logback.slick.compiler.QueryCompiler = ${?EXCHANGE_LOG_SLICK_COMPILER_QUERY} +logback.slick.compiler.QueryCompilerBenchmark = ${?EXCHANGE_LOG_SLICK_COMPILER_QUERY_BENCHMARK} +logback.slick.compiler.RemoveFieldNames = ${?EXCHANGE_LOG_SLICK_COMPILER_REMOVEFIELDNAMES} +logback.slick.compiler.RemoveMappedTypes = ${?EXCHANGE_LOG_SLICK_COMPILER_REMOVEMAPPEDTYPES} +logback.slick.compiler.RemoveTakeDrop = ${?EXCHANGE_LOG_SLICK_COMPILER_REMOVETAKEDROP} +logback.slick.compiler.ReorderOperations = ${?EXCHANGE_LOG_SLICK_COMPILER_REORDEROPERATIONS} +logback.slick.compiler.ResolveZipJoins = ${?EXCHANGE_LOG_SLICK_COMPILER_RESOLVEZIPJOINS} +logback.slick.compiler.RewriteBooleans = ${?EXCHANGE_LOG_SLICK_COMPILER_REWRITEBOOLEANS} +logback.slick.compiler.RewriteDistinct = ${?EXCHANGE_LOG_SLICK_COMPILER_REWRITEDISTINCT} +logback.slick.compiler.RewriteJoins = ${?EXCHANGE_LOG_SLICK_COMPILER_REWRITEJOINS} +logback.slick.compiler.SpecializeParameters = ${?EXCHANGE_LOG_SLICK_COMPILER_SPECIALPARAMETERS} +logback.slick.compiler.VerifyTypes = ${?EXCHANGE_LOG_SLICK_COMPILER_VERIFYTYPES} +logback.slick.jdbc.DriverDataSource = ${?EXCHANGE_LOG_SLICK_JDBC_DRIVERDATASOURCE} +logback.slick.jdbc.JdbcBackend.benchmark = ${?EXCHANGE_LOG_SLICK_JDBC_BENCHMARK} +logback.slick.jdbc.JdbcBackend.parameter = ${?EXCHANGE_LOG_SLICK_JDBC_PARAMETER} +logback.slick.jdbc.JdbcBackend.statement = ${?EXCHANGE_LOG_SLICK_JDBC_STATEMENT} +logback.slick.jdbc.JdbcBackend.statementAndParameter = ${?EXCHANGE_LOG_SLICK_JDBC_STATEMENTANDPARAMETER} +logback.slick.jdbc.JdbcModelBuilder = ${?EXCHANGE_LOG_SLICK_JDBC_JDBCMODELBUILDER} +logback.slick.jdbc.StatementInvoker.result = ${?EXCHANGE_LOG_SLICK_JDBC_STATEMENTINVOLKER_RESULT} +logback.slick.memory.HeapBackend = ${?EXCHANGE_LOG_SLICK_MEMORY_HEAPBACKEND} +logback.slick.memory.QueryInterpreter = ${?EXCHANGE_LOG_SLICK_MEMORY_QUERYINTERPRETER} +logback.slick.relational.ResultConverterCompiler = ${?EXCHANGE_LOG_SLICK_RELATIONAL} +logback.slick.util.AsyncExecutor = ${?EXCHANGE_LOG_SLICK_ASYNCEXECUTOR} +logback.swagger.v3.core.converter.ModelConverterContextImpl = ${?EXCHANGE_LOG_SWAGGER_MODELCONVERTERCONTEXTIMPL} +logback.swagger.v3.jaxrs2.Reader = ${?EXCHANGE_LOG_SWAGGER_JAXRS2_READER} diff --git a/src/main/resources/config.json b/src/main/resources/config.json deleted file mode 100644 index a60742f4..00000000 --- a/src/main/resources/config.json +++ /dev/null @@ -1,203 +0,0 @@ -{ - "pekko": { - "coordinated-shutdown": { - "phases.service-unbind.timeout": "60s" - }, - "http": { - "parsing": { - "max-header-name-length": 128 - }, - "loglevel": "INFO", - "server": { - "backlog": 103, - "bind-timeout": "1s", - "idle-timeout": "60s", - "linger-timeout": "1m", - "max-connections": "1024", - "pipelining-limit": 1, - "request-timeout": "45s", - "server-header": "" - } - } - }, - "pekko-http-cors": { - "allow-credentials": true, - "allow-generic-http-requests": true, - "allowed-headers": ["*"], - "allowed-methods": ["DELETE", "GET", "OPTIONS","PATCH", "POST", "PUT"], - "allowed-origins": ["*"], - "exposed-headers": ["*"], - "max-age": "0s" - }, - "api": { - "acls": { - "AdminUser": [ - "ALL_IN_ORG", - "WRITE_AGENT_CONFIG_MGMT" - ], - "Agbot": [ - "DATA_HEARTBEAT_MY_AGBOTS", - "MAXCHANGEID", - "READ_MYSELF", - "READ_AGENT_CONFIG_MGMT", - "READ_ALL_AGBOTS", - "READ_ALL_NODES", - "READ_ALL_SERVICES", - "READ_ALL_PATTERNS", - "READ_ALL_BUSINESS", - "READ_ALL_MANAGEMENT_POLICY", - "READ_MY_AGBOTS", - "READ_MY_ORG", - "SEND_MSG_TO_NODE", - "WRITE_AGENT_CONFIG_MGMT", - "WRITE_MYSELF" - ], - "Anonymous": [], - "HubAdmin": [ - "CREATE_IN_OTHER_ORGS", - "CREATE_ORGS", - "CREATE_USER", - "DELETE_ORG", - "ORGSTATUS", - "READ_AGENT_CONFIG_MGMT", - "READ_ALL_AGBOTS", - "READ_IBM_ORGS", - "READ_MY_ORG", - "READ_MY_USERS", - "READ_MYSELF", - "READ_OTHER_ORGS", - "SET_IBM_ORG_TYPE", - "STATUS", - "UTILITIES", - "WRITE_AGENT_CONFIG_MGMT", - "WRITE_ALL_AGBOTS", - "WRITE_MY_ORG", - "WRITE_MY_USERS", - "WRITE_MYSELF", - "WRITE_OTHER_ORGS" - ], - "Node": [ - "MAXCHANGEID", - "READ_AGENT_CONFIG_MGMT", - "READ_ALL_AGBOTS", - "READ_ALL_BUSINESS", - "READ_ALL_MANAGEMENT_POLICY", - "READ_ALL_PATTERNS", - "READ_ALL_SERVICES", - "READ_MY_ORG", - "READ_MYSELF", - "SEND_MSG_TO_AGBOT", - "WRITE_MYSELF" - ], - "SuperUser": ["ALL"], - "User": [ - "CREATE_AGBOT", - "CREATE_BUSINESS", - "CREATE_NODE", - "CREATE_PATTERNS", - "CREATE_SERVICES", - "DATA_HEARTBEAT_MY_AGBOTS", - "READ_AGENT_CONFIG_MGMT", - "READ_ALL_AGBOTS", - "READ_ALL_BUSINESS", - "READ_ALL_MANAGEMENT_POLICY", - "READ_ALL_PATTERNS", - "READ_ALL_SERVICES", - "READ_IBM_ORGS", - "READ_MY_AGBOTS", - "READ_MY_BUSINESS", - "READ_MY_NODES", - "READ_MY_PATTERNS", - "READ_MY_MANAGEMENT_POLICY", - "READ_MY_ORG", - "READ_MY_SERVICES", - "READ_MYSELF", - "STATUS", - "UTILITIES", - "WRITE_MY_AGBOTS", - "WRITE_MY_BUSINESS", - "WRITE_MY_NODES", - "WRITE_MY_PATTERNS", - "WRITE_MY_SERVICES", - "WRITE_MYSELF" - ] - }, - "akka": {}, - "cache": { - "authDbTimeoutSeconds": 15, - "IAMusersMaxSize": 300, - "IAMusersTtlSeconds": 300, - "idsMaxSize": 47000, - "idsTtlSeconds": 5400, - "resourcesMaxSize": 300, - "resourcesTtlSeconds": 300, - "type": "guava" - }, - "db": { - "acquireIncrement": 1, - "driverClass": "org.postgresql.Driver", - "idleConnectionTestPeriod": 0, - "initialPoolSize": 1, - "jdbcUrl": "", - "maxConnectionAge": 0, - "maxIdleTime": 0, - "maxIdleTimeExcessConnections": 0, - "maxPoolSize": 50, - "maxStatementsPerConnection": 0, - "minPoolSize": 1, - "numHelperThreads": 3, - "password": "", - "queueSize": 1000, - "testConnectionOnCheckin": false, - "upgradeTimeoutSeconds": 180, - "user": "" - }, - "defaults": { - "businessPolicy": { - "check_agreement_status": 1800, - "missing_heartbeat_interval": 1800 - }, - "msgs": { - "expired_msgs_removal_interval": 1800 - }, - "pattern": { - "missing_heartbeat_interval": 1800, - "check_agreement_status": 1800 - } - }, - "hubadmins": [], - "limits": { - "maxAgbots": 1000, - "maxAgreements": 0, - "maxBusinessPolicies": 5000, - "maxManagementPolicies": 5000, - "maxMessagesInMailbox": 0, - "maxNodes": 45000, - "maxPatterns": 1000, - "maxServices": 1000 - }, - "logging": { - "level": "" - }, - "resourceChanges": { - "cleanupInterval": 3600, - "maxRecordsCap": 10000, - "ttl": 14400 - }, - "root": { - "account_id": null, - "enabled": true, - "password": "" - }, - "service": { - "host": "0.0.0.0", - "port": 8080, - "portEncrypted": 8083, - "shutdownWaitForRequestsToComplete": -1 - }, - "tls": { - "password": null, - "truststore": null - } - } -} diff --git a/src/main/resources/exchange.conf b/src/main/resources/exchange.conf new file mode 100644 index 00000000..b83c32e9 --- /dev/null +++ b/src/main/resources/exchange.conf @@ -0,0 +1,72 @@ +include required("application.conf") +include file("/etc/horizon/exchange/config.json") + +# These are in the exchange.conf so that the environment variables take precendence over the backwards supported config file: "/etc/horizon/exchange/config.json". + +api { + acls = ${?EXCHANGE_ACLS} + + db.upgradeTimeoutSeconds = ${?EXCHANGE_DB_UPGRADE_TIMEOUT} + + hubadmins = ${?EXCHANGE_HUBADMINS} + + language = ${?LANG} + language = ${?HZN_EXCHANGE_LANG} + language = ${?EXCHANGE_LANGUAGE} + + limits.maxAgbots = ${?EXCHANGE_MAX_AGBOTS} + limits.maxAgreements = ${?EXCHANGE_MAX_AGREEMENTS} + limits.maxBusinessPolicies = ${?EXCHANGE_MAX_DEPLOY_POLICIES} + limits.maxManagementPolicies = ${?EXCHANGE_MAX_MGMT_POLICIES} + limits.maxMessagesInMailbox = ${?EXCHANGE_MAILBOX_SIZE} + limits.maxNodes = ${?EXCHANGE_MAX_NODES} + limits.maxPatterns = ${?EXCHANGE_MAX_DEPLOY_PATTERNS} + limits.maxServices = ${?EXCHANGE_MAX_SERVICES} + + resourceChanges.cleanupInterval = ${?EXCHANGE_CHANGES_TRIM} + resourceChanges.maxRecordsCap = ${?EXCHANGE_MAX_CHANGES} + resourceChanges.ttl = ${?EXCHANGE_CHANGES_TTL} + + root.enabled = ${?EXCHANGE_ROOT_ENABLED} + root.password = ${?EXCHANGE_ROOT_PW} + + tls.password = ${?EXCHANGE_TLS_PASSWORD} + tls.truststore = ${?EXCHANGE_TLS_TRUSTSTORE} +} + +exchange-db-connection { + # HikariCP https://github.com/brettwooldridge/HikariCP + idleTimeout = ${?api.db.maxIdleTime} + idleTimeout = ${?HIKARICP_IDLETIMEOUT} + minimumIdle = ${?api.db.minPoolSize} + minimumIdle = ${?HIKARICP_MINIMUMIDLE} + + # SLICK + ## 2024-09-14: HikariCP defaults to 10, Slick overrides this and defaults to 20. + ## https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing + numThreads = ${?api.db.maxPoolSize} + numThreads = ${?EXCHANGE_DB_NUMTHREADS} # Must be the same value as HikariCP's maximumPoolSize + # maximumPoolSize = ${?exchange-db-connection.numThreads} # Must be the same value as SLICK's numThreads. Slick will by default make this the same value of numThreads. + + ## queueSize: + ## `0` for direct hand-off + ## `-1` for unlimited + ## Using either of the above queueSize values requires numThreads to be the value of Int.MaxValue + queueSize = ${?api.db.queueSize} + queueSize = ${?EXCHANGE_DB_QUEUE_SIZE} + + # Data Source Class + properties.password = ${?api.db.password} + properties.password = ${?EXCHANGE_DB_PW} + properties.user = ${?api.db.user} + properties.user = ${?EXCHANGE_DB_USER} +} + +pekko { + http.server.default-http-port = ${?api.service.portUnencrypted} + http.server.default-http-port = ${?EXCHANGE_PEKKO_HTTP_PORT} + http.server.default-https-port = ${?api.service.portEncrypted} + http.server.default-https-port = ${?EXCHANGE_PEKKO_HTTPS_PORT} + loglevel = ${?api.logging.level} + loglevel = ${?EXCHANGE_PEKKO_LOGLEVEL} +} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 19fb587d..4360f3d5 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -1,13 +1,96 @@ - + + + - + + + [%date{ISO8601}] %-44([%logger]) [%marker] %-42([%thread]) %highlight(%-7([%level])) -%kvp- %msg%n + + + + + target/myapp-dev.log - %date [%thread] %-5level %logger{36} - %msg%n + [%date{ISO8601}] %-42([%logger]) [%marker] %-42([%thread]) %-7([%level]) - %msg%n - + + 8192 + true + + + + 8192 + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/reference.conf b/src/main/resources/reference.conf new file mode 100644 index 00000000..160889c2 --- /dev/null +++ b/src/main/resources/reference.conf @@ -0,0 +1,256 @@ +# Exchange default reference settings +api { + acls { + AdminUser = + ["ALL_IN_ORG", + "WRITE_AGENT_CONFIG_MGMT"] + Agbot = + ["DATA_HEARTBEAT_MY_AGBOTS", + "MAXCHANGEID", + "READ_MYSELF", + "READ_AGENT_CONFIG_MGMT", + "READ_ALL_AGBOTS", + "READ_ALL_NODES", + "READ_ALL_SERVICES", + "READ_ALL_PATTERNS", + "READ_ALL_BUSINESS", + "READ_ALL_MANAGEMENT_POLICY", + "READ_MY_AGBOTS", + "READ_MY_ORG", + "SEND_MSG_TO_NODE", + "WRITE_AGENT_CONFIG_MGMT", + "WRITE_MYSELF"] + Anonymous = [] # Not actually used + HubAdmin = + ["CREATE_IN_OTHER_ORGS", + "CREATE_ORGS", + "CREATE_USER", + "DELETE_ORG", + "ORGSTATUS", + "READ_AGENT_CONFIG_MGMT", + "READ_ALL_AGBOTS", + "READ_IBM_ORGS", + "READ_MY_ORG", + "READ_MY_USERS", + "READ_MYSELF", + "READ_OTHER_ORGS", + "SET_IBM_ORG_TYPE", + "STATUS", + "UTILITIES", + "WRITE_AGENT_CONFIG_MGMT", + "WRITE_ALL_AGBOTS", + "WRITE_MY_ORG", + "WRITE_MY_USERS", + "WRITE_MYSELF", + "WRITE_OTHER_ORGS"] + Node = + ["MAXCHANGEID", + "READ_AGENT_CONFIG_MGMT", + "READ_ALL_AGBOTS", + "READ_ALL_BUSINESS", + "READ_ALL_MANAGEMENT_POLICY", + "READ_ALL_PATTERNS", + "READ_ALL_SERVICES", + "READ_MY_ORG", + "READ_MYSELF", + "SEND_MSG_TO_AGBOT", + "WRITE_MYSELF"], + SuperUser = ["ALL"] + User = + ["CREATE_AGBOT", + "CREATE_BUSINESS", + "CREATE_NODE", + "CREATE_PATTERNS", + "CREATE_SERVICES", + "DATA_HEARTBEAT_MY_AGBOTS", + "READ_AGENT_CONFIG_MGMT", + "READ_ALL_AGBOTS", + "READ_ALL_BUSINESS", + "READ_ALL_MANAGEMENT_POLICY", + "READ_ALL_PATTERNS", + "READ_ALL_SERVICES", + "READ_IBM_ORGS", + "READ_MY_AGBOTS", + "READ_MY_BUSINESS", + "READ_MY_NODES", + "READ_MY_PATTERNS", + "READ_MY_MANAGEMENT_POLICY", + "READ_MY_ORG", + "READ_MY_SERVICES", + "READ_MYSELF", + "STATUS", + "UTILITIES", + "WRITE_MY_AGBOTS", + "WRITE_MY_BUSINESS", + "WRITE_MY_NODES", + "WRITE_MY_PATTERNS", + "WRITE_MY_SERVICES", + "WRITE_MYSELF"] + } + + db.upgradeTimeoutSeconds = 180 + + defaults.businessPolicy.check_agreement_status = 1800 + defaults.businessPolicy.missing_heartbeat_interval = 1800 # Used if the service.nodeHealth section is omitted + defaults.msgs.expired_msgs_removal_interval = 1800 # Number of seconds between deletions of expired node and agbot messages + defaults.pattern.missing_heartbeat_interval = 1800 # Used if the service.nodeHealth section is omitted + defaults.pattern.check_agreement_status = 1800 + + # Create new Hub Admin users. Cannot update existing users. + # Hubadmin Syntax + # hubadmins = [ + # {org = "root", + # password = "", + # user = "user1"}, + # {org = "root", + # password = "", + # user = "user2"}, + # {org = "root", + # password = "", + # user = "user3"} + # ] + # User's organization must be "root". + # Username must be defined, and cannot be "root". + # Will only create the first instance of a User. + hubadmins = [] + + language = "en" + + limits.maxAgbots = 1000 # Maximum number of agbots 1 user is allowed to create, 0 for unlimited + limits.maxAgreements = 0 # Maximum number of agreements 1 node or agbot is allowed to create, 0 for unlimited + limits.maxBusinessPolicies = 5000 # Maximum number of business policies 1 user is allowed to create, 0 for unlimited + limits.maxManagementPolicies = 5000 # Maximum number of management policies 1 user is allowed to create, 0 for unlimited + limits.maxMessagesInMailbox = 0 # Maximum number of msgs currently in 1 node or agbot mailbox (the sending side is handled by rate limiting), 0 for unlimited + limits.maxNodes = 45000 # Maximum number of nodes 1 user is allowed to create, 0 for unlimited + limits.maxPatterns = 1000 # Maximum number of patterns 1 user is allowed to create, 0 for unlimited + limits.maxServices = 1000 # Maximum number of services 1 user is allowed to create, 0 for unlimited + + resourceChanges.cleanupInterval = 3600 # Number of seconds between pruning the resourcechanges table in the db of expired changes - 3600 is 1 hour + resourceChanges.maxRecordsCap = 10000 # Maximum number of records the notification framework route will return + resourceChanges.ttl = 14400 # Number of seconds to keep the history records of resource changes (14400 is 4 hours). When agents miss 1 or more heartbeats, they reset querying the /changes route, so they do not need very old entries + + + + # ie. root.account_id.ibmcloud_id = 012345689 + root.account_id = null + root.enabled = true # If set to false it will not honor the root credentials + root.password = null + + service.host = "0.0.0.0" + service.shutdownWaitForRequestsToComplete = 5 + + tls.password = null # Truststore's password + tls.truststore = null # Absolute path and name of your pkcs12 (.p12) truststore that contains your tls certificate and private key + + cache = { + authDbTimeoutSeconds = 15 # Timeout for db access for critical auth info when cache missing + IAMusersMaxSize = 300 # The users that are backed by IAM users + IAMusersTtlSeconds = 300 + idsMaxSize = 47000 # Includes: local exchange users, nodes, agbots (all together) + idsTtlSeconds = 5400 + resourcesMaxSize = 300 # Each of: users, agbots, services, patterns, policies + resourcesTtlSeconds = 300 + type = "guava" # Currently guava is the only option + } +} + +# Scala SLICK and HikariCP settings for a PostgreSQL instance +# HikariCP Settings Reference: https://github.com/brettwooldridge/HikariCP +# Add thread pool settings directly. +# DO NOT use numThreads and maximumPoolSize seperately! +exchange-db-connection = { # No default application reference + connectionPool = "HikariCP" + dataSourceClass = "org.postgresql.ds.PGSimpleDataSource" + # properties are tied to the datasource class. + properties { + databaseName = "openhorizon" + password = null + portNumber = 5432 + serverName = "localhost" + user = "root" + } + registerMbeans = true +} + +ibm { + common-services { + external-management-ingress = null # transport protocol and domain + management-ingress-service-port = 4300 + identity-provider-service-port = 8443 + } +} + +# Logback loggers +logback { + core.model.processor.AppenderRefModelHandler = "OFF" + classic.model.processor.LoggerModelHandler = "OFF" + + hikari { + HikariConfig = "DEBUG" + HikariDataSource = "DEBUG" + pool.HikariPool = "INFO" + pool.PoolBase = "INFO" + } + + scalacache.guava.GuavaCache = "INFO" + + slick { + basic.BasicBackend.action = "INFO" + basic.BasicBackend.stream = "INFO" + + compiler-log = "INFO" + compiler { + AssignUniqueSymbols = "INHERITED" + CreateAggregates = "INHERITED" + CreateResultSetMapping = "INHERITED" + CodeGen = "INHERITED" + EmulateOuterJoins = "INHERITED" + ExpandConditionals = "INHERITED" + ExpandRecords = "INHERITED" + ExpandSums = "INHERITED" + ExpandTables = "INHERITED" + FixRowNumberOrdering = "INHERITED" + FlattenProjections = "INHERITED" + ForceOuterBinds = "INHERITED" + HoistClientOps = "INHERITED" + InferTypes = "INHERITED" + Inline = "INHERITED" + InsertCompiler = "INHERITED" + MergeToComprehensions = "INHERITED" + OptimizeScalar = "INHERITED" + PruneProjections = "INHERITED" + QueryCompiler = "INHERITED" + QueryCompilerBenchmark = "INHERITED" + RemoveFieldNames = "INHERITED" + RemoveMappedTypes = "INHERITED" + RemoveTakeDrop = "INHERITED" + ReorderOperations = "INHERITED" + ResolveZipJoins = "INHERITED" + RewriteBooleans = "INHERITED" + RewriteDistinct = "INHERITED" + RewriteJoins = "INHERITED" + SpecializeParameters = "INHERITED" + VerifyTypes = "INHERITED" + } + + jdbc { + DriverDataSource = "INFO" + JdbcBackend.benchmark = "INFO" + JdbcBackend.parameter = "INFO" + JdbcBackend.statement = "INFO" + JdbcBackend.statementAndParameter = "INFO" + JdbcModelBuilder = "INFO" + StatementInvoker.result = "INFO" + } + + memory.HeapBackend = "INHERITED" + memory.QueryInterpreter = "INHERITED" + + relational.ResultConverterCompiler = "INFO" + + util.AsyncExecutor = "DEBUG" + } + + swagger.v3.core.converter.ModelConverterContextImpl = "INFO" + swagger.v3.jaxrs2.Reader = "INFO" +} diff --git a/src/main/resources/version.txt b/src/main/resources/version.txt index bef06dbf..67d5c273 100644 --- a/src/main/resources/version.txt +++ b/src/main/resources/version.txt @@ -1 +1 @@ -2.123.0 +2.124.0 diff --git a/src/main/scala/org/openhorizon/exchangeapi/ExchangeApiApp.scala b/src/main/scala/org/openhorizon/exchangeapi/ExchangeApiApp.scala index 23b70a74..d82a229d 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/ExchangeApiApp.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/ExchangeApiApp.scala @@ -6,8 +6,6 @@ package org.openhorizon.exchangeapi -import com.mchange.v2.c3p0.ComboPooledDataSource -import com.mchange.v2.log.FallbackMLog import slick.jdbc.PostgresProfile.api._ import scala.concurrent.{Await, ExecutionContext, Future} @@ -17,7 +15,7 @@ import org.openhorizon.exchangeapi.route.agreement.Confirm import org.openhorizon.exchangeapi.route.agreementbot.{AgbotsRoutes, Agreement, AgreementBot, AgreementBots, Agreements, DeploymentPattern, DeploymentPatterns, DeploymentPolicies, DeploymentPolicy, Heartbeat, Message, Messages} import org.openhorizon.exchangeapi.table import org.openhorizon.exchangeapi.table.{ExchangeApiTables, ExchangePostgresProfile} -import com.typesafe.config.ConfigFactory +import com.typesafe.config.{ConfigFactory, ConfigParseOptions, ConfigSyntax, ConfigValue} import jakarta.ws.rs.{DELETE, GET, PUT, Path} import org.apache.pekko.Done import org.apache.pekko.actor.{Actor, ActorRef, ActorSystem, CoordinatedShutdown, Props, Timers} @@ -30,11 +28,11 @@ import org.apache.pekko.http.scaladsl.model.headers.CacheDirectives.{`max-age`, import org.apache.pekko.http.scaladsl.model.headers.{RawHeader, `Cache-Control`} import org.apache.pekko.http.scaladsl.server.{AuthorizationFailedRejection, ExceptionHandler, MalformedQueryParamRejection, MalformedRequestContentRejection, MethodRejection, Rejection, RejectionHandler, Route, RouteResult, TransformationRejection, ValidationRejection} import org.apache.pekko.http.scaladsl.server.RouteResult.Rejected -import org.apache.pekko.http.scaladsl.server.directives.{DebuggingDirectives, LogEntry} +import org.apache.pekko.http.scaladsl.server.directives.{Credentials, DebuggingDirectives, LogEntry} import org.apache.pekko.http.scaladsl.server.Directives._ import org.json4s._ import org.openhorizon.exchangeapi.SwaggerDocService.complete -import org.openhorizon.exchangeapi.auth.{AuthCache, AuthenticationSupport} +import org.openhorizon.exchangeapi.auth.{AuthCache, AuthenticationSupport, InvalidCredentialsException, Password, Token} import org.openhorizon.exchangeapi.route.administration.dropdatabase.Token import org.openhorizon.exchangeapi.route.agent.AgentConfigurationManagement import org.openhorizon.exchangeapi.route.catalog.{OrganizationDeploymentPatterns, OrganizationServices} @@ -52,28 +50,30 @@ import org.openhorizon.exchangeapi.route.service.{Policy, Service, Services} import org.openhorizon.exchangeapi.route.user.{ChangePassword, Confirm, User, Users} import org.openhorizon.exchangeapi.table.agreementbot.message.AgbotMsgsTQ import org.openhorizon.exchangeapi.table.node.message.NodeMsgsTQ +import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ -import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, ApiUtils, ExchConfig, ExchMsg, ExchangeRejection, NotFoundRejection} +import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} +import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, ApiUtils, Configuration, DatabaseConnection, ExchMsg, ExchangeRejection, NotFoundRejection} import slick.jdbc.TransactionIsolation.Serializable -import java.io.{FileInputStream, InputStream} +import java.io.{File, FileInputStream, InputStream} import java.security.{KeyStore, SecureRandom} import java.sql.Timestamp -import java.util.Optional +import java.util +import java.util.{Map, Optional} import javax.net.ssl.{KeyManagerFactory, SSLContext, SSLEngine, TrustManagerFactory} import scala.concurrent.duration._ import scala.concurrent.{Await, ExecutionContext, Future} import scala.io.{BufferedSource, Source} +import scala.jdk.CollectionConverters.CollectionHasAsScala import scala.util.matching.Regex +import scala.util.Properties import scala.util.{Failure, Success} // Global vals and methods object ExchangeApi { // Global vals - these values are stored here instead of in ExchangeApiApp, because the latter extends DelayedInit, so the compiler checking wouldn't know when they are available. See https://stackoverflow.com/questions/36710169/why-are-implicit-variables-not-initialized-in-scala-when-called-from-unit-test/36710170 // But putting them here and using them from here implies we have to manually verify that we set them before they are used - var serviceHost = "" - var servicePortEncrypted: Option[Int] = scala.None - var servicePortUnencrypted: Option[Int] = scala.None var defaultExecutionContext: ExecutionContext = _ var defaultLogger: LoggingAdapter = _ @@ -84,14 +84,6 @@ object ExchangeApi { def adminVersion(): String = versionText } -/* moved to config.json -object ExchangeApiConstants { - //val serviceHost = "localhost" - val serviceHost = "0.0.0.0" - val servicePort = 8080 -} -*/ - /** * Main pekko server for the Exchange REST API. @@ -187,24 +179,33 @@ object ExchangeApiApp extends App // Set up ActorSystem and other dependencies here println(s"Running with java arguments: ${ApiUtils.getJvmArgs}") - ExchConfig.load() // get config file, normally in /etc/horizon/exchange/config.json - //val something = ConfigFactory.load("config.json") - //(ExchangeApi.serviceHost, ExchangeApi.servicePort) = ExchConfig.getHostAndPort // <- scala does not support this - ExchConfig.getHostAndPort match { - case (h, pe, pu) => - ExchangeApi.serviceHost = h - ExchangeApi.servicePortEncrypted = pe - ExchangeApi.servicePortUnencrypted = pu - } + //val actorConfig = ConfigFactory.parseString("pekko.loglevel=" + ExchConfig.getLogLevel) - implicit val system: ActorSystem = ActorSystem("actors", ExchConfig.getAkkaConfig) // includes the loglevel + //implicit val system: ActorSystem = ActorSystem("actors", ExchConfig.getAkkaConfig) + //implicit val system: ActorSystem = ActorSystem("actors", ConfigFactory.parseFile(new File("/etc/horizon/exchange/config.json"), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON).setAllowMissing(false)).withFallback(ConfigFactory.load("application"))) + + implicit val system: ActorSystem = ActorSystem("actors", ConfigFactory.load("exchange")) // includes the loglevel + + val serviceHost = system.settings.config.getString("api.service.host") + val servicePortEncrypted = + if (system.settings.config.hasPath("pekko.http.server.default-https-port")) + Option(system.settings.config.getInt("pekko.http.server.default-https-port")) + else + None + val servicePortUnencrypted = + if (system.settings.config.hasPath("pekko.http.server.default-http-port")) + Option(system.settings.config.getInt("pekko.http.server.default-http-port")) + else + None + implicit val executionContext: ExecutionContext = system.dispatcher ExchangeApi.defaultExecutionContext = executionContext // need this set in an object that doesn't use DelayedInit - implicit val logger: LoggingAdapter = Logging(system, "ExchApi") + implicit val logger: LoggingAdapter = Logging(system, "Exchange") ExchangeApi.defaultLogger = logger // need this set in an object that doesn't use DelayedInit - ExchConfig.createRootInCache() + + AuthCache.createRootInCache() // Check common overwritten pekko configuration parameters logger.debug("pekko.corrdinated-shutdown: " + system.settings.config.getConfig("pekko.coordinated-shutdown").toString) @@ -288,13 +289,39 @@ object ExchangeApiApp extends App def testRoute = { path("test") { get { - logger.debug("In /test") - complete(testResp()) } } } + +// TODO: Basic authentication idea +/* def myUserPassAuthenticator(credentials: Credentials): Future[Option[String]] = { + credentials match { + case p @ Credentials.Provided(username) => + Future { + AuthCache.getUser(username) match { + case Some(userHashedTok) => + if(p.verify(secret = userHashedTok, + hasher = + { + unhashedCred => + if (Password.check(unhashedCred, userHashedTok)) + userHashedTok + else + "" + })) // Need to use verify to prevent timing attacks. Need to use BCrypt's builtin comparator to evaluate equality of hashes. + Some(username) + else + None + case None => None + } + } + case _ => + Future.successful(None) + } + } */ + //someday: use directive https://doc.pekko.io/docs/pekko-http/current/routing-dsl/directives/misc-directives/selectPreferredLanguage.html to support a different language for each client lazy val routes: Route = DebuggingDirectives.logRequestResult(requestResponseLogging _) { @@ -309,86 +336,89 @@ object ExchangeApiApp extends App cors() { handleExceptions(myExceptionHandler) { handleRejections(corsRejectionHandler.withFallback(myRejectionHandler)) { - agentConfigurationManagement ~ - agreement ~ - agreementBot ~ - agreementBots ~ - agreementNode ~ - agreements ~ - agreementsNode ~ - changePassword ~ - changes ~ - cleanup ~ - clearAuthCache ~ - confirm ~ - confirmAgreement ~ - configuration ~ - configurationState ~ - deploymentPattern ~ - deploymentPatternAgreementBot ~ - deploymentPatterns ~ - deploymentPatternsAgreementBot ~ - deploymentPatternsCatalog ~ - deploymentPolicies ~ - deploymentPoliciesAgreementBot ~ - deploymentPolicy ~ - deploymentPolicyAgreementBot ~ - deploymentPolicySearch ~ - details ~ - dockerAuth ~ - dockerAuths ~ - dropDB ~ - errors ~ - hashPW ~ - heartbeatAgreementBot ~ - heartbeatNode ~ - initializeDB ~ - keyDeploymentPattern ~ - keyService ~ - keysDeploymentPattern ~ - keysService ~ - managementPolicies ~ - managementPolicy ~ - maxChangeId ~ - messageAgreementBot ~ - messageNode ~ - messagesAgreementBot ~ - messagesNode ~ - myOrganizations ~ - node ~ - nodeErrorSearch ~ - nodeErrorsSearch ~ - nodeHealthDeploymentPattern ~ - nodeHealthSearch ~ - nodeHighAvailabilityGroup ~ - nodes ~ - nodeServiceSearch ~ - nodeGroup ~ - nodeGroups ~ - organization ~ - organizationDeploymentPatterns ~ - organizations ~ - organizationServices ~ - organizationStatus ~ - policyNode ~ - policyService ~ - reload ~ - searchNode ~ - service ~ - services ~ - servicesCatalog ~ - status ~ - statusManagementPolicy ~ - statusNode ~ - statusOrganization ~ - statuses ~ - SwaggerDocService.routes ~ - swaggerUiRoutes ~ - testRoute ~ - token ~ - user ~ - users ~ - version +// authenticateBasicAsync(realm = "Exchange", myUserPassAuthenticator) { // TODO: Basic authentication idea, does not cover other auth types. +// cred => + agentConfigurationManagement ~ + agreement ~ + agreementBot ~ + agreementBots ~ + agreementNode ~ + agreements ~ + agreementsNode ~ + changePassword ~ + changes ~ + cleanup ~ + clearAuthCache ~ + confirm ~ + confirmAgreement ~ + configuration ~ + configurationState ~ + deploymentPattern ~ + deploymentPatternAgreementBot ~ + deploymentPatterns ~ + deploymentPatternsAgreementBot ~ + deploymentPatternsCatalog ~ + deploymentPolicies ~ + deploymentPoliciesAgreementBot ~ + deploymentPolicy ~ + deploymentPolicyAgreementBot ~ + deploymentPolicySearch ~ + details ~ + dockerAuth ~ + dockerAuths ~ + dropDB ~ + errors ~ + hashPW ~ + heartbeatAgreementBot ~ + heartbeatNode ~ + initializeDB ~ + keyDeploymentPattern ~ + keyService ~ + keysDeploymentPattern ~ + keysService ~ + managementPolicies ~ + managementPolicy ~ + maxChangeId ~ + messageAgreementBot ~ + messageNode ~ + messagesAgreementBot ~ + messagesNode ~ + myOrganizations ~ + node ~ + nodeErrorSearch ~ + nodeErrorsSearch ~ + nodeHealthDeploymentPattern ~ + nodeHealthSearch ~ + nodeHighAvailabilityGroup ~ + nodes ~ + nodeServiceSearch ~ + nodeGroup ~ + nodeGroups ~ + organization ~ + organizationDeploymentPatterns ~ + organizations ~ + organizationServices ~ + organizationStatus ~ + policyNode ~ + policyService ~ + reload ~ + searchNode ~ + service ~ + services ~ + servicesCatalog ~ + status ~ + statusManagementPolicy ~ + statusNode ~ + statusOrganization ~ + statuses ~ + SwaggerDocService.routes ~ + swaggerUiRoutes ~ + testRoute ~ + token ~ + user ~ + users ~ + version +// } } } } @@ -397,66 +427,16 @@ object ExchangeApiApp extends App } } } - - // Load the db backend. The db access info must be in config.json - // https://www.mchange.com/projects/c3p0/#configuration_properties - var cpds: ComboPooledDataSource = new ComboPooledDataSource() - cpds.setAcquireIncrement(ExchConfig.getInt("api.db.acquireIncrement")) - cpds.setDriverClass(ExchConfig.getString("api.db.driverClass")) //loads the jdbc driver - cpds.setIdleConnectionTestPeriod(ExchConfig.getInt("api.db.idleConnectionTestPeriod")) - cpds.setInitialPoolSize(ExchConfig.getInt("api.db.initialPoolSize")) - cpds.setJdbcUrl(ExchConfig.getString("api.db.jdbcUrl")) - cpds.setMaxConnectionAge(ExchConfig.getInt("api.db.maxConnectionAge")) - cpds.setMaxIdleTimeExcessConnections(ExchConfig.getInt("api.db.maxIdleTimeExcessConnections")) - cpds.setMaxPoolSize(ExchConfig.getInt("api.db.maxPoolSize")) - cpds.setMaxStatementsPerConnection(ExchConfig.getInt("api.db.maxStatementsPerConnection")) - cpds.setMinPoolSize(ExchConfig.getInt("api.db.minPoolSize")) - cpds.setNumHelperThreads(ExchConfig.getInt("api.db.numHelperThreads")) - cpds.setPassword(ExchConfig.getString("api.db.password")) - cpds.setTestConnectionOnCheckin(ExchConfig.getBoolean("api.db.testConnectionOnCheckin")) - cpds.setUser(ExchConfig.getString("api.db.user")) - - // maxConnections, maxThreads, and minThreads should all be the same size. - val maxConns: Int = ExchConfig.getInt("api.db.maxPoolSize") - val db: Database = - if (cpds != null) { - Database.forDataSource( - cpds, - Option(maxConns), - AsyncExecutor("ExchangeExecutor", maxConns, maxConns, ExchConfig.getInt("api.db.queueSize"), maxConns)) - } - else - null - + val db: Database = DatabaseConnection.getDatabase//Database.forConfig("exchange-db-connection", system.settings.config.getConfig("exchange-db-connection")) def getDb: Database = db system.registerOnTermination(() => db.close()) - /* - * When we were using scalatra - left here for reference, until we investigate the CORS support in pekko-http - * before() { // Before every action runs... - * // We have to set these ourselves because we had to disable scalatra's builtin CorsSupport because for some inexplicable reason it doesn't set Access-Control-Allow-Origin which is critical - * //response.setHeader("Access-Control-Allow-Origin", "*") // <- this can only be used for unauthenticated requests - * response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin")) - * response.setHeader("Access-Control-Allow-Credentials", "true") - * response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers")) - * //response.setHeader("Access-Control-Allow-Headers", "Cookie,Host,X-Forwarded-For,Accept-Charset,If-Modified-Since,Accept-Language,X-Forwarded-Port,Connection,X-Forwarded-Proto,User-Agent,Referer,Accept-Encoding,X-Requested-With,Authorization,Accept,Content-Type,X-Requested-With") // this is taken from what CorsSupport sets - * response.setHeader("Access-Control-Max-Age", "1800") - * response.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,HEAD,OPTIONS,PATCH") - * } - */ - - // Browsers sometimes do a preflight check of this before making the real rest api call - //options("/*"){ - // val creds = credsForAnonymous() - // val userOrId = if (creds.isAnonymous) "(anonymous)" else creds.id - // val clientIp = request.header("X-Forwarded-For").orElse(Option(request.getRemoteAddr)).get // haproxy inserts the real client ip into the header for us - // logger.info("User or id "+userOrId+" from "+clientIp+" running "+request.getMethod+" "+request.getPathInfo+" with request header "+request.getHeader("Access-Control-Request-Headers")) - //} - // Upgrade the db if necessary - try { ExchangeApiTables.upgradeDb(db) } + try { + ExchangeApiTables.upgradeDb(db) + } catch { // Handle db problems case timeout: java.util.concurrent.TimeoutException => @@ -473,7 +453,7 @@ object ExchangeApiApp extends App /** Task for trimming `resourcechanges` table */ def trimResourceChanges(): Unit ={ // Get the time for trimming rows from the table - val timeExpires: Timestamp = ApiTime.pastUTCTimestamp(ExchConfig.getInt("api.resourceChanges.ttl")) + val timeExpires: Timestamp = ApiTime.pastUTCTimestamp(system.settings.config.getInt("api.resourceChanges.ttl")) db.run(ResourceChangesTQ.getRowsExpired(timeExpires).delete.asTry).map({ case Success(v) => if (v <= 0) logger.debug("No resource changes to trim") @@ -484,7 +464,7 @@ object ExchangeApiApp extends App /** Variables and pekko Actor for trimming `resourcechanges` table */ val Cleanup = "cleanup"; - class ChangesCleanupActor(timerInterval: Int = ExchConfig.getInt("api.resourceChanges.cleanupInterval")) extends Actor with Timers{ + class ChangesCleanupActor(timerInterval: Int = system.settings.config.getInt("api.resourceChanges.cleanupInterval")) extends Actor with Timers{ override def preStart(): Unit = { timers.startTimerAtFixedRate(interval = timerInterval.seconds, key = "trimResourceChanges", msg = Cleanup) logger.info("Scheduling change record cleanup every "+ timerInterval.seconds + " seconds") @@ -513,7 +493,7 @@ object ExchangeApiApp extends App /** Variables and pekko Actor for removing expired nodemsgs and agbotmsgs */ val CleanupExpiredMessages = "cleanupExpiredMessages" - class MsgsCleanupActor(timerInterval: Int = ExchConfig.getInt("api.defaults.msgs.expired_msgs_removal_interval")) extends Actor with Timers { + class MsgsCleanupActor(timerInterval: Int = system.settings.config.getInt("api.defaults.msgs.expired_msgs_removal_interval")) extends Actor with Timers { override def preStart(): Unit = { timers.startSingleTimer(key = "removeExpiredMsgsOnStart", msg = CleanupExpiredMessages, timeout = 0.seconds) timers.startTimerAtFixedRate(interval = timerInterval.seconds, key = "removeExpiredMsgs", msg = CleanupExpiredMessages) @@ -527,14 +507,14 @@ object ExchangeApiApp extends App } } val msgsCleanupActor: ActorRef = system.actorOf(Props(new MsgsCleanupActor())) - val secondsToWait: Int = ExchConfig.getInt("api.service.shutdownWaitForRequestsToComplete") // ExchConfig.getpekkoConfig() also makes the pekko unbind phase this long + val secondsToWait: Int = system.settings.config.getInt("api.service.shutdownWaitForRequestsToComplete") // ExchConfig.getpekkoConfig() also makes the pekko unbind phase this long var serverBindingHttp: Option[Http.ServerBinding] = None var serverBindingHttps: Option[Http.ServerBinding] = None val truststore: Option[String] = try { - Option(ExchConfig.getString("api.tls.truststore")) + Option(system.settings.config.getString("api.tls.truststore")) } catch { case _: Exception => None @@ -548,15 +528,15 @@ object ExchangeApiApp extends App try { keyStore.load(new FileInputStream(truststore.get), - ExchConfig.getString("api.tls.password").toCharArray) - keyManager.init(keyStore, ExchConfig.getString("api.tls.password").toCharArray) + system.settings.config.getString("api.tls.password").toCharArray) + keyManager.init(keyStore, system.settings.config.getString("api.tls.password").toCharArray) trustManager.init(keyStore) sslContext.init(keyManager.getKeyManagers, trustManager.getTrustManagers, new SecureRandom) // Start serving client requests - Http().newServerAt(ExchangeApi.serviceHost, ExchangeApi.servicePortEncrypted.get) + Http().newServerAt(serviceHost, -1) // pekko.http.server.default-https-port .enableHttps(ConnectionContext.httpsServer(() => { // Custom TLS parameters val engine: SSLEngine = sslContext.createSSLEngine() @@ -572,7 +552,7 @@ object ExchangeApiApp extends App .map(_.addToCoordinatedShutdown(hardTerminationDeadline = secondsToWait.seconds)) .onComplete { case Success(binding) => - logger.info("Server online at "+ s"https://${ExchangeApi.serviceHost}:${binding.localAddress.getPort}/") // This will schedule to send the Cleanup-message and the CleanupExpiredMessages-message + logger.info("Server online at "+ s"https://${serviceHost}:${binding.localAddress.getPort}/") // This will schedule to send the Cleanup-message and the CleanupExpiredMessages-message serverBindingHttps = Option(binding) case Failure(e) => logger.error("HTTPS server could not start!") @@ -596,13 +576,13 @@ object ExchangeApiApp extends App } } - if(ExchangeApi.servicePortUnencrypted.isDefined) { - Http().newServerAt(ExchangeApi.serviceHost, ExchangeApi.servicePortUnencrypted.get) + if(servicePortUnencrypted.isDefined) { + Http().newServerAt(serviceHost, -1) // pekko.http.server.default-http-port .bind(routes) //.bind(cors.corsHandler(routes)) .map(_.addToCoordinatedShutdown(hardTerminationDeadline = secondsToWait.seconds)) .onComplete { case Success(binding) => - logger.info("Server online at "+ s"http://${ExchangeApi.serviceHost}:${binding.localAddress.getPort}/") // This will schedule to send the Cleanup-message and the CleanupExpiredMessages-message + logger.info("Server online at "+ s"http://${serviceHost}:${binding.localAddress.getPort}/") // This will schedule to send the Cleanup-message and the CleanupExpiredMessages-message serverBindingHttp = Option(binding) case Failure(e) => logger.error("HTTP server could not start!") diff --git a/src/main/scala/org/openhorizon/exchangeapi/SwaggerDocService.scala b/src/main/scala/org/openhorizon/exchangeapi/SwaggerDocService.scala index dab37a48..bb02489d 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/SwaggerDocService.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/SwaggerDocService.scala @@ -17,7 +17,7 @@ import org.openhorizon.exchangeapi.route.deploymentpattern.{DeploymentPatterns, import org.openhorizon.exchangeapi.route.deploymentpolicy.{DeploymentPolicy, DeploymentPolicySearch} import org.openhorizon.exchangeapi.route.managementpolicy.{ManagementPolicies, ManagementPolicy} import org.openhorizon.exchangeapi.route.node.managementpolicy.Statuses -import org.openhorizon.exchangeapi.route.node.{ConfigurationState, Details, Errors, Node, Nodes} +import org.openhorizon.exchangeapi.route.node.{ConfigurationState, Details, Errors, Node, NodeDetails, Nodes} import org.openhorizon.exchangeapi.route.nodegroup.{NodeGroup, NodeGroups} import org.openhorizon.exchangeapi.route.organization.{Changes, Cleanup, MaxChangeId, MyOrganizations, Organization, Organizations} import org.openhorizon.exchangeapi.route.search.{NodeError, NodeErrors, NodeHealth, NodeService} @@ -25,6 +25,8 @@ import org.openhorizon.exchangeapi.route.service.dockerauth.{DockerAuth, DockerA import org.openhorizon.exchangeapi.route.service.key.{Key, Keys} import org.openhorizon.exchangeapi.route.service.{Policy, Service, Services} import org.openhorizon.exchangeapi.route.user.{ChangePassword, Confirm, User, Users} +import org.openhorizon.exchangeapi.table.node.OneService +import org.openhorizon.exchangeapi.utility.Configuration /*Swagger references: - Swagger with pekko-http: https://github.com/swagger-pekko-http/swagger-pekko-http @@ -87,6 +89,7 @@ object SwaggerDocService extends SwaggerHttpService { classOf[org.openhorizon.exchangeapi.route.node.message.Message], classOf[org.openhorizon.exchangeapi.route.node.message.Messages], classOf[org.openhorizon.exchangeapi.route.node.Node], + classOf[org.openhorizon.exchangeapi.route.node.NodeDetails], classOf[org.openhorizon.exchangeapi.route.node.Nodes], classOf[org.openhorizon.exchangeapi.route.node.Policy], classOf[org.openhorizon.exchangeapi.route.node.Status], @@ -118,7 +121,28 @@ object SwaggerDocService extends SwaggerHttpService { classOf[org.openhorizon.exchangeapi.route.user.Users]) override def apiDocsPath: String = "api-docs" //where you want the swagger-json endpoint exposed // override def basePath: String = "" - override def host: String = (if(ExchangeApi.serviceHost.equals("0.0.0.0")) "localhost" else ExchangeApi.serviceHost) + ":" + ExchangeApi.servicePortEncrypted.getOrElse(ExchangeApi.servicePortUnencrypted) //the url of your api, not swagger's json endpoint + private def domain: String = { + val configServiceHost = Configuration.getConfig.getString("api.service.host") + if (configServiceHost.equals("0.0.0.0")) + "localhost" + else + configServiceHost + } + + private def servicePortEncrypted: Option[Int] = + if (Configuration.getConfig.hasPath("pekko.http.server.default-https-port")) + Option(Configuration.getConfig.getInt("pekko.http.server.default-https-port")) + else + None + + private def servicePortUnencrypted: Option[Int] = + if (Configuration.getConfig.hasPath("pekko.http.server.default-http-port")) + Option(Configuration.getConfig.getInt("pekko.http.server.default-http-port")) + else + None + + override def host: String = domain + ":" + servicePortEncrypted.getOrElse(servicePortUnencrypted) //the url of your api, not swagger's json endpoint + override def info: com.github.swagger.pekko.model.Info = com.github.swagger.pekko.model.Info( description = "Note: Test the API with curl:

curl -sS -u <org>/iamapikey:<key> https://<host>:<port>/edge-exchange/v1/orgs/... | jq

This API specification is intended to be used by developers", @@ -128,12 +152,14 @@ object SwaggerDocService extends SwaggerHttpService { override def externalDocs: Option[ExternalDocumentation] = Option(new ExternalDocumentation().description("Open-horizon ExchangeAPI").url("https://github.com/open-horizon/exchange-api")) override def schemes: List[String] = - if (ExchangeApi.servicePortEncrypted.isDefined) + if (servicePortEncrypted.isDefined) List("http", "https") else List("http") //override val securitySchemeDefinitions = Map("basicAuth" -> new BasicAuthDefinition()) override def unwantedDefinitions: Seq[String] = Seq("Function1", "Function1RequestContextFutureRouteResult") + + } /* Defines a route (that can be used in a browser) to return the swagger.json file that is built by SwaggerDocService. diff --git a/src/main/scala/org/openhorizon/exchangeapi/auth/AuthCache.scala b/src/main/scala/org/openhorizon/exchangeapi/auth/AuthCache.scala index b395a26c..e01e34af 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/auth/AuthCache.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/auth/AuthCache.scala @@ -5,6 +5,7 @@ import com.google.common.cache import com.google.common.cache.CacheBuilder import org.openhorizon.exchangeapi.ExchangeApi import org.openhorizon.exchangeapi.auth.CacheIdType.CacheIdType +import org.openhorizon.exchangeapi.auth.cloud.IbmCloudAuth import org.openhorizon.exchangeapi.table.agreementbot.AgbotsTQ import org.openhorizon.exchangeapi.table.deploymentpattern.PatternsTQ import org.openhorizon.exchangeapi.table.deploymentpolicy.BusinessPoliciesTQ @@ -12,7 +13,7 @@ import org.openhorizon.exchangeapi.table.managementpolicy.ManagementPoliciesTQ import org.openhorizon.exchangeapi.table.node.NodesTQ import org.openhorizon.exchangeapi.table.service.ServicesTQ import org.openhorizon.exchangeapi.table.user.UsersTQ -import org.openhorizon.exchangeapi.utility.{ExchConfig, ExchMsg} +import org.openhorizon.exchangeapi.utility.{Configuration, ExchMsg} import scalacache._ import scalacache.guava.GuavaCache import scalacache.modes.try_._ @@ -27,8 +28,9 @@ import scala.util.{Failure, Success, Try} object AuthCache /* extends Control with ServletApiImplicits */ { def logger: LoggingAdapter = ExchangeApi.defaultLogger implicit def executionContext: ExecutionContext = ExchangeApi.defaultExecutionContext - - var cacheType = "" // set from the config file by ExchConfig.load(). Note: currently there is no other value besides guava + + // set from the config file by ExchConfig.load(). Note: currently there is no other value besides guava + val cacheType: String = Configuration.getConfig.getString("api.cache.type") // The unhashed and hashed values of the token are not always both set, but if they are they are in sync. final case class Tokens(unhashed: String, hashed: String) @@ -40,8 +42,8 @@ object AuthCache /* extends Control with ServletApiImplicits */ { case class CacheVal(hashedToken: String, unhashedToken: String = "", idType: CacheIdType = CacheIdType.None) private val guavaCache: cache.Cache[String, Entry[CacheVal]] = CacheBuilder.newBuilder() - .maximumSize(ExchConfig.getInt("api.cache.idsMaxSize")) - .expireAfterWrite(ExchConfig.getInt("api.cache.idsTtlSeconds"), TimeUnit.SECONDS) + .maximumSize(Configuration.getConfig.getInt("api.cache.idsMaxSize")) + .expireAfterWrite(Configuration.getConfig.getInt("api.cache.idsTtlSeconds"), TimeUnit.SECONDS) .build[String, Entry[CacheVal]] // the cache key is /, and the value is CacheVal implicit val userCache: GuavaCache[CacheVal] = GuavaCache(guavaCache) // needed so ScalaCache API can find it. Another effect of this is that these methods don't need to be qualified private var db: Database = _ @@ -102,7 +104,7 @@ object AuthCache /* extends Control with ServletApiImplicits */ { //val dbAction = NodesTQ.getToken(id).result val dbHashedTok: String = try { //logger.debug("awaiting for DB query of local exchange creds for "+id+"...") - val respVector: Seq[String] = Await.result(db.run(dbAction), Duration(ExchConfig.getInt("api.cache.authDbTimeoutSeconds"), SECONDS)) + val respVector: Seq[String] = Await.result(db.run(dbAction), Duration(Configuration.getConfig.getInt("api.cache.authDbTimeoutSeconds"), SECONDS)) //logger.debug("...back from awaiting for DB query of local exchange creds for "+id+".") if (respVector.nonEmpty) respVector.head else "" } catch { @@ -170,7 +172,7 @@ object AuthCache /* extends Control with ServletApiImplicits */ { private val guavaCache: cache.Cache[String, Entry[Boolean]] = CacheBuilder.newBuilder() .maximumSize(maxSize) - .expireAfterWrite(ExchConfig.getInt("api.cache.resourcesTtlSeconds"), TimeUnit.SECONDS) + .expireAfterWrite(Configuration.getConfig.getInt("api.cache.resourcesTtlSeconds"), TimeUnit.SECONDS) .build[String, Entry[Boolean]] // the cache key is org/id, and the value is admin priv or isPublic implicit val userCache: GuavaCache[Boolean] = GuavaCache(guavaCache) // needed so ScalaCache API can find it. Another effect of this is that these methods don't need to be qualified private var db: Database = _ @@ -194,7 +196,7 @@ object AuthCache /* extends Control with ServletApiImplicits */ { //val dbAction = UsersTQ.getAdmin(id).result try { //logger.debug("CacheBoolean:getId(): awaiting for DB query of local exchange bool value for "+id+"...") - val respVector: Seq[Boolean] = Await.result(db.run(getDbAction(id)), Duration(ExchConfig.getInt("api.cache.authDbTimeoutSeconds"), SECONDS)) + val respVector: Seq[Boolean] = Await.result(db.run(getDbAction(id)), Duration(Configuration.getConfig.getInt("api.cache.authDbTimeoutSeconds"), SECONDS)) //logger.debug("CacheBoolean:getId(): ...back from awaiting for DB query of local exchange bool value for "+id+".") if (respVector.nonEmpty) { val isValue: Boolean = respVector.head @@ -228,19 +230,19 @@ object AuthCache /* extends Control with ServletApiImplicits */ { } } // end of class CacheBoolean - class CacheAdmin() extends CacheBoolean("admin", ExchConfig.getInt("api.cache.resourcesMaxSize")) { + class CacheAdmin() extends CacheBoolean("admin", Configuration.getConfig.getInt("api.cache.resourcesMaxSize")) { def getDbAction(id: String): DBIO[Seq[Boolean]] = UsersTQ.getAdmin(id).result } - class CacheHubAdmin() extends CacheBoolean("hubadmin", ExchConfig.getInt("api.cache.resourcesMaxSize")) { + class CacheHubAdmin() extends CacheBoolean("hubadmin", Configuration.getConfig.getInt("api.cache.resourcesMaxSize")) { def getDbAction(id: String): DBIO[Seq[Boolean]] = UsersTQ.getHubAdmin(id).result } - class CachePublicService() extends CacheBoolean("public", ExchConfig.getInt("api.cache.resourcesMaxSize")) { + class CachePublicService() extends CacheBoolean("public", Configuration.getConfig.getInt("api.cache.resourcesMaxSize")) { def getDbAction(id: String): DBIO[Seq[Boolean]] = ServicesTQ.getPublic(id).result } - class CachePublicPattern() extends CacheBoolean("public", ExchConfig.getInt("api.cache.resourcesMaxSize")) { + class CachePublicPattern() extends CacheBoolean("public", Configuration.getConfig.getInt("api.cache.resourcesMaxSize")) { def getDbAction(id: String): DBIO[Seq[Boolean]] = PatternsTQ.getPublic(id).result } @@ -266,7 +268,7 @@ object AuthCache /* extends Control with ServletApiImplicits */ { private val guavaCache: cache.Cache[String, Entry[String]] = CacheBuilder.newBuilder() .maximumSize(maxSize) - .expireAfterWrite(ExchConfig.getInt("api.cache.resourcesTtlSeconds"), TimeUnit.SECONDS) + .expireAfterWrite(Configuration.getConfig.getInt("api.cache.resourcesTtlSeconds"), TimeUnit.SECONDS) .build[String, Entry[String]] // the cache key is org/id, and the value is the owner implicit val userCache: GuavaCache[String] = GuavaCache(guavaCache) // needed so ScalaCache API can find it. Another effect of this is that these methods don't need to be qualified private var db: Database = _ @@ -289,7 +291,7 @@ object AuthCache /* extends Control with ServletApiImplicits */ { logger.debug("CacheOwner:getId(): " + id + " was not in the cache, so attempting to get it from the db") try { //logger.debug("CacheOwner:getId(): awaiting for DB query of local exchange admin value for "+id+"...") - val respVector: Seq[String] = Await.result(db.run(getDbAction(id)), Duration(ExchConfig.getInt("api.cache.authDbTimeoutSeconds"), SECONDS)) + val respVector: Seq[String] = Await.result(db.run(getDbAction(id)), Duration(Configuration.getConfig.getInt("api.cache.authDbTimeoutSeconds"), SECONDS)) //logger.debug("CacheOwner:getId(): ...back from awaiting for DB query of local exchange admin value for "+id+".") if (respVector.nonEmpty) { val owner: String = respVector.head @@ -323,27 +325,27 @@ object AuthCache /* extends Control with ServletApiImplicits */ { } } // end of class CacheOwner - class CacheOwnerNode() extends CacheOwner(ExchConfig.getInt("api.cache.idsMaxSize")) { + class CacheOwnerNode() extends CacheOwner(Configuration.getConfig.getInt("api.cache.idsMaxSize")) { def getDbAction(id: String): DBIO[Seq[String]] = NodesTQ.getOwner(id).result } - class CacheOwnerAgbot() extends CacheOwner(ExchConfig.getInt("api.cache.resourcesMaxSize")) { + class CacheOwnerAgbot() extends CacheOwner(Configuration.getConfig.getInt("api.cache.resourcesMaxSize")) { def getDbAction(id: String): DBIO[Seq[String]] = AgbotsTQ.getOwner(id).result } - class CacheOwnerService() extends CacheOwner(ExchConfig.getInt("api.cache.resourcesMaxSize")) { + class CacheOwnerService() extends CacheOwner(Configuration.getConfig.getInt("api.cache.resourcesMaxSize")) { def getDbAction(id: String): DBIO[Seq[String]] = ServicesTQ.getOwner(id).result } - class CacheOwnerPattern() extends CacheOwner(ExchConfig.getInt("api.cache.resourcesMaxSize")) { + class CacheOwnerPattern() extends CacheOwner(Configuration.getConfig.getInt("api.cache.resourcesMaxSize")) { def getDbAction(id: String): DBIO[Seq[String]] = PatternsTQ.getOwner(id).result } - class CacheOwnerBusiness() extends CacheOwner(ExchConfig.getInt("api.cache.resourcesMaxSize")) { + class CacheOwnerBusiness() extends CacheOwner(Configuration.getConfig.getInt("api.cache.resourcesMaxSize")) { def getDbAction(id: String): DBIO[Seq[String]] = BusinessPoliciesTQ.getOwner(id).result } - class CacheOwnerManagementPolicy() extends CacheOwner(ExchConfig.getInt("api.cache.resourcesMaxSize")) { + class CacheOwnerManagementPolicy() extends CacheOwner(Configuration.getConfig.getInt("api.cache.resourcesMaxSize")) { def getDbAction(id: String): DBIO[Seq[String]] = ManagementPoliciesTQ.getOwner(id).result } @@ -536,7 +538,6 @@ object AuthCache /* extends Control with ServletApiImplicits */ { } def initAllCaches(db: Database, includingIbmAuth: Boolean): Unit = { - ExchConfig.createRoot(db) ids.init(db) usersAdmin.init(db) usersHubAdmin.init(db) @@ -565,4 +566,23 @@ object AuthCache /* extends Control with ServletApiImplicits */ { val patternsPublic = new CachePublicPattern() val businessPublic = new CachePublicBusiness() val managementPolicyPublic = new CachePublicManagementPolicy() + + // Put the root user in the auth cache in case the db has not been inited yet and they need to be able to run POST /admin/initdb + def createRootInCache(): Unit = { + val configRootPasswdHashed = { + try { + if(Configuration.getConfig.getBoolean("api.root.enabled")) + Password.hashIfNot(Configuration.getConfig.getString("api.root.password")) + else + "" + } + catch { + case _: Exception => "" + } + } + + putUser(Role.superUser, configRootPasswdHashed, "") + + logger.info("Root user from config.json added to the in-memory authentication cache") + } } diff --git a/src/main/scala/org/openhorizon/exchangeapi/auth/AuthenticationSupport.scala b/src/main/scala/org/openhorizon/exchangeapi/auth/AuthenticationSupport.scala index 39687d4c..3452dcd9 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/auth/AuthenticationSupport.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/auth/AuthenticationSupport.scala @@ -4,14 +4,14 @@ import org.apache.pekko.event.LoggingAdapter import org.apache.pekko.http.scaladsl.server.Directives._ import org.apache.pekko.http.scaladsl.server.{Directive, Directive0, Directive1, ValidationRejection} import org.openhorizon.exchangeapi.auth.Access.Access -import org.openhorizon.exchangeapi.utility.{AuthRejection, ExchConfig} +import org.openhorizon.exchangeapi.utility.{AuthRejection, Configuration} import org.openhorizon.exchangeapi.{ExchangeApi, ExchangeApiApp} import slick.jdbc.PostgresProfile.api._ import java.util import java.util.Base64 import javax.security.auth.Subject -import javax.security.auth.login.{AppConfigurationEntry, Configuration, LoginContext} +import javax.security.auth.login.{AppConfigurationEntry, LoginContext} import scala.concurrent.duration._ import scala.jdk.CollectionConverters.CollectionHasAsScala import scala.util._ @@ -55,7 +55,7 @@ object AuthenticationSupport { }; But i had trouble getting it loaded from the docker image that the sbt-native-packager builds. So just putting the config in our code for now. */ - val loginConfig: Configuration = new Configuration { + val loginConfig: javax.security.auth.login.Configuration = new javax.security.auth.login.Configuration { override def getAppConfigurationEntry(name: String): Array[AppConfigurationEntry] = { Array[AppConfigurationEntry]( new AppConfigurationEntry("org.openhorizon.exchangeapi.auth.IbmCloudModule", AppConfigurationEntry.LoginModuleControlFlag.SUFFICIENT, new util.HashMap[String, String]()), @@ -82,7 +82,7 @@ trait AuthenticationSupport extends AuthorizationSupport { // error 'Substream Source cannot be materialized more than once'. So if you use this directive, you'll have to use // parse(request.body).extract[PatchNodesRequest] yourself to unmarshal the request body. def extractRawBodyAsStr: Directive1[String] = { - extractStrictEntity(Duration(ExchConfig.getInt("api.cache.authDbTimeoutSeconds"), SECONDS)).flatMap { entity => + extractStrictEntity(Duration(Configuration.getConfig.getInt("api.cache.authDbTimeoutSeconds"), SECONDS)).flatMap { entity => provide(entity.data.utf8String) } } diff --git a/src/main/scala/org/openhorizon/exchangeapi/auth/Module.scala b/src/main/scala/org/openhorizon/exchangeapi/auth/Module.scala index 17e8b1be..7dc28d2a 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/auth/Module.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/auth/Module.scala @@ -2,7 +2,8 @@ package org.openhorizon.exchangeapi.auth import org.openhorizon.exchangeapi._ import org.openhorizon.exchangeapi.utility.ExchMsg -import org.openhorizon.exchangeapi.{ExchangeApi} +import org.openhorizon.exchangeapi.ExchangeApi +import org.openhorizon.exchangeapi.auth.cloud.IbmCloudAuth import javax.security.auth._ import javax.security.auth.callback._ diff --git a/src/main/scala/org/openhorizon/exchangeapi/auth/Password.scala b/src/main/scala/org/openhorizon/exchangeapi/auth/Password.scala index a8e84b7d..5091bd83 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/auth/Password.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/auth/Password.scala @@ -34,5 +34,5 @@ object Password { } /** If already hash, return it, otherwise hash it */ - def hashIfNot(password: String): String = if (isHashed(password)) password else hash(password) + def hashIfNot(password: String): String = if (password.isEmpty || isHashed(password)) password else hash(password) } diff --git a/src/main/scala/org/openhorizon/exchangeapi/auth/Role.scala b/src/main/scala/org/openhorizon/exchangeapi/auth/Role.scala index e8f0ea25..ede4b4f0 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/auth/Role.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/auth/Role.scala @@ -1,9 +1,13 @@ package org.openhorizon.exchangeapi.auth +import com.typesafe.config.ConfigValue import org.apache.pekko.event.LoggingAdapter import org.openhorizon.exchangeapi.auth.Access.Access +import org.openhorizon.exchangeapi.utility.Configuration import scala.collection.mutable.{HashMap => MutableHashMap} +import scala.jdk.CollectionConverters.{CollectionHasAsScala, MapHasAsScala} +import scala.language.postfixOps /** Who is allowed to do what. */ object Role { @@ -11,9 +15,6 @@ object Role { type AccessList = Set[String] //case class AccessList extends Set[String] - // Making the roles and their ACLs a map, so it is more flexible at runtime - val roles = new MutableHashMap[String, AccessList]() - // Sets the access list to the specified role def setRole(role: String, accessValues: AccessList): Option[AccessList] = roles.put(role, accessValues) @@ -22,6 +23,16 @@ object Role { def haveRequiredRoles: Boolean = roles.keySet == AuthRoles.requiredRoles // will return true even if the elements are in a different order val allAccessValues: Set[String] = Access.values.map(_.toString) + + // Making the roles and their ACLs a map, so it is more flexible at runtime + var roles: MutableHashMap[String, AccessList] = { + val roles: MutableHashMap[String, AccessList] = loadRoles() + + if (!AuthRoles.requiredRoles.subsetOf(roles.keySet)) + println("Error: at least these roles must be set in the config file: " + AuthRoles.requiredRoles.mkString(", ")) + + roles + } // Returns true if the specified access string is valid. Used to check input from config.json. def isValidAcessValues(accessValues: AccessList): Boolean = { @@ -42,6 +53,22 @@ object Role { false } } + + def loadRoles(): MutableHashMap[String, AccessList] = { + // Read the ACLs and set them in our Role object + val something = new MutableHashMap[String, AccessList] + for ((role, _) <- Configuration.getConfig.getObject("api.acls").asScala.toMap) { + val accessSet: Set[String] = Configuration.getConfig.getStringList("api.acls." + role).asScala.toSet + if (!isValidAcessValues(accessSet)) + println("Error: invalid value in ACLs in config file for role " + role) + else + something.put(role, accessSet) + } + + println(s"Roles: ${something}") + + something + } def superUser = "root/root" def isSuperUser(username: String): Boolean = username == superUser // only checks the username, does not verify the pw diff --git a/src/main/scala/org/openhorizon/exchangeapi/auth/IbmCloudModule.scala b/src/main/scala/org/openhorizon/exchangeapi/auth/cloud/IBM.scala similarity index 94% rename from src/main/scala/org/openhorizon/exchangeapi/auth/IbmCloudModule.scala rename to src/main/scala/org/openhorizon/exchangeapi/auth/cloud/IBM.scala index 5c119c85..9cb17ac6 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/auth/IbmCloudModule.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/auth/cloud/IBM.scala @@ -1,36 +1,30 @@ -package org.openhorizon.exchangeapi.auth +package org.openhorizon.exchangeapi.auth.cloud -import org.apache.pekko.event.LoggingAdapter import com.google.common.cache - -import java.io.{BufferedInputStream, File, FileInputStream} -import java.security.cert.{Certificate, CertificateFactory} -import java.util.concurrent.TimeUnit -import java.security.KeyStore import com.google.common.cache.CacheBuilder -import org.openhorizon.exchangeapi._ - -import javax.net.ssl.{SSLContext, SSLSocketFactory, TrustManagerFactory} -import javax.security.auth._ -import javax.security.auth.callback._ -import javax.security.auth.spi.LoginModule +import org.apache.pekko.event.LoggingAdapter import org.json4s._ import org.json4s.jackson.JsonMethods._ +import org.openhorizon.exchangeapi.ExchangeApi +import org.openhorizon.exchangeapi.auth._ import org.openhorizon.exchangeapi.table.organization.OrgsTQ import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ExchConfig, ExchMsg, HttpCode} -import org.openhorizon.exchangeapi.{ExchangeApi} - -import scala.concurrent.ExecutionContext +import org.openhorizon.exchangeapi.utility.{ApiTime, Configuration, ExchMsg, HttpCode} import scalacache._ import scalacache.guava.GuavaCache import scalacache.modes.try_._ import scalaj.http._ -import slick.dbio.Effect -import slick.sql.SqlAction +import java.io.{BufferedInputStream, File, FileInputStream} +import java.security.KeyStore +import java.security.cert.{Certificate, CertificateFactory} +import java.util.concurrent.TimeUnit +import javax.net.ssl.{SSLContext, SSLSocketFactory, TrustManagerFactory} +import javax.security.auth._ +import javax.security.auth.callback._ +import javax.security.auth.spi.LoginModule import scala.collection.mutable.ListBuffer -import scala.concurrent.Await +import scala.concurrent.{Await, ExecutionContext} import scala.concurrent.duration._ import scala.util.matching.Regex import scala.util.{Failure, Success, Try} @@ -177,15 +171,36 @@ object IbmCloudAuth { def logger: LoggingAdapter = ExchangeApi.defaultLogger private val guavaCache: cache.Cache[String, Entry[String]] = CacheBuilder.newBuilder() - .maximumSize(ExchConfig.getInt("api.cache.IAMusersMaxSize")) - .expireAfterWrite(ExchConfig.getInt("api.cache.IAMusersTtlSeconds"), TimeUnit.SECONDS) + .maximumSize(Configuration.getConfig.getInt("api.cache.IAMusersMaxSize")) + .expireAfterWrite(Configuration.getConfig.getInt("api.cache.IAMusersTtlSeconds"), TimeUnit.SECONDS) .build[String, Entry[String]] // the cache key is /: (where keytype is iamapikey or iamtoken), and the value is / implicit val userCache: GuavaCache[String] = GuavaCache(guavaCache) // the effect of this is that these methods don't need to be qualified + + def getICPExternalMgmtIngress: String = + try + Configuration.getConfig.getString("ibm.common-services.external-management-ingress") + catch { + case _: Exception => "" + } + + def getICPManagementIngressServicePort: String = + try + Configuration.getConfig.getString("ibm.common-services.management-ingress-service-port") + catch { + case _: Exception => "8443" + } + + def getPlatformIdentityProviderServicePort: String = + try + Configuration.getConfig.getString("ibm.common-services.identity-provider-service-port") + catch { + case _: Exception => "4300" + } // Called by ExchangeApiApp after db is established and upgraded def init(db: Database): Unit = { this.db = db - logger.info(s"IBM authentication-related env vars: PLATFORM_IDENTITY_PROVIDER_SERVICE_PORT=${sys.env.get("PLATFORM_IDENTITY_PROVIDER_SERVICE_PORT")}, ICP_EXTERNAL_MGMT_INGRESS=${sys.env.get("ICP_EXTERNAL_MGMT_INGRESS")}, ICP_MANAGEMENT_INGRESS_SERVICE_PORT=${sys.env.get("ICP_MANAGEMENT_INGRESS_SERVICE_PORT")}") + logger.debug(s"IBM authentication-related env vars: PLATFORM_IDENTITY_PROVIDER_SERVICE_PORT=$getPlatformIdentityProviderServicePort, ICP_EXTERNAL_MGMT_INGRESS=$getICPExternalMgmtIngress, ICP_MANAGEMENT_INGRESS_SERVICE_PORT=$getICPManagementIngressServicePort") if (isIcp) { this.sslSocketFactory = buildSslSocketFactory(getIcpCertFile) getIcpClusterName // this caches the result in member var icpClusterNameTry @@ -223,44 +238,37 @@ object IbmCloudAuth { removeAll().map(_ => ()) // i think this map() just transforms the removeAll() return of a future into Unit } - // Note: we need these 2 methods because if the env var is set to "", sys.env.get() will return Some("") instead of None - private def isEnvSet(envVarName: String): Boolean = sys.env.get(envVarName) match { - case Some(v) => v != "" - case None => false - } - private def getEnv(envVarName: String, defaultVal: String): String = sys.env.get(envVarName) match { - case Some(v) => if (v == "") defaultVal else v - case None => defaultVal - } - private def isIcp: Boolean = { // ICP kube automatically sets the 1st one, our development environment sets the 2nd one when locally testing //logger.debug("isIcp: ICP_EXTERNAL_MGMT_INGRESS: " + sys.env.get("ICP_EXTERNAL_MGMT_INGRESS")) - isEnvSet("PLATFORM_IDENTITY_PROVIDER_SERVICE_PORT") || isEnvSet("ICP_EXTERNAL_MGMT_INGRESS") + (Configuration.getConfig.hasPath("ibm.cloud.platform-identity-provider-service-port") || + Configuration.getConfig.hasPath("ibm.cloud.icp-external-management-ingress")) } + // Common Services IAM private def getIcpIdentityProviderUrl: String = { // https://$ICP_EXTERNAL_MGMT_INGRESS/idprovider or https://platform-identity-provider:$PLATFORM_IDENTITY_PROVIDER_SERVICE_PORT - if (isEnvSet("ICP_EXTERNAL_MGMT_INGRESS")) { - var ICP_EXTERNAL_MGMT_INGRESS: String = getEnv("ICP_EXTERNAL_MGMT_INGRESS", "") + if (Configuration.getConfig.hasPath("ibm.cloud.icp-external-management-ingress")) { + var ICP_EXTERNAL_MGMT_INGRESS: String = getICPExternalMgmtIngress if (!ICP_EXTERNAL_MGMT_INGRESS.startsWith("https://")) ICP_EXTERNAL_MGMT_INGRESS = s"https://$ICP_EXTERNAL_MGMT_INGRESS" s"$ICP_EXTERNAL_MGMT_INGRESS/idprovider" } else { // ICP kube automatically sets this env var and hostname - val PLATFORM_IDENTITY_PROVIDER_SERVICE_PORT: String = getEnv("PLATFORM_IDENTITY_PROVIDER_SERVICE_PORT", "4300") + val PLATFORM_IDENTITY_PROVIDER_SERVICE_PORT: String = getPlatformIdentityProviderServicePort s"https://platform-identity-provider:$PLATFORM_IDENTITY_PROVIDER_SERVICE_PORT" } } + // private def getIcpMgmtIngressUrl: String = { // https://$ICP_EXTERNAL_MGMT_INGRESS or https://icp-management-ingress.kube-system:$ICP_MANAGEMENT_INGRESS_SERVICE_PORT - if (isEnvSet("ICP_EXTERNAL_MGMT_INGRESS")) { - var ICP_EXTERNAL_MGMT_INGRESS: String = getEnv("ICP_EXTERNAL_MGMT_INGRESS", "") + if (Configuration.getConfig.hasPath("ibm.cloud.icp-external-management-ingress")) { + var ICP_EXTERNAL_MGMT_INGRESS: String = getICPExternalMgmtIngress if (!ICP_EXTERNAL_MGMT_INGRESS.startsWith("https://")) ICP_EXTERNAL_MGMT_INGRESS = s"https://$ICP_EXTERNAL_MGMT_INGRESS" ICP_EXTERNAL_MGMT_INGRESS } else { // ICP kube automatically sets this env var and hostname - val ICP_MANAGEMENT_INGRESS_SERVICE_PORT: String = getEnv("ICP_MANAGEMENT_INGRESS_SERVICE_PORT", "8443") + val ICP_MANAGEMENT_INGRESS_SERVICE_PORT: String = getICPManagementIngressServicePort s"https://icp-management-ingress.kube-system:$ICP_MANAGEMENT_INGRESS_SERVICE_PORT" } } @@ -292,6 +300,7 @@ object IbmCloudAuth { } // Internal method called from getIcpClusterName + // Common Services private def _getIcpClusterName: Try[String] = { for (i <- 1 to iamRetryNum) { try { @@ -348,7 +357,7 @@ object IbmCloudAuth { // Using the IAM token get the ibm cloud account id (which we'll use to verify the exchange org) and users email (which we'll use as the exchange user) // For ICP IAM see: https://github.ibm.com/IBMPrivateCloud/roadmap/blob/master/feature-specs/security/security-services-apis.md private def getUserInfo(token: IamToken, authInfo: IamAuthCredentials): Try[IamUserInfo] = { - if (isIcp && token.tokenType.getOrElse("") == "iamapikey") { + if (isIcp && token.tokenType.getOrElse("") == "iamapikey") { // Common Services IAM // An icp platform apikey that we can use directly to authenticate and get the username var delayedReturn: Try[IamUserInfo] = Failure(new IamApiTimeoutException(ExchMsg.translate("iam.return.value.not.set", "GET introspect", iamRetryNum))) for (i <- 1 to iamRetryNum) { @@ -373,7 +382,7 @@ object IbmCloudAuth { } } delayedReturn // if we tried the max times and never got a successful positive or negative, return what we last got - } else if (isIcp) { + } else if (isIcp) { // Common Services Identity Provider // An icp token from the UI var delayedReturn: Try[IamUserInfo] = Failure(new IamApiTimeoutException(ExchMsg.translate("iam.return.value.not.set", "GET userinfo", iamRetryNum))) for (i <- 1 to iamRetryNum) { @@ -422,6 +431,7 @@ object IbmCloudAuth { } } + // Common Services def getOIDCToken(icpapikey: IamToken) : Try[IamToken] = { /* curl -k -X POST -H "Content-Type: application/x-www-form-urlencoded" -H "Accept: application/json" @@ -441,6 +451,7 @@ object IbmCloudAuth { } else Failure(new InvalidCredentialsException(ExchMsg.translate("invalid.iam.token"))) } + // Common Services def getUserAccounts(token: IamToken, userInfo: IamUserInfo) : Try[List[IamAccountInfo]] = { if (isIcp){ /* @@ -499,7 +510,7 @@ object IbmCloudAuth { //logger.debug("awaiting for DB query of ibm cloud creds for "+authInfo.org+"/"+userInfo.user+"...") if (hint.getOrElse("") == "exchangeNoOrgForMultLogin") { Success(UserRow(userInfo.user, "", "", admin = false, hubAdmin = false, "", "", "")) - } else Await.result(db.run(userQuery.transactionally), Duration(ExchConfig.getInt("api.cache.authDbTimeoutSeconds"), SECONDS)) + } else Await.result(db.run(userQuery.transactionally), Duration(Configuration.getConfig.getInt("api.cache.authDbTimeoutSeconds"), SECONDS)) //logger.debug("...back from awaiting for DB query of ibm cloud creds for "+authInfo.org+"/"+userInfo.user+".") } catch { // Handle any exceptions, including db problems. Note: exceptions from this get caught in login() above diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/administration/Configuration.scala b/src/main/scala/org/openhorizon/exchangeapi/route/administration/Configuration.scala index fd1e43fb..a1f2d009 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/administration/Configuration.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/administration/Configuration.scala @@ -6,7 +6,7 @@ import org.apache.pekko.http.scaladsl.server.Directives._ import org.apache.pekko.http.scaladsl.server.Route import com.github.pjfanning.pekkohttpjackson.JacksonSupport import org.openhorizon.exchangeapi.auth.{Access, AuthenticationSupport, TAction} -import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ExchConfig, ExchMsg, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, Configuration, ExchMsg, HttpCode} import slick.jdbc.PostgresProfile.api._ import java.util.Properties @@ -29,7 +29,7 @@ trait Configuration extends JacksonSupport with AuthenticationSupport { complete({ val props = new Properties() props.setProperty(reqBody.varPath, reqBody.value) - ExchConfig.mod(props) + Configuration.reload(properties = props) (HttpCode.PUT_OK, ApiResponse(ApiRespType.OK, ExchMsg.translate("config.value.set"))) }) // end of complete } diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/administration/InitializeDatabase.scala b/src/main/scala/org/openhorizon/exchangeapi/route/administration/InitializeDatabase.scala index dab96f8f..4d7b0940 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/administration/InitializeDatabase.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/administration/InitializeDatabase.scala @@ -11,7 +11,7 @@ import jakarta.ws.rs.{POST, Path} import org.checkerframework.checker.units.qual.t import org.openhorizon.exchangeapi.auth.{Access, AuthCache, AuthenticationSupport, TAction} import org.openhorizon.exchangeapi.table.ExchangeApiTables -import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ExchConfig, ExchMsg, ExchangePosgtresErrorHandling, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ExchMsg, ExchangePosgtresErrorHandling, HttpCode} import slick.jdbc.PostgresProfile.api._ import scala.concurrent.ExecutionContext @@ -40,13 +40,12 @@ trait InitializeDatabase extends JacksonSupport with AuthenticationSupport{ new responses.ApiResponse(responseCode = "403", description = "access denied"))) def postInitializeDB: Route = { logger.debug("Doing POST /admin/initdb") - ExchConfig.createRootInCache() // need to do this before authenticating, because dropdb cleared it out (can not do this in dropdb, because it might expire) complete ({ db.run(ExchangeApiTables.initDB.transactionally.asTry) .map({ case Success(v) => logger.debug(s"POST /admin/initdb result: $v") - ExchConfig.createRoot(db) // initialize the users table with the root user from config.json + ExchangeApiTables.upgradeDb(db = db)(logger = logger, executionContext = executionContext) // initialize the users table with the root user from config.json (HttpCode.POST_OK, ApiResponse(ApiRespType.OK, ExchMsg.translate("db.init"))) case Failure(t: org.postgresql.util.PSQLException) => ExchangePosgtresErrorHandling.ioProblemError(t, ExchMsg.translate("db.not.init", t.toString)) @@ -59,6 +58,7 @@ trait InitializeDatabase extends JacksonSupport with AuthenticationSupport{ val initializeDB: Route = path("admin" / "initdb") { post { + AuthCache.createRootInCache() // need to do this before authenticating, because dropdb cleared it out (can not do this in dropdb, because it might expire) exchAuth(TAction(), Access.ADMIN, hint = "token") { _ => postInitializeDB diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/administration/Reload.scala b/src/main/scala/org/openhorizon/exchangeapi/route/administration/Reload.scala index 7f962c79..cf25be4b 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/administration/Reload.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/administration/Reload.scala @@ -12,7 +12,7 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody import jakarta.ws.rs.{OPTIONS, POST, Path} import org.openhorizon.exchangeapi.auth.{Access, AuthCache, AuthenticationSupport, Password, Role, TAction} import org.openhorizon.exchangeapi.table.ExchangeApiTables -import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ExchConfig, ExchMsg, ExchangePosgtresErrorHandling, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, Configuration, ExchMsg, ExchangePosgtresErrorHandling, HttpCode} import slick.jdbc.PostgresProfile.api._ import java.util.Properties @@ -47,7 +47,7 @@ trait Reload extends JacksonSupport with AuthenticationSupport { def postReload: Route = { logger.debug("Doing POST /admin/reload") complete({ - ExchConfig.reload() + Configuration.reload() (HttpCode.POST_OK, ApiResponse(ApiRespType.OK, ExchMsg.translate("reload.successful"))) }) // end of complete } diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/agreementbot/Agreement.scala b/src/main/scala/org/openhorizon/exchangeapi/route/agreementbot/Agreement.scala index 4b50aae6..73b75ed9 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/agreementbot/Agreement.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/agreementbot/Agreement.scala @@ -15,7 +15,7 @@ import org.openhorizon.exchangeapi.auth.{Access, AuthenticationSupport, Composit import org.openhorizon.exchangeapi.table.agreementbot.agreement.{AgbotAgreement, AgbotAgreementsTQ} import org.openhorizon.exchangeapi.table.resourcechange import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChange} -import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ExchConfig, ExchMsg, ExchangePosgtresErrorHandling, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, Configuration, ExchMsg, ExchangePosgtresErrorHandling, HttpCode} import org.openhorizon.exchangeapi.table import slick.jdbc.PostgresProfile.api._ @@ -185,7 +185,7 @@ trait Agreement extends JacksonSupport with AuthenticationSupport { reqBody => validateWithMsg(reqBody.getAnyProblem) { complete({ - val maxAgreements: Int = ExchConfig.getInt("api.limits.maxAgreements") + val maxAgreements: Int = Configuration.getConfig.getInt("api.limits.maxAgreements") val getNumOwnedDbio = if (maxAgreements == 0) DBIO.successful(0) diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/agreementbot/AgreementBot.scala b/src/main/scala/org/openhorizon/exchangeapi/route/agreementbot/AgreementBot.scala index f299155e..889fa09b 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/agreementbot/AgreementBot.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/agreementbot/AgreementBot.scala @@ -15,7 +15,7 @@ import org.openhorizon.exchangeapi.auth.{Access, AuthCache, AuthenticationSuppor import org.openhorizon.exchangeapi.table.agreementbot.{Agbot, AgbotsTQ} import org.openhorizon.exchangeapi.table.resourcechange import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChange} -import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ExchConfig, ExchMsg, ExchangePosgtresErrorHandling, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, Configuration, ExchMsg, ExchangePosgtresErrorHandling, HttpCode} import org.openhorizon.exchangeapi.table import slick.jdbc.PostgresProfile import slick.jdbc.PostgresProfile.api._ @@ -158,7 +158,7 @@ trait AgreementBot extends JacksonSupport with AuthenticationSupport { xs => logger.debug("PUT /orgs/" + organization + "/agbots/" + agreementBot + " num owned: " + xs) val numOwned: Int = xs - val maxAgbots: Int = ExchConfig.getInt("api.limits.maxAgbots") + val maxAgbots: Int = Configuration.getConfig.getInt("api.limits.maxAgbots") if (maxAgbots == 0 || numOwned <= maxAgbots || owner == "") { // when owner=="" we know it is only an update, otherwise we are not sure, but if they are already over the limit, stop them anyway diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/agreementbot/Messages.scala b/src/main/scala/org/openhorizon/exchangeapi/route/agreementbot/Messages.scala index 62173513..9fe6919d 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/agreementbot/Messages.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/agreementbot/Messages.scala @@ -17,7 +17,7 @@ import org.openhorizon.exchangeapi.table.agreementbot.message.{AgbotMsg, AgbotMs import org.openhorizon.exchangeapi.table.node.NodesTQ import org.openhorizon.exchangeapi.table.resourcechange import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChange} -import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, ExchConfig, ExchMsg, ExchangePosgtresErrorHandling, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, Configuration, ExchMsg, ExchangePosgtresErrorHandling, HttpCode} import org.openhorizon.exchangeapi.table import slick.jdbc.PostgresProfile.api._ @@ -124,7 +124,7 @@ trait Messages extends JacksonSupport with AuthenticationSupport { complete({ val nodeId: String = identity.creds.id //somday: handle the case where the acls allow users to send msgs var msgNum = "" - val maxMessagesInMailbox: Int = ExchConfig.getInt("api.limits.maxMessagesInMailbox") + val maxMessagesInMailbox: Int = Configuration.getConfig.getInt("api.limits.maxMessagesInMailbox") val getNumOwnedDbio = if (maxMessagesInMailbox == 0) DBIO.successful(0) diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/DeploymentPattern.scala b/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/DeploymentPattern.scala index 03071132..37414b0b 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/DeploymentPattern.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/DeploymentPattern.scala @@ -15,7 +15,7 @@ import org.openhorizon.exchangeapi.auth.{Access, AuthCache, AuthenticationSuppor import org.openhorizon.exchangeapi.table.deploymentpattern.{Pattern, PatternsTQ} import org.openhorizon.exchangeapi.table.organization.OrgsTQ import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChange} -import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ExchConfig, ExchMsg, ExchangePosgtresErrorHandling, HttpCode, Nth} +import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, Configuration, ExchMsg, ExchangePosgtresErrorHandling, HttpCode, Nth} import slick.dbio.DBIO import slick.jdbc.PostgresProfile.api._ @@ -582,7 +582,7 @@ trait DeploymentPattern extends JacksonSupport with AuthenticationSupport { case Success(num) => logger.debug("POST /orgs/" + organization + "/patterns" + deploymentPattern + " num owned by " + owner + ": " + num) val numOwned: Int = num - val maxPatterns: Int = ExchConfig.getInt("api.limits.maxPatterns") + val maxPatterns: Int = Configuration.getConfig.getInt("api.limits.maxPatterns") if (maxPatterns == 0 || numOwned <= maxPatterns) { // we are not sure if this is a create or update, but if they are already over the limit, stop them anyway reqBody.toPatternRow(resource, organization, owner).insert.asTry } @@ -812,7 +812,7 @@ trait DeploymentPattern extends JacksonSupport with AuthenticationSupport { } val deploymentPattern: Route = - path("orgs" / Segment / "patterns" / Segment) { + path("orgs" / Segment / ("patterns" | "deployment" ~ Slash ~ "patterns") / Segment) { (organization, deploymentPattern) => val resource: String = OrgAndId(organization, deploymentPattern).toString diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/DeploymentPatterns.scala b/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/DeploymentPatterns.scala index b03686bd..bffc2aea 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/DeploymentPatterns.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/DeploymentPatterns.scala @@ -24,7 +24,7 @@ import org.openhorizon.exchangeapi.table.node.agreement.NodeAgreementsTQ import org.openhorizon.exchangeapi.table.node.{NodeType, NodesTQ} import org.openhorizon.exchangeapi.table.organization.OrgsTQ import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChange} -import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, ExchConfig, ExchMsg, ExchangePosgtresErrorHandling, HttpCode, Nth, RouteUtils, Version} +import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, ExchMsg, ExchangePosgtresErrorHandling, HttpCode, Nth, RouteUtils, Version} import org.openhorizon.exchangeapi.auth import slick.jdbc.PostgresProfile.api._ @@ -168,7 +168,7 @@ trait DeploymentPatterns extends JacksonSupport with AuthenticationSupport { } val deploymentPatterns: Route = - path("orgs" / Segment / "patterns") { + path("orgs" / Segment / ("patterns" | "deployment" ~ Slash ~ "patterns")) { organization => get { exchAuth(TPattern(OrgAndId(organization, "*").toString), Access.READ) { diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/NodeHealth.scala b/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/NodeHealth.scala index 5bce8df5..8c951c9b 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/NodeHealth.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/NodeHealth.scala @@ -135,7 +135,7 @@ trait NodeHealth extends JacksonSupport with AuthenticationSupport { } val nodeHealthDeploymentPattern: Route = - path("orgs" / Segment / "patterns" / Segment / "nodehealth") { + path("orgs" / Segment / ("patterns" | "deployment" ~ Slash ~ "patterns") / Segment / "nodehealth") { (organization, deploymentPattern) => post { diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/PostPutPatternRequest.scala b/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/PostPutPatternRequest.scala index 819defcc..ec397c32 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/PostPutPatternRequest.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/PostPutPatternRequest.scala @@ -4,7 +4,7 @@ import org.json4s.jackson.Serialization.write import org.json4s.{DefaultFormats, Formats} import org.openhorizon.exchangeapi.table.deploymentpattern.{OneSecretBindingService, OneUserInputService, PServices, PatternRow, PatternsTQ} import org.openhorizon.exchangeapi.table.service.ServiceRef2 -import org.openhorizon.exchangeapi.utility.{ApiTime, ExchConfig, ExchMsg} +import org.openhorizon.exchangeapi.utility.{ApiTime, Configuration, ExchMsg} import slick.dbio.DBIO import slick.jdbc.PostgresProfile.api._ @@ -30,9 +30,9 @@ final case class PostPutPatternRequest(label: String, // Note: write() handles correctly the case where the optional fields are None. def toPatternRow(pattern: String, orgid: String, owner: String): PatternRow = { // The nodeHealth field is optional, so fill in a default in each element of services if not specified. (Otherwise json4s will omit it in the DB and the GETs.) - val agrChkDefault: Int = ExchConfig.getInt("api.defaults.pattern.check_agreement_status") + val agrChkDefault: Int = Configuration.getConfig.getInt("api.defaults.pattern.check_agreement_status") val agreementProtocols2: Option[List[Map[String, String]]] = agreementProtocols.orElse(Option(List(Map("name" -> "Basic")))) - val hbDefault: Int = ExchConfig.getInt("api.defaults.pattern.missing_heartbeat_interval") + val hbDefault: Int = Configuration.getConfig.getInt("api.defaults.pattern.missing_heartbeat_interval") val services2: Seq[PServices] = if (services.nonEmpty) { services.map({ diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/Search.scala b/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/Search.scala index 024495e1..38fcb05c 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/Search.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/Search.scala @@ -214,7 +214,7 @@ trait Search extends JacksonSupport with AuthenticationSupport { } val searchNode: Route = - path("orgs" / Segment / "patterns" / Segment / "search") { + path("orgs" / Segment / ("patterns" | "deployment" ~ Slash ~ "patterns") / Segment / "search") { (organization, deploymentPattern) => val resource: String = OrgAndId(organization, deploymentPattern).toString diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/key/Key.scala b/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/key/Key.scala index 8a358de9..1cc21a06 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/key/Key.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/key/Key.scala @@ -186,7 +186,7 @@ trait Key extends JacksonSupport with AuthenticationSupport { } val keyDeploymentPattern: Route = - path("orgs" / Segment / "patterns" / Segment / "keys" / Segment) { + path("orgs" / Segment / ("patterns" | "deployment" ~ Slash ~ "patterns") / Segment / "keys" / Segment) { (organization, deploymentPattern, key) => diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/key/Keys.scala b/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/key/Keys.scala index 4ab3092b..adfe678c 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/key/Keys.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpattern/key/Keys.scala @@ -111,7 +111,7 @@ trait Keys extends JacksonSupport with AuthenticationSupport { } val keysDeploymentPattern: Route = - path("orgs" / Segment / "patterns" / Segment / "keys") { + path("orgs" / Segment / ("patterns" | "deployment" ~ Slash ~ "patterns") / Segment / "keys") { (organization, deploymentPattern) => val resource: String = OrgAndId(organization, deploymentPattern).toString diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpolicy/DeploymentPolicies.scala b/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpolicy/DeploymentPolicies.scala index 9fee1968..6fb799dc 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpolicy/DeploymentPolicies.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpolicy/DeploymentPolicies.scala @@ -124,7 +124,7 @@ trait DeploymentPolicies extends JacksonSupport with AuthenticationSupport { } val deploymentPolicies: Route = - path("orgs" / Segment / "business" / "policies") { + path("orgs" / Segment / ("business" | "deployment") / "policies") { organization => get { exchAuth(TBusiness(OrgAndId(organization, "*").toString), Access.READ) { diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpolicy/DeploymentPolicy.scala b/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpolicy/DeploymentPolicy.scala index 4dc3dd8e..da52a0e6 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpolicy/DeploymentPolicy.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpolicy/DeploymentPolicy.scala @@ -21,7 +21,7 @@ import org.openhorizon.exchangeapi.table.deploymentpolicy.{BService, BusinessPol import org.openhorizon.exchangeapi.table.node.agreement.NodeAgreementsTQ import org.openhorizon.exchangeapi.table.node.{NodeType, NodesTQ} import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChange} -import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, ExchConfig, ExchMsg, ExchangePosgtresErrorHandling, HttpCode, Nth, Version} +import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, Configuration, ExchMsg, ExchangePosgtresErrorHandling, HttpCode, Nth, Version} import slick.jdbc.PostgresProfile.api._ import scala.collection.immutable._ @@ -486,7 +486,7 @@ trait DeploymentPolicy extends JacksonSupport with AuthenticationSupport { case Success(num) => logger.debug("POST /orgs/" + organization + "/business/policies" + deploymentPolicy + " num owned by " + owner + ": " + num) val numOwned: Int = num - val maxBusinessPolicies: Int = ExchConfig.getInt("api.limits.maxBusinessPolicies") + val maxBusinessPolicies: Int = Configuration.getConfig.getInt("api.limits.maxBusinessPolicies") if (maxBusinessPolicies == 0 || numOwned <= maxBusinessPolicies) { // we are not sure if this is a create or update, but if they are already over the limit, stop them anyway reqBody.getDbInsert(resource, organization, owner).asTry } @@ -669,7 +669,7 @@ trait DeploymentPolicy extends JacksonSupport with AuthenticationSupport { val deploymentPolicy: Route = - path("orgs" / Segment / "business" / "policies" / Segment) { + path("orgs" / Segment / ("business" | "deployment") / "policies" / Segment) { (organization, deploymentPolicy) => val resource: String = OrgAndId(organization, deploymentPolicy).toString diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpolicy/DeploymentPolicySearch.scala b/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpolicy/DeploymentPolicySearch.scala index d17a1523..5cffbcc0 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpolicy/DeploymentPolicySearch.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpolicy/DeploymentPolicySearch.scala @@ -305,7 +305,7 @@ trait DeploymentPolicySearch extends JacksonSupport with AuthenticationSupport { }) def deploymentPolicySearch: Route = - path("orgs" / Segment / "business" / "policies" / Segment / "search") { + path("orgs" / Segment / ("business" | "deployment") / "policies" / Segment / "search") { (organization, policy) => post { diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpolicy/PostPutBusinessPolicyRequest.scala b/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpolicy/PostPutBusinessPolicyRequest.scala index e5a4d6ae..0eca3acd 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpolicy/PostPutBusinessPolicyRequest.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/deploymentpolicy/PostPutBusinessPolicyRequest.scala @@ -5,7 +5,7 @@ import org.json4s.{DefaultFormats, Formats} import org.openhorizon.exchangeapi.table.deploymentpolicy.{BService, BusinessPoliciesTQ, BusinessPolicyRow} import org.openhorizon.exchangeapi.table.service.{OneProperty, ServiceRef2} import org.openhorizon.exchangeapi.table.deploymentpattern.{OneSecretBindingService, OneUserInputService} -import org.openhorizon.exchangeapi.utility.{ApiTime, ExchConfig} +import org.openhorizon.exchangeapi.utility.{ApiTime, Configuration} import slick.dbio.DBIO import slick.jdbc.PostgresProfile.api._ @@ -32,8 +32,8 @@ final case class PostPutBusinessPolicyRequest(label: String, // The nodeHealth field is optional, so fill in a default in service if not specified. (Otherwise json4s will omit it in the DB and the GETs.) def defaultNodeHealth(service: BService): BService = { if (service.nodeHealth.nonEmpty) return service - val agrChkDefault: Int = ExchConfig.getInt("api.defaults.businessPolicy.check_agreement_status") - val hbDefault: Int = ExchConfig.getInt("api.defaults.businessPolicy.missing_heartbeat_interval") + val agrChkDefault: Int = Configuration.getConfig.getInt("api.defaults.businessPolicy.check_agreement_status") + val hbDefault: Int = Configuration.getConfig.getInt("api.defaults.businessPolicy.missing_heartbeat_interval") val nodeHealth2: Option[Map[String, Int]] = Option(Map("missing_heartbeat_interval" -> hbDefault, "check_agreement_status" -> agrChkDefault)) // provide defaults for node health BService(arch = service.arch, diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/managementpolicy/ManagementPolicies.scala b/src/main/scala/org/openhorizon/exchangeapi/route/managementpolicy/ManagementPolicies.scala index 8549d914..5dc36abe 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/managementpolicy/ManagementPolicies.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/managementpolicy/ManagementPolicies.scala @@ -19,7 +19,7 @@ import org.openhorizon.exchangeapi.auth.{Access, AccessDeniedException, AuthCach import org.openhorizon.exchangeapi.table.managementpolicy.{ManagementPoliciesTQ, ManagementPolicy} import org.openhorizon.exchangeapi.table._ import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChange} -import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, ExchConfig, ExchMsg, ExchangePosgtresErrorHandling, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, ExchMsg, ExchangePosgtresErrorHandling, HttpCode} import slick.jdbc.PostgresProfile.api._ import java.nio.file.AccessDeniedException diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/managementpolicy/ManagementPolicy.scala b/src/main/scala/org/openhorizon/exchangeapi/route/managementpolicy/ManagementPolicy.scala index 6685aa17..4c5aea72 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/managementpolicy/ManagementPolicy.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/managementpolicy/ManagementPolicy.scala @@ -14,7 +14,7 @@ import org.apache.pekko.http.scaladsl.server.Route import org.openhorizon.exchangeapi.auth.{Access, AuthCache, AuthenticationSupport, DBProcessingError, IUser, Identity, OrgAndId, TManagementPolicy} import org.openhorizon.exchangeapi.table.managementpolicy.ManagementPoliciesTQ import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChange} -import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ExchConfig, ExchMsg, ExchangePosgtresErrorHandling, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, Configuration, ExchMsg, ExchangePosgtresErrorHandling, HttpCode} import slick.jdbc.PostgresProfile.api._ import scala.concurrent.ExecutionContext @@ -203,7 +203,7 @@ trait ManagementPolicy extends JacksonSupport with AuthenticationSupport { case Success(num) => logger.debug("POST /orgs/" + organization + "/managementpolicies" + managementPolicy + " num owned by " + owner + ": " + num) val numOwned: Int = num - val maxManagementPolicies: Int = ExchConfig.getInt("api.limits.maxManagementPolicies") + val maxManagementPolicies: Int = Configuration.getConfig.getInt("api.limits.maxManagementPolicies") if (maxManagementPolicies == 0 || numOwned <= maxManagementPolicies) { // we are not sure if this is a create or update, but if they are already over the limit, stop them anyway reqBody.getDbInsert(resource, organization, owner).asTry } diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/node/GetNodeAttributeResponse.scala b/src/main/scala/org/openhorizon/exchangeapi/route/node/GetNodeAttributeResponse.scala index dfdaa9a5..5523d23f 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/node/GetNodeAttributeResponse.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/node/GetNodeAttributeResponse.scala @@ -1,3 +1,4 @@ package org.openhorizon.exchangeapi.route.node -final case class GetNodeAttributeResponse(attribute: String, value: String) +final case class GetNodeAttributeResponse(attribute: String, + value: String) diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/node/Node.scala b/src/main/scala/org/openhorizon/exchangeapi/route/node/Node.scala index 9ef43493..89df0310 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/node/Node.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/node/Node.scala @@ -18,15 +18,15 @@ import org.openhorizon.exchangeapi.utility.ApiTime.fixFormatting import org.openhorizon.exchangeapi.ExchangeApiApp.{exchAuth, validateWithMsg} import org.openhorizon.exchangeapi.auth.{Access, AccessDeniedException, AuthCache, AuthRoles, AuthenticationSupport, BadInputException, DBProcessingError, IUser, Identity, OrgAndId, Password, ResourceNotFoundException, TNode} import org.openhorizon.exchangeapi.table.deploymentpattern.{PatternRow, Patterns, PatternsTQ} +import org.openhorizon.exchangeapi.table.node.{Node, NodeRow, NodeType, NodesTQ} import org.openhorizon.exchangeapi.table.node.group.NodeGroupTQ import org.openhorizon.exchangeapi.table.node.group.assignment.NodeGroupAssignmentTQ -import org.openhorizon.exchangeapi.table.node.{NodeType, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgLimits, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChange, ResourceChangeRow, ResourceChangesTQ} import org.openhorizon.exchangeapi.table.service.{SearchServiceKey, SearchServiceTQ, ServicesTQ} -import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, ExchConfig, ExchMsg, ExchangePosgtresErrorHandling, HttpCode, Nth, VersionRange} -import org.openhorizon.exchangeapi.{table} +import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, Configuration, ExchMsg, ExchangePosgtresErrorHandling, HttpCode, Nth, VersionRange} +import org.openhorizon.exchangeapi.table import slick.dbio.DBIO import slick.jdbc.PostgresProfile.api._ import slick.lifted.{Compiled, CompiledExecutable} @@ -199,33 +199,138 @@ trait Node extends JacksonSupport with AuthenticationSupport { parameter ("attribute".?) { attribute => logger.debug(s"Doing GET /orgs/$organization/nodes/$node") - exchAuth(TNode(resource), Access.READ) { ident => - val q = if (attribute.isDefined) NodesTQ.getAttribute(resource, attribute.get) else null - validate(attribute.isEmpty || q != null, ExchMsg.translate("node.name.not.in.resource")) { + exchAuth(TNode(resource), Access.READ) { + ident => complete({ + + val nodes = + NodesTQ.filter(_.id === resource) + .filter(_.orgid === organization) + .filterIf(!ident.isAdmin && !ident.role.equals(AuthRoles.Agbot) && !ident.role.equals(AuthRoles.Node))(_.owner === ident.identityString) + .filterIf(ident.role.equals(AuthRoles.Node))(_.id === ident.identityString) + //.filterIf(!(!ident.isMultiTenantAgbot && ident.role.equals(AuthRoles.Agbot)))(_.owner === ident.identityString) + .map(node => + (node.arch, + node.clusterNamespace, + node.heartbeatIntervals, + node.id, + node.isNamespaceScoped, + node.lastHeartbeat, + node.lastUpdated, + node.msgEndPoint, + node.name, + node.nodeType, + //node.orgid, TODO: + node.owner, + node.pattern, + node.publicKey, + node.regServices, + node.softwareVersions, + node.owner.toString() != ident.identityString match { + case true => node.id.substring(0, 0) ++ "***************" // Do NOT query the Token + case _ => node.token + }, + node.userInput)) + + def validAttributes(attribute: String): Boolean = + attribute match { + case "arch" | + "clusterNamespace" | + "ha_group" | + "heartbeatIntervals" | + "id" | + "lastHeartbeat" | + "lastUpdated" | + "msgEndPoint" | + "name" | + "nodeType" | + "owner" | + "pattern" | + "publicKey" | + "registeredServices" | + "softwareVersions" | + "token" | + "userInput" => true + case _ => false + } + attribute match { - case Some(attr) => // Only returning 1 attr of the node - val q = NodesTQ.getAttribute(resource, attr) - if (q == null) (HttpCode.BAD_INPUT, ApiResponse(ApiRespType.BAD_INPUT, ExchMsg.translate("not.a.node.attribute", attr))) else db.run(q.result).map({ list => - logger.debug("GET /orgs/" + organization + "/nodes/" + node + " attribute result: " + list.size) - if (list.nonEmpty) (HttpCode.OK, GetNodeAttributeResponse(attr, list.head.toString)) else (HttpCode.NOT_FOUND, ApiResponse(ApiRespType.NOT_FOUND, ExchMsg.translate("not.found"))) // validateAccessToNode() will return ApiRespType.NOT_FOUND to the client so do that here for consistency - }) - case None => // Return the whole node - val q = for {((node, _), group) <- (NodesTQ.getNode(resource) joinLeft NodeGroupAssignmentTQ on (_.id === _.node)).joinLeft(NodeGroupTQ).on(_._2.map(_.group) === _.group)} yield (node, group.map(_.name)) - db.run(q.result).map({ list => - logger.debug("GET /orgs/" + organization + "/nodes/" + node + " result: " + list.size) - if (list.nonEmpty) { - //val nodes = NodesTQ.parseJoin(ident.isSuperUser, list) - val nodes: Map[String, table.node.Node] = list.map(e => e._1.id -> e._1.toNode(ident.isSuperUser, e._2)).toMap - (HttpCode.OK, GetNodesResponse(nodes, 0)) - } else { - (HttpCode.NOT_FOUND, ApiResponse(ApiRespType.NOT_FOUND, ExchMsg.translate("not.found"))) // validateAccessToNode() will return ApiRespType.NOT_FOUND to the client so do that here for consistency - } + case Some(attribute) if attribute.nonEmpty && validAttributes(attribute) => // Only returning 1 attr of the node + val filteredNodeAttribute = + for { + value <- + if (attribute == "ha_group") + (nodes joinLeft NodeGroupAssignmentTQ on ((someNode, assignment) => someNode._4 === assignment.node)) + .joinLeft(NodeGroupTQ).on(_._2.map(_.group) === _.group) // ((A left Join B) Left Join C) + .map(record => record._2.map(_.name).getOrElse("")) + else if (attribute == "isNamespaceScoped") + nodes.map(record => record._5.toString()) + else + nodes.map(record => + attribute match { + case "arch" => record._1 + case "clusterNamespace" => record._2.getOrElse("") + case "heartbeatIntervals" => record._3 + case "id" => record._4 + case "lastHeartbeat" => record._6.getOrElse("") + case "lastUpdated" => record._7 + case "msgEndPoint" => record._8 + case "name" => record._9 + case "nodeType" => record._10 + case "owner" => record._11 + case "pattern" => record._12 + case "publicKey" => record._13 + case "registeredServices" => record._14 + case "softwareVersions" => record._15 + case "token" => record._16 + case "userInput" => record._17}) + } yield((attribute, value)) + + db.run(Compiled(filteredNodeAttribute).result.transactionally).map({ + result => + if (result.length == 1) + (HttpCode.OK, GetNodeAttributeResponse(result.head._1, result.head._2)) + else + (HttpCode.NOT_FOUND, ApiResponse(ApiRespType.NOT_FOUND, ExchMsg.translate("not.found"))) // validateAccessToNode() will return ApiRespType.NOT_FOUND to the client so do that here for consistency }) - } - }) // end of complete - } // end of validate - } // end of exchAuth + case _ => // Return the whole node + val filteredNode = + for { + node <- + (nodes joinLeft NodeGroupAssignmentTQ on ((someNode, assignment) => someNode._4 === assignment.node)) + .joinLeft(NodeGroupTQ).on(_._2.map(_.group) === _.group) // ((A left Join B) Left Join C) + .map(record => + (record._1._1._4, // Node.id + (record._1._1._1, // Node.arch + record._1._1._2, // Node.clusterNamespace + record._2.map(_.name), // NodeGroup.group + record._1._1._3, // Node.heartbeatIntervals + record._1._1._5, // Node.isNamespaceScoped + record._1._1._6, // Node.lastHeartbeat + record._1._1._7, // Node.lastUpdated + record._1._1._8, // Node.msgEndPoint + record._1._1._9, // Node.name + record._1._1._10, // Node.nodeType + // record._1._1._11, // Node.orgid TODO: + record._1._1._11, // Node.owner + record._1._1._12, // Node.pattern + record._1._1._13, // Node.publicKey + record._1._1._14, // Node.regServices + record._1._1._15, // Node.softwareVersions + record._1._1._16, // Node.token + record._1._1._17))) // Node.userInput + } yield (node) + + db.run(Compiled(filteredNode).result.transactionally).map({ + result => + if (result.length == 1) + (HttpCode.OK, GetNodesResponse((result.map(node => node._1 -> new org.openhorizon.exchangeapi.table.node.Node(node = node._2.copy())).toMap), 0)) + else + (HttpCode.NOT_FOUND, ApiResponse(ApiRespType.NOT_FOUND, ExchMsg.translate("not.found"))) // validateAccessToNode() will return ApiRespType.NOT_FOUND to the client so do that here for consistency + }) + } + }) + } } } @@ -774,7 +879,7 @@ trait Node extends JacksonSupport with AuthenticationSupport { case Failure(t) => DBIO.failed(t).asTry }).flatMap({ case Success(numOwned) => // Check if num nodes owned is below limit, then create/update node logger.debug("PUT /orgs/" + organization + "/nodes/" + node + " num owned: " + numOwned) - val maxNodes: Int = ExchConfig.getInt("api.limits.maxNodes") + val maxNodes: Int = Configuration.getConfig.getInt("api.limits.maxNodes") if (maxNodes == 0 || numOwned <= maxNodes || owner == "") // when owner=="" we know it is only an update, otherwise we are not sure, but if they are already over the limit, stop them anyway NodesTQ.getLastHeartbeat(resource).result.asTry else DBIO.failed(new DBProcessingError(HttpCode.ACCESS_DENIED, ApiRespType.ACCESS_DENIED, ExchMsg.translate("over.max.limit.of.nodes", maxNodes))).asTry case Failure(t) => DBIO.failed(t).asTry @@ -835,33 +940,33 @@ trait Node extends JacksonSupport with AuthenticationSupport { * additional overhead of adding the records in at the beginning and then removing the * records at the end. */ - /*(ServicesTQ.filterIf(!ident.isSuperUser && - ident.isAdmin) - (service => - (service.orgid === orgid || - service.public === true)) - .filterIf(!(ident.isAdmin || - ident.isSuperUser || - ident.isHubAdmin)) - (service => - ((service.orgid === orgid || - service.public === true) && - service.owner === ident.identityString)) - .map(service => - (service.arch, - service.orgid, - service.service, - service.url, - service.version))) - .filter({ - service => - servicesToSearch.foldLeft[Rep[Boolean]](false) { - case (matched, searchKey) => - matched || ((service._1 like searchKey.architecture) && - service._2 === searchKey.organization && - service._4 === searchKey.domain && - (service._5 like searchKey.version)) - } - }) - .result*/ + /* (ServicesTQ.filterIf(!ident.isSuperUser && + ident.isAdmin) + (service => + (service.orgid === orgid || + service.public === true)) + .filterIf(!(ident.isAdmin || + ident.isSuperUser || + ident.isHubAdmin)) + (service => + ((service.orgid === orgid || + service.public === true) && + service.owner === ident.identityString)) + .map(service => + (service.arch, + service.orgid, + service.service, + service.url, + service.version))) + .filter({ + service => + servicesToSearch.foldLeft[Rep[Boolean]](false) { + case (matched, searchKey) => + matched || ((service._1 like searchKey.architecture) && + service._2 === searchKey.organization && + service._4 === searchKey.domain && + (service._5 like searchKey.version)) + } + }) + .result */ } diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/node/Nodes.scala b/src/main/scala/org/openhorizon/exchangeapi/route/node/Nodes.scala index 6acc03b8..6ee6e215 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/node/Nodes.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/node/Nodes.scala @@ -11,11 +11,14 @@ import io.swagger.v3.oas.annotations.{Operation, Parameter, responses} import io.swagger.v3.oas.annotations.enums.ParameterIn import io.swagger.v3.oas.annotations.media.{Content, ExampleObject, Schema} import jakarta.ws.rs.{GET, Path} +import org.json4s.{DefaultFormats, Formats} +import org.json4s.jackson.Serialization.read import org.openhorizon.exchangeapi.auth.{Access, AuthRoles, AuthenticationSupport, OrgAndId, TNode} import org.openhorizon.exchangeapi.table.node.group.NodeGroupTQ -import org.openhorizon.exchangeapi.table.node.{Node, NodeType, NodesTQ} +import org.openhorizon.exchangeapi.table.node.{Node, NodeHeartbeatIntervals, NodeType, NodesTQ} import org.openhorizon.exchangeapi.table.node.group.assignment.NodeGroupAssignmentTQ import slick.jdbc.PostgresProfile.api._ +import slick.lifted.Rep import scala.concurrent.ExecutionContext @@ -129,64 +132,139 @@ trait Nodes extends JacksonSupport with AuthenticationSupport { @io.swagger.v3.oas.annotations.tags.Tag(name = "node") def getNodes(organization: String): Route = get { - parameter("idfilter".?, "name".?, "owner".?, "arch".?, "nodetype".?, "clusternamespace".?, "isNamespaceScoped".as[Boolean].?) { + parameter("idfilter".?, + "name".?, + "owner".?, + "arch".?, + "nodetype".?, + "clusternamespace".?, + "isNamespaceScoped".as[Boolean].?) { (idfilter, name, owner, arch, nodetype, clusterNamespace, isNamespaceScoped) => logger.debug(s"Doing GET /orgs/$organization/nodes") - exchAuth(TNode(OrgAndId(organization, "#").toString), Access.READ) { ident => - validateWithMsg(GetNodesUtils.getNodesProblem(nodetype)) { - complete({ - logger.debug(s"GET /orgs/$organization/nodes identity: ${ident.creds.id}") // can't display the whole ident object, because that contains the pw/token - - var q = NodesTQ.getAllNodes(organization) - - arch.foreach(arch => { - if (arch.contains("%")) q = q.filter(_.arch like arch) else q = q.filter(_.arch === arch) - }) - - clusterNamespace.foreach(namespace => { - if (namespace.contains("%")) q = q.filter(_.clusterNamespace like namespace) else q = q.filter(_.clusterNamespace === namespace) - }) - - idfilter.foreach(id => { - if (id.contains("%")) q = q.filter(_.id like id) else q = q.filter(_.id === id) - }) - - if (isNamespaceScoped.isDefined) - q = q.filter(_.isNamespaceScoped === isNamespaceScoped.get) - - name.foreach(name => { - if (name.contains("%")) q = q.filter(_.name like name) else q = q.filter(_.name === name) - }) - - if (ident.isAdmin || ident.role.equals(AuthRoles.Agbot)) { + exchAuth(TNode(OrgAndId(organization, "#").toString), Access.READ) { + ident => + validateWithMsg(GetNodesUtils.getNodesProblem(nodetype)) { + complete({ + logger.debug(s"GET /orgs/$organization/nodes identity: ${ident.creds.id}") // can't display the whole ident object, because that contains the pw/token + implicit val jsonFormats: Formats = DefaultFormats + + /*var q = NodesTQ.getAllNodes(organization) + + arch.foreach(arch => { + if (arch.contains("%")) q = q.filter(_.arch like arch) else q = q.filter(_.arch === arch) + }) + + clusterNamespace.foreach(namespace => { + if (namespace.contains("%")) q = q.filter(_.clusterNamespace like namespace) else q = q.filter(_.clusterNamespace === namespace) + }) + + idfilter.foreach(id => { + if (id.contains("%")) q = q.filter(_.id like id) else q = q.filter(_.id === id) + }) + + if (isNamespaceScoped.isDefined) + q = q.filter(_.isNamespaceScoped === isNamespaceScoped.get) + + name.foreach(name => { + if (name.contains("%")) q = q.filter(_.name like name) else q = q.filter(_.name === name) + }) + + if (ident.isAdmin || ident.role.equals(AuthRoles.Agbot)) { + owner.foreach(owner => { + if (owner.contains("%")) q = q.filter(_.owner like owner) else q = q.filter(_.owner === owner) + }) + } else q = q.filter(_.owner === ident.identityString) + owner.foreach(owner => { if (owner.contains("%")) q = q.filter(_.owner like owner) else q = q.filter(_.owner === owner) }) - } else q = q.filter(_.owner === ident.identityString) - - owner.foreach(owner => { - if (owner.contains("%")) q = q.filter(_.owner like owner) else q = q.filter(_.owner === owner) - }) - - - if (nodetype.isDefined) { - val nt: String = nodetype.get.toLowerCase - if (NodeType.isDevice(nt)) q = q.filter(r => { - r.nodeType === nt || r.nodeType === "" - }) else if (NodeType.isCluster(nt)) q = q.filter(_.nodeType === nt) - } - - val combinedQuery = for {((nodes, _), groups) <- (q joinLeft NodeGroupAssignmentTQ on (_.id === _.node)).joinLeft(NodeGroupTQ).on(_._2.map(_.group) === _.group)} yield (nodes, groups.map(_.name)) - - db.run(combinedQuery.result).map({ result => - logger.debug(s"GET /orgs/$organization/nodes result size: ${result.size}") - //val nodes = NodesTQ.parseJoin(ident.isSuperUser, list) - val nodes: Map[String, Node] = result.map(e => e._1.id -> e._1.toNode(ident.isSuperUser, e._2)).toMap - val code: StatusCode = if (nodes.nonEmpty) StatusCodes.OK else StatusCodes.NotFound - (code, GetNodesResponse(nodes, 0)) - }) - }) // end of complete - } + + + if (nodetype.isDefined) { + val nt: String = nodetype.get.toLowerCase + if (NodeType.isDevice(nt)) q = q.filter(r => { + r.nodeType === nt || r.nodeType === "" + }) else if (NodeType.isCluster(nt)) q = q.filter(_.nodeType === nt) + } + + val combinedQuery = for {((nodes, _), groups) <- (q joinLeft NodeGroupAssignmentTQ on (_.id === _.node)).joinLeft(NodeGroupTQ).on(_._2.map(_.group) === _.group)} yield (nodes, groups.map(_.name)) */ + + val nodes = + NodesTQ.filter(_.orgid === organization) + .filterOpt(arch)((node, arch) => node.arch like arch) + .filterOpt(clusterNamespace)((node, clusterNamespace) => node.clusterNamespace like clusterNamespace) + .filterOpt(idfilter)((node, id) => node.id like id) + .filterOpt(isNamespaceScoped)((node, isNamespaceScoped) => node.isNamespaceScoped === isNamespaceScoped) + .filterOpt(name)((node, name) => node.name like name) + .filterIf(nodetype.isDefined && "cluster" == nodetype.get.toLowerCase())(_.nodeType === "cluster") + .filterIf(nodetype.isDefined && "device|".r.matches(nodetype.get.toLowerCase()))(_.nodeType inSet Set("", "device")) + .filterIf(owner.isDefined && (ident.isAdmin || ident.role.equals(AuthRoles.Agbot)))(_.owner like owner.get) + .filterIf(!ident.isAdmin && !ident.role.equals(AuthRoles.Agbot))(_.owner === ident.identityString) + .filterIf(ident.role.equals(AuthRoles.Node))(_.id === ident.identityString) + .map(node => + (node.arch, + node.clusterNamespace, + node.heartbeatIntervals, + node.id, + node.isNamespaceScoped, + node.lastHeartbeat, + node.lastUpdated, + node.msgEndPoint, + node.name, + node.nodeType, + node.owner, + node.pattern, + node.publicKey, + node.regServices, + node.softwareVersions, + node.owner.toString() != ident.identityString match { + case true => node.id.substring(0, 0) ++ "***************" // Do NOT query the Token + case _ => node.token + }, + // (if (node.owner != ident.identityString) + // node.id.substring(0, 0) ++ "***************" // Do NOT query the Token + // else + // node.token), + node.userInput)) + + val filteredNodes = + for { + nodes <- + (nodes joinLeft NodeGroupAssignmentTQ on ((someNode, assignment) => someNode._4 === assignment.node)) + .joinLeft(NodeGroupTQ).on(_._2.map(_.group) === _.group) // ((A left Join B) Left Join C) + .map(record => + (record._1._1._4, // Node.id + (record._1._1._1, // Node.arch + record._1._1._2, // Node.clusterNamespace + record._2.map(_.name), // NodeGroup.group + record._1._1._3, // Node.heartbeatIntervals + record._1._1._5, // Node.isNamespaceScoped + record._1._1._6, // Node.lastHeartbeat + record._1._1._7, // Node.lastUpdated + record._1._1._8, // Node.msgEndPoint + record._1._1._9, // Node.name + record._1._1._10, // Node.nodeType + record._1._1._11, // Node.owner + record._1._1._12, // Node.pattern + record._1._1._13, // Node.publicKey + record._1._1._14, // Node.regServices + record._1._1._15, // Node.softwareVersions + record._1._1._16, // Node.token + record._1._1._17))) // Node.userInput + } yield (nodes) + + db.run(Compiled(filteredNodes).result.transactionally).map({ + result => + logger.debug(s"GET /orgs/$organization/nodes result size: ${result.size}") + val nodes: Map[String, Node] = + result.map(result => + result._1 -> new Node(node = result._2)).toMap + + val code: StatusCode = if (nodes.nonEmpty) StatusCodes.OK else StatusCodes.NotFound + (code, GetNodesResponse(nodes, 0)) + }) + }) // end of complete + } } // end of exchAuth } } diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/node/agreement/Agreement.scala b/src/main/scala/org/openhorizon/exchangeapi/route/node/agreement/Agreement.scala index a3133010..d506aeb8 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/node/agreement/Agreement.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/node/agreement/Agreement.scala @@ -9,14 +9,14 @@ import jakarta.ws.rs.{DELETE, GET, PUT, Path} import org.apache.pekko.actor.ActorSystem import org.apache.pekko.event.LoggingAdapter import org.apache.pekko.http.scaladsl.model.{StatusCode, StatusCodes} -import org.apache.pekko.http.scaladsl.server.Directives.{as, complete, delete, entity, get, path, parameter, put, _} +import org.apache.pekko.http.scaladsl.server.Directives.{as, complete, delete, entity, get, parameter, path, put, _} import org.apache.pekko.http.scaladsl.server.Route import org.openhorizon.exchangeapi.auth.{Access, AuthenticationSupport, DBProcessingError, OrgAndId, TNode} import org.openhorizon.exchangeapi.route.node.{GetNodeAgreementsResponse, PutNodeAgreementRequest} import org.openhorizon.exchangeapi.table.node.NodesTQ import org.openhorizon.exchangeapi.table.node.agreement.{NodeAgreement, NodeAgreementsTQ} import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChange} -import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, ExchConfig, ExchMsg, ExchangePosgtresErrorHandling, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, Configuration, ExchMsg, ExchangePosgtresErrorHandling, HttpCode} import slick.dbio.DBIO import slick.jdbc.PostgresProfile.api._ @@ -172,7 +172,7 @@ trait Agreement extends JacksonSupport with AuthenticationSupport { validateWithMsg(reqBody.getAnyProblem(noheartbeat)) { complete({ val noHB = if (noheartbeat.isEmpty) false else if (noheartbeat.get.toLowerCase == "true") true else false - val maxAgreements: Int = ExchConfig.getInt("api.limits.maxAgreements") + val maxAgreements: Int = Configuration.getConfig.getInt("api.limits.maxAgreements") val getNumOwnedDbio = if (maxAgreements == 0) DBIO.successful(0) else NodeAgreementsTQ.getNumOwned(resource).result // avoid DB read for this if there is no max db.run(getNumOwnedDbio.flatMap({ xs => if (maxAgreements != 0) logger.debug("PUT /orgs/"+organization+"/nodes/"+node+"/agreements/"+agreement+" num owned: "+xs) diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/node/message/Messages.scala b/src/main/scala/org/openhorizon/exchangeapi/route/node/message/Messages.scala index e059889d..6f50d5cf 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/node/message/Messages.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/node/message/Messages.scala @@ -16,7 +16,7 @@ import org.openhorizon.exchangeapi.route.node.{GetNodeMsgsResponse, PostNodesMsg import org.openhorizon.exchangeapi.table.agreementbot.AgbotsTQ import org.openhorizon.exchangeapi.table.node.message.{NodeMsg, NodeMsgRow, NodeMsgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChange} -import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, ExchConfig, ExchMsg, ExchangePosgtresErrorHandling, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, Configuration, ExchMsg, ExchangePosgtresErrorHandling, HttpCode} import slick.dbio.DBIO import slick.jdbc.PostgresProfile.api._ @@ -139,7 +139,7 @@ trait Messages extends JacksonSupport with AuthenticationSupport { complete({ val agbotId: String = identity.creds.id //someday: handle the case where the acls allow users to send msgs var msgNum = "" - val maxMessagesInMailbox: Int = ExchConfig.getInt("api.limits.maxMessagesInMailbox") + val maxMessagesInMailbox: Int = Configuration.getConfig.getInt("api.limits.maxMessagesInMailbox") val getNumOwnedDbio = if (maxMessagesInMailbox == 0) DBIO.successful(0) else NodeMsgsTQ.getNumOwned(resource).result // avoid DB read for this if there is no max // Remove msgs whose TTL is past, then check the mailbox is not full, then get the agbot publicKey, then write the nodemsgs row, all in the same db.run thread db.run(getNumOwnedDbio.flatMap({ xs => diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/organization/Changes.scala b/src/main/scala/org/openhorizon/exchangeapi/route/organization/Changes.scala index 8ccff65f..b593e9fb 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/organization/Changes.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/organization/Changes.scala @@ -14,8 +14,8 @@ import org.openhorizon.exchangeapi.auth.{Access, AuthenticationSupport, IAgbot, import org.openhorizon.exchangeapi.table.agreementbot.AgbotsTQ import org.openhorizon.exchangeapi.table.node.NodesTQ import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeOperation, ResourceChangeRow, ResourceChanges, ResourceChangesTQ} -import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, ExchConfig, ExchMsg, ExchangePosgtresErrorHandling, HttpCode} -import org.openhorizon.exchangeapi.{ExchangeApi} +import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, Configuration, ExchMsg, ExchangePosgtresErrorHandling, HttpCode} +import org.openhorizon.exchangeapi.ExchangeApi import slick.jdbc.PostgresProfile import slick.jdbc.PostgresProfile.api._ import slick.lifted.Compiled @@ -39,7 +39,7 @@ trait Changes extends JacksonSupport with AuthenticationSupport{ inputChangeId: Long, maxChangeIdOfTable: Long): ResourceChangesRespObject ={ // Sort the rows based on the changeId. Default order is ascending, which is what we want - logger.info(s"POST /orgs/{organization}/changes sorting ${inputList.size} rows") + logger.debug(s"POST /orgs/{organization}/changes sorting ${inputList.size} rows") // val inputList = inputListUnsorted.sortBy(_.changeId) // Note: we are doing the sorting here instead of in the db via sql, because the latter seems to use a lot of db cpu // fill in some values we can before processing @@ -131,7 +131,7 @@ trait Changes extends JacksonSupport with AuthenticationSupport{ complete({ logger.debug(s"Doing POST /orgs/$organization/changes - identity: ${identity.identityString}") // make sure callers obey maxRecords cap set in config, defaults is 10,000 - val maxRecordsCap: Int = ExchConfig.getInt("api.resourceChanges.maxRecordsCap") + val maxRecordsCap: Int = Configuration.getConfig.getInt("api.resourceChanges.maxRecordsCap") logger.debug(s"Doing POST /orgs/$organization/changes - maxRecordsCap: $maxRecordsCap") val maxRecords: Int = diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/organization/GetMyOrgsRequest.scala b/src/main/scala/org/openhorizon/exchangeapi/route/organization/GetMyOrgsRequest.scala index 66a1398a..4d826823 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/organization/GetMyOrgsRequest.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/organization/GetMyOrgsRequest.scala @@ -1,6 +1,6 @@ package org.openhorizon.exchangeapi.route.organization -import org.openhorizon.exchangeapi.auth.IamAccountInfo +import org.openhorizon.exchangeapi.auth.cloud.IamAccountInfo final case class GetMyOrgsRequest(accounts: List[IamAccountInfo]) { def getAnyProblem: Option[String] = None diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/organization/MyOrganizations.scala b/src/main/scala/org/openhorizon/exchangeapi/route/organization/MyOrganizations.scala index 0ae4cb13..e69aa230 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/organization/MyOrganizations.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/organization/MyOrganizations.scala @@ -9,9 +9,10 @@ import jakarta.ws.rs.{POST, Path} import org.apache.pekko.actor.ActorSystem import org.apache.pekko.event.LoggingAdapter import org.apache.pekko.http.scaladsl.model.{StatusCode, StatusCodes} -import org.apache.pekko.http.scaladsl.server.Directives.{as, complete, entity, path, post, _} +import org.apache.pekko.http.scaladsl.server.Directives._ import org.apache.pekko.http.scaladsl.server.Route -import org.openhorizon.exchangeapi.auth.{Access, AuthenticationSupport, IamAccountInfo, TOrg} +import org.openhorizon.exchangeapi.auth.cloud.IamAccountInfo +import org.openhorizon.exchangeapi.auth.{Access, AuthenticationSupport, TOrg} import org.openhorizon.exchangeapi.table.organization.{Org, OrgsTQ} import org.openhorizon.exchangeapi.table.ExchangePostgresProfile.api._ diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/organization/Organization.scala b/src/main/scala/org/openhorizon/exchangeapi/route/organization/Organization.scala index d37b43c8..e420b03c 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/organization/Organization.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/organization/Organization.scala @@ -9,9 +9,10 @@ import jakarta.ws.rs.{DELETE, GET, PATCH, POST, PUT, Path} import org.apache.pekko.actor.ActorSystem import org.apache.pekko.event.LoggingAdapter import org.apache.pekko.http.scaladsl.model.{StatusCode, StatusCodes} -import org.apache.pekko.http.scaladsl.server.Directives.{as, complete, delete, entity, get, parameter, patch, path, post, put, _} +import org.apache.pekko.http.scaladsl.server.Directives._ import org.apache.pekko.http.scaladsl.server.Route -import org.openhorizon.exchangeapi.auth.{Access, AuthCache, AuthenticationSupport, DBProcessingError, IbmCloudAuth, TOrg} +import org.openhorizon.exchangeapi.auth.cloud.IbmCloudAuth +import org.openhorizon.exchangeapi.auth.{Access, AuthCache, AuthenticationSupport, DBProcessingError, TOrg} import org.openhorizon.exchangeapi.table.agreementbot.AgbotsTQ import org.openhorizon.exchangeapi.table.node.NodesTQ import org.openhorizon.exchangeapi.table.organization.{Org, OrgLimits, OrgsTQ} diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/organization/Organizations.scala b/src/main/scala/org/openhorizon/exchangeapi/route/organization/Organizations.scala index d59a81b9..b7639584 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/organization/Organizations.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/organization/Organizations.scala @@ -10,13 +10,14 @@ import jakarta.ws.rs.{DELETE, GET, PATCH, POST, PUT, Path} import org.apache.pekko.actor.ActorSystem import org.apache.pekko.event.LoggingAdapter import org.apache.pekko.http.scaladsl.model.{StatusCode, StatusCodes} -import org.apache.pekko.http.scaladsl.server.Directives.{complete, get, path, parameter, validate, _} +import org.apache.pekko.http.scaladsl.server.Directives._ import org.apache.pekko.http.scaladsl.server.Route import org.checkerframework.checker.units.qual.t import org.json4s._ import org.json4s.jackson.Serialization.write import org.openhorizon.exchangeapi -import org.openhorizon.exchangeapi.auth.{Access, AuthCache, AuthenticationSupport, DBProcessingError, IAgbot, INode, IUser, IamAccountInfo, IbmCloudAuth, Identity, OrgAndId, TAction, TAgbot, TNode, TOrg} +import org.openhorizon.exchangeapi.auth.cloud.{IamAccountInfo, IbmCloudAuth} +import org.openhorizon.exchangeapi.auth.{Access, AuthCache, AuthenticationSupport, DBProcessingError, IAgbot, INode, IUser, Identity, OrgAndId, TAction, TAgbot, TNode, TOrg} import org.openhorizon.exchangeapi.route.agreementbot.PostAgreementsConfirmRequest import org.openhorizon.exchangeapi.route.node.{PostNodeErrorResponse, PostServiceSearchRequest, PostServiceSearchResponse} import org.openhorizon.exchangeapi.table.ExchangePostgresProfile.api._ @@ -32,7 +33,7 @@ import org.openhorizon.exchangeapi.table.organization.{Org, OrgLimits, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChange} import org.openhorizon.exchangeapi.table.schema.SchemaTQ import org.openhorizon.exchangeapi.table.user.UsersTQ -import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, ApiUtils, ExchConfig, ExchMsg, ExchangePosgtresErrorHandling, HttpCode, RouteUtils} +import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, ApiUtils, ExchMsg, ExchangePosgtresErrorHandling, HttpCode, RouteUtils} import org.openhorizon.exchangeapi.{ExchangeApi, table} import java.lang.IllegalCallerException diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/organization/PatchOrgRequest.scala b/src/main/scala/org/openhorizon/exchangeapi/route/organization/PatchOrgRequest.scala index 8d0732d5..c97547d8 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/organization/PatchOrgRequest.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/organization/PatchOrgRequest.scala @@ -4,7 +4,7 @@ import org.json4s.jackson.Serialization.write import org.json4s.{DefaultFormats, Formats} import org.openhorizon.exchangeapi.table.node.NodeHeartbeatIntervals import org.openhorizon.exchangeapi.table.organization.{OrgLimits, OrgsTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, ExchConfig, ExchMsg} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, ExchMsg} import slick.dbio.DBIO import scala.concurrent.ExecutionContext @@ -18,7 +18,7 @@ final case class PatchOrgRequest(orgType: Option[String], protected implicit val jsonFormats: Formats = DefaultFormats def getAnyProblem(orgMaxNodes: Int): Option[String] = { - val exchangeMaxNodes: Int = ExchConfig.getInt("api.limits.maxNodes") + val exchangeMaxNodes: Int = Configuration.getConfig.getInt("api.limits.maxNodes") if (orgMaxNodes > exchangeMaxNodes) Some.apply(ExchMsg.translate("org.limits.cannot.be.over.exchange.limits", orgMaxNodes, exchangeMaxNodes)) else None } diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/organization/PostPutOrgRequest.scala b/src/main/scala/org/openhorizon/exchangeapi/route/organization/PostPutOrgRequest.scala index 7a3c6f5a..684c6200 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/organization/PostPutOrgRequest.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/organization/PostPutOrgRequest.scala @@ -4,7 +4,7 @@ import org.json4s.jackson.Serialization.write import org.json4s.{DefaultFormats, Formats} import org.openhorizon.exchangeapi.table.node.NodeHeartbeatIntervals import org.openhorizon.exchangeapi.table.organization.{OrgLimits, OrgRow} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, ExchConfig, ExchMsg} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, ExchMsg} final case class PostPutOrgRequest(orgType: Option[String], label: String, @@ -15,7 +15,7 @@ final case class PostPutOrgRequest(orgType: Option[String], require(label!=null && description!=null) protected implicit val jsonFormats: Formats = DefaultFormats def getAnyProblem(orgMaxNodes: Int): Option[String] = { - val exchangeMaxNodes: Int = ExchConfig.getInt("api.limits.maxNodes") + val exchangeMaxNodes: Int = Configuration.getConfig.getInt("api.limits.maxNodes") if (orgMaxNodes > exchangeMaxNodes) Some.apply(ExchMsg.translate("org.limits.cannot.be.over.exchange.limits", orgMaxNodes, exchangeMaxNodes)) else None } diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/service/Service.scala b/src/main/scala/org/openhorizon/exchangeapi/route/service/Service.scala index ebfa2ef8..9de0092e 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/service/Service.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/service/Service.scala @@ -15,7 +15,7 @@ import org.openhorizon.exchangeapi.auth.{Access, AuthCache, AuthenticationSuppor import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChange, ResourceChangeRow, ResourceChangesTQ} import org.openhorizon.exchangeapi.table.service import org.openhorizon.exchangeapi.table.service.{ServiceRef, ServiceRow, Services, ServicesTQ} -import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, ExchConfig, ExchMsg, ExchangePosgtresErrorHandling, HttpCode, Version, VersionRange} +import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, ExchMsg, ExchangePosgtresErrorHandling, HttpCode, Version, VersionRange} import slick.jdbc.PostgresProfile.api._ import java.lang.IllegalStateException diff --git a/src/main/scala/org/openhorizon/exchangeapi/route/service/Services.scala b/src/main/scala/org/openhorizon/exchangeapi/route/service/Services.scala index dd7890de..4a0a9e53 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/route/service/Services.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/route/service/Services.scala @@ -10,7 +10,7 @@ import jakarta.ws.rs.{DELETE, GET, PATCH, POST, PUT, Path} import org.apache.pekko.actor.ActorSystem import org.apache.pekko.event.LoggingAdapter import org.apache.pekko.http.scaladsl.model.{StatusCode, StatusCodes} -import org.apache.pekko.http.scaladsl.server.Directives.{as, complete, entity, get, path, parameter, post, _} +import org.apache.pekko.http.scaladsl.server.Directives.{as, complete, entity, get, parameter, path, post, _} import org.apache.pekko.http.scaladsl.server.Route import org.json4s._ import org.json4s.jackson.Serialization.write @@ -22,7 +22,7 @@ import org.openhorizon.exchangeapi.table.service.dockerauth.{ServiceDockAuth, Se import org.openhorizon.exchangeapi.table.service.key.ServiceKeysTQ import org.openhorizon.exchangeapi.table.service.policy.{ServicePolicy, ServicePolicyTQ} import org.openhorizon.exchangeapi.table.service.{Service, ServiceRef, ServicesTQ} -import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, ExchConfig, ExchMsg, ExchangePosgtresErrorHandling, HttpCode, Version, VersionRange} +import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, Configuration, ExchMsg, ExchangePosgtresErrorHandling, HttpCode, Version, VersionRange} import slick.jdbc import slick.jdbc.PostgresProfile.api._ @@ -315,7 +315,7 @@ trait Services extends JacksonSupport with AuthenticationSupport { case Success(num) => logger.debug("POST /orgs/" + organization + "/services num owned by " + owner + ": " + num) val numOwned: Int = num - val maxServices: Int = ExchConfig.getInt("api.limits.maxServices") + val maxServices: Int = Configuration.getConfig.getInt("api.limits.maxServices") if (maxServices == 0 || maxServices >= numOwned) { // we are not sure if this is a create or update, but if they are already over the limit, stop them anyway reqBody.toServiceRow(service, organization, owner).insert.asTry } diff --git a/src/main/scala/org/openhorizon/exchangeapi/table/ExchangeApiTables.scala b/src/main/scala/org/openhorizon/exchangeapi/table/ExchangeApiTables.scala index 4f8bcc43..cfd81aff 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/table/ExchangeApiTables.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/table/ExchangeApiTables.scala @@ -1,7 +1,10 @@ package org.openhorizon.exchangeapi.table import org.apache.pekko.event.LoggingAdapter +import org.json4s.{DefaultFormats, Formats, JObject, JString, JValue, _} +import org.json4s.native.JsonMethods._ import org.openhorizon.exchangeapi.ExchangeApiApp.system +import org.openhorizon.exchangeapi.auth.{AuthCache, Password, Role} import org.openhorizon.exchangeapi.table.agent.AgentVersionsChangedTQ import org.openhorizon.exchangeapi.table.agent.certificate.AgentCertificateVersionsTQ import org.openhorizon.exchangeapi.table.agent.configuration.AgentConfigurationVersionsTQ @@ -15,6 +18,7 @@ import org.openhorizon.exchangeapi.table.deploymentpattern.PatternsTQ import org.openhorizon.exchangeapi.table.deploymentpattern.key.PatternKeysTQ import org.openhorizon.exchangeapi.table.deploymentpolicy.BusinessPoliciesTQ import org.openhorizon.exchangeapi.table.deploymentpolicy.search.SearchOffsetPolicyTQ +import org.openhorizon.exchangeapi.table.ExchangePostgresProfile.api._ import org.openhorizon.exchangeapi.table.managementpolicy.ManagementPoliciesTQ import org.openhorizon.exchangeapi.table.node.NodesTQ import org.openhorizon.exchangeapi.table.node.agreement.NodeAgreementsTQ @@ -25,20 +29,25 @@ import org.openhorizon.exchangeapi.table.node.group.assignment.NodeGroupAssignme import org.openhorizon.exchangeapi.table.node.managementpolicy.status.NodeMgmtPolStatuses import org.openhorizon.exchangeapi.table.node.message.NodeMsgsTQ import org.openhorizon.exchangeapi.table.node.status.NodeStatusTQ -import org.openhorizon.exchangeapi.table.organization.OrgsTQ -import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ +import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} +import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChangeRow, ResourceChangesTQ} import org.openhorizon.exchangeapi.table.schema.{SchemaRow, SchemaTQ} import org.openhorizon.exchangeapi.table.service.dockerauth.ServiceDockAuthsTQ import org.openhorizon.exchangeapi.table.service.key.ServiceKeysTQ import org.openhorizon.exchangeapi.table.service.policy.ServicePolicyTQ import org.openhorizon.exchangeapi.table.service.{SearchServiceTQ, ServicesTQ} -import org.openhorizon.exchangeapi.table.user.UsersTQ -import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ExchConfig, ExchMsg} +import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} +import org.openhorizon.exchangeapi.utility.ApiTime.fixFormatting +import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, Configuration, ExchMsg} import org.postgresql.util.PSQLException -import slick.jdbc.PostgresProfile.api._ +//import slick.jdbc.PostgresProfile.api._ +import java.sql.Timestamp +import java.time.ZoneId +import scala.collection.mutable import scala.concurrent.{Await, ExecutionContext} import scala.concurrent.duration._ +import scala.jdk.CollectionConverters.CollectionHasAsScala import scala.util.{Failure, Success} /** The umbrella class for the DB tables. The specific table classes are in the tables subdir. */ @@ -133,6 +142,173 @@ object ExchangeApiTables { sqlu"DROP TABLE IF EXISTS public.blockchains CASCADE", sqlu"DROP TABLE IF EXISTS public.bctypes CASCADE") + /** Create the root user in the DB. This is done separately from load() because we need the db execution context */ + def createRoot(db: Database)(implicit logger: LoggingAdapter, executionContext: ExecutionContext): Unit = { + val changeTimestamp: Timestamp = ApiTime.nowUTCTimestamp + val timeStamp: String = fixFormatting(changeTimestamp.toInstant.atZone(ZoneId.of("UTC")).withZoneSameInstant(ZoneId.of("UTC")).toString) + val configHubAdmins: scala.collection.mutable.Map[String, UserRow] = mutable.Map.empty[String, UserRow] + + // IBM Cloud Account ID for the root Organization + val ibmCloudAcctID: Option[JValue] = + try { + Some(JObject("ibmcloud_id" -> JString(Configuration.getConfig.getString("api.root.account_id")))) + } + catch { + case _: Exception => None + } + + implicit val jsonFormats: Formats = DefaultFormats + + val InitialOrganizations = + Seq(OrgRow(description = "Organization containing IBM services", + heartbeatIntervals = "", + label = "IBM Org", + lastUpdated = timeStamp, + limits = "", + orgId = "IBM", + orgType = "IBM", + tags = None), + OrgRow(description = "Organization for the root user only", + heartbeatIntervals = "", + label = "Root Org", + lastUpdated = timeStamp, + limits = "", + orgId = "root", + orgType = "", + tags = ibmCloudAcctID)) + + Configuration.getConfig.getObjectList("api.hubadmins").asScala.foreach({ + hubAdminConfigObject => + try { + val organization = hubAdminConfigObject.toConfig.getString("org") + val user = hubAdminConfigObject.toConfig.getString("user") + val resource = organization + "/" + user + + if(organization.nonEmpty && organization == "root") { + if(user.nonEmpty && user != "root") { + configHubAdmins += (resource -> + UserRow(admin = false, + email = "", + hashedPw = + try { + Password.hashIfNot(hubAdminConfigObject.toConfig.getString("password")) + } + catch { + case _: Exception => "" + }, + hubAdmin = true, + lastUpdated = timeStamp, + orgid = organization, + username = resource, + updatedBy = "")) + } + else + logger.error(s"Hub Admin user cannot be root") + } else + logger.error(s"Hub Admin user {} must be a member of the root organization", resource) + } + catch { + case _: Exception => + logger.error(s"Hub Admin user not created from configuration.") + } + }) + + // Root is disabled on type mismatches, null values, and explicit disables in config. + val configRootPasswdHashed = { + try { + if(Configuration.getConfig.getBoolean("api.root.enabled")) + Password.hashIfNot(Configuration.getConfig.getString("api.root.password")) + else + "" + } + catch { + case _: Exception => "" + } + } + + val something = + for { + numOrgsUpdated <- + Compiled(OrgsTQ.filter(_.orgid === "root") + .map(organization => (organization.lastUpdated, organization.tags))) + .update((timeStamp, ibmCloudAcctID)) + + organizationUpdated: Seq[ResourceChangeRow] = + if(numOrgsUpdated.equals(1)) + Seq(ResourceChangeRow(category = ResChangeCategory.ORG.toString, + changeId = 0L, + id = "root", + lastUpdated = changeTimestamp, + operation = ResChangeOperation.MODIFIED.toString, + orgId = "root", + public = "false", + resource = ResChangeResource.ORG.toString)) + else + Seq.empty[ResourceChangeRow] + + requiredOrgs <- + Compiled(OrgsTQ.filter(_.orgid inSet Seq("IBM", "root")) + .map(_.orgid)) + .result + + createdOrgs <- + (OrgsTQ returning OrgsTQ.map(_.orgid)) ++= + InitialOrganizations.filterNot(organization => + requiredOrgs.nonEmpty && + requiredOrgs.contains(organization.orgId)) + + organizationsCreated: Seq[ResourceChangeRow] = + organizationUpdated :++ createdOrgs.map( + organization => + (ResourceChangeRow(category = ResChangeCategory.ORG.toString, + changeId = 0L, + id = organization, + lastUpdated = changeTimestamp, + operation = ResChangeOperation.CREATED.toString, + orgId = organization, + public = "false", + resource = ResChangeResource.ORG.toString)) + ) + + _ <- + ResourceChangesTQ ++= organizationsCreated + + numUsersUpdated <- + Compiled(UsersTQ.filter(_.username === "root/root") + .map(user => (user.lastUpdated, user.password))) + .update((timeStamp, configRootPasswdHashed)) + + existingUsers <- + Compiled(UsersTQ.filter(_.orgid === "root") + .map(_.username)) + .result + + createdUsers <- + (UsersTQ returning UsersTQ.map(_.username)) ++= { + if (numUsersUpdated == 1) + configHubAdmins.filterNot(hubadmin => existingUsers.contains(hubadmin._1)).values.toSeq + else + configHubAdmins.filterNot(hubadmin => existingUsers.contains(hubadmin._1)).values.toSeq :+ + UserRow(admin = true, email = "", hashedPw = configRootPasswdHashed, hubAdmin = true, lastUpdated = timeStamp, orgid = "root", username = "root/root", updatedBy = "") + } + + _ = { + AuthCache.putUser(Role.superUser, configRootPasswdHashed, "") + for (user <- createdUsers.filterNot(_ == "root/root")) { + AuthCache.putUser(user, configHubAdmins(user).hashedPw, "") + } + } + + } yield() + + db.run(something.transactionally.asTry).map({ + case Success(_) => + logger.info("Successfully updated/inserted root org, root user, IBM org, and hub admins from config") + case Failure(t) => + logger.error(s"Failed to update/insert root org, root user, IBM org, and hub admins from config: ${t.toString}") + }) + } + /** Upgrades the db schema, or inits the db if necessary. Called every start up. */ // The timeout exception that this can throw is handled by the caller of upgradeDb() def upgradeDb(db: Database)(implicit logger: LoggingAdapter, executionContext: ExecutionContext): Unit = { @@ -186,7 +362,7 @@ object ExchangeApiTables { else ApiResponse(ApiRespType.INTERNAL_ERROR, ExchMsg.translate("db.not.upgraded", t.toString)) }), - Duration(ExchConfig.getInt("api.db.upgradeTimeoutSeconds"), SECONDS)) // this is the rest of Await.result(), wait 1 minute for db init/upgrade to complete + Duration(Configuration.getConfig.getInt("api.db.upgradeTimeoutSeconds"), SECONDS)) // this is the rest of Await.result(), wait 1 minute for db init/upgrade to complete val initializeSchema = @@ -241,7 +417,7 @@ object ExchangeApiTables { ApiResponse(ApiRespType.INTERNAL_ERROR, ExchMsg.translate("db.not.upgraded", t.toString)) } }), - Duration(ExchConfig.getInt("api.db.upgradeTimeoutSeconds"), SECONDS)) + Duration(Configuration.getConfig.getInt("api.db.upgradeTimeoutSeconds"), SECONDS)) else ApiResponse(ApiRespType.OK, ExchMsg.translate("db.upgraded.successfully")) } @@ -261,6 +437,8 @@ object ExchangeApiTables { logger.error("ERROR: failure to upgrade db: " + upgradeResult.msg) system.terminate() } + + createRoot(db) } /* The timeout exception that this can throw is handled by the caller of upgradeDb() val upgradeResult: ApiResponse = Await.result(db.run(SchemaTQ.getSchemaRow.result.asTry.flatMap({ xs => @@ -300,5 +478,5 @@ object ExchangeApiTables { else ApiResponse(ApiRespType.INTERNAL_ERROR, ExchMsg.translate("db.not.upgraded", t.toString)) // we hit some problem } - }), Duration(ExchConfig.getInt("api.db.upgradeTimeoutSeconds"), SECONDS)) // this is the rest of Await.result(), wait 1 minute for db init/upgrade to complete */ + }), Duration(Configuration.getConfig.getInt("api.db.upgradeTimeoutSeconds"), SECONDS)) // this is the rest of Await.result(), wait 1 minute for db init/upgrade to complete */ } diff --git a/src/main/scala/org/openhorizon/exchangeapi/table/node/Node.scala b/src/main/scala/org/openhorizon/exchangeapi/table/node/Node.scala index 22aa47a0..0322405b 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/table/node/Node.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/table/node/Node.scala @@ -1,41 +1,205 @@ package org.openhorizon.exchangeapi.table.node +import org.json4s.{DefaultFormats, Formats} +import org.json4s.jackson.Serialization.read import org.openhorizon.exchangeapi.table.deploymentpattern.OneUserInputService // This is the node table minus the key - used as the data structure to return to the REST clients -class Node(var token: String, - var name: String, - var owner: String, - var nodeType: String, - var pattern: String, - var registeredServices: List[RegService], - var userInput: List[OneUserInputService], - var msgEndPoint: String, - var softwareVersions: Map[String,String], - var lastHeartbeat: String, - var publicKey: String, - var arch: String, - var heartbeatIntervals: NodeHeartbeatIntervals, - var ha_group: Option[String], - var lastUpdated: String, - var clusterNamespace: String = "", - var isNamespaceScoped: Boolean = false) { - def copy = - new Node(arch = arch, - clusterNamespace = clusterNamespace, +// Default Constructor +case class Node(var token: String, + var name: String, + var owner: String, + var nodeType: String, + var pattern: String, + var registeredServices: List[RegService], + var userInput: List[OneUserInputService], + var msgEndPoint: String, + var softwareVersions: Map[String,String], + var lastHeartbeat: String, + var publicKey: String, + var arch: String, + var heartbeatIntervals: NodeHeartbeatIntervals, + var ha_group: Option[String], + var lastUpdated: String, + var clusterNamespace: String = "", + var isNamespaceScoped: Boolean = false) { + // NodeRow Constructor. Have to provide the HA Group separately. + def this(haGroup: Option[String], nodeRow: NodeRow) = + this (arch = nodeRow.arch, + clusterNamespace = nodeRow.clusterNamespace.getOrElse(""), + ha_group = haGroup, + heartbeatIntervals = + if (nodeRow.heartbeatIntervals.nonEmpty) { + implicit val jsonFormats: Formats = DefaultFormats + read[NodeHeartbeatIntervals](nodeRow.heartbeatIntervals) + } + else + NodeHeartbeatIntervals(0, 0, 0), + isNamespaceScoped = nodeRow.isNamespaceScoped, + lastHeartbeat = nodeRow.lastHeartbeat.orNull, + lastUpdated = nodeRow.lastUpdated, + msgEndPoint = nodeRow.msgEndPoint, + name = nodeRow.name, + nodeType = + if (nodeRow.nodeType.isEmpty) + NodeType.DEVICE.toString + else + nodeRow.nodeType, + owner = nodeRow.owner, + pattern = nodeRow.pattern, + publicKey = nodeRow.publicKey, + registeredServices = + (if (nodeRow.regServices.nonEmpty) { + implicit val jsonFormats: Formats = DefaultFormats + read[List[RegService]](nodeRow.regServices) + } + else + List[RegService]()).map(rs => RegService(rs.url, rs.numAgreements, rs.configState.orElse(Option("active")), rs.policy, rs.properties, rs.version.orElse(Some("")))), + softwareVersions = + if (nodeRow.softwareVersions.nonEmpty) { + implicit val jsonFormats: Formats = DefaultFormats + read[Map[String, String]](nodeRow.softwareVersions) + } + else + Map[String, String](), + token = nodeRow.token, + userInput = + (if (nodeRow.userInput.nonEmpty) { + implicit val jsonFormats: Formats = DefaultFormats + read[List[OneUserInputService]](nodeRow.userInput) + } + else + List[OneUserInputService]())) + + // Basic Tuple Constructor. When dealing with non-proven shapes from Slick. + def this(node: (String, // arch + Option[String], // clusterNamespace + Option[String], // ha_group + String, // heartbeatIntervals + Boolean, // isNamespaceScoped + Option[String], // lastHeartbeat + String, // lastUpdated + String, // msgEndPoint + String, // name + String, // nodeType + String, // owner + String, // pattern + String, // publicKey + String, // regServices + String, // softwareVersions + String, // token + String)) = // userInput + this(arch = node._1, + clusterNamespace = node._2.getOrElse(""), + ha_group = node._3, + heartbeatIntervals = + if (node._4.nonEmpty) { + implicit val jsonFormats: Formats = DefaultFormats + read[NodeHeartbeatIntervals](node._4) + } + else + NodeHeartbeatIntervals(0, 0, 0), + isNamespaceScoped = node._5, + lastHeartbeat = node._6.orNull, + lastUpdated = node._7, + msgEndPoint = node._8, + name = node._9, + nodeType = + if (node._10.isEmpty) + NodeType.DEVICE.toString + else + node._10, + owner = node._11, + pattern = node._12, + publicKey = node._13, + registeredServices = + (if (node._14.nonEmpty) { + implicit val jsonFormats: Formats = DefaultFormats + read[List[RegService]](node._14) + } + else + List[RegService]()).map(rs => RegService(rs.url, rs.numAgreements, rs.configState.orElse(Option("active")), rs.policy, rs.properties, rs.version.orElse(Some("")))), + softwareVersions = + if (node._15.nonEmpty) { + implicit val jsonFormats: Formats = DefaultFormats + read[Map[String, String]](node._15) + } + else + Map[String, String](), + token = node._16, + userInput = + if (node._17.nonEmpty) { + implicit val jsonFormats: Formats = DefaultFormats + read[List[OneUserInputService]](node._17) + } + else + List[OneUserInputService]()) + + // Values Constructor. Will convert strings to JSON. + def this(arch: String, + clusterNamespace: Option[String], + ha_group: Option[String], + heartbeatIntervals: String, + isNamespaceScoped: Boolean, + lastHeartbeat: Option[String], + lastUpdated: String, + msgEndPoint: String, + name: String, + nodeType: String, + owner: String, + pattern: String, + publicKey: String, + regServices: String, + softwareVersions: String, + token: String, + userInput: String) = + this(arch = arch, + clusterNamespace = clusterNamespace.getOrElse(""), ha_group = ha_group, - heartbeatIntervals = heartbeatIntervals, + heartbeatIntervals = + if (heartbeatIntervals.nonEmpty) { + implicit val jsonFormats: Formats = DefaultFormats + read[NodeHeartbeatIntervals](heartbeatIntervals) + } + else + NodeHeartbeatIntervals(0, 0, 0), isNamespaceScoped = isNamespaceScoped, - lastHeartbeat = lastHeartbeat, + lastHeartbeat = lastHeartbeat.orNull, lastUpdated = lastUpdated, msgEndPoint = msgEndPoint, name = name, - nodeType = nodeType, - owner = owner, - pattern = pattern, - publicKey = publicKey, - registeredServices = registeredServices, - softwareVersions = softwareVersions, - token = token, - userInput = userInput) + nodeType = + if (nodeType.isEmpty) + NodeType.DEVICE.toString + else + nodeType, + owner = owner, + pattern = pattern, + publicKey = publicKey, + registeredServices = + (if (regServices.nonEmpty) { + implicit val jsonFormats: Formats = DefaultFormats + read[List[RegService]](regServices) + } + else + List[RegService]()).map(rs => RegService(rs.url, rs.numAgreements, rs.configState.orElse(Option("active")), rs.policy, rs.properties, rs.version.orElse(Some("")))), + softwareVersions = + if (softwareVersions.nonEmpty) { + implicit val jsonFormats: Formats = DefaultFormats + read[Map[String, String]](softwareVersions) + } + else + Map[String, String](), + token = + if(token.isEmpty) + "***************" + else + token, + userInput = + if (userInput.nonEmpty) { + implicit val jsonFormats: Formats = DefaultFormats + read[List[OneUserInputService]](userInput) + } + else + List[OneUserInputService]()) } diff --git a/src/main/scala/org/openhorizon/exchangeapi/table/node/NodesTQ.scala b/src/main/scala/org/openhorizon/exchangeapi/table/node/NodesTQ.scala index 041210bd..f689e91a 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/table/node/NodesTQ.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/table/node/NodesTQ.scala @@ -1,5 +1,6 @@ package org.openhorizon.exchangeapi.table.node +import org.openhorizon.exchangeapi.auth.Identity import org.openhorizon.exchangeapi.table.deploymentpattern.OneUserInputService import org.openhorizon.exchangeapi.table.node.group.NodeGroupTQ import org.openhorizon.exchangeapi.table.node.group.assignment.NodeGroupAssignmentTQ diff --git a/src/main/scala/org/openhorizon/exchangeapi/utility/ApiTime.scala b/src/main/scala/org/openhorizon/exchangeapi/utility/ApiTime.scala index 918ee2c0..e6a4cb56 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/utility/ApiTime.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/utility/ApiTime.scala @@ -49,20 +49,22 @@ object ApiTime { } def fixFormatting(time: String): String = { - val timeLength: Int = time.length /* - This implementation uses length of the string instead of a regex to make it as fast as possible - The problem that was happening is described here: https://bugs.openjdk.java.net/browse/JDK-8193307 - Essentially the returned string would truncate milliseconds or seconds and milliseconds if those values happened to be 0 - So we would be getting: - uuuu-MM-dd'T'HH:mm (Ex: "2020-02-05T20:28Z[UTC]") - uuuu-MM-dd'T'HH:mm:ss (Ex: "2020-02-05T20:28:14Z[UTC]") - Instead of what we want : uuuu-MM-dd'T'HH:mm:ss.SSS (Ex: "2020-02-05T20:28:14.469Z[UTC]") - This implementation serves to ensure we always get time in the format we expect - This is explained in the docs here: https://docs.oracle.com/javase/9/docs/api/java/time/LocalDateTime.html#toString-- - length when time is fully filled out is 29 - length when time has no milliseconds 25 - length when time has no seconds and no milliseconds is 22 - */ if (timeLength >= 29) { // if its the correct length just return it + val timeLength: Int = time.length + /* + This implementation uses length of the string instead of a regex to make it as fast as possible + The problem that was happening is described here: https://bugs.openjdk.java.net/browse/JDK-8193307 + Essentially the returned string would truncate milliseconds or seconds and milliseconds if those values happened to be 0 + So we would be getting: + uuuu-MM-dd'T'HH:mm (Ex: "2020-02-05T20:28Z[UTC]") + uuuu-MM-dd'T'HH:mm:ss (Ex: "2020-02-05T20:28:14Z[UTC]") + Instead of what we want : uuuu-MM-dd'T'HH:mm:ss.SSS (Ex: "2020-02-05T20:28:14.469Z[UTC]") + This implementation serves to ensure we always get time in the format we expect + This is explained in the docs here: https://docs.oracle.com/javase/9/docs/api/java/time/LocalDateTime.html#toString-- + length when time is fully filled out is 29 + length when time has no milliseconds 25 + length when time has no seconds and no milliseconds is 22 + */ + if (timeLength >= 29) { // if its the correct length just return it time } else if (timeLength == 25) { // need to add milliseconds on time.substring(0, 19) + ".000Z[UTC]" diff --git a/src/main/scala/org/openhorizon/exchangeapi/utility/Configuration.scala b/src/main/scala/org/openhorizon/exchangeapi/utility/Configuration.scala new file mode 100644 index 00000000..3dc2707a --- /dev/null +++ b/src/main/scala/org/openhorizon/exchangeapi/utility/Configuration.scala @@ -0,0 +1,128 @@ +package org.openhorizon.exchangeapi.utility + +import com.typesafe.config.{Config, ConfigException, ConfigFactory, ConfigParseOptions} + +import scala.util.Properties + +case object Configuration { + private val configResource: String = "exchange.conf" + private var config: Config = init() + + private def init(): Config = { + config = load() + + // Set Java System Properties for logback.xml when the application config is initialized + setLogBackProperties(config) + + config + } + + /** + * @return Config + * + * @throws ConfigException.BugOrBroken throws on config validation failure. + * @throws ConfigException.NotResolved Throws on config validation failure. + * @throws ConfigException.ValidationFailed Throws on config validation failure. + **/ + private def load(): Config = { + val config = ConfigFactory.load(configResource) + + config.checkValid(ConfigFactory.defaultReference(), "api") + + config + } + + /** + * @param properties Java system properties map + * + * @return Config + * + * @throws ConfigException.BugOrBroken throws on config validation failure. + * @throws ConfigException.NotResolved Throws on config validation failure. + * @throws ConfigException.ValidationFailed Throws on config validation failure. + **/ + private def load(properties: java.util.Properties): Config = { + val config = ConfigFactory.parseProperties(properties, ConfigParseOptions.defaults()).withFallback(ConfigFactory.load(configResource)) + + config.checkValid(ConfigFactory.defaultReference(), "api") + + config + } + + // Convert Typesafe Config to Java System Properties. These will be substituted into the logback.xml config. + // These have to be set early enough in the application's start-up to take effect. + private def setLogBackProperties(config: Config): Unit = { + Properties.setProp("log.logback.appenderrefmodelhandler", config.getString("logback.core.model.processor.AppenderRefModelHandler")) + Properties.setProp("log.logback.loggermodelhandler", config.getString("logback.classic.model.processor.LoggerModelHandler")) + Properties.setProp("log.hikari.config", config.getString("logback.hikari.HikariConfig")) + Properties.setProp("log.hikari.datasource", config.getString("logback.hikari.HikariDataSource")) + Properties.setProp("log.hikari.pool", config.getString("logback.hikari.pool.HikariPool")) + Properties.setProp("log.hikari.pool.base", config.getString("logback.hikari.pool.PoolBase")) + Properties.setProp("log.guavacache", config.getString("logback.scalacache.guava.GuavaCache")) + Properties.setProp("log.action", config.getString("logback.slick.basic.BasicBackend.action")) + Properties.setProp("log.stream", config.getString("logback.slick.basic.BasicBackend.stream")) + Properties.setProp("log.qcomp", config.getString("logback.slick.compiler-log")) + Properties.setProp("log.qcomp.assignUniqueSymbols", config.getString("logback.slick.compiler.AssignUniqueSymbols")) + Properties.setProp("log.qcomp.codeGen", config.getString("logback.slick.compiler.CodeGen")) + Properties.setProp("log.qcomp.createAggregates", config.getString("logback.slick.compiler.CreateAggregates")) + Properties.setProp("log.qcomp.createResultSetMapping", config.getString("logback.slick.compiler.CreateResultSetMapping")) + Properties.setProp("log.qcomp.emulateOuterJoins", config.getString("logback.slick.compiler.EmulateOuterJoins")) + Properties.setProp("log.qcomp.expandConditionals", config.getString("logback.slick.compiler.ExpandConditionals")) + Properties.setProp("log.qcomp.expandRecords", config.getString("logback.slick.compiler.ExpandRecords")) + Properties.setProp("log.qcomp.expandSums", config.getString("logback.slick.compiler.ExpandSums")) + Properties.setProp("log.qcomp.expandTables", config.getString("logback.slick.compiler.ExpandTables")) + Properties.setProp("log.qcomp.fixRowNumberOrdering", config.getString("logback.slick.compiler.FixRowNumberOrdering")) + Properties.setProp("log.qcomp.flattenProjections", config.getString("logback.slick.compiler.FlattenProjections")) + Properties.setProp("log.qcomp.forceOuterBinds", config.getString("logback.slick.compiler.ForceOuterBinds")) + Properties.setProp("log.qcomp.hoistClientOps", config.getString("logback.slick.compiler.HoistClientOps")) + Properties.setProp("log.qcomp.inferTypes", config.getString("logback.slick.compiler.InferTypes")) + Properties.setProp("log.qcomp.inline", config.getString("logback.slick.compiler.Inline")) + Properties.setProp("log.qcomp.insertCompiler", config.getString("logback.slick.compiler.InsertCompiler")) + Properties.setProp("log.qcomp.mergeToComprehensions", config.getString("logback.slick.compiler.MergeToComprehensions")) + Properties.setProp("log.qcomp.optimizeScalar", config.getString("logback.slick.compiler.OptimizeScalar")) + Properties.setProp("log.qcomp.pruneProjections", config.getString("logback.slick.compiler.PruneProjections")) + Properties.setProp("log.qcomp.phases", config.getString("logback.slick.compiler.QueryCompiler")) + Properties.setProp("log.qcomp.bench", config.getString("logback.slick.compiler.QueryCompilerBenchmark")) + Properties.setProp("log.qcomp.removeFieldNames", config.getString("logback.slick.compiler.RemoveFieldNames")) + Properties.setProp("log.qcomp.removeMappedTypes", config.getString("logback.slick.compiler.RemoveMappedTypes")) + Properties.setProp("log.qcomp.removeTakeDrop", config.getString("logback.slick.compiler.RemoveTakeDrop")) + Properties.setProp("log.qcomp.reorderOperations", config.getString("logback.slick.compiler.ReorderOperations")) + Properties.setProp("log.qcomp.resolveZipJoins", config.getString("logback.slick.compiler.ResolveZipJoins")) + Properties.setProp("log.qcomp.rewriteBooleans", config.getString("logback.slick.compiler.RewriteBooleans")) + Properties.setProp("log.qcomp.rewriteDistinct", config.getString("logback.slick.compiler.RewriteDistinct")) + Properties.setProp("log.qcomp.rewriteJoins", config.getString("logback.slick.compiler.RewriteJoins")) + Properties.setProp("log.qcomp.specializeParameters", config.getString("logback.slick.compiler.SpecializeParameters")) + Properties.setProp("log.qcomp.verifyTypes", config.getString("logback.slick.compiler.VerifyTypes")) + Properties.setProp("log.jdbc.driver", config.getString("logback.slick.jdbc.DriverDataSource")) + Properties.setProp("log.jdbc.bench", config.getString("logback.slick.jdbc.JdbcBackend.benchmark")) + Properties.setProp("log.jdbc.parameter", config.getString("logback.slick.jdbc.JdbcBackend.parameter")) + Properties.setProp("log.jdbc.statement", config.getString("logback.slick.jdbc.JdbcBackend.statement")) + Properties.setProp("log.jdbc.parameter", config.getString("logback.slick.jdbc.JdbcBackend.statementAndParameter")) + Properties.setProp("log.jdbc.result", config.getString("logback.slick.jdbc.JdbcModelBuilder")) + Properties.setProp("log.createModel", config.getString("logback.slick.jdbc.StatementInvoker.result")) + Properties.setProp("log.heap", config.getString("logback.slick.memory.HeapBackend")) + Properties.setProp("log.interpreter", config.getString("logback.slick.memory.QueryInterpreter")) + Properties.setProp("log.resultConverter", config.getString("logback.slick.relational.ResultConverterCompiler")) + Properties.setProp("log.asyncExecutor", config.getString("logback.slick.util.AsyncExecutor")) + Properties.setProp("log.swagger.modelconvertercontextimpl", config.getString("logback.swagger.v3.core.converter.ModelConverterContextImpl")) + Properties.setProp("log.swagger.jaxrs2.reader", config.getString("logback.swagger.v3.jaxrs2.Reader")) + } + + def getConfig: Config = config + def setConfig(config: Config): Unit = + this.config = config + + // Logback can only be configured on startup. + def reload(): Unit = { + ConfigFactory.invalidateCaches() + + config = load() + } + + // Logback can only be configured on startup. + def reload(properties: java.util.Properties): Unit = { + ConfigFactory.invalidateCaches() + + config = load(properties = properties) + } +} diff --git a/src/main/scala/org/openhorizon/exchangeapi/utility/DatabaseConnection.scala b/src/main/scala/org/openhorizon/exchangeapi/utility/DatabaseConnection.scala new file mode 100644 index 00000000..1c510fec --- /dev/null +++ b/src/main/scala/org/openhorizon/exchangeapi/utility/DatabaseConnection.scala @@ -0,0 +1,9 @@ +package org.openhorizon.exchangeapi.utility + +import slick.jdbc.PostgresProfile.api._ + +case object DatabaseConnection { + private val database: Database = Database.forConfig("exchange-db-connection", Configuration.getConfig) + + def getDatabase: Database = database +} diff --git a/src/main/scala/org/openhorizon/exchangeapi/utility/ExchConfig.scala b/src/main/scala/org/openhorizon/exchangeapi/utility/ExchConfig.scala deleted file mode 100644 index cc6c033a..00000000 --- a/src/main/scala/org/openhorizon/exchangeapi/utility/ExchConfig.scala +++ /dev/null @@ -1,223 +0,0 @@ -package org.openhorizon.exchangeapi.utility - -import org.apache.pekko.event.LoggingAdapter -import com.typesafe.config.{Config, ConfigFactory, ConfigParseOptions, ConfigSyntax, ConfigValue, ConfigValueFactory} -import org.json4s.JObject -import org.json4s.JsonAST.JString -import org.openhorizon.exchangeapi.auth.{AuthCache, AuthRoles, Password, Role} -import org.openhorizon.exchangeapi.table.organization.OrgRow -import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.{ExchangeApi} -import slick.jdbc -import slick.jdbc.PostgresProfile.api._ -import slick.jdbc.PostgresProfile.api.Database - -import java.io.File -import java.util.Properties -import scala.collection.mutable.ListBuffer -import scala.concurrent.ExecutionContext -import scala.jdk.CollectionConverters.{CollectionHasAsScala, MapHasAsJava, MapHasAsScala} -import scala.util.{Failure, Success} - -/** Global config parameters for the exchange. See typesafe config classes: http://typesafehub.github.io/config/latest/api/ */ -object ExchConfig { - val configResourceName = "config.json" - val configFileName: String = "/etc/horizon/exchange/" + configResourceName - // The syntax called CONF is typesafe's superset of json that allows comments, etc. See https://github.com/typesafehub/config#using-hocon-the-json-superset. Strict json would be ConfigSyntax.JSON. - val configOpts: ConfigParseOptions = ConfigParseOptions.defaults().setSyntax(ConfigSyntax.CONF).setAllowMissing(false) - var config: Config = ConfigFactory.parseResources(configResourceName, configOpts) // these are the default values, this file is bundled in the jar - var configUser: Config = _ - - //var defaultExecutionContext: ExecutionContext = _ // this gets set early by ExchangeApiApp - implicit def executionContext: ExecutionContext = ExchangeApi.defaultExecutionContext - - def logger: LoggingAdapter = ExchangeApi.defaultLogger - - var rootHashedPw = "" // so we can remember the hashed pw between load() and createRoot() - - // Tries to load the user's external config file - // Note: logger doesn't have a valid value at the time of this call - def load(): Unit = { - val f = new File(configFileName) - if (f.isFile) { // checks if it exists and is a regular file - configUser = ConfigFactory.parseFile(f, configOpts) - config = configUser.withFallback(config) // uses the defaults for anything not specified in the external config file - println("Using config file " + configFileName) - } else { - println("Config file " + configFileName + " not found. Running with defaults suitable for local development.") - } - - // Read the ACLs and set them in our Role object - val roles: Map[String, ConfigValue] = config.getObject("api.acls").asScala.toMap - for ((role, _) <- roles) { - val accessSet: Set[String] = getStringList("api.acls." + role).toSet - if (!Role.isValidAcessValues(accessSet)) println("Error: invalid value in ACLs in config file for role " + role) else Role.setRole(role, accessSet) - } - println(s"Roles: ${Role.roles}") - if (!Role.haveRequiredRoles) println("Error: at least these roles must be set in the config file: " + AuthRoles.requiredRoles.mkString(", ")) - - // Note: currently there is no other value besides guava - AuthCache.cacheType = config.getString("api.cache.type") // need to do this before using the cache in the next step - } - - def getLogLevel: String = { - val loglev: String = config.getString("api.logging.level") - if (loglev == "") LogLevel.INFO // default - else if (LogLevel.validLevels.contains(loglev)) loglev else { - println("Invalid logging level '" + loglev + "' specified in config.json. Continuing with the default logging level " + LogLevel.INFO + ".") - LogLevel.INFO // fallback - } - } - - def getHostAndPort: (String, Option[Int], Option[Int]) = { - var host: String = config.getString("api.service.host") - if (host.isEmpty) host = "0.0.0.0" - - val portEncrypted: Option[Int] = { - try { - Option(config.getInt("api.service.portEncrypted")) - } catch { - case _: Exception => None - } - } - val portUnencrypted: Option[Int] = { - try { - Option(config.getInt("api.service.port")) - } catch { - case _: Exception => None - } - } - - (portEncrypted, portUnencrypted) match { - case (None, None) => (host, None, Option(8080)) - case (a, b) if (a == b) => (host, None, portUnencrypted) - case _ => (host, portEncrypted, portUnencrypted) - } - } - - - // Get relevant values from our config file to create the akka config - /* - * Akka has a better way of handling application configuration, unfortunately the project also needs to be - * backwards-compatible with this custom method of handling configuration. Primarily has an impact on the - * configurations for akka-core and akka-http (this method). - */ def getAkkaConfig: Config = { - var akkaConfig: Map[String, ConfigValue] = config.getObject("api.akka").asScala.toMap - val secondsToWait: Int = ExchConfig.getInt("api.service.shutdownWaitForRequestsToComplete") - - akkaConfig = akkaConfig ++ Map[scala.Predef.String, ConfigValue]("pekko.coordinated-shutdown.phases.service-unbind.timeout" -> ConfigValueFactory.fromAnyRef(s"${secondsToWait}s")) - akkaConfig = akkaConfig ++ Map[scala.Predef.String, ConfigValue]("pekko.loglevel" -> ConfigValueFactory.fromAnyRef(ExchConfig.getLogLevel)) - - // printf("Running with akka config: %s\n", akkaConfig.toString()) - - // Highest priority to lowest priority. - configUser.withFallback(ConfigFactory.parseMap(akkaConfig.asJava)).withFallback(ConfigFactory.parseResources("config.json")) - } - - // Put the root user in the auth cache in case the db has not been inited yet and they need to be able to run POST /admin/initdb - def createRootInCache(): Unit = { - val rootpw: String = config.getString("api.root.password") - val rootIsEnabled: Boolean = config.getBoolean("api.root.enabled") - if (rootpw == "" || !rootIsEnabled) { - logger.warning("Root password is not specified in config.json or is not enabled. You will not be able to do exchange operations that require root privilege.") - return - } - if (rootHashedPw == "") { // this is the 1st time, we need to hash and save it - rootHashedPw = Password.hashIfNot(rootpw) - } - val rootUnhashedPw: String = if (Password.isHashed(rootpw)) "" else rootpw // this is the 1 case in which an id cache entry could end up with a blank unhashed pw/tok - AuthCache.putUser(Role.superUser, rootHashedPw, rootUnhashedPw) - logger.info("Root user from config.json added to the in-memory authentication cache") - } - - //todo: investigate if this does the right things when called from POST /admin/reload - def reload(): Unit = load() - - /** - * Set a few values on top of the current config. These values are not saved persistently, and therefore will only set it in this 1 exchange instance, - * and therefore will *not* work when the exchange is running in multi-node config. This method is used mostly for automated testing. */ - def mod(props: Properties): Unit = { - config = ConfigFactory.parseProperties(props).withFallback(config) - } - - /** Create the root user in the DB. This is done separately from load() because we need the db execution context */ - def createRoot(db: Database): Unit = { - // If the root pw is set in the config file, create or update the root user in the db to match - val rootpw: String = config.getString("api.root.password") - val rootIsEnabled: Boolean = config.getBoolean("api.root.enabled") - if (rootpw == "" || !rootIsEnabled) { - rootHashedPw = "" // this should already be true, but just make sure - } else { // there is a real, enabled root pw - //val hashedPw = Password.hashIfNot(rootpw) <- can't hash this again, because it would be different - if (rootHashedPw == "") logger.error("Internal Error: rootHashedPw not already set") - val rootUnhashedPw: String = if (Password.isHashed(rootpw)) "" else rootpw // this is the 1 case in which an id cache entry could not have an unhashed pw/tok - AuthCache.putUser(Role.superUser, rootHashedPw, rootUnhashedPw) // put it in AuthCache even if it does not get successfully written to the db, so we have a chance to fix it - } - // Put the root org and user in the db, even if root is disabled (because in that case we want all exchange instances to know the root pw is blank - //val rootemail = config.getString("api.root.email") - val rootemail = "" // Create the root org, create the IBM org, create the root user, and create hub admins listed in config.json (all only if necessary) - - val rootOrg: OrgRow = OrgRow(orgId = "root", orgType = "", label = "Root Org", description = "Organization for the root user only", lastUpdated = ApiTime.nowUTC, tags = { - try { - Some(JObject("ibmcloud_id" -> JString(config.getString("api.root.account_id")))) - } catch { - case _: Exception => None - } - }, limits = "", heartbeatIntervals = "") - - val rootUser: UserRow = UserRow(admin = true, email = rootemail, hashedPw = rootHashedPw, hubAdmin = true, lastUpdated = ApiTime.nowUTC, orgid = "root", updatedBy = Role.superUser, username = Role.superUser) - - val IBMOrg: OrgRow = OrgRow(description = "Organization containing IBM services", heartbeatIntervals = "", label = "IBM Org", lastUpdated = ApiTime.nowUTC, limits = "", orgId = "IBM", orgType = "IBM", tags = None) - - val configHubAdmins: ListBuffer[UserRow] = ListBuffer.empty[UserRow] - config.getObjectList("api.hubadmins").asScala.foreach({ c => - if (c.toConfig.getString("org") == "root") { - configHubAdmins += UserRow(hashedPw = { - val credential: Option[String] = try { - Option(c.toConfig.getString("password")) - } catch { - case _: Exception => None - } - if (credential.isEmpty) "" // No password, IAM User. - else if (Password.isHashed(credential.get)) credential.get // Password is already hashed. - else Password.hash(credential.get) // Plain-text, hash. - }, orgid = c.toConfig.getString("org"), username = c.toConfig.getString("org") + "/" + c.toConfig.getString("user"), admin = false, hubAdmin = true, email = "", lastUpdated = ApiTime.nowUTC, updatedBy = "") - } else { - logger.error(s"Hub Admin '${c.toConfig.getString("user")}' not created: hub admin must be in the root org") - } - }) - - val query = for {existingUsers <- UsersTQ.filter(_.username inSet configHubAdmins.map(_.username)).map(_.username).result //get all users whose usernames match a hub admin username in config.json - _ <- DBIO.seq(rootOrg.upsert, rootUser.upsertUser, IBMOrg.upsert, UsersTQ ++= configHubAdmins.filter(a => !existingUsers.contains(a.username)) //only insert the ones whose usernames don't already exist - )} yield existingUsers - - db.run(query.transactionally.asTry).map({ case Success(result) => for (badUser <- result) { - logger.warning(s"Hub Admin '$badUser' not created: a user with this username already exists") - } - logger.info("Successfully updated/inserted root org, root user, IBM org, and hub admins from config") - case Failure(t) => logger.error(s"Failed to update/insert root org, root user, IBM org, and hub admins from config: ${t.toString}") - }) - } - - //someday: we should catch ConfigException.Missing and ConfigException.WrongType, but they are always set by the built-in config.json - - /** Returns the value of the specified config variable. Throws com.typesafe.config.ConfigException.* if not found. */ - def getString(key: String): String = { - config.getString(key) - } - - /** Returns the value of the specified config variable. Throws com.typesafe.config.ConfigException.* if not found. */ - def getStringList(key: String): List[String] = { - config.getStringList(key).asScala.toList - } - - /** Returns the value of the specified config variable. Throws com.typesafe.config.ConfigException.* if not found. */ - def getInt(key: String): Int = { - config.getInt(key) - } - - /** Returns the value of the specified config variable. Throws com.typesafe.config.ConfigException.* if not found. */ - def getBoolean(key: String): Boolean = { - config.getBoolean(key) - } -} diff --git a/src/main/scala/org/openhorizon/exchangeapi/utility/ExchMsg.scala b/src/main/scala/org/openhorizon/exchangeapi/utility/ExchMsg.scala index b606e3c3..0d56e55c 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/utility/ExchMsg.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/utility/ExchMsg.scala @@ -7,10 +7,10 @@ object ExchMsg { def translate(key: String, args: Any*): String = { try { //todo: remove these 2 debug statements - val exchLang: String = sys.env.getOrElse("HZN_EXCHANGE_LANG", sys.env.getOrElse("LANG", "en")) + val exchLang: String = getLang if (exchLang.startsWith("zh") || exchLang.startsWith("pt")) println("using lang for msgs: " + exchLang) - implicit val userLang: Lang = Lang(sys.env.getOrElse("HZN_EXCHANGE_LANG", sys.env.getOrElse("LANG", "en"))) + implicit val userLang: Lang = Lang(exchLang) if (args.nonEmpty) { return Messages(key, args: _*) } @@ -20,5 +20,10 @@ object ExchMsg { } } - def getLang: String = sys.env.getOrElse("HZN_EXCHANGE_LANG", sys.env.getOrElse("LANG", "en")) + def getLang: String = + try + Configuration.getConfig.getString("api.language") + catch { + case _: Exception => "en" + } } diff --git a/src/main/scala/org/openhorizon/exchangeapi/utility/NodeAgbotTokenValidation.scala b/src/main/scala/org/openhorizon/exchangeapi/utility/NodeAgbotTokenValidation.scala index 27fa43b2..dc9ea947 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/utility/NodeAgbotTokenValidation.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/utility/NodeAgbotTokenValidation.scala @@ -9,12 +9,25 @@ object NodeAgbotTokenValidation { // (?=.*[a-z]) lowercase letter must occur at least once // (?=.*[A-Z]) uppercase letter must occur at least once // .{15,} minimum 15 chars - val exchLang: String = sys.env.getOrElse("HZN_EXCHANGE_LANG", sys.env.getOrElse("LANG", "en")) - val pwRegex: Regex = if (exchLang.contains("ja") || exchLang.contains("ko") || exchLang.contains("zh")) """^(?=.*[0-9]).{15,}$""".r else """^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z]).{15,}$""".r + val exchLang: String = getLanguage + val pwRegex: Regex = + if (exchLang.contains("ja") || + exchLang.contains("ko") || + exchLang.contains("zh")) + """^(?=.*[0-9]).{15,}$""".r + else + """^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z]).{15,}$""".r val valid: Boolean = token match { case pwRegex(_*) => true case _ => false } valid } + + def getLanguage: String = + try + Configuration.getConfig.getString("api.language") + catch { + case _: Exception => "en" + } } diff --git a/src/main/scala/org/openhorizon/exchangeapi/utility/StrConstants.scala b/src/main/scala/org/openhorizon/exchangeapi/utility/StrConstants.scala index 692d51b2..0d87f56a 100644 --- a/src/main/scala/org/openhorizon/exchangeapi/utility/StrConstants.scala +++ b/src/main/scala/org/openhorizon/exchangeapi/utility/StrConstants.scala @@ -1,5 +1,5 @@ package org.openhorizon.exchangeapi.utility object StrConstants { - val hiddenPw = "********" + val hiddenPw = "***************" } diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml new file mode 100644 index 00000000..7c98575c --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,96 @@ + + + + + + + [%date{ISO8601}] %-44([%logger]) [%marker] %-42([%thread]) %highlight(%-7([%level])) -%kvp- %msg%n + + + + + target/myapp-dev.log + c + [%date{ISO8601}] %-42([%logger]) [%marker] %-42([%thread]) %-7([%level]) - %msg%n + + + + + 8192 + true + + + + + 8192 + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/scala/org/openhorizon/exchangeapi/AdminSuite.scala b/src/test/scala/org/openhorizon/exchangeapi/AdminSuite.scala index 74436ce7..aeeae76d 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/AdminSuite.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/AdminSuite.scala @@ -6,7 +6,7 @@ import scala.util.matching.Regex import org.json4s.DefaultFormats import org.openhorizon.exchangeapi.route.administration.{AdminHashpwResponse, GetAdminOrgStatusResponse, GetAdminStatusResponse} import org.openhorizon.exchangeapi.table.organization.OrgRow -import org.openhorizon.exchangeapi.utility.ApiTime +import org.openhorizon.exchangeapi.utility.{ApiTime, Configuration} import scala.collection.immutable.List import scala.collection.mutable.ListBuffer @@ -52,7 +52,7 @@ class AdminSuite extends AnyFunSuite with BeforeAndAfterAll { private val CONTENT = ("Content-Type", "application/json") private val NODE: String = "node" private val PATTERN: String = "pattern" - private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", "") /* need to put this root pw in config.json */)) + private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val SERVICE: String = "service" private val URL = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1" private val USERS: List[String] = List("admin", "user") diff --git a/src/test/scala/org/openhorizon/exchangeapi/AgbotsSuite.scala b/src/test/scala/org/openhorizon/exchangeapi/AgbotsSuite.scala index fa26ce3a..a83c60a5 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/AgbotsSuite.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/AgbotsSuite.scala @@ -18,7 +18,7 @@ import org.openhorizon.exchangeapi.table.agreementbot.AAService import org.openhorizon.exchangeapi.table.deploymentpattern.{PServiceVersions, PServices} import org.openhorizon.exchangeapi.table.deploymentpolicy.{BService, BServiceVersions} import org.openhorizon.exchangeapi.table.resourcechange.ResChangeOperation -import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, ApiUtils, ExchConfig, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, ApiUtils, Configuration, HttpCode} import scala.collection.mutable.ListBuffer //import org.json4s.JsonDSL._ @@ -59,7 +59,7 @@ class AgbotsSuite extends AnyFunSuite { val USERAUTH2 = ("Authorization","Basic "+ApiUtils.encode(authpref2+user+":"+pw)) val BADAUTH = ("Authorization","Basic "+ApiUtils.encode(authpref+user+":"+pw+"x")) val rootuser = Role.superUser - val rootpw = sys.env.getOrElse("EXCHANGE_ROOTPW", "") // need to put this root pw in config.json + val rootpw = (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }) // need to put this root pw in config.json val ROOTAUTH = ("Authorization","Basic "+ApiUtils.encode(rootuser+":"+rootpw)) val agbotId = "9930" val orgagbotId = authpref+agbotId @@ -855,8 +855,8 @@ class AgbotsSuite extends AnyFunSuite { test("PUT /orgs/"+orgid+"/agbots/"+agbotId+"/agreements/9952 - with low maxAgreements") { if (runningLocally) { // changing limits via POST /admin/config does not work in multi-node mode // Get the current config value so we can restore it afterward - ExchConfig.load() - val origMaxAgreements = ExchConfig.getInt("api.limits.maxAgreements") + Configuration.reload() + val origMaxAgreements = Configuration.getConfig.getInt("api.limits.maxAgreements") // Change the maxAgreements config value in the svr var configInput = AdminConfigRequest("api.limits.maxAgreements", "1") @@ -1006,7 +1006,7 @@ class AgbotsSuite extends AnyFunSuite { if (runningLocally) { // changing limits via POST /admin/config does not work in multi-node mode // Get the current config value so we can restore it afterward // ExchConfig.load <-- done up above - val origMaxAgbots = ExchConfig.getInt("api.limits.maxAgbots") + val origMaxAgbots = Configuration.getConfig.getInt("api.limits.maxAgbots") // Change the maxAgbots config value in the svr var configInput = AdminConfigRequest("api.limits.maxAgbots", "1") @@ -1055,8 +1055,8 @@ class AgbotsSuite extends AnyFunSuite { test("PUT /orgs/"+orgid+"/changes - with low maxRecords") { if (runningLocally) { // changing limits via POST /admin/config does not work in multi-node mode // Get the current config value so we can restore it afterward - ExchConfig.load() - val origMaxRecords = ExchConfig.getInt("api.resourceChanges.maxRecordsCap") + Configuration.reload() + val origMaxRecords = Configuration.getConfig.getInt("api.resourceChanges.maxRecordsCap") val newMaxRecords = 1 // Change the maxNodes config value in the svr var configInput = AdminConfigRequest("api.resourceChanges.maxRecordsCap", newMaxRecords.toString) @@ -1082,7 +1082,7 @@ class AgbotsSuite extends AnyFunSuite { response = Http(NOORGURL+"/admin/config").postData(write(configInput)).method("put").headers(CONTENT).headers(ACCEPT).headers(ROOTAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.PUT_OK.intValue) - val origMaxRecords2 = ExchConfig.getInt("api.resourceChanges.maxRecordsCap") + val origMaxRecords2 = Configuration.getConfig.getInt("api.resourceChanges.maxRecordsCap") assert(origMaxRecords == origMaxRecords2) } } diff --git a/src/test/scala/org/openhorizon/exchangeapi/BusinessSuite.scala b/src/test/scala/org/openhorizon/exchangeapi/BusinessSuite.scala index e60f708d..a3d2d836 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/BusinessSuite.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/BusinessSuite.scala @@ -20,7 +20,7 @@ import org.openhorizon.exchangeapi.table.deploymentpolicy.{BService, BServiceVer import org.openhorizon.exchangeapi.table.node.{Prop, RegService} import org.openhorizon.exchangeapi.table.resourcechange.ResChangeOperation import org.openhorizon.exchangeapi.table.service.OneProperty -import org.openhorizon.exchangeapi.utility.{ApiResponse, ApiTime, ApiUtils, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiResponse, ApiTime, ApiUtils, Configuration, HttpCode} import org.scalatest.funsuite.AnyFunSuite import org.scalatestplus.junit.JUnitRunner import scalaj.http._ @@ -63,7 +63,7 @@ class BusinessSuite extends AnyFunSuite { val pw2 = user2+"pw" val USER2AUTH = ("Authorization","Basic "+ApiUtils.encode(orguser2+":"+pw2)) val rootuser = Role.superUser - val rootpw = sys.env.getOrElse("EXCHANGE_ROOTPW", "") // need to put this root pw in config.json + val rootpw = (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }) // need to put this root pw in config.json val ROOTAUTH = ("Authorization","Basic "+ApiUtils.encode(rootuser+":"+rootpw)) val nodeId = "9913" // the 1st node created, that i will use to run some rest methods val nodeToken = nodeId+"TokAbcDefGh1234" @@ -798,7 +798,7 @@ class BusinessSuite extends AnyFunSuite { if (runningLocally) { // changing limits via POST /admin/config does not work in multi-node mode // Get the current config value so we can restore it afterward // ExchConfig.load <-- already do this earlier - val origMaxBusinessPolicies = ExchConfig.getInt("api.limits.maxBusinessPolicies") + val origMaxBusinessPolicies = Configuration.getConfig.getInt("api.limits.maxBusinessPolicies") info(origMaxBusinessPolicies.toString) // Change the maxMessagesInMailbox config value in the svr var configInput = AdminConfigRequest("api.limits.maxBusinessPolicies", "1") diff --git a/src/test/scala/org/openhorizon/exchangeapi/CatalogSuite.scala b/src/test/scala/org/openhorizon/exchangeapi/CatalogSuite.scala index f19d4662..bbf3cb85 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/CatalogSuite.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/CatalogSuite.scala @@ -14,7 +14,7 @@ import org.openhorizon.exchangeapi.route.organization.PostPutOrgRequest import org.openhorizon.exchangeapi.route.service.{GetServicesResponse, PostPutServiceRequest} import org.openhorizon.exchangeapi.route.user.PostPutUsersRequest import org.openhorizon.exchangeapi.table.deploymentpattern.{PServiceVersions, PServices} -import org.openhorizon.exchangeapi.utility.{ApiResponse, ApiUtils, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiResponse, ApiUtils, Configuration, HttpCode} import org.scalatest.funsuite.AnyFunSuite import org.scalatestplus.junit.JUnitRunner @@ -50,7 +50,7 @@ class CatalogSuite extends AnyFunSuite { val orguser3 = orgid3+"/"+user val USERAUTH3 = ("Authorization","Basic "+ApiUtils.encode(orguser3+":"+pw)) val rootuser = Role.superUser - val rootpw = sys.env.getOrElse("EXCHANGE_ROOTPW", "") // need to put this root pw in config.json + val rootpw = (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }) // need to put this root pw in config.json val ROOTAUTH = ("Authorization","Basic "+ApiUtils.encode(rootuser+":"+rootpw)) // node and agbot in the 1st org val nodeId = "n1" // the 1st node created, that i will use to run some rest methods diff --git a/src/test/scala/org/openhorizon/exchangeapi/ExchConfigSuite.scala b/src/test/scala/org/openhorizon/exchangeapi/ExchConfigSuite.scala index 51155449..cdc7da1f 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/ExchConfigSuite.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/ExchConfigSuite.scala @@ -4,7 +4,7 @@ import org.scalatest.funsuite.AnyFunSuite import org.junit.runner.RunWith import org.scalatestplus.junit.JUnitRunner import org.openhorizon.exchangeapi._ -import org.openhorizon.exchangeapi.utility.ExchConfig +import org.openhorizon.exchangeapi.utility.Configuration /** * Tests for the Version and VersionRange case classes @@ -12,14 +12,14 @@ import org.openhorizon.exchangeapi.utility.ExchConfig @RunWith(classOf[JUnitRunner]) class ExchConfigSuite extends AnyFunSuite { test("ExchConfig tests") { - ExchConfig.load() + Configuration.reload() // Note: this test needs to work with the default version of config.json that is in src/main/resources (so that 'make test' in travis works) - assert(ExchConfig.getInt("api.limits.maxNodes") === 45000) - assert(ExchConfig.getInt("api.limits.maxAgreements") === 0) - assert(ExchConfig.getInt("api.limits.maxMessagesInMailbox") === 0) - assert(ExchConfig.getString("api.db.driverClass") === "org.postgresql.Driver") - assert(ExchConfig.getInt("api.db.minPoolSize") === 1) - assert(ExchConfig.getInt("api.db.acquireIncrement") === 1) - assert(ExchConfig.getInt("api.db.maxPoolSize") === 50) + assert(Configuration.getConfig.getInt("api.limits.maxNodes") === 45000) + assert(Configuration.getConfig.getInt("api.limits.maxAgreements") === 0) + assert(Configuration.getConfig.getInt("api.limits.maxMessagesInMailbox") === 0) + assert(Configuration.getConfig.getString("exchange-db-connection.dataSourceClass") === "org.postgresql.ds.PGSimpleDataSource") + // assert(Configuration.getConfig.getInt("api.db.minPoolSize") === 1) + // assert(Configuration.getConfig.getInt("api.db.acquireIncrement") === 1) + // assert(Configuration.getConfig.getInt("api.db.maxPoolSize") === 50) } } \ No newline at end of file diff --git a/src/test/scala/org/openhorizon/exchangeapi/NodesSuite.scala b/src/test/scala/org/openhorizon/exchangeapi/NodesSuite.scala index ecbae0f0..b17d094b 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/NodesSuite.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/NodesSuite.scala @@ -32,9 +32,10 @@ import org.openhorizon.exchangeapi.table.node.group.assignment.PostPutNodeGroups import org.openhorizon.exchangeapi.table.organization.{OrgLimits, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResourceChangesTQ} import org.openhorizon.exchangeapi.table.service.{OneProperty, ServicesTQ} -import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, ApiUtils, ExchConfig, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiRespType, ApiResponse, ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.BeforeAndAfterAll import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.concurrent.{Await, Future} @@ -81,7 +82,7 @@ class NodesSuite extends AnyFunSuite with BeforeAndAfterAll { val USERAUTH2 = ("Authorization", "Basic " + ApiUtils.encode(org2user + ":" + pw)) val BADAUTH = ("Authorization", "Basic " + ApiUtils.encode(orguser + ":" + pw + "x")) val rootuser = Role.superUser - val rootpw = sys.env.getOrElse("EXCHANGE_ROOTPW", "") // need to put this root pw in config.json + val rootpw = (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }) // need to put this root pw in config.json val ROOTAUTH = ("Authorization","Basic "+ApiUtils.encode(rootuser+":"+rootpw)) val nodeId = "n1" // the 1st node created, that i will use to run some rest methods val orgnodeId = authpref+nodeId @@ -153,7 +154,7 @@ class NodesSuite extends AnyFunSuite with BeforeAndAfterAll { val orgsList = List(orgid, orgid2, orgid3) private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase implicit val formats: Formats = DefaultFormats.withLong // Brings in default date formats etc. @@ -161,15 +162,13 @@ class NodesSuite extends AnyFunSuite with BeforeAndAfterAll { // Teardown test harness. override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(record => + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(record => ((record.orgId startsWith "NodesSuiteTests") || (record.orgId startsWith "NodeSuit") || (record.category === ResChangeCategory.SERVICE.toString && record.orgId === "IBM" && record.id === (ibmService + "_" + svcversion2 + "_" + svcarch2)))).delete andThen OrgsTQ.filter(_.orgid startsWith "NodesSuiteTests").delete andThen ServicesTQ.filter(_.service === "IBM/" + ibmService + "_" + svcversion2 + "_" + svcarch2).delete), AWAITDURATION) - - DBCONNECTION.getDb.close() } @@ -183,7 +182,7 @@ class NodesSuite extends AnyFunSuite with BeforeAndAfterAll { } def patchNodePublicKey(nodeid: String, publicKey: String): Unit = { - val result: Int = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === nodeid).map(_.publicKey).update(publicKey)), AWAITDURATION) + val result: Int = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === nodeid).map(_.publicKey).update(publicKey)), AWAITDURATION) // val jsonInput = """{ "publicKey": """"+publicKey+"""" }""" // val response = Http(URL + "/nodes/" + nodeid).postData(jsonInput).method("PATCH").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString @@ -192,7 +191,7 @@ class NodesSuite extends AnyFunSuite with BeforeAndAfterAll { } def patchNodePattern(nodeid: String, pattern: String): Unit = { - val result: Int = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === nodeid).map(_.pattern).update(pattern)), AWAITDURATION) + val result: Int = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === nodeid).map(_.pattern).update(pattern)), AWAITDURATION) // val jsonInput = """{ "pattern": """"+pattern+"""" }""" // val response = Http(URL + "/nodes/" + nodeid).postData(jsonInput).method("PATCH").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString @@ -404,7 +403,7 @@ class NodesSuite extends AnyFunSuite with BeforeAndAfterAll { //~~~~~ Create nodes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - ExchConfig.load() + Configuration.reload() test("PUT /orgs/"+orgid+"/nodes/iamapikey - add node with id iamapikey - should fail") { val input = PutNodesRequest(nodeToken, "bad", None, "", None, None, None, None, "", None, None) @@ -2468,7 +2467,7 @@ class NodesSuite extends AnyFunSuite with BeforeAndAfterAll { if (runningLocally) { // changing limits via POST /admin/config does not work in multi-node mode // Get the current config value so we can restore it afterward // ExchConfig.load <-- already do this earlier - val origMaxAgreements = ExchConfig.getInt("api.limits.maxAgreements") + val origMaxAgreements = Configuration.getConfig.getInt("api.limits.maxAgreements") // Change the maxAgreements config value in the svr var configInput = AdminConfigRequest("api.limits.maxAgreements", "1") @@ -2521,7 +2520,7 @@ class NodesSuite extends AnyFunSuite with BeforeAndAfterAll { if (runningLocally) { // changing limits via POST /admin/config does not work in multi-node mode // Get the current config value so we can restore it afterward // ExchConfig.load <-- already do this earlier - val origMaxNodes = ExchConfig.getInt("api.limits.maxNodes") + val origMaxNodes = Configuration.getConfig.getInt("api.limits.maxNodes") // Change the maxNodes config value in the svr var configInput = AdminConfigRequest("api.limits.maxNodes", "2") @@ -2869,7 +2868,7 @@ class NodesSuite extends AnyFunSuite with BeforeAndAfterAll { if (runningLocally) { // changing limits via POST /admin/config does not work in multi-node mode // Get the current config value so we can restore it afterward // ExchConfig.load <-- already do this earlier - val origMaxMessagesInMailbox = ExchConfig.getInt("api.limits.maxMessagesInMailbox") + val origMaxMessagesInMailbox = Configuration.getConfig.getInt("api.limits.maxMessagesInMailbox") // Change the maxMessagesInMailbox config value in the svr var configInput = AdminConfigRequest("api.limits.maxMessagesInMailbox", "3") @@ -2911,7 +2910,7 @@ class NodesSuite extends AnyFunSuite with BeforeAndAfterAll { if (runningLocally) { // changing limits via POST /admin/config does not work in multi-node mode // Get the current config value so we can restore it afterward // ExchConfig.load <-- already do this earlier - val origMaxMessagesInMailbox = ExchConfig.getInt("api.limits.maxMessagesInMailbox") + val origMaxMessagesInMailbox = Configuration.getConfig.getInt("api.limits.maxMessagesInMailbox") // Change the maxMessagesInMailbox config value in the svr var configInput = AdminConfigRequest("api.limits.maxMessagesInMailbox", "3") @@ -3354,7 +3353,7 @@ class NodesSuite extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.OK.intValue) val responseBody = parse(response.body).extract[GetNodesResponse].nodes assert(responseBody.contains(orgid+"/"+nodeId)) - assert(responseBody(orgid+"/"+nodeId).ha_group.get === "ng") + assert(responseBody(orgid+"/"+nodeId).ha_group.getOrElse("") === "ng") } test("GET /orgs/"+orgid+"/nodes - make sure node group is in response body") { @@ -3364,7 +3363,7 @@ class NodesSuite extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.OK.intValue) val responseBody = parse(response.body).extract[GetNodesResponse].nodes assert(responseBody.contains(orgid+"/"+nodeId)) - assert(responseBody(orgid+"/"+nodeId).ha_group.get === "ng") + assert(responseBody(orgid+"/"+nodeId).ha_group.getOrElse("") === "ng") } test("GET /orgs/"+orgid+"/nodes/"+nodeId+" - get only node group") { diff --git a/src/test/scala/org/openhorizon/exchangeapi/PatternsSuite.scala b/src/test/scala/org/openhorizon/exchangeapi/PatternsSuite.scala index 90067f90..c0e563ae 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/PatternsSuite.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/PatternsSuite.scala @@ -19,7 +19,7 @@ import org.openhorizon.exchangeapi.route.user.PostPutUsersRequest import org.openhorizon.exchangeapi.table.deploymentpattern.{OneSecretBindingService, OneUserInputService, OneUserInputValue, PServiceVersions, PServices} import org.openhorizon.exchangeapi.table.node.{Prop, RegService} import org.openhorizon.exchangeapi.table.resourcechange.ResChangeOperation -import org.openhorizon.exchangeapi.utility.{ApiResponse, ApiTime, ApiUtils, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiResponse, ApiTime, ApiUtils, Configuration, HttpCode} import org.scalatest.funsuite.AnyFunSuite import org.scalatestplus.junit.JUnitRunner @@ -70,7 +70,7 @@ class PatternsSuite extends AnyFunSuite { val pw4 = user4+"pw" val USER4AUTH = ("Authorization","Basic "+ApiUtils.encode(org3user4+":"+pw4)) val rootuser = Role.superUser - val rootpw = sys.env.getOrElse("EXCHANGE_ROOTPW", "") // need to put this root pw in config.json + val rootpw = (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }) // need to put this root pw in config.json val ROOTAUTH = ("Authorization","Basic "+ApiUtils.encode(rootuser+":"+rootpw)) val nodeId = "9913" // the 1st node created, that i will use to run some rest methods val nodeToken = nodeId+"TokAbcDefGh1" @@ -715,7 +715,7 @@ class PatternsSuite extends AnyFunSuite { // Get the current config value so we can restore it afterward ExchConfig.load() val NOORGURL = urlRoot+"/v1" - val origMaxPatterns = ExchConfig.getInt("api.limits.maxPatterns") + val origMaxPatterns = Configuration.getConfig.getInt("api.limits.maxPatterns") // Change the maxPatterns config value in the svr var configInput = AdminConfigRequest("api.limits.maxPatterns", "1") // user only owns 1 currently diff --git a/src/test/scala/org/openhorizon/exchangeapi/ServicesSuite.scala b/src/test/scala/org/openhorizon/exchangeapi/ServicesSuite.scala index 9c6a7bf9..95a0cc0b 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/ServicesSuite.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/ServicesSuite.scala @@ -18,7 +18,7 @@ import org.openhorizon.exchangeapi.table.resourcechange.ResChangeOperation import org.openhorizon.exchangeapi.table.service.dockerauth.ServiceDockAuth import org.openhorizon.exchangeapi.table.service.policy.ServicePolicy import org.openhorizon.exchangeapi.table.service.{OneProperty, ServiceRef} -import org.openhorizon.exchangeapi.utility.{ApiResponse, ApiTime, ApiUtils, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiResponse, ApiTime, ApiUtils, Configuration, HttpCode} import org.scalatest.funsuite.AnyFunSuite import org.scalatestplus.junit.JUnitRunner @@ -58,7 +58,7 @@ class ServicesSuite extends AnyFunSuite { val pw2 = user2+"pw" val USER2AUTH = ("Authorization","Basic "+ApiUtils.encode(orguser2+":"+pw2)) val rootuser = Role.superUser - val rootpw = sys.env.getOrElse("EXCHANGE_ROOTPW", "") // need to put this root pw in config.json + val rootpw = (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }) // need to put this root pw in config.json val ROOTAUTH = ("Authorization","Basic "+ApiUtils.encode(rootuser+":"+rootpw)) val nodeId = "9912" // the 1st node created, that i will use to run some rest methods val nodeToken = nodeId+"TokAbcDefGh1" @@ -463,7 +463,7 @@ class ServicesSuite extends AnyFunSuite { if (runningLocally) { // changing limits via POST /admin/config does not work in multi-node mode // Get the current config value so we can restore it afterward ExchConfig.load() - val origMaxServices = ExchConfig.getInt("api.limits.maxServices") + val origMaxServices = Configuration.getConfig.getInt("api.limits.maxServices") info(origMaxServices.toString) val NOORGURL = urlRoot+"/v1" diff --git a/src/test/scala/org/openhorizon/exchangeapi/TestDBConnection.scala b/src/test/scala/org/openhorizon/exchangeapi/TestDBConnection.scala deleted file mode 100644 index 85731da1..00000000 --- a/src/test/scala/org/openhorizon/exchangeapi/TestDBConnection.scala +++ /dev/null @@ -1,51 +0,0 @@ -package org.openhorizon.exchangeapi - -import org.openhorizon.exchangeapi.ExchangeApiApp.logger -import com.mchange.v2.c3p0.ComboPooledDataSource -import org.openhorizon.exchangeapi.utility.ExchConfig -import slick.util.AsyncExecutor -import slick.jdbc.PostgresProfile.api.Database - -class TestDBConnection { - ExchConfig.load() // get config file, normally in /etc/horizon/exchange/config.json - ExchConfig.getHostAndPort match { - case (h, pe, pu) => - ExchangeApi.serviceHost = h - ExchangeApi.servicePortEncrypted = pe - ExchangeApi.servicePortUnencrypted = pu - } - - val maxPoolSizeConfig: Int = ExchConfig.getInt("api.db.maxPoolSize") - - var cpds: ComboPooledDataSource = new ComboPooledDataSource() - cpds.setAcquireIncrement(ExchConfig.getInt("api.db.acquireIncrement")) - cpds.setDriverClass(ExchConfig.getString("api.db.driverClass")) //loads the jdbc driver - cpds.setIdleConnectionTestPeriod(ExchConfig.getInt("api.db.idleConnectionTestPeriod")) - cpds.setInitialPoolSize(ExchConfig.getInt("api.db.initialPoolSize")) - cpds.setJdbcUrl(ExchConfig.getString("api.db.jdbcUrl")) - cpds.setMaxConnectionAge(ExchConfig.getInt("api.db.maxConnectionAge")) - cpds.setMaxIdleTimeExcessConnections(ExchConfig.getInt("api.db.maxIdleTimeExcessConnections")) - cpds.setMaxPoolSize(maxPoolSizeConfig) - cpds.setMaxStatementsPerConnection(ExchConfig.getInt("api.db.maxStatementsPerConnection")) - cpds.setMinPoolSize(ExchConfig.getInt("api.db.minPoolSize")) - cpds.setNumHelperThreads(ExchConfig.getInt("api.db.numHelperThreads")) - cpds.setPassword(ExchConfig.getString("api.db.password")) - cpds.setTestConnectionOnCheckin(ExchConfig.getBoolean("api.db.testConnectionOnCheckin")) - cpds.setUser(ExchConfig.getString("api.db.user")) - - // maxConnections, maxThreads, and minThreads should all be the same size. - val db: Database = - if (cpds != null) { - Database.forDataSource(ds = cpds, - executor = AsyncExecutor(name = "ExchangeExecutor", - maxConnections = maxPoolSizeConfig, - maxThreads = maxPoolSizeConfig, - minThreads = maxPoolSizeConfig, - queueSize = ExchConfig.getInt("api.db.queueSize")), - maxConnections = Option(maxPoolSizeConfig)) - } - else - null - - def getDb: Database = db -} diff --git a/src/test/scala/org/openhorizon/exchangeapi/UsersSuite.scala b/src/test/scala/org/openhorizon/exchangeapi/UsersSuite.scala index d019c142..3e52ed74 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/UsersSuite.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/UsersSuite.scala @@ -17,8 +17,9 @@ import scala.collection.mutable.ListBuffer import org.openhorizon.exchangeapi.table._ import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.BeforeAndAfterAll +import slick.jdbc import scala.collection.immutable._ import scala.concurrent.Await @@ -80,7 +81,7 @@ class UsersSuite extends AnyFunSuite with BeforeAndAfterAll { val creds4new = orguser4 + ":" + pw4new val USERAUTH4NEW = ("Authorization", "Basic " + ApiUtils.encode(creds4new)) val rootuser = Role.superUser - val rootpw = sys.env.getOrElse("EXCHANGE_ROOTPW", "") // need to put this same root pw in config.json + val rootpw = (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }) // need to put this same root pw in config.json val ROOTAUTH = ("Authorization", "Basic " + ApiUtils.encode(rootuser + ":" + rootpw)) val CONNTIMEOUT = HttpOptions.connTimeout(20000) val READTIMEOUT = HttpOptions.readTimeout(20000) @@ -142,8 +143,8 @@ class UsersSuite extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.DELETED.intValue || response.code === HttpCode.NOT_FOUND.intValue) } } - - private val DBCONNECTION: TestDBConnection = new TestDBConnection + + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val AWAITDURATION: Duration = 15.seconds private val TESTORGANIZATIONS: Seq[OrgRow] = @@ -189,14 +190,12 @@ class UsersSuite extends AnyFunSuite with BeforeAndAfterAll { limits = "")) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run((OrgsTQ ++= TESTORGANIZATIONS)), AWAITDURATION) + Await.ready(DBCONNECTION.run((OrgsTQ ++= TESTORGANIZATIONS)), AWAITDURATION) } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "UsersSuiteTest").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "UsersSuiteTest").delete andThen OrgsTQ.filter(_.orgid startsWith "UsersSuiteTest").delete), AWAITDURATION) - - DBCONNECTION.getDb.close() } @@ -379,7 +378,7 @@ class UsersSuite extends AnyFunSuite with BeforeAndAfterAll { test("Multitenancy Pathway") { //todo: ICP_EXTERNAL_MGMT_INGRESS is not used in these tests, so we should not require it be set - if((sys.env.getOrElse("ICP_EXTERNAL_MGMT_INGRESS", "") != "") && ocpAccountId.nonEmpty && iamKey.nonEmpty && iamUser.nonEmpty){ + if(Configuration.getConfig.hasPath("ibm.common-services.external-management-ingress") && ocpAccountId.nonEmpty && iamKey.nonEmpty && iamUser.nonEmpty){ info("Try deleting the test org first in case it stuck around") val responseOrg = Http(URL3).method("delete").headers(ACCEPT).headers(ROOTAUTH).asString info("code: "+responseOrg.code+", response.body: "+responseOrg.body) diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/agent/TestDeleteAgentConfigMgmt.scala b/src/test/scala/org/openhorizon/exchangeapi/route/agent/TestDeleteAgentConfigMgmt.scala index 2b8f9444..2c37bd02 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/agent/TestDeleteAgentConfigMgmt.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/agent/TestDeleteAgentConfigMgmt.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.agent -import org.openhorizon.exchangeapi.{TestDBConnection} import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods.parse import org.json4s.native.Serialization.write @@ -19,7 +18,7 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChangeRow, ResourceChangesTQ} import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.{BeforeAndAfterAll, DoNotDiscover, Suite} import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} @@ -35,9 +34,9 @@ class TestDeleteAgentConfigMgmt extends AnyFunSuite with BeforeAndAfterAll with private val ACCEPT: (String, String) = ("Accept","application/json") private val CONTENT = ("Content-Type","application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: Database = DatabaseConnection.getDatabase // private val ORGID = "TestDeleteAgentConfigMgmt" - private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val URL: String = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private implicit val formats: DefaultFormats.type = DefaultFormats @@ -79,80 +78,78 @@ class TestDeleteAgentConfigMgmt extends AnyFunSuite with BeforeAndAfterAll with username = "TestDeleteAgentConfigMgmt/u1")) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run((OrgsTQ ++= TESTORGANIZATIONS) andThen + Await.ready(DBCONNECTION.run((OrgsTQ ++= TESTORGANIZATIONS) andThen (UsersTQ ++= TESTUSERS) andThen (AgbotsTQ += TESTAGBOT) ), AWAITDURATION) } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(change => {(change.orgId startsWith "TestPutAgentConfigMgmt") || + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(change => {(change.orgId startsWith "TestPutAgentConfigMgmt") || (change.orgId === "IBM" && change.resource === "agentfileversion")}).delete andThen OrgsTQ.filter(_.orgid startsWith "TestDeleteAgentConfigMgmt").delete), AWAITDURATION) - - DBCONNECTION.getDb.close() } // Agreement Bots that are dynamically needed, specific to the test case. def fixtureAgbots(testCode: Seq[AgbotRow] => Any, testData: Seq[AgbotRow]): Any = { try{ - Await.result(DBCONNECTION.getDb.run(AgbotsTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(AgbotsTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(AgbotsTQ.filter(_.id inSet testData.map(_.id)).delete), AWAITDURATION) + Await.result(DBCONNECTION.run(AgbotsTQ.filter(_.id inSet testData.map(_.id)).delete), AWAITDURATION) } // Agent Certificate Versions that are dynamically needed, specific to the test case. def fixtureAgentCertVersions(testCode: Seq[(String, String, Option[Long])] => Any, testData: Seq[(String, String, Option[Long])]): Any = { try{ - Await.result(DBCONNECTION.getDb.run(AgentCertificateVersionsTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(AgentCertificateVersionsTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(AgentCertificateVersionsTQ.delete), AWAITDURATION) + Await.result(DBCONNECTION.run(AgentCertificateVersionsTQ.delete), AWAITDURATION) } // Agent Configuration Versions that are dynamically needed, specific to the test case. def fixtureAgentConfigVersions(testCode: Seq[(String, String, Option[Long])] => Any, testData: Seq[(String, String, Option[Long])]): Any = { try{ - Await.result(DBCONNECTION.getDb.run(AgentConfigurationVersionsTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(AgentConfigurationVersionsTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(AgentConfigurationVersionsTQ.delete), AWAITDURATION) + Await.result(DBCONNECTION.run(AgentConfigurationVersionsTQ.delete), AWAITDURATION) } // Agent Software Versions that are dynamically needed, specific to the test case. def fixtureAgentSoftVersions(testCode: Seq[(String, String, Option[Long])] => Any, testData: Seq[(String, String, Option[Long])]): Any = { try{ - Await.result(DBCONNECTION.getDb.run(AgentSoftwareVersionsTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(AgentSoftwareVersionsTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(AgentSoftwareVersionsTQ.delete), AWAITDURATION) + Await.result(DBCONNECTION.run(AgentSoftwareVersionsTQ.delete), AWAITDURATION) } // Agent Certificate Versions that are dynamically needed, specific to the test case. def fixtureUsers(testCode: Seq[UserRow] => Any, testData: Seq[UserRow]): Any = { try{ - Await.result(DBCONNECTION.getDb.run(UsersTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(UsersTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username inSet testData.map(_.username)).delete), AWAITDURATION) + Await.result(DBCONNECTION.run(UsersTQ.filter(_.username inSet testData.map(_.username)).delete), AWAITDURATION) } // Users that are dynamically needed, specific to the test case. def fixtureVersionsChanged(testCode: Seq[(java.sql.Timestamp, String)] => Any, testData: Seq[(java.sql.Timestamp, String)]): Any = { try{ - Await.result(DBCONNECTION.getDb.run(AgentVersionsChangedTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(AgentVersionsChangedTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(AgentVersionsChangedTQ.delete), AWAITDURATION) + Await.result(DBCONNECTION.run(AgentVersionsChangedTQ.delete), AWAITDURATION) } @@ -238,12 +235,12 @@ class TestDeleteAgentConfigMgmt extends AnyFunSuite with BeforeAndAfterAll with assert(response.code === HttpCode.DELETED.intValue) - val certificates: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.getDb.run(AgentCertificateVersionsTQ.result), AWAITDURATION) - val changed: Seq[(java.sql.Timestamp, String)] = Await.result(DBCONNECTION.getDb.run(AgentVersionsChangedTQ.result), AWAITDURATION) - val configurations: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.getDb.run(AgentConfigurationVersionsTQ.result), AWAITDURATION) + val certificates: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.run(AgentCertificateVersionsTQ.result), AWAITDURATION) + val changed: Seq[(java.sql.Timestamp, String)] = Await.result(DBCONNECTION.run(AgentVersionsChangedTQ.result), AWAITDURATION) + val configurations: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.run(AgentConfigurationVersionsTQ.result), AWAITDURATION) val resource: Seq[ResourceChangeRow] = Await.result( - DBCONNECTION.getDb.run( + DBCONNECTION.run( ResourceChangesTQ.filter(_.category === ResChangeCategory.ORG.toString) .filter(_.id === "IBM") .filter(_.operation === ResChangeOperation.MODIFIED.toString) @@ -252,7 +249,7 @@ class TestDeleteAgentConfigMgmt extends AnyFunSuite with BeforeAndAfterAll with .sortBy(_.changeId.desc) .result ), AWAITDURATION) - val software: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.getDb.run(AgentSoftwareVersionsTQ.result), AWAITDURATION) + val software: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.run(AgentSoftwareVersionsTQ.result), AWAITDURATION) assert(certificates.isEmpty) assert(changed.length === 1) @@ -303,11 +300,11 @@ class TestDeleteAgentConfigMgmt extends AnyFunSuite with BeforeAndAfterAll with assert(response.code === HttpCode.DELETED.intValue) - val certificates: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.getDb.run(AgentCertificateVersionsTQ.result), AWAITDURATION) - val changed: Seq[(Timestamp, String)] = Await.result(DBCONNECTION.getDb.run(AgentVersionsChangedTQ.result), AWAITDURATION) - val configurations: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.getDb.run(AgentConfigurationVersionsTQ.result), AWAITDURATION) + val certificates: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.run(AgentCertificateVersionsTQ.result), AWAITDURATION) + val changed: Seq[(Timestamp, String)] = Await.result(DBCONNECTION.run(AgentVersionsChangedTQ.result), AWAITDURATION) + val configurations: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.run(AgentConfigurationVersionsTQ.result), AWAITDURATION) val resource: Seq[ResourceChangeRow] = - Await.result(DBCONNECTION.getDb.run( + Await.result(DBCONNECTION.run( ResourceChangesTQ.filter(_.category === ResChangeCategory.ORG.toString) .filter(_.id === "IBM") .filter(_.operation === ResChangeOperation.MODIFIED.toString) @@ -316,7 +313,7 @@ class TestDeleteAgentConfigMgmt extends AnyFunSuite with BeforeAndAfterAll with .sortBy(_.changeId.desc) .result ), AWAITDURATION) - val software: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.getDb.run(AgentSoftwareVersionsTQ.result), AWAITDURATION) + val software: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.run(AgentSoftwareVersionsTQ.result), AWAITDURATION) assert(certificates.isEmpty) assert(changed.length === 1) diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/agent/TestGetAgentConfigMgmt.scala b/src/test/scala/org/openhorizon/exchangeapi/route/agent/TestGetAgentConfigMgmt.scala index 5d16cef5..508131ac 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/agent/TestGetAgentConfigMgmt.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/agent/TestGetAgentConfigMgmt.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.agent -import org.openhorizon.exchangeapi.{TestDBConnection} import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods.parse import org.json4s.native.Serialization.write @@ -19,10 +18,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ, Prop, RegServic import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.{BeforeAndAfterAll, DoNotDiscover, Suite} import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import java.time.ZoneId @@ -35,9 +35,9 @@ class TestGetAgentConfigMgmt extends AnyFunSuite with BeforeAndAfterAll with Sui private val ACCEPT: (String, String) = ("Accept","application/json") private val CONTENT: (String, String) = ("Content-Type","application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase // private val ORGID = "TestGetAgentConfigMgmt" - private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val URL: String = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private implicit val formats: DefaultFormats.type = DefaultFormats @@ -96,90 +96,88 @@ class TestGetAgentConfigMgmt extends AnyFunSuite with BeforeAndAfterAll with Sui username = "TestGetAgentConfigMgmt/u1")) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run((OrgsTQ ++= TESTORGANIZATIONS) andThen + Await.ready(DBCONNECTION.run((OrgsTQ ++= TESTORGANIZATIONS) andThen (UsersTQ ++= TESTUSERS) andThen (AgbotsTQ += TESTAGBOT) andThen (NodesTQ ++= TESTNODE)), AWAITDURATION) } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(change => {(change.orgId startsWith "TestPutAgentConfigMgmt") || + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(change => {(change.orgId startsWith "TestPutAgentConfigMgmt") || (change.orgId === "IBM" && change.resource === "agentfileversion")}).delete andThen OrgsTQ.filter(_.orgid startsWith "TestGetAgentConfigMgmt").delete), AWAITDURATION) - - DBCONNECTION.getDb.close() } // Agreement Bots that are dynamically needed, specific to the test case. def fixtureAgbots(testCode: Seq[AgbotRow] => Any, testData: Seq[AgbotRow]): Any = { try{ - Await.result(DBCONNECTION.getDb.run(AgbotsTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(AgbotsTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(AgbotsTQ.filter(_.id inSet testData.map(_.id)).delete), AWAITDURATION) + Await.result(DBCONNECTION.run(AgbotsTQ.filter(_.id inSet testData.map(_.id)).delete), AWAITDURATION) } // Agent Certificate Versions that are dynamically needed, specific to the test case. def fixtureAgentCertVersions(testCode: Seq[(String, String, Option[Long])] => Any, testData: Seq[(String, String, Option[Long])]): Any = { try{ - Await.result(DBCONNECTION.getDb.run(AgentCertificateVersionsTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(AgentCertificateVersionsTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(AgentCertificateVersionsTQ.delete), AWAITDURATION) + Await.result(DBCONNECTION.run(AgentCertificateVersionsTQ.delete), AWAITDURATION) } // Agent Configuration Versions that are dynamically needed, specific to the test case. def fixtureAgentConfigVersions(testCode: Seq[(String, String, Option[Long])] => Any, testData: Seq[(String, String, Option[Long])]): Any = { try{ - Await.result(DBCONNECTION.getDb.run(AgentConfigurationVersionsTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(AgentConfigurationVersionsTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(AgentConfigurationVersionsTQ.delete), AWAITDURATION) + Await.result(DBCONNECTION.run(AgentConfigurationVersionsTQ.delete), AWAITDURATION) } // Agent Software Versions that are dynamically needed, specific to the test case. def fixtureAgentSoftVersions(testCode: Seq[(String, String, Option[Long])] => Any, testData: Seq[(String, String, Option[Long])]): Any = { try{ - Await.result(DBCONNECTION.getDb.run(AgentSoftwareVersionsTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(AgentSoftwareVersionsTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(AgentSoftwareVersionsTQ.delete), AWAITDURATION) + Await.result(DBCONNECTION.run(AgentSoftwareVersionsTQ.delete), AWAITDURATION) } // Nodes that are dynamically needed, specific to the test case. def fixtureNodes(testCode: Seq[NodeRow] => Any, testData: Seq[NodeRow]): Any = { try { - Await.result(DBCONNECTION.getDb.run(NodesTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(NodesTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id inSet testData.map(_.id)).delete), AWAITDURATION) + Await.result(DBCONNECTION.run(NodesTQ.filter(_.id inSet testData.map(_.id)).delete), AWAITDURATION) } // Agent Certificate Versions that are dynamically needed, specific to the test case. def fixtureUsers(testCode: Seq[UserRow] => Any, testData: Seq[UserRow]): Any = { try{ - Await.result(DBCONNECTION.getDb.run(UsersTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(UsersTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username inSet testData.map(_.username)).delete), AWAITDURATION) + Await.result(DBCONNECTION.run(UsersTQ.filter(_.username inSet testData.map(_.username)).delete), AWAITDURATION) } // Users that are dynamically needed, specific to the test case. def fixtureVersionsChanged(testCode: Seq[(java.sql.Timestamp, String)] => Any, testData: Seq[(java.sql.Timestamp, String)]): Any = { try{ - Await.result(DBCONNECTION.getDb.run(AgentVersionsChangedTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(AgentVersionsChangedTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(AgentVersionsChangedTQ.delete), AWAITDURATION) + Await.result(DBCONNECTION.run(AgentVersionsChangedTQ.delete), AWAITDURATION) } diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/agent/TestPutAgentConfigMgmt.scala b/src/test/scala/org/openhorizon/exchangeapi/route/agent/TestPutAgentConfigMgmt.scala index 62aade33..df206ef2 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/agent/TestPutAgentConfigMgmt.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/agent/TestPutAgentConfigMgmt.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.agent -import org.openhorizon.exchangeapi.{TestDBConnection} import org.json4s.{DefaultFormats, Formats} import org.json4s.jackson.JsonMethods.parse import org.json4s.native.Serialization.write @@ -19,10 +18,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChangeRow, ResourceChangesTQ} import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach, DoNotDiscover, Suite} import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.concurrent.Await @@ -33,9 +33,9 @@ class TestPutAgentConfigMgmt extends AnyFunSuite with BeforeAndAfterAll with Bef private val ACCEPT: (String, String) = ("Accept", "application/json") private val CONTENT: (String, String) = ("Content-Type", "application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase // private val ORGID = "TestPutAgentConfigMgmt" - private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val URL: String = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private implicit val formats: Formats = DefaultFormats.withLong @@ -77,23 +77,21 @@ class TestPutAgentConfigMgmt extends AnyFunSuite with BeforeAndAfterAll with Bef username = "TestPutAgentConfigMgmt/u1")) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run((OrgsTQ ++= TESTORGANIZATIONS) andThen + Await.ready(DBCONNECTION.run((OrgsTQ ++= TESTORGANIZATIONS) andThen (UsersTQ ++= TESTUSERS) andThen (AgbotsTQ += TESTAGBOT) ), AWAITDURATION) } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(change => {(change.orgId startsWith "TestPutAgentConfigMgmt") || + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(change => {(change.orgId startsWith "TestPutAgentConfigMgmt") || (change.orgId === "IBM" && change.resource === "agentfileversion")}).delete andThen OrgsTQ.filter(_.orgid startsWith "TestPutAgentConfigMgmt").delete), AWAITDURATION) - - DBCONNECTION.getDb.close() } override def afterEach(): Unit = { - Await.ready(DBCONNECTION.getDb.run(AgentCertificateVersionsTQ.delete andThen + Await.ready(DBCONNECTION.run(AgentCertificateVersionsTQ.delete andThen AgentConfigurationVersionsTQ.delete andThen AgentSoftwareVersionsTQ.delete andThen AgentVersionsChangedTQ.delete), AWAITDURATION) @@ -103,61 +101,61 @@ class TestPutAgentConfigMgmt extends AnyFunSuite with BeforeAndAfterAll with Bef // Agreement Bots that are dynamically needed, specific to the test case. def fixtureAgbots(testCode: Seq[AgbotRow] => Any, testData: Seq[AgbotRow]): Any = { try { - Await.result(DBCONNECTION.getDb.run(AgbotsTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(AgbotsTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(AgbotsTQ.filter(_.id inSet testData.map(_.id)).delete), AWAITDURATION) + Await.result(DBCONNECTION.run(AgbotsTQ.filter(_.id inSet testData.map(_.id)).delete), AWAITDURATION) } // Agent Certificate Versions that are dynamically needed, specific to the test case. def fixtureAgentCertVersions(testCode: Seq[(String, String, Option[Long])] => Any, testData: Seq[(String, String, Option[Long])]): Any = { try { - Await.result(DBCONNECTION.getDb.run(AgentCertificateVersionsTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(AgentCertificateVersionsTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(AgentCertificateVersionsTQ.delete), AWAITDURATION) + Await.result(DBCONNECTION.run(AgentCertificateVersionsTQ.delete), AWAITDURATION) } // Agent Configuration Versions that are dynamically needed, specific to the test case. def fixtureAgentConfigVersions(testCode: Seq[(String, String, Option[Long])] => Any, testData: Seq[(String, String, Option[Long])]): Any = { try { - Await.result(DBCONNECTION.getDb.run(AgentConfigurationVersionsTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(AgentConfigurationVersionsTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(AgentConfigurationVersionsTQ.delete), AWAITDURATION) + Await.result(DBCONNECTION.run(AgentConfigurationVersionsTQ.delete), AWAITDURATION) } // Agent Software Versions that are dynamically needed, specific to the test case. def fixtureAgentSoftVersions(testCode: Seq[(String, String, Option[Long])] => Any, testData: Seq[(String, String, Option[Long])]): Any = { try { - Await.result(DBCONNECTION.getDb.run(AgentSoftwareVersionsTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(AgentSoftwareVersionsTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(AgentSoftwareVersionsTQ.delete), AWAITDURATION) + Await.result(DBCONNECTION.run(AgentSoftwareVersionsTQ.delete), AWAITDURATION) } // Agent Certificate Versions that are dynamically needed, specific to the test case. def fixtureUsers(testCode: Seq[UserRow] => Any, testData: Seq[UserRow]): Any = { try { - Await.result(DBCONNECTION.getDb.run(UsersTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(UsersTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username inSet testData.map(_.username)).delete), AWAITDURATION) + Await.result(DBCONNECTION.run(UsersTQ.filter(_.username inSet testData.map(_.username)).delete), AWAITDURATION) } // Users that are dynamically needed, specific to the test case. def fixtureVersionsChanged(testCode: Seq[(java.sql.Timestamp, String)] => Any, testData: Seq[(java.sql.Timestamp, String)]): Any = { try { - Await.result(DBCONNECTION.getDb.run(AgentVersionsChangedTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(AgentVersionsChangedTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(AgentVersionsChangedTQ.delete), AWAITDURATION) + Await.result(DBCONNECTION.run(AgentVersionsChangedTQ.delete), AWAITDURATION) } @@ -247,12 +245,12 @@ class TestPutAgentConfigMgmt extends AnyFunSuite with BeforeAndAfterAll with Bef val versions: AgentVersionsRequest = parse(request.body).extract[AgentVersionsRequest] - val certificates: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.getDb.run(AgentCertificateVersionsTQ.sortBy(_.priority.asc.nullsLast).result), AWAITDURATION) - val changed: Seq[(java.sql.Timestamp, String)] = Await.result(DBCONNECTION.getDb.run(AgentVersionsChangedTQ.result), AWAITDURATION) - val configurations: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.getDb.run(AgentConfigurationVersionsTQ.sortBy(_.priority.asc.nullsLast).result), AWAITDURATION) + val certificates: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.run(AgentCertificateVersionsTQ.sortBy(_.priority.asc.nullsLast).result), AWAITDURATION) + val changed: Seq[(java.sql.Timestamp, String)] = Await.result(DBCONNECTION.run(AgentVersionsChangedTQ.result), AWAITDURATION) + val configurations: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.run(AgentConfigurationVersionsTQ.sortBy(_.priority.asc.nullsLast).result), AWAITDURATION) val resource: Seq[ResourceChangeRow] = Await.result( - DBCONNECTION.getDb.run( + DBCONNECTION.run( ResourceChangesTQ.filter(_.category === ResChangeCategory.ORG.toString) .filter(_.id === "IBM") .filter(_.operation === ResChangeOperation.MODIFIED.toString) @@ -261,7 +259,7 @@ class TestPutAgentConfigMgmt extends AnyFunSuite with BeforeAndAfterAll with Bef .sortBy(_.changeId.desc) .result ), AWAITDURATION) - val software: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.getDb.run(AgentSoftwareVersionsTQ.sortBy(_.priority.asc.nullsLast).result), AWAITDURATION) + val software: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.run(AgentSoftwareVersionsTQ.sortBy(_.priority.asc.nullsLast).result), AWAITDURATION) assert(certificates.length === 2) assert(changed.length === 1) @@ -305,7 +303,7 @@ class TestPutAgentConfigMgmt extends AnyFunSuite with BeforeAndAfterAll with Bef } test("PUT /v1/orgs/IBM/AgentFileVersion -- 201 Updated - Root") { - Await.ready(DBCONNECTION.getDb.run((AgentCertificateVersionsTQ += ("1.0.0", "IBM", None)) andThen + Await.ready(DBCONNECTION.run((AgentCertificateVersionsTQ += ("1.0.0", "IBM", None)) andThen (AgentConfigurationVersionsTQ += ("1.0.1", "IBM", None)) andThen (AgentSoftwareVersionsTQ += ("IBM", "1.1.0", None)) andThen (AgentVersionsChangedTQ += (ApiTime.nowUTCTimestamp, "IBM"))), AWAITDURATION) @@ -325,12 +323,12 @@ class TestPutAgentConfigMgmt extends AnyFunSuite with BeforeAndAfterAll with Bef assert(request.code === HttpCode.PUT_OK.intValue) - val certificates: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.getDb.run(AgentCertificateVersionsTQ.result), AWAITDURATION) - val changed: Seq[(java.sql.Timestamp, String)] = Await.result(DBCONNECTION.getDb.run(AgentVersionsChangedTQ.result), AWAITDURATION) - val configurations: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.getDb.run(AgentConfigurationVersionsTQ.result), AWAITDURATION) + val certificates: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.run(AgentCertificateVersionsTQ.result), AWAITDURATION) + val changed: Seq[(java.sql.Timestamp, String)] = Await.result(DBCONNECTION.run(AgentVersionsChangedTQ.result), AWAITDURATION) + val configurations: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.run(AgentConfigurationVersionsTQ.result), AWAITDURATION) val resource: Seq[ResourceChangeRow] = Await.result( - DBCONNECTION.getDb.run( + DBCONNECTION.run( ResourceChangesTQ.filter(_.category === ResChangeCategory.ORG.toString) .filter(_.id === "IBM") .filter(_.operation === ResChangeOperation.MODIFIED.toString) @@ -339,7 +337,7 @@ class TestPutAgentConfigMgmt extends AnyFunSuite with BeforeAndAfterAll with Bef .sortBy(_.changeId.desc) .result ), AWAITDURATION) - val software: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.getDb.run(AgentSoftwareVersionsTQ.result), AWAITDURATION) + val software: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.run(AgentSoftwareVersionsTQ.result), AWAITDURATION) assert(certificates.length === 1) assert(changed.length === 1) @@ -386,10 +384,10 @@ class TestPutAgentConfigMgmt extends AnyFunSuite with BeforeAndAfterAll with Bef val versions: AgentVersionsRequest = parse(request.body).extract[AgentVersionsRequest] - val certificates: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.getDb.run(AgentCertificateVersionsTQ.result), AWAITDURATION) - val changed: Seq[(java.sql.Timestamp, String)] = Await.result(DBCONNECTION.getDb.run(AgentVersionsChangedTQ.result), AWAITDURATION) - val configurations: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.getDb.run(AgentConfigurationVersionsTQ.result), AWAITDURATION) - val software: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.getDb.run(AgentSoftwareVersionsTQ.result), AWAITDURATION) + val certificates: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.run(AgentCertificateVersionsTQ.result), AWAITDURATION) + val changed: Seq[(java.sql.Timestamp, String)] = Await.result(DBCONNECTION.run(AgentVersionsChangedTQ.result), AWAITDURATION) + val configurations: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.run(AgentConfigurationVersionsTQ.result), AWAITDURATION) + val software: Seq[(String, String, Option[Long])] = Await.result(DBCONNECTION.run(AgentSoftwareVersionsTQ.result), AWAITDURATION) assert(certificates.length === 1) assert(changed.length === 1) diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/agreementbot/TestAgbotGetMsgRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/agreementbot/TestAgbotGetMsgRoute.scala index 35c595e8..2a762ee2 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/agreementbot/TestAgbotGetMsgRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/agreementbot/TestAgbotGetMsgRoute.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.agreementbot -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods.parse import org.openhorizon.exchangeapi.auth.Role @@ -11,10 +10,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.concurrent.Await @@ -23,9 +23,9 @@ import scala.concurrent.duration.{Duration, DurationInt} class TestAgbotGetMsgRoute extends AnyFunSuite with BeforeAndAfterAll { private val ACCEPT = ("Accept","application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase // private val ORGID = "TestAgbotGetMsgRoute" - private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val URL = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/TestAgbotGetMsgRoute/agbots/a1/msgs/" private implicit val formats = DefaultFormats @@ -85,7 +85,7 @@ class TestAgbotGetMsgRoute extends AnyFunSuite with BeforeAndAfterAll { override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run((OrgsTQ ++= TESTORGANIZATIONS) andThen + Await.ready(DBCONNECTION.run((OrgsTQ ++= TESTORGANIZATIONS) andThen (UsersTQ ++= TESTUSERS) andThen (AgbotsTQ += TESTAGBOT) andThen (AgbotMsgsTQ ++= TESTAGBOTMESSAGES) andThen @@ -93,10 +93,8 @@ class TestAgbotGetMsgRoute extends AnyFunSuite with BeforeAndAfterAll { } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "TestAgbotGetMsgRoute").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "TestAgbotGetMsgRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "TestAgbotGetMsgRoute").delete), AWAITDURATION) - - DBCONNECTION.getDb.close() } @@ -109,7 +107,7 @@ class TestAgbotGetMsgRoute extends AnyFunSuite with BeforeAndAfterAll { test("GET /orgs/" + "TestAgbotGetMsgRoute" + "/agbots/" + "a1" + "/msgs/{msgid} -- Message") { val TESTAGBOTMESSAGEID: Int = - Await.result(DBCONNECTION.getDb.run(AgbotMsgsTQ + Await.result(DBCONNECTION.run(AgbotMsgsTQ .filter(_.agbotId === TESTAGBOTMESSAGES(0).agbotId) .filter(_.nodeId === TESTAGBOTMESSAGES(0).nodeId) .filter(_.message === TESTAGBOTMESSAGES(0).message) diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/deploymentpolicy/TestBusPolPostSearchRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/deploymentpolicy/TestBusPolPostSearchRoute.scala index 05678318..68bca4d7 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/deploymentpolicy/TestBusPolPostSearchRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/deploymentpolicy/TestBusPolPostSearchRoute.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.deploymentpolicy -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.jackson.JsonMethods.parse import org.json4s.{DefaultFormats, Formats, JValue, JsonInput, jvalue2extractable} import org.json4s.native.Serialization.write @@ -16,11 +15,12 @@ import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.service.{ServiceRow, ServicesTQ} import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} import org.scalatest.funsuite.AnyFunSuite import org.scalatestplus.junit.JUnitRunner import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.collection.immutable @@ -32,10 +32,10 @@ class TestBusPolPostSearchRoute extends AnyFunSuite with BeforeAndAfterAll with private val ACCEPT: (String, String) = ("Content-Type", "application/json") private val AGBOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode("TestPolicySearchPost/a1" + ":" + "a1tok")) private val CONTENT: (String, String) = ACCEPT - private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val URL: String = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" + "TestPolicySearchPost" private val USERAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode("TestPolicySearchPost/u1" + ":" + "u1pw")) - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val AWAITDURATION: Duration = 15.seconds @@ -106,7 +106,7 @@ class TestBusPolPostSearchRoute extends AnyFunSuite with BeforeAndAfterAll with // Begin building testing harness. override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run((OrgsTQ += TESTORGANIZATION) andThen + Await.ready(DBCONNECTION.run((OrgsTQ += TESTORGANIZATION) andThen (UsersTQ += TESTUSER) andThen (AgbotsTQ += TESTAGBOT) andThen (ServicesTQ ++= TESTSERVICES)), AWAITDURATION) @@ -114,53 +114,51 @@ class TestBusPolPostSearchRoute extends AnyFunSuite with BeforeAndAfterAll with // Teardown testing harness and cleanup. override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "TestPolicySearchPost").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "TestPolicySearchPost").delete andThen OrgsTQ.filter(_.orgid startsWith "TestPolicySearchPost").delete), AWAITDURATION) - - DBCONNECTION.getDb.close() } // Isolates test cases. override def afterEach(): Unit = { - Await.ready(DBCONNECTION.getDb.run(SearchOffsetPolicyTQ.dropAllOffsets()), AWAITDURATION) + Await.ready(DBCONNECTION.run(SearchOffsetPolicyTQ.dropAllOffsets()), AWAITDURATION) } // Node Agreements that are dynamically needed, specific to the test case. def fixtureNodeAgreements(testCode: Seq[NodeAgreementRow] => Any, testData: Seq[NodeAgreementRow]): Any = { // Create resources and continue. try { - Await.result(DBCONNECTION.getDb.run(NodeAgreementsTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(NodeAgreementsTQ ++= testData), AWAITDURATION) testCode(testData) } // Teardown created resources. finally - Await.result(DBCONNECTION.getDb.run(NodeAgreementsTQ.filter(_.agId inSet testData.map(_.agId)).delete), AWAITDURATION) + Await.result(DBCONNECTION.run(NodeAgreementsTQ.filter(_.agId inSet testData.map(_.agId)).delete), AWAITDURATION) } // Nodes that are dynamically needed, specific to the test case. def fixtureNodes(testCode: Seq[NodeRow] => Any, testData: Seq[NodeRow]): Any = { try { - Await.result(DBCONNECTION.getDb.run(NodesTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(NodesTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id inSet testData.map(_.id)).delete), AWAITDURATION) + Await.result(DBCONNECTION.run(NodesTQ.filter(_.id inSet testData.map(_.id)).delete), AWAITDURATION) } // Organizations that are dynamically needed, specific to the test case. def fixtureOrganizations(testCode: Seq[OrgRow] => Any, testData: Seq[OrgRow]): Any = { try { - Await.result(DBCONNECTION.getDb.run(OrgsTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(OrgsTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(OrgsTQ.filter(_.orgid inSet testData.map(_.orgId)).delete), AWAITDURATION) + Await.result(DBCONNECTION.run(OrgsTQ.filter(_.orgid inSet testData.map(_.orgId)).delete), AWAITDURATION) } // Offsets/Sessions that are dynamically needed, specific to the test case. def fixturePagination(testCode: Seq[SearchOffsetPolicyAttributes] => Any, testData: Seq[SearchOffsetPolicyAttributes]): Any = { try{ - Await.result(DBCONNECTION.getDb.run(SearchOffsetPolicyTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(SearchOffsetPolicyTQ ++= testData), AWAITDURATION) testCode(testData) } finally @@ -170,11 +168,11 @@ class TestBusPolPostSearchRoute extends AnyFunSuite with BeforeAndAfterAll with // Policies that are dynamically needed, specific to the test case. def fixturePolicies(testCode: Seq[BusinessPolicyRow] => Any, testData: Seq[BusinessPolicyRow]): Any = { try { - Await.result(DBCONNECTION.getDb.run(BusinessPoliciesTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(BusinessPoliciesTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(BusinessPoliciesTQ.filter(_.businessPolicy inSet testData.map(_.businessPolicy)).delete), AWAITDURATION) + Await.result(DBCONNECTION.run(BusinessPoliciesTQ.filter(_.businessPolicy inSet testData.map(_.businessPolicy)).delete), AWAITDURATION) } @@ -204,7 +202,7 @@ class TestBusPolPostSearchRoute extends AnyFunSuite with BeforeAndAfterAll with assert(responseBody.nodes.isEmpty) assert(responseBody.offsetUpdated === false) - //val offset: Seq[(Option[String], Long)] = Await.result(DBCONNECTION.getDb.run(SearchOffsetPolicyTQ.getOffsetSession("TestPolicySearchPost/a1", "TestPolicySearchPost/pol1").result), AWAITDURATION) + //val offset: Seq[(Option[String], Long)] = Await.result(DBCONNECTION.run(SearchOffsetPolicyTQ.getOffsetSession("TestPolicySearchPost/a1", "TestPolicySearchPost/pol1").result), AWAITDURATION) //assert(offset.nonEmpty) //assert(offset.head._1.isEmpty) //assert(offset.head._2 === 0L) @@ -231,7 +229,7 @@ class TestBusPolPostSearchRoute extends AnyFunSuite with BeforeAndAfterAll with assert(responseBody.nodes.isEmpty) assert(responseBody.offsetUpdated === false) - val offset: Seq[(Option[String], Option[String])] = Await.result(DBCONNECTION.getDb.run(SearchOffsetPolicyTQ.getOffsetSession("TestPolicySearchPost/a1", "TestPolicySearchPost/pol1").result), AWAITDURATION) + val offset: Seq[(Option[String], Option[String])] = Await.result(DBCONNECTION.run(SearchOffsetPolicyTQ.getOffsetSession("TestPolicySearchPost/a1", "TestPolicySearchPost/pol1").result), AWAITDURATION) assert(offset.nonEmpty) assert(offset.head._1 === TESTOFFSET.head.offset) assert(offset.head._2 === TESTOFFSET.head.session) @@ -938,7 +936,7 @@ class TestBusPolPostSearchRoute extends AnyFunSuite with BeforeAndAfterAll with assert(RESPONSEBODY.offsetUpdated === true) assert(RESPONSEBODY.nodes.head.id === "TestPolicySearchPost/n1") - val offset: Seq[(Option[String], Option[String])] = Await.result(DBCONNECTION.getDb.run(SearchOffsetPolicyTQ.getOffsetSession("TestPolicySearchPost/a1", "TestPolicySearchPost/pol1").result), AWAITDURATION) + val offset: Seq[(Option[String], Option[String])] = Await.result(DBCONNECTION.run(SearchOffsetPolicyTQ.getOffsetSession("TestPolicySearchPost/a1", "TestPolicySearchPost/pol1").result), AWAITDURATION) assert(offset.nonEmpty) assert(offset.head._1 === Some(TESTNODE.head.lastUpdated)) assert(offset.head._2 === Some("token")) @@ -1003,7 +1001,7 @@ class TestBusPolPostSearchRoute extends AnyFunSuite with BeforeAndAfterAll with assert(RESPONSEBODY.offsetUpdated === true) assert(RESPONSEBODY.nodes.head.id === "TestPolicySearchPost/n1") - val offset: Seq[(Option[String], Option[String])] = Await.result(DBCONNECTION.getDb.run(SearchOffsetPolicyTQ.getOffsetSession("TestPolicySearchPost/a1", "TestPolicySearchPost/pol1").result), AWAITDURATION) + val offset: Seq[(Option[String], Option[String])] = Await.result(DBCONNECTION.run(SearchOffsetPolicyTQ.getOffsetSession("TestPolicySearchPost/a1", "TestPolicySearchPost/pol1").result), AWAITDURATION) assert(offset.nonEmpty) assert(offset.head._1 === Some(TESTNODE.head.lastUpdated)) assert(offset.head._2 === Some("token")) diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/managementpolicy/ManagementPoliciesSuite.scala b/src/test/scala/org/openhorizon/exchangeapi/route/managementpolicy/ManagementPoliciesSuite.scala index c4bacbb4..6f8b3e77 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/managementpolicy/ManagementPoliciesSuite.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/managementpolicy/ManagementPoliciesSuite.scala @@ -11,13 +11,13 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.service.OneProperty import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ -import org.openhorizon.exchangeapi.utility.{ApiResponse, ApiTime, ApiUtils, HttpCode} -import org.openhorizon.exchangeapi.{TestDBConnection} +import org.openhorizon.exchangeapi.utility.{ApiResponse, ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.openhorizon.exchangeapi.auth.Role import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import org.scalatestplus.junit.JUnitRunner import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.collection.mutable.ListBuffer @@ -51,14 +51,14 @@ class ManagementPoliciesSuite extends AnyFunSuite with BeforeAndAfterAll{ val pw: String = user + "pw" val USERAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(orguser + ":" + pw)) val rootuser: String = Role.superUser - val rootpw: String = sys.env.getOrElse("EXCHANGE_ROOTPW", "") // need to put this root pw in config.json + val rootpw: String = (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }) // need to put this root pw in config.json val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(rootuser + ":" + rootpw)) val managementPolicy = "mymgmtpol" val orgManagementPolicy: String = authpref + managementPolicy val ALL_VERSIONS = "[0.0.0,INFINITY)" val NOORGURL: String = urlRoot + "/v1" val orgsList = new ListBuffer[String]() - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase implicit val formats: DefaultFormats.type = DefaultFormats // Brings in default date formats etc. @@ -76,25 +76,23 @@ class ManagementPoliciesSuite extends AnyFunSuite with BeforeAndAfterAll{ // Begin building testing harness. override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run((OrgsTQ += TESTORGANIZATION)), AWAITDURATION) + Await.ready(DBCONNECTION.run((OrgsTQ += TESTORGANIZATION)), AWAITDURATION) } // Teardown testing harness and cleanup. override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "MgmtPolSuite").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "MgmtPolSuite").delete andThen OrgsTQ.filter(_.orgid startsWith "MgmtPolSuite").delete), AWAITDURATION) - - DBCONNECTION.getDb.close() } // Nodes that are dynamically needed, specific to the test case. def fixtureNodes(testCode: Seq[NodeRow] => Any, testData: Seq[NodeRow]): Any = { try { - Await.result(DBCONNECTION.getDb.run(NodesTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(NodesTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id inSet testData.map(_.id)).delete), AWAITDURATION) + Await.result(DBCONNECTION.run(NodesTQ.filter(_.id inSet testData.map(_.id)).delete), AWAITDURATION) } //~~~~~ Clean up from previous run, and create orgs, users ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/managementpolicy/TestMgmtPolsGet.scala b/src/test/scala/org/openhorizon/exchangeapi/route/managementpolicy/TestMgmtPolsGet.scala index 8a9b529c..950a4ace 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/managementpolicy/TestMgmtPolsGet.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/managementpolicy/TestMgmtPolsGet.scala @@ -7,11 +7,11 @@ import org.openhorizon.exchangeapi.table.managementpolicy.{ManagementPoliciesTQ, import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} -import org.openhorizon.exchangeapi.TestDBConnection +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.concurrent.Await @@ -20,9 +20,9 @@ import scala.concurrent.duration.{Duration, DurationInt} class TestMgmtPolsGet extends AnyFunSuite with BeforeAndAfterAll { private val ACCEPT: (String, String) = ("Content-Type", "application/json") private val CONTENT: (String, String) = ACCEPT - private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val URL: String = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val AWAITDURATION: Duration = 15.seconds implicit val formats: DefaultFormats.type = DefaultFormats // Brings in default date formats etc. @@ -117,26 +117,24 @@ class TestMgmtPolsGet extends AnyFunSuite with BeforeAndAfterAll { override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run((OrgsTQ ++= TESTORGANIZATIONS) andThen + Await.ready(DBCONNECTION.run((OrgsTQ ++= TESTORGANIZATIONS) andThen (UsersTQ ++= TESTUSERS) andThen (ManagementPoliciesTQ ++= TESTMANAGEMENTPOLICES)), AWAITDURATION) } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "TestMgmtPolsGet").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "TestMgmtPolsGet").delete andThen OrgsTQ.filter(_.orgid startsWith "TestMgmtPolsGet").delete), AWAITDURATION) - - DBCONNECTION.getDb.close() } def fixtureNodeMgmtPol(testCode: Seq[ManagementPolicyRow] => Any, testData: Seq[ManagementPolicyRow]): Any = { try { - Await.result(DBCONNECTION.getDb.run(ManagementPoliciesTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(ManagementPoliciesTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(ManagementPoliciesTQ.filter(_.managementPolicy inSet testData.map(_.managementPolicy)).delete), AWAITDURATION) + Await.result(DBCONNECTION.run(ManagementPoliciesTQ.filter(_.managementPolicy inSet testData.map(_.managementPolicy)).delete), AWAITDURATION) } test("GET /v1/orgs/" + TESTORGANIZATIONS(0).orgId + "/managementpolicies -- 200 ok - root") { diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/node/TestGetNode.scala b/src/test/scala/org/openhorizon/exchangeapi/route/node/TestGetNode.scala new file mode 100644 index 00000000..98d6c948 --- /dev/null +++ b/src/test/scala/org/openhorizon/exchangeapi/route/node/TestGetNode.scala @@ -0,0 +1,136 @@ +package org.openhorizon.exchangeapi.route.node + +import org.json4s.DefaultFormats +import org.json4s.native.Serialization.write +import org.openhorizon.exchangeapi.auth.{Password, Role} +import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} +import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} +import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ +import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} +import org.scalatest.BeforeAndAfterAll +import org.scalatest.funsuite.AnyFunSuite +import scalaj.http.{Http, HttpResponse} +import slick.jdbc +import slick.jdbc.PostgresProfile + +import scala.concurrent.Await +import scala.concurrent.duration.{Duration, DurationInt} + +class TestGetNode extends AnyFunSuite with BeforeAndAfterAll { + private val ACCEPT: (String, String) = ("Accept","application/json") + private val ADMINAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode("TestGetNode" + "/" + "u2" + ":" + "u2pw")) + private val AWAITDURATION: Duration = 15.seconds + private val CONTENT: (String, String) = ("Content-Type","application/json") + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase + private val NODEAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode("TestGetNode" + "/" + "n2" + ":" + "n2tok")) + // private val ORGID = "TestGetNode" + private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) + private val URL: String = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" + private val USERAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode("TestGetNode" + "/" + "u1" + ":" + "u1pw")) + import slick.jdbc.PostgresProfile.api._ + + private implicit val formats: DefaultFormats.type = DefaultFormats + + private val TESTNODES: Seq[NodeRow] = + Seq(NodeRow(arch = "", + id = "TestGetNode/n1", + heartbeatIntervals = "", + lastHeartbeat = None, + lastUpdated = ApiTime.nowUTC, + msgEndPoint = "", + name = "", + nodeType = "", + orgid = "TestGetNode", + owner = "TestGetNode/u1", + pattern = "", + publicKey = "", + regServices = "", + softwareVersions = "", + token = "", + userInput = "")) + private val TESTORGANIZATIONS: Seq[OrgRow] = + Seq(OrgRow(description = "", + heartbeatIntervals = "", + label = "", + lastUpdated = ApiTime.nowUTC, + limits = "", + orgId = "TestGetNode", + orgType = "", + tags = None), + OrgRow(description = "", + heartbeatIntervals = "", + label = "", + lastUpdated = ApiTime.nowUTC, + limits = "", + orgId = "TestGetNode2", + orgType = "", + tags = None), + OrgRow(description = "", + heartbeatIntervals = "", + label = "", + lastUpdated = ApiTime.nowUTC, + limits = "", + orgId = "TestGetNode3@somecomp.com", + orgType = "", + tags = None)) + private val TESTUSERS: Seq[UserRow] = + Seq(UserRow(admin = false, + email = "", + hashedPw = Password.hash("u1pw"), + hubAdmin = false, + lastUpdated = ApiTime.nowUTC, + orgid = "TestGetNode", + updatedBy = "", + username = "TestGetNode/u1")) + + override def beforeAll(): Unit = { + Await.ready(DBCONNECTION.run((OrgsTQ ++= TESTORGANIZATIONS) andThen + (UsersTQ ++= TESTUSERS)), AWAITDURATION) + } + + override def afterAll(): Unit = { + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "TestGetNode").delete andThen + OrgsTQ.filter(_.orgid startsWith "TestGetNode").delete), AWAITDURATION) + } + + + def fixtureNodes(testCode: Seq[NodeRow] => Any, testData: Seq[NodeRow]): Any = { + try { + Await.result(DBCONNECTION.run(NodesTQ ++= testData), AWAITDURATION) + testCode(testData) + } finally Await.result(DBCONNECTION.run(NodesTQ.filter(_.id inSet testData.map(_.id)).delete), AWAITDURATION) + } + + + test("GET /v1/orgs/" + "TestGetNode" + "/nodes/" + "n2" + " -- something") { + val testnodes: Seq[NodeRow] = + Seq(NodeRow(arch = "", + clusterNamespace = None, + id = "TestGetNode/n2", + heartbeatIntervals = "", + lastHeartbeat = None, + lastUpdated = ApiTime.nowUTC, + msgEndPoint = "", + name = "", + nodeType = "", + orgid = "TestGetNode", + owner = "TestGetNode/u1", + pattern = "", + publicKey = "", + regServices = "", + softwareVersions = "", + token = "", + userInput = "")) + + fixtureNodes( + _ => { + val response: HttpResponse[String] = Http(URL + TESTORGANIZATIONS(0).orgId + "/nodes/" + testnodes(0).id.split("/")(1)).param("attribute", "owner").method("get").headers(CONTENT).headers(ACCEPT).headers(ROOTAUTH).asString + info("Code: " + response.code) + info("Body: " + response.body) + + assert(response.code === HttpCode.OK.intValue) + }, + testnodes) + } +} diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodeDeleteMgmtPolStatus.scala b/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodeDeleteMgmtPolStatus.scala index 303a480f..3cbe967d 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodeDeleteMgmtPolStatus.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodeDeleteMgmtPolStatus.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.node -import org.openhorizon.exchangeapi.{TestDBConnection} import org.json4s.DefaultFormats import org.openhorizon.exchangeapi.auth.Role import org.openhorizon.exchangeapi.table.managementpolicy.{ManagementPoliciesTQ, ManagementPolicyRow} @@ -9,11 +8,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} -import org.openhorizon.exchangeapi.{TestDBConnection} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.concurrent.Await @@ -23,9 +22,9 @@ import scala.concurrent.duration.{Duration, DurationInt} class TestNodeDeleteMgmtPolStatus extends AnyFunSuite with BeforeAndAfterAll { private val ACCEPT: (String, String) = ("Content-Type", "application/json") private val CONTENT: (String, String) = ACCEPT - private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val URL: String = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val AWAITDURATION: Duration = 15.seconds implicit val formats: DefaultFormats.type = DefaultFormats // Brings in default date formats etc. @@ -121,7 +120,7 @@ class TestNodeDeleteMgmtPolStatus extends AnyFunSuite with BeforeAndAfterAll { // Build test harness. override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run((OrgsTQ += TESTORGANIZATION) andThen + Await.ready(DBCONNECTION.run((OrgsTQ += TESTORGANIZATION) andThen (UsersTQ += TESTUSER) andThen (NodesTQ += TESTNODE) andThen (ManagementPoliciesTQ ++= TESTMANAGEMENTPOLICY) andThen @@ -130,29 +129,27 @@ class TestNodeDeleteMgmtPolStatus extends AnyFunSuite with BeforeAndAfterAll { // Teardown testing harness and cleanup. override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "TestNodeDeleteMgmtPolStatus").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "TestNodeDeleteMgmtPolStatus").delete andThen OrgsTQ.filter(_.orgid startsWith "TestNodeDeleteMgmtPolStatus").delete), AWAITDURATION) - - DBCONNECTION.getDb.close() } // Management Policies that are dynamically needed, specific to the test case. def fixtureNodeMgmtPol(testCode: Seq[ManagementPolicyRow] => Any, testData: Seq[ManagementPolicyRow]): Any = { try { - Await.result(DBCONNECTION.getDb.run(ManagementPoliciesTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(ManagementPoliciesTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(ManagementPoliciesTQ.filter(_.managementPolicy inSet testData.map(_.managementPolicy)).delete), AWAITDURATION) + Await.result(DBCONNECTION.run(ManagementPoliciesTQ.filter(_.managementPolicy inSet testData.map(_.managementPolicy)).delete), AWAITDURATION) } def fixtureNodeMgmtPolStatus(testCode: Seq[NodeMgmtPolStatusRow] => Any, testData: Seq[NodeMgmtPolStatusRow]): Any = { try { - Await.result(DBCONNECTION.getDb.run(NodeMgmtPolStatuses ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(NodeMgmtPolStatuses ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(NodeMgmtPolStatuses.filter(_.policy inSet testData.map(_.policy)).delete), AWAITDURATION) + Await.result(DBCONNECTION.run(NodeMgmtPolStatuses.filter(_.policy inSet testData.map(_.policy)).delete), AWAITDURATION) } @@ -187,7 +184,7 @@ class TestNodeDeleteMgmtPolStatus extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.DELETED.intValue) - assert(Await.result(DBCONNECTION.getDb.run(NodeMgmtPolStatuses.getNodeMgmtPolStatus(TESTNODE.id, "TestNodeDeleteMgmtPolStatus/pol1").result), AWAITDURATION).size === 0) - assert(Await.result(DBCONNECTION.getDb.run(NodeMgmtPolStatuses.getNodeMgmtPolStatus(TESTNODE.id, "TestNodeDeleteMgmtPolStatus/pol2").result), AWAITDURATION).size === 1) + assert(Await.result(DBCONNECTION.run(NodeMgmtPolStatuses.getNodeMgmtPolStatus(TESTNODE.id, "TestNodeDeleteMgmtPolStatus/pol1").result), AWAITDURATION).size === 0) + assert(Await.result(DBCONNECTION.run(NodeMgmtPolStatuses.getNodeMgmtPolStatus(TESTNODE.id, "TestNodeDeleteMgmtPolStatus/pol2").result), AWAITDURATION).size === 1) } } \ No newline at end of file diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodeGetAllMgmtPolStatus.scala b/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodeGetAllMgmtPolStatus.scala index 43938aec..d33fa58d 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodeGetAllMgmtPolStatus.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodeGetAllMgmtPolStatus.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.node -import org.openhorizon.exchangeapi.{TestDBConnection} import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods.parse import org.openhorizon.exchangeapi.auth.Role @@ -10,10 +9,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.concurrent.Await @@ -23,9 +23,9 @@ import scala.concurrent.duration.{Duration, DurationInt} class TestNodeGetAllMgmtPolStatus extends AnyFunSuite with BeforeAndAfterAll { private val ACCEPT: (String, String) = ("Content-Type", "application/json") private val CONTENT: (String, String) = ACCEPT - private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val URL: String = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val AWAITDURATION: Duration = 15.seconds implicit val formats: DefaultFormats.type = DefaultFormats // Brings in default date formats etc. @@ -121,7 +121,7 @@ class TestNodeGetAllMgmtPolStatus extends AnyFunSuite with BeforeAndAfterAll { // Build test harness. override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run((OrgsTQ += TESTORGANIZATION) andThen + Await.ready(DBCONNECTION.run((OrgsTQ += TESTORGANIZATION) andThen (UsersTQ += TESTUSER) andThen (NodesTQ += TESTNODE) andThen (ManagementPoliciesTQ ++= TESTMANAGEMENTPOLICY) andThen @@ -130,30 +130,28 @@ class TestNodeGetAllMgmtPolStatus extends AnyFunSuite with BeforeAndAfterAll { // Teardown testing harness and cleanup. override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "TestNodeGetAllMgmtPolStatus").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "TestNodeGetAllMgmtPolStatus").delete andThen OrgsTQ.filter(_.orgid startsWith "TestNodeGetAllMgmtPolStatus").delete), AWAITDURATION) - - DBCONNECTION.getDb.close() } // Management Policies that are dynamically needed, specific to the test case. def fixtureNodeMgmtPol(testCode: Seq[ManagementPolicyRow] => Any, testData: Seq[ManagementPolicyRow]): Any = { try { - Await.result(DBCONNECTION.getDb.run(ManagementPoliciesTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(ManagementPoliciesTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(ManagementPoliciesTQ.filter(_.managementPolicy inSet testData.map(_.managementPolicy)).delete), AWAITDURATION) + Await.result(DBCONNECTION.run(ManagementPoliciesTQ.filter(_.managementPolicy inSet testData.map(_.managementPolicy)).delete), AWAITDURATION) } // Management Policy Statuses that are dynamically needed, specific to the test case. def fixtureNodeMgmtPolStatus(testCode: Seq[NodeMgmtPolStatusRow] => Any, testData: Seq[NodeMgmtPolStatusRow]): Any = { try { - Await.result(DBCONNECTION.getDb.run(NodeMgmtPolStatuses ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(NodeMgmtPolStatuses ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(NodeMgmtPolStatuses.filter(_.policy inSet testData.map(_.policy)).delete), AWAITDURATION) + Await.result(DBCONNECTION.run(NodeMgmtPolStatuses.filter(_.policy inSet testData.map(_.policy)).delete), AWAITDURATION) } test("GET /orgs/TestNodeGetAllMgmtPolStatus-someorganization/nodes/n1/managementStatus -- 404 Not Found - Organization") { diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodeGetMgmtPolStatus.scala b/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodeGetMgmtPolStatus.scala index 122ecbee..15ddad77 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodeGetMgmtPolStatus.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodeGetMgmtPolStatus.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.node -import org.openhorizon.exchangeapi.{TestDBConnection} import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods.parse import org.openhorizon.exchangeapi.auth.Role @@ -10,11 +9,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} -import org.openhorizon.exchangeapi.{TestDBConnection} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.concurrent.Await @@ -24,9 +23,9 @@ import scala.concurrent.duration.{Duration, DurationInt} class TestNodeGetMgmtPolStatus extends AnyFunSuite with BeforeAndAfterAll { private val ACCEPT: (String, String) = ("Content-Type", "application/json") private val CONTENT: (String, String) = ACCEPT - private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val URL: String = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val AWAITDURATION: Duration = 15.seconds implicit val formats: DefaultFormats.type = DefaultFormats // Brings in default date formats etc. val managementPolicy1: String = "pol1" @@ -124,7 +123,7 @@ class TestNodeGetMgmtPolStatus extends AnyFunSuite with BeforeAndAfterAll { // Build test harness. override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run((OrgsTQ += TESTORGANIZATION) andThen + Await.ready(DBCONNECTION.run((OrgsTQ += TESTORGANIZATION) andThen (UsersTQ += TESTUSER) andThen (NodesTQ += TESTNODE) andThen (ManagementPoliciesTQ ++= TESTMANAGEMENTPOLICY) andThen @@ -133,29 +132,27 @@ class TestNodeGetMgmtPolStatus extends AnyFunSuite with BeforeAndAfterAll { // Teardown testing harness and cleanup. override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "TestNodeGetMgmtPolStatus").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "TestNodeGetMgmtPolStatus").delete andThen OrgsTQ.filter(_.orgid startsWith "TestNodeGetMgmtPolStatus").delete), AWAITDURATION) - - DBCONNECTION.getDb.close() } // Management Policies that are dynamically needed, specific to the test case. def fixtureNodeMgmtPol(testCode: Seq[ManagementPolicyRow] => Any, testData: Seq[ManagementPolicyRow]): Any = { try { - Await.result(DBCONNECTION.getDb.run(ManagementPoliciesTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(ManagementPoliciesTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(ManagementPoliciesTQ.filter(_.managementPolicy inSet testData.map(_.managementPolicy)).delete), AWAITDURATION) + Await.result(DBCONNECTION.run(ManagementPoliciesTQ.filter(_.managementPolicy inSet testData.map(_.managementPolicy)).delete), AWAITDURATION) } def fixtureNodeMgmtPolStatus(testCode: Seq[NodeMgmtPolStatusRow] => Any, testData: Seq[NodeMgmtPolStatusRow]): Any = { try { - Await.result(DBCONNECTION.getDb.run(NodeMgmtPolStatuses ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(NodeMgmtPolStatuses ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(NodeMgmtPolStatuses.filter(_.policy inSet testData.map(_.policy)).delete), AWAITDURATION) + Await.result(DBCONNECTION.run(NodeMgmtPolStatuses.filter(_.policy inSet testData.map(_.policy)).delete), AWAITDURATION) } test("GET /orgs/TestNodeGetMgmtPolStatus-someorganization/nodes/n1/managementStatus/pol1 -- 404 Not Found - Organization") { diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodeGetMsgRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodeGetMsgRoute.scala index d43f7430..9d25dec1 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodeGetMsgRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodeGetMsgRoute.scala @@ -1,6 +1,6 @@ package org.openhorizon.exchangeapi.route.node -import org.openhorizon.exchangeapi.{TestDBConnection, table} +import org.openhorizon.exchangeapi.table import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods.parse import org.openhorizon.exchangeapi.auth.Role @@ -10,11 +10,11 @@ import org.openhorizon.exchangeapi.table.node.message.{NodeMsgRow, NodeMsgsTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} -import org.openhorizon.exchangeapi.TestDBConnection +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.concurrent.Await @@ -23,9 +23,9 @@ import scala.concurrent.duration.{Duration, DurationInt} class TestNodeGetMsgRoute extends AnyFunSuite with BeforeAndAfterAll { private val ACCEPT = ("Accept","application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase // private val ORGID = "TestNodeGetMsgRoute" - private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val URL = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/TestNodeGetMsgRoute/nodes/n1/msgs/" private implicit val formats = DefaultFormats @@ -85,7 +85,7 @@ class TestNodeGetMsgRoute extends AnyFunSuite with BeforeAndAfterAll { override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run((OrgsTQ ++= TESTORGANIZATIONS) andThen + Await.ready(DBCONNECTION.run((OrgsTQ ++= TESTORGANIZATIONS) andThen (UsersTQ ++= TESTUSERS) andThen (AgbotsTQ += TESTAGBOT) andThen (NodesTQ ++= TESTNODES) andThen @@ -93,10 +93,8 @@ class TestNodeGetMsgRoute extends AnyFunSuite with BeforeAndAfterAll { } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "TestNodeGetMsgRoute").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "TestNodeGetMsgRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "TestNodeGetMsgRoute").delete), AWAITDURATION) - - DBCONNECTION.getDb.close() } @@ -109,7 +107,7 @@ class TestNodeGetMsgRoute extends AnyFunSuite with BeforeAndAfterAll { test("GET /orgs/" + "TestNodeGetMsgRoute" + "/nodes/" + "n1" + "/msgs/{msgid} -- Message") { val TESTNODEMESSAGEID: Int = - Await.result(DBCONNECTION.getDb.run(NodeMsgsTQ + Await.result(DBCONNECTION.run(NodeMsgsTQ .filter(_.agbotId === TESTNODEMESSAGES(0).agbotId) .filter(_.agbotPubKey === TESTNODEMESSAGES(0).agbotPubKey) .filter(_.nodeId === TESTNODEMESSAGES(0).nodeId) diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodePatch.scala b/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodePatch.scala index 165cf961..27d2314b 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodePatch.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodePatch.scala @@ -13,11 +13,11 @@ import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChangeRow, ResourceChangesTQ} import org.openhorizon.exchangeapi.table.service.{ServiceRow, ServicesTQ} import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} -import org.openhorizon.exchangeapi.TestDBConnection +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import java.time.ZoneId @@ -29,10 +29,10 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { private val ADMINAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode("TestNodePatch" + "/" + "u2" + ":" + "u2pw")) private val AWAITDURATION: Duration = 15.seconds private val CONTENT: (String, String) = ("Content-Type","application/json") - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val NODEAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode("TestNodePatch" + "/" + "n2" + ":" + "n2tok")) // private val ORGID = "TestNodePatch" - private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val URL: String = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private val USERAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode("TestNodePatch" + "/" + "u1" + ":" + "u1pw")) @@ -295,25 +295,23 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run((OrgsTQ ++= TESTORGANIZATIONS) andThen + Await.ready(DBCONNECTION.run((OrgsTQ ++= TESTORGANIZATIONS) andThen (UsersTQ ++= TESTUSERS) andThen (PatternsTQ ++= TESTPATTERNS) andThen (ServicesTQ ++= TESTSERVICES)), AWAITDURATION) } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "TestNodePatch").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "TestNodePatch").delete andThen OrgsTQ.filter(_.orgid startsWith "TestNodePatch").delete), AWAITDURATION) - - DBCONNECTION.getDb.close() } def fixtureNodes(testCode: Seq[NodeRow] => Any, testData: Seq[NodeRow]): Any = { try { - Await.result(DBCONNECTION.getDb.run(NodesTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(NodesTQ ++= testData), AWAITDURATION) testCode(testData) - } finally Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id inSet testData.map(_.id)).delete), AWAITDURATION) + } finally Await.result(DBCONNECTION.run(NodesTQ.filter(_.id inSet testData.map(_.id)).delete), AWAITDURATION) } @@ -553,7 +551,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.arch === testnodes(0).arch) assert(node.clusterNamespace.isDefined) @@ -575,7 +573,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(node.token === testnodes(0).token) assert(node.userInput === testnodes(0).userInput) - val change: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId like "TestNodePatch").result), AWAITDURATION) + val change: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId like "TestNodePatch").result), AWAITDURATION) assert(change.length === 1) assert(change.head.category === ResChangeCategory.NODE.toString) @@ -629,7 +627,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.arch === testnodes(0).arch) assert(node.clusterNamespace === testnodes(0).clusterNamespace) @@ -700,7 +698,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.arch === testnodes(0).arch) assert(node.clusterNamespace === testnodes(0).clusterNamespace) @@ -764,7 +762,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.msgEndPoint === input.msgEndPoint.get) }, testnodes) @@ -895,7 +893,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(Password.check(plainPw = input.token.get, hashedPw = node.token) === true) }, testnodes) @@ -983,7 +981,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(Password.check(plainPw = input.token.get, hashedPw = node.token) === true) }, testnodes) @@ -1030,7 +1028,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.name === input.name.get) }, testnodes) @@ -1077,7 +1075,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.nodeType === input.nodeType.get) }, testnodes) @@ -1124,7 +1122,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.pattern === input.pattern.get) }, testnodes) @@ -1170,7 +1168,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.pattern === input.pattern.get) }, testnodes) @@ -1216,7 +1214,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.pattern === input.pattern.get) }, testnodes) @@ -1262,7 +1260,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.pattern === input.pattern.get) }, testnodes) @@ -1308,7 +1306,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.pattern === input.pattern.get) }, testnodes) @@ -1354,7 +1352,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.pattern === input.pattern.get) }, testnodes) @@ -1400,7 +1398,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.pattern === input.pattern.get) }, testnodes) @@ -1489,7 +1487,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.pattern === input.pattern.get) }, testnodes) @@ -1535,7 +1533,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.pattern === input.pattern.get) }, testnodes) @@ -1581,7 +1579,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.pattern === input.pattern.get) }, testnodes) @@ -1669,7 +1667,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.pattern === input.pattern.get) }, testnodes) @@ -1758,7 +1756,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.pattern === input.pattern.get) }, testnodes) @@ -1804,7 +1802,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.pattern === input.pattern.get) }, testnodes) @@ -1934,7 +1932,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.pattern === input.pattern.get) }, testnodes) @@ -2023,7 +2021,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.pattern === input.pattern.get) }, testnodes) @@ -2069,7 +2067,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.pattern === input.pattern.get) }, testnodes) @@ -2199,7 +2197,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.pattern === input.pattern.get) }, testnodes) @@ -2288,7 +2286,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.publicKey === input.publicKey.get) }, testnodes) @@ -2334,7 +2332,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.publicKey === input.publicKey.get) }, testnodes) @@ -2380,7 +2378,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.publicKey === input.publicKey.get) }, testnodes) @@ -2510,7 +2508,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.publicKey === input.publicKey.get) }, testnodes) @@ -2577,7 +2575,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head val services: Seq[RegService] = parse(node.regServices).extract[Seq[RegService]] assert(services.nonEmpty) assert(services.length === 2) @@ -2651,7 +2649,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head val versions: Map[String, String] = parse(node.softwareVersions).extract[Map[String, String]] assert(versions.nonEmpty) assert(versions.size === 2) @@ -2732,7 +2730,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.userInput.nonEmpty) val userInputs: Seq[OneUserInputService] = parse(node.userInput).extract[Seq[OneUserInputService]] @@ -2812,7 +2810,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.userInput.nonEmpty) val userInputs: Seq[OneUserInputService] = parse(node.userInput).extract[Seq[OneUserInputService]] @@ -3133,7 +3131,7 @@ class TestNodePatch extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.POST_OK.intValue) - val node: NodeRow = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head + val node: NodeRow = Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === testnodes.head.id).result), AWAITDURATION).head assert(node.userInput.nonEmpty) val userInputs: Seq[OneUserInputService] = parse(node.userInput).extract[Seq[OneUserInputService]] diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodePutMgmtPolStatus.scala b/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodePutMgmtPolStatus.scala index 5386eaf8..4a6f2e72 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodePutMgmtPolStatus.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodePutMgmtPolStatus.scala @@ -1,7 +1,6 @@ package org.openhorizon.exchangeapi.route.node import org.openhorizon.exchangeapi -import org.openhorizon.exchangeapi.{TestDBConnection} import org.checkerframework.checker.units.qual.s import org.json4s.DefaultFormats import org.json4s.native.Serialization.write @@ -13,11 +12,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChangeRow, ResourceChangesTQ} import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} -import org.openhorizon.exchangeapi.{TestDBConnection} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.concurrent.Await @@ -28,10 +27,10 @@ class TestNodePutMgmtPolStatus extends AnyFunSuite with BeforeAndAfterAll with B private val AGBOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode("TestNodePutMgmtPolStatus" + "/" + "a1" + ":" + "a1pw")) private val AWAITDURATION: Duration = 15.seconds private val CONTENT: (String, String) = ("Content-Type","application/json") - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val NODEAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode("TestNodePutMgmtPolStatus" + "/" + "n1" + ":" + "n1pw")) // private val ORGID = "TestNodePutMgmtPolStatus" - private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val URL: String = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private val USERAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode("TestNodePutMgmtPolStatus" + "/" + "u1" + ":" + "u1pw")) @@ -92,7 +91,7 @@ class TestNodePutMgmtPolStatus extends AnyFunSuite with BeforeAndAfterAll with B // Build test harness. override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run((OrgsTQ += TESTORGANIZATION) andThen + Await.ready(DBCONNECTION.run((OrgsTQ += TESTORGANIZATION) andThen (UsersTQ += TESTUSER) andThen (NodesTQ += TESTNODE) andThen (ManagementPoliciesTQ += TESTMANAGEMENTPOLICY)), AWAITDURATION) @@ -100,14 +99,12 @@ class TestNodePutMgmtPolStatus extends AnyFunSuite with BeforeAndAfterAll with B // Teardown test harness. override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "TestNodePutMgmtPolStatus").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "TestNodePutMgmtPolStatus").delete andThen OrgsTQ.filter(_.orgid startsWith "TestNodePutMgmtPolStatus").delete), AWAITDURATION) - - DBCONNECTION.getDb.close() } override def afterEach(): Unit = { - Await.ready(DBCONNECTION.getDb.run(NodeMgmtPolStatuses.filter(_.node === TESTNODE.id).delete andThen + Await.ready(DBCONNECTION.run(NodeMgmtPolStatuses.filter(_.node === TESTNODE.id).delete andThen ResourceChangesTQ.filter(_.orgId === TESTORGANIZATION.orgId).delete), AWAITDURATION) } @@ -131,7 +128,7 @@ class TestNodePutMgmtPolStatus extends AnyFunSuite with BeforeAndAfterAll with B assert(response.code === HttpCode.PUT_OK.intValue) - val status: Seq[NodeMgmtPolStatusRow] = Await.result(DBCONNECTION.getDb.run(NodeMgmtPolStatuses.getNodeMgmtPolStatus(TESTNODE.id, TESTMANAGEMENTPOLICY.managementPolicy).result), AWAITDURATION) + val status: Seq[NodeMgmtPolStatusRow] = Await.result(DBCONNECTION.run(NodeMgmtPolStatuses.getNodeMgmtPolStatus(TESTNODE.id, TESTMANAGEMENTPOLICY.managementPolicy).result), AWAITDURATION) assert(status.length === 1) @@ -146,7 +143,7 @@ class TestNodePutMgmtPolStatus extends AnyFunSuite with BeforeAndAfterAll with B assert(status.head.softwareVersion === requestBody.agentUpgradePolicyStatus.upgradedVersions.get.softwareVersion) assert(status.head.updated.isEmpty === false) - val change: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === "TestNodePutMgmtPolStatus").result), AWAITDURATION) + val change: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === "TestNodePutMgmtPolStatus").result), AWAITDURATION) assert(change.length === 1) @@ -174,7 +171,7 @@ class TestNodePutMgmtPolStatus extends AnyFunSuite with BeforeAndAfterAll with B status = Option("in progress"), updated = ApiTime.pastUTC(120)) - Await.ready(DBCONNECTION.getDb.run(NodeMgmtPolStatuses += mgmtPolStatus), AWAITDURATION) + Await.ready(DBCONNECTION.run(NodeMgmtPolStatuses += mgmtPolStatus), AWAITDURATION) val requestBody: PutNodeMgmtPolStatusRequest = PutNodeMgmtPolStatusRequest( @@ -194,7 +191,7 @@ class TestNodePutMgmtPolStatus extends AnyFunSuite with BeforeAndAfterAll with B assert(response.code === HttpCode.PUT_OK.intValue) - val status: Seq[NodeMgmtPolStatusRow] = Await.result(DBCONNECTION.getDb.run(NodeMgmtPolStatuses.getNodeMgmtPolStatus(TESTNODE.id, TESTMANAGEMENTPOLICY.managementPolicy).result), AWAITDURATION) + val status: Seq[NodeMgmtPolStatusRow] = Await.result(DBCONNECTION.run(NodeMgmtPolStatuses.getNodeMgmtPolStatus(TESTNODE.id, TESTMANAGEMENTPOLICY.managementPolicy).result), AWAITDURATION) assert(status.length === 1) @@ -209,7 +206,7 @@ class TestNodePutMgmtPolStatus extends AnyFunSuite with BeforeAndAfterAll with B assert(status.head.softwareVersion === requestBody.agentUpgradePolicyStatus.upgradedVersions.get.softwareVersion) assert(status.head.updated.isEmpty === false) - val change: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === "TestNodePutMgmtPolStatus").result), AWAITDURATION) + val change: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === "TestNodePutMgmtPolStatus").result), AWAITDURATION) assert(change.length === 1) @@ -243,7 +240,7 @@ class TestNodePutMgmtPolStatus extends AnyFunSuite with BeforeAndAfterAll with B assert(response.code === HttpCode.PUT_OK.intValue) - val status: Seq[NodeMgmtPolStatusRow] = Await.result(DBCONNECTION.getDb.run(NodeMgmtPolStatuses.getNodeMgmtPolStatus(TESTNODE.id, TESTMANAGEMENTPOLICY.managementPolicy).result), AWAITDURATION) + val status: Seq[NodeMgmtPolStatusRow] = Await.result(DBCONNECTION.run(NodeMgmtPolStatuses.getNodeMgmtPolStatus(TESTNODE.id, TESTMANAGEMENTPOLICY.managementPolicy).result), AWAITDURATION) assert(status.length === 1) @@ -276,7 +273,7 @@ class TestNodePutMgmtPolStatus extends AnyFunSuite with BeforeAndAfterAll with B assert(response.code === HttpCode.PUT_OK.intValue) - val status: Seq[NodeMgmtPolStatusRow] = Await.result(DBCONNECTION.getDb.run(NodeMgmtPolStatuses.getNodeMgmtPolStatus(TESTNODE.id, TESTMANAGEMENTPOLICY.managementPolicy).result), AWAITDURATION) + val status: Seq[NodeMgmtPolStatusRow] = Await.result(DBCONNECTION.run(NodeMgmtPolStatuses.getNodeMgmtPolStatus(TESTNODE.id, TESTMANAGEMENTPOLICY.managementPolicy).result), AWAITDURATION) assert(status.length === 1) diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodesGetDetails.scala b/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodesGetDetails.scala index 82c10e41..d151dfa5 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodesGetDetails.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/node/TestNodesGetDetails.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.node -import org.openhorizon.exchangeapi.{TestDBConnection} import org.json4s.DefaultFormats import org.{json4s, scalatest} import org.json4s.{DefaultFormats, convertToJsonInput} @@ -17,10 +16,11 @@ import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.service.OneProperty import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode, StrConstants} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode, StrConstants} import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} import org.scalatest.BeforeAndAfterAll +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.collection.immutable.{List, Map} @@ -33,10 +33,10 @@ class TestNodesGetDetails extends AnyFunSuite with BeforeAndAfterAll { private val AGBOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode("TestNodesGetDetails" + "/" + "a1" + ":" + "a1pw")) private val AWAITDURATION: Duration = 15.seconds private val CONTENT: (String, String) = ("Content-Type","application/json") - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val NODEAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode("TestNodesGetDetails" + "/" + "n1" + ":" + "n1pw")) // private val ORGID = "TestNodesGetDetails" - private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val URL: String = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private val USERAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode("TestNodesGetDetails" + "/" + "u1" + ":" + "u1pw")) @@ -208,7 +208,7 @@ class TestNodesGetDetails extends AnyFunSuite with BeforeAndAfterAll { // Build test harness. override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run((OrgsTQ ++= TESTORGANIZATIONS) andThen + Await.ready(DBCONNECTION.run((OrgsTQ ++= TESTORGANIZATIONS) andThen (UsersTQ ++= TESTUSERS) andThen (AgbotsTQ += TESTAGBOT) andThen (NodesTQ ++= TESTNODES) andThen @@ -219,10 +219,8 @@ class TestNodesGetDetails extends AnyFunSuite with BeforeAndAfterAll { // Teardown test harness. override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "TestNodesGetDetails").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "TestNodesGetDetails").delete andThen OrgsTQ.filter(_.orgid startsWith "TestNodesGetDetails").delete), AWAITDURATION) - - DBCONNECTION.getDb.close() } diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestDeleteNodeFromNodeGroup.scala b/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestDeleteNodeFromNodeGroup.scala index e47fe017..179550c4 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestDeleteNodeFromNodeGroup.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestDeleteNodeFromNodeGroup.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.nodegroup -import org.openhorizon.exchangeapi.{TestDBConnection} import org.checkerframework.checker.units.qual.A import org.json4s.DefaultFormats import org.openhorizon.exchangeapi.auth.Role @@ -10,19 +9,12 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChangeRow, ResourceChangesTQ} import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} -import slick.jdbc.PostgresProfile.api.{anyToShapedValue, - columnExtensionMethods, - columnToOrdered, - longColumnType, - queryDeleteActionExtensionMethods, - queryInsertActionExtensionMethods, - streamableQueryActionExtensionMethods, - stringColumnExtensionMethods, - stringColumnType} +import slick.jdbc +import slick.jdbc.PostgresProfile.api.{anyToShapedValue, columnExtensionMethods, columnToOrdered, longColumnType, queryDeleteActionExtensionMethods, queryInsertActionExtensionMethods, streamableQueryActionExtensionMethods, stringColumnExtensionMethods, stringColumnType} import scala.concurrent.Await import scala.concurrent.duration.{Duration, DurationInt} @@ -30,9 +22,9 @@ import scala.concurrent.duration.{Duration, DurationInt} class TestDeleteNodeFromNodeGroup extends AnyFunSuite with BeforeAndAfterAll { private val ACCEPT: (String, String) = ("Content-Type", "application/json") private val CONTENT: (String, String) = ACCEPT - private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val URL: String = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val AWAITDURATION: Duration = 15.seconds implicit val formats: DefaultFormats.type = DefaultFormats // Brings in default date formats etc. @@ -99,26 +91,24 @@ class TestDeleteNodeFromNodeGroup extends AnyFunSuite with BeforeAndAfterAll { override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run((OrgsTQ ++= TESTORGS) andThen + Await.ready(DBCONNECTION.run((OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS) andThen (NodesTQ ++= TESTNODES) andThen (NodeGroupTQ ++= TESTNODEGROUPS)), AWAITDURATION) - val nodeGroup: Long = Await.result(DBCONNECTION.getDb.run(NodeGroupTQ.filter(_.name === TESTNODEGROUPS.head.name).map(_.group).result.head), AWAITDURATION) + val nodeGroup: Long = Await.result(DBCONNECTION.run(NodeGroupTQ.filter(_.name === TESTNODEGROUPS.head.name).map(_.group).result.head), AWAITDURATION) val TESTNODEGROUPASSIGNMENTS: Seq[NodeGroupAssignmentRow] = Seq(NodeGroupAssignmentRow(group = nodeGroup, node = TESTNODES.head.id), NodeGroupAssignmentRow(group = nodeGroup, node = TESTNODES.last.id)) - Await.ready(DBCONNECTION.getDb.run(NodeGroupAssignmentTQ ++= TESTNODEGROUPASSIGNMENTS), AWAITDURATION) + Await.ready(DBCONNECTION.run(NodeGroupAssignmentTQ ++= TESTNODEGROUPASSIGNMENTS), AWAITDURATION) } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "TestDeleteNodeFromNodeGroup").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "TestDeleteNodeFromNodeGroup").delete andThen OrgsTQ.filter(_.orgid startsWith "TestDeleteNodeFromNodeGroup").delete), AWAITDURATION) - - DBCONNECTION.getDb.close() } test("DELETE /orgs/someorg/hagroup/ng0/nodes/n1 -- 404 not found - Bad Organization") { @@ -152,11 +142,11 @@ class TestDeleteNodeFromNodeGroup extends AnyFunSuite with BeforeAndAfterAll { assert(response.code === HttpCode.DELETED.intValue) - val nodeAssignments: Seq[NodeGroupAssignmentRow] = Await.result(DBCONNECTION.getDb.run(NodeGroupAssignmentTQ.join(NodeGroupTQ.filter(_.organization === TESTORGS.head.orgId)).on(_.group === _.group).map(_._1).result), AWAITDURATION) + val nodeAssignments: Seq[NodeGroupAssignmentRow] = Await.result(DBCONNECTION.run(NodeGroupAssignmentTQ.join(NodeGroupTQ.filter(_.organization === TESTORGS.head.orgId)).on(_.group === _.group).map(_._1).result), AWAITDURATION) assert(nodeAssignments.size === 1) assert(nodeAssignments.head.node === TESTNODES(1).id) - val changeRecords: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === TESTORGS.head.orgId).sortBy(_.category.asc.nullsLast).result), AWAITDURATION) + val changeRecords: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === TESTORGS.head.orgId).sortBy(_.category.asc.nullsLast).result), AWAITDURATION) assert(changeRecords.size === 2) assert(changeRecords.head.category === ResChangeCategory.NODEGROUP.toString) diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestDeleteNodeGroup.scala b/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestDeleteNodeGroup.scala index 85f6e441..9a57061b 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestDeleteNodeGroup.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestDeleteNodeGroup.scala @@ -1,7 +1,7 @@ package org.openhorizon.exchangeapi.route.nodegroup import org.openhorizon.exchangeapi.utility.ApiTime.fixFormatting -import org.openhorizon.exchangeapi.{TestDBConnection, table} +import org.openhorizon.exchangeapi.table import org.json4s.DefaultFormats import org.openhorizon.exchangeapi.auth.Role import org.openhorizon.exchangeapi.table.node.group.{NodeGroupRow, NodeGroupTQ, NodeGroups} @@ -10,11 +10,12 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChangeRow, ResourceChangesTQ} import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} import slick.dbio.{Effect, NoStream} +import slick.jdbc import slick.jdbc.PostgresProfile.api.{anyToShapedValue, columnExtensionMethods, columnToOrdered, longColumnType, queryDeleteActionExtensionMethods, queryInsertActionExtensionMethods, streamableQueryActionExtensionMethods, stringColumnExtensionMethods, stringColumnType} import slick.sql.FixedSqlAction @@ -28,9 +29,9 @@ import scala.math.Ordered.orderingToOrdered class TestDeleteNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAndAfterEach { private val ACCEPT: (String, String) = ("Content-Type", "application/json") private val CONTENT: (String, String) = ACCEPT - private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val URL: String = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val AWAITDURATION: Duration = 15.seconds implicit val formats: DefaultFormats.type = DefaultFormats // Brings in default date formats etc. @@ -227,7 +228,7 @@ class TestDeleteNodeGroup extends AnyFunSuite with BeforeAndAfterAll with Before // Build test harness. override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run((OrgsTQ ++= TESTORGS) andThen + Await.ready(DBCONNECTION.run((OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS) andThen (NodesTQ ++= TESTNODES) andThen (NodeGroupTQ ++= TESTNODEGROUPS)), AWAITDURATION) @@ -235,25 +236,23 @@ class TestDeleteNodeGroup extends AnyFunSuite with BeforeAndAfterAll with Before // Teardown testing harness and cleanup. override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "TestDeleteNodeGroup").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "TestDeleteNodeGroup").delete andThen OrgsTQ.filter(_.orgid startsWith "TestDeleteNodeGroup").delete), AWAITDURATION) - - DBCONNECTION.getDb.close() } override def afterEach(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "TestPutNodeGroup").delete), AWAITDURATION) + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "TestPutNodeGroup").delete), AWAITDURATION) } // Node Groups that are dynamically needed, specific to the test case. def fixtureNodeGroups(testCode: Seq[NodeGroupRow] => Any, testData: Seq[NodeGroupRow]): Any = { var nodeGroups: Seq[NodeGroupRow] = Seq() try { - nodeGroups = Await.result(DBCONNECTION.getDb.run((NodeGroupTQ returning NodeGroupTQ) ++= testData), AWAITDURATION) + nodeGroups = Await.result(DBCONNECTION.run((NodeGroupTQ returning NodeGroupTQ) ++= testData), AWAITDURATION) testCode(nodeGroups) } finally - Await.result(DBCONNECTION.getDb.run(NodeGroupTQ.filter(_.group inSet nodeGroups.map(_.group)).delete), AWAITDURATION) + Await.result(DBCONNECTION.run(NodeGroupTQ.filter(_.group inSet nodeGroups.map(_.group)).delete), AWAITDURATION) } test("DELETE /orgs/TestDeleteNodeGroup/hagroups/randomgroup -- 404 not found - bad group - root") { @@ -282,7 +281,7 @@ class TestDeleteNodeGroup extends AnyFunSuite with BeforeAndAfterAll with Before fixtureNodeGroups( assignedTestNodeGroups => { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( NodeGroupAssignmentTQ += NodeGroupAssignmentRow(group = assignedTestNodeGroups.head.group, node = TESTNODES.head.id)), AWAITDURATION) @@ -292,17 +291,17 @@ class TestDeleteNodeGroup extends AnyFunSuite with BeforeAndAfterAll with Before assert(response.code === HttpCode.DELETED.intValue) - val nodeGroups: Seq[NodeGroupRow] = Await.result(DBCONNECTION.getDb.run(NodeGroupTQ.filter(_.organization === TESTORGS.head.orgId).result), AWAITDURATION) + val nodeGroups: Seq[NodeGroupRow] = Await.result(DBCONNECTION.run(NodeGroupTQ.filter(_.organization === TESTORGS.head.orgId).result), AWAITDURATION) assert(nodeGroups.sizeIs == 1) assert(nodeGroups.head.organization === TESTNODEGROUPS.head.organization) assert(nodeGroups.head.lastUpdated === TESTNODEGROUPS.head.lastUpdated) assert(nodeGroups.head.name === TESTNODEGROUPS.head.name) - val assignedNodes: Seq[NodeGroupAssignmentRow] = Await.result(DBCONNECTION.getDb.run(NodeGroupAssignmentTQ.filter(_.group === assignedTestNodeGroups.head.group).result), AWAITDURATION) + val assignedNodes: Seq[NodeGroupAssignmentRow] = Await.result(DBCONNECTION.run(NodeGroupAssignmentTQ.filter(_.group === assignedTestNodeGroups.head.group).result), AWAITDURATION) assert(assignedNodes.sizeIs == 0) - val changes: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === TESTNODEGROUP.head.organization).sortBy(change => (change.category.asc.nullsLast, change.id.asc.nullsLast)).result), AWAITDURATION) + val changes: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === TESTNODEGROUP.head.organization).sortBy(change => (change.category.asc.nullsLast, change.id.asc.nullsLast)).result), AWAITDURATION) assert(changes.sizeIs == 2) assert(changes.head.category === ResChangeCategory.NODEGROUP.toString) @@ -336,7 +335,7 @@ class TestDeleteNodeGroup extends AnyFunSuite with BeforeAndAfterAll with Before fixtureNodeGroups( assignedTestNodeGroups => { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( NodeGroupAssignmentTQ += NodeGroupAssignmentRow(group = assignedTestNodeGroups.head.group, node = TESTNODES.head.id)), AWAITDURATION) @@ -358,7 +357,7 @@ class TestDeleteNodeGroup extends AnyFunSuite with BeforeAndAfterAll with Before organization = TESTORGS.head.orgId)) fixtureNodeGroups(assignedTestNodeGroups => { - Await.ready(DBCONNECTION.getDb.run(NodeGroupAssignmentTQ += NodeGroupAssignmentRow(group = assignedTestNodeGroups.head.group, + Await.ready(DBCONNECTION.run(NodeGroupAssignmentTQ += NodeGroupAssignmentRow(group = assignedTestNodeGroups.head.group, node = TESTNODES(3).id)), AWAITDURATION) val response: HttpResponse[String] = Http(URL + TESTNODEGROUP.head.organization + "/hagroups/" + TESTNODEGROUP.head.name).method("delete").headers(CONTENT).headers(ACCEPT).headers(("Authorization", "Basic " + ApiUtils.encode("TestDeleteNodeGroup/u1" + ":" + "u1pw"))).asString diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestGetAllNodeGroups.scala b/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestGetAllNodeGroups.scala index e8516ece..b7c8f18b 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestGetAllNodeGroups.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestGetAllNodeGroups.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.nodegroup -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods import org.openhorizon.exchangeapi.auth.{Password, Role} @@ -11,10 +10,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.concurrent.Await @@ -24,7 +24,7 @@ class TestGetAllNodeGroups extends AnyFunSuite with BeforeAndAfterAll { private val ACCEPT: (String, String) = ("Accept","application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL: String = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private val ROUTE = "/hagroups" @@ -235,7 +235,7 @@ class TestGetAllNodeGroups extends AnyFunSuite with BeforeAndAfterAll { //since 'group' is dynamically set when Node Groups are added to the DB, we must define NodeGroupAssignments after Node Groups are added (dynamically in beforeAll()) - private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(0).username + ":" + HUBADMINPASSWORD)) private val ORGADMINAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + ORGADMINPASSWORD)) private val USERAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(2).username + ":" + USERPASSWORD)) @@ -243,15 +243,15 @@ class TestGetAllNodeGroups extends AnyFunSuite with BeforeAndAfterAll { private val AGBOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(TESTAGBOTS(0).id + ":" + AGBOTTOKEN)) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( (OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS) andThen (AgbotsTQ ++= TESTAGBOTS) andThen (NodesTQ ++= TESTNODES) andThen (NodeGroupTQ ++= TESTNODEGROUPS) ), AWAITDURATION) - val mainGroup: Long = Await.result(DBCONNECTION.getDb.run(NodeGroupTQ.filter(_.name === TESTNODEGROUPS(1).name).result), AWAITDURATION).head.group - val nodeGroupAdmin: Long = Await.result(DBCONNECTION.getDb.run(NodeGroupTQ.filter(_.name === TESTNODEGROUPS.last.name).result), AWAITDURATION).head.group + val mainGroup: Long = Await.result(DBCONNECTION.run(NodeGroupTQ.filter(_.name === TESTNODEGROUPS(1).name).result), AWAITDURATION).head.group + val nodeGroupAdmin: Long = Await.result(DBCONNECTION.run(NodeGroupTQ.filter(_.name === TESTNODEGROUPS.last.name).result), AWAITDURATION).head.group val TESTNODEGROUPASSIGNMENTS: Seq[NodeGroupAssignmentRow] = Seq(NodeGroupAssignmentRow(group = mainGroup, node = TESTNODES.head.id), @@ -264,16 +264,15 @@ class TestGetAllNodeGroups extends AnyFunSuite with BeforeAndAfterAll { NodeGroupAssignmentRow(group = nodeGroupAdmin, node = TESTNODES.last.id)) - Await.ready(DBCONNECTION.getDb.run(NodeGroupAssignmentTQ ++= TESTNODEGROUPASSIGNMENTS), AWAITDURATION) + Await.ready(DBCONNECTION.run(NodeGroupAssignmentTQ ++= TESTNODEGROUPASSIGNMENTS), AWAITDURATION) } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( ResourceChangesTQ.filter(_.orgId startsWith "testGetAllNodeGroupsRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "testGetAllNodeGroupsRoute").delete andThen UsersTQ.filter(_.username startsWith TESTUSERS(0).username).delete ), AWAITDURATION) - DBCONNECTION.getDb.close() } test("GET /orgs/doesNotExist" + ROUTE + " -- 404 NOT FOUND") { diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestGetNodeGroup.scala b/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestGetNodeGroup.scala index 2beb4520..a1dcf7e6 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestGetNodeGroup.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestGetNodeGroup.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.nodegroup -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods import org.openhorizon.exchangeapi.auth.{Password, Role} @@ -11,10 +10,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.concurrent.Await @@ -24,7 +24,7 @@ class TestGetNodeGroup extends AnyFunSuite with BeforeAndAfterAll { private val ACCEPT: (String, String) = ("Accept","application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL: String = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private val ROUTE = "/hagroups/" @@ -231,7 +231,7 @@ class TestGetNodeGroup extends AnyFunSuite with BeforeAndAfterAll { //since 'group' is dynamically set when Node Groups are added to the DB, we must define NodeGroupAssignments after Node Groups are added (dynamically in beforeAll()) - private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(0).username + ":" + HUBADMINPASSWORD)) private val ORGADMINAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + ORGADMINPASSWORD)) private val USERAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(2).username + ":" + USERPASSWORD)) @@ -239,15 +239,15 @@ class TestGetNodeGroup extends AnyFunSuite with BeforeAndAfterAll { private val AGBOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(TESTAGBOTS(0).id + ":" + AGBOTTOKEN)) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( (OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS) andThen (AgbotsTQ ++= TESTAGBOTS) andThen (NodesTQ ++= TESTNODES) andThen (NodeGroupTQ ++= TESTNODEGROUPS) ), AWAITDURATION) - val mainGroup: Long = Await.result(DBCONNECTION.getDb.run(NodeGroupTQ.filter(_.name === TESTNODEGROUPS(1).name).result), AWAITDURATION).head.group - val nodeGroupAdmin: Long = Await.result(DBCONNECTION.getDb.run(NodeGroupTQ.filter(_.name === TESTNODEGROUPS.last.name).result), AWAITDURATION).head.group + val mainGroup: Long = Await.result(DBCONNECTION.run(NodeGroupTQ.filter(_.name === TESTNODEGROUPS(1).name).result), AWAITDURATION).head.group + val nodeGroupAdmin: Long = Await.result(DBCONNECTION.run(NodeGroupTQ.filter(_.name === TESTNODEGROUPS.last.name).result), AWAITDURATION).head.group val TESTNODEGROUPASSIGNMENTS: Seq[NodeGroupAssignmentRow] = Seq(NodeGroupAssignmentRow(group = mainGroup, node = TESTNODES(0).id), @@ -257,18 +257,17 @@ class TestGetNodeGroup extends AnyFunSuite with BeforeAndAfterAll { node = TESTNODES(2).id), NodeGroupAssignmentRow(group = nodeGroupAdmin, node = TESTNODES.last.id)) - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( NodeGroupAssignmentTQ ++= TESTNODEGROUPASSIGNMENTS ), AWAITDURATION) } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( ResourceChangesTQ.filter(_.orgId startsWith "testGetNodeGroupRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "testGetNodeGroupRoute").delete andThen UsersTQ.filter(_.username startsWith TESTUSERS(0).username).delete ), AWAITDURATION) - DBCONNECTION.getDb.close() } test("GET /orgs/doesNotExist" + ROUTE + TESTNODEGROUPS(1).name + " -- 404 NOT FOUND") { diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestPostNodeGroup.scala b/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestPostNodeGroup.scala index 24709057..472c2bb8 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestPostNodeGroup.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestPostNodeGroup.scala @@ -1,7 +1,7 @@ package org.openhorizon.exchangeapi.route.nodegroup import org.openhorizon.exchangeapi.utility.ApiTime.fixFormatting -import org.openhorizon.exchangeapi.{TestDBConnection, table} +import org.openhorizon.exchangeapi.table import org.json4s.DefaultFormats import org.json4s.native.Serialization import org.openhorizon.exchangeapi.auth.Role @@ -12,10 +12,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChangeRow, ResourceChangesTQ} import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import java.sql.Timestamp @@ -29,7 +30,7 @@ class TestPostNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAn private val ACCEPT: (String, String) = ("Accept", "application/json") private val CONTENT: (String, String) = ("Content-Type", "application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL: String = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private val ROUTE = "/hagroups/" @@ -220,24 +221,22 @@ class TestPostNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAn //since 'group' is dynamically set when Node Groups are added to the DB, we must define NodeGroupAssignments after Node Groups are added (dynamically in beforeAll()) - private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run((OrgsTQ ++= TESTORGS) andThen + Await.ready(DBCONNECTION.run((OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS) andThen (AgbotsTQ ++= TESTAGBOTS) andThen (NodesTQ ++= TESTNODES)), AWAITDURATION) } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "TestPostNodeGroup").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "TestPostNodeGroup").delete andThen OrgsTQ.filter(_.orgid startsWith "TestPostNodeGroup").delete), AWAITDURATION) - - DBCONNECTION.getDb.close() } override def afterEach(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "TestPostNodeGroup").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "TestPostNodeGroup").delete andThen NodeGroupTQ.filter(_.organization startsWith "TestPostNodeGroup").delete), AWAITDURATION) } @@ -282,13 +281,13 @@ class TestPostNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAn test("POST /orgs/" + TESTORGS.head.orgId + ROUTE + "test -- 409 Already Exists - assigning nodes to more than one node group - root") { val testDataGroup: Long = - Await.result(DBCONNECTION.getDb.run( + Await.result(DBCONNECTION.run( (NodeGroupTQ returning NodeGroupTQ.map(_.group)) += NodeGroupRow(description = Option(""), group = 0L, lastUpdated = INITIALTIMESTAMPSTRING, name = "ng0", organization = TESTORGS.head.orgId)), AWAITDURATION) - Await.ready(DBCONNECTION.getDb.run(NodeGroupAssignmentTQ += NodeGroupAssignmentRow(group = testDataGroup, node = TESTNODES.head.id)), AWAITDURATION) + Await.ready(DBCONNECTION.run(NodeGroupAssignmentTQ += NodeGroupAssignmentRow(group = testDataGroup, node = TESTNODES.head.id)), AWAITDURATION) val members: Seq[String] = Seq("node0", "node1") @@ -304,7 +303,7 @@ class TestPostNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAn } test("POST /orgs/" + TESTORGS.head.orgId + ROUTE + "king -- 409 Already Exists - attempt creating the same node group twice - root") { - Await.result(DBCONNECTION.getDb.run( + Await.result(DBCONNECTION.run( NodeGroupTQ += NodeGroupRow(description = Option(""), group = 0L, lastUpdated = INITIALTIMESTAMPSTRING, @@ -336,7 +335,7 @@ class TestPostNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAn assert(request.code === HttpCode.POST_OK.intValue) - val nodeGroup: Seq[NodeGroupRow] = Await.result(DBCONNECTION.getDb.run(NodeGroupTQ.filter(_.organization === "TestPostNodeGroup").result), AWAITDURATION) + val nodeGroup: Seq[NodeGroupRow] = Await.result(DBCONNECTION.run(NodeGroupTQ.filter(_.organization === "TestPostNodeGroup").result), AWAITDURATION) assert(nodeGroup.length === 1) assert(nodeGroup.head.admin === true) @@ -345,14 +344,14 @@ class TestPostNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAn assert(nodeGroup.head.name === "king") assert(nodeGroup.head.organization === TESTORGS.head.orgId) - val nodeAssignments: Seq[NodeGroupAssignmentRow] = Await.result(DBCONNECTION.getDb.run(NodeGroupAssignmentTQ.filter(_.group === nodeGroup.head.group).sortBy(_.node.asc.nullsLast).result), AWAITDURATION) + val nodeAssignments: Seq[NodeGroupAssignmentRow] = Await.result(DBCONNECTION.run(NodeGroupAssignmentTQ.filter(_.group === nodeGroup.head.group).sortBy(_.node.asc.nullsLast).result), AWAITDURATION) assert(nodeAssignments.length === members.length) assert(nodeAssignments.head.node === (TESTORGS.head.orgId + "/" + members.head)) assert(nodeAssignments(1).node === (TESTORGS.head.orgId + "/" + members(1))) assert(nodeAssignments.last.node === (TESTORGS.head.orgId + "/" + members.last)) - val changes: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === "TestPostNodeGroup").sortBy(_.category.asc.nullsLast).sortBy(_.id.asc.nullsLast).result), AWAITDURATION) + val changes: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === "TestPostNodeGroup").sortBy(_.category.asc.nullsLast).sortBy(_.id.asc.nullsLast).result), AWAITDURATION) assert(changes.length === 4) assert(changes.head.category === ResChangeCategory.NODEGROUP.toString) @@ -402,7 +401,7 @@ class TestPostNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAn assert(request.code === HttpCode.POST_OK.intValue) - val nodeGroup: Seq[NodeGroupRow] = Await.result(DBCONNECTION.getDb.run(NodeGroupTQ.filter(_.organization === "TestPostNodeGroup").result), AWAITDURATION) + val nodeGroup: Seq[NodeGroupRow] = Await.result(DBCONNECTION.run(NodeGroupTQ.filter(_.organization === "TestPostNodeGroup").result), AWAITDURATION) assert(nodeGroup.length === 1) assert(nodeGroup.head.admin === true) @@ -411,10 +410,10 @@ class TestPostNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAn assert(nodeGroup.head.name === "ng1") assert(nodeGroup.head.organization === TESTORGS.head.orgId) - val nodeAssignments: Seq[NodeGroupAssignmentRow] = Await.result(DBCONNECTION.getDb.run(NodeGroupAssignmentTQ.filter(_.group === nodeGroup.head.group).sortBy(_.node.asc.nullsLast).result), AWAITDURATION) + val nodeAssignments: Seq[NodeGroupAssignmentRow] = Await.result(DBCONNECTION.run(NodeGroupAssignmentTQ.filter(_.group === nodeGroup.head.group).sortBy(_.node.asc.nullsLast).result), AWAITDURATION) assert(nodeAssignments.length === members.length) - val changes: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === "TestPostNodeGroup").sortBy(_.category.asc.nullsLast).sortBy(_.id.asc.nullsLast).result), AWAITDURATION) + val changes: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === "TestPostNodeGroup").sortBy(_.category.asc.nullsLast).sortBy(_.id.asc.nullsLast).result), AWAITDURATION) assert(changes.length === 1) assert(changes.head.category === ResChangeCategory.NODEGROUP.toString) @@ -435,7 +434,7 @@ class TestPostNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAn assert(request.code === HttpCode.POST_OK.intValue) - val nodeGroup: Seq[NodeGroupRow] = Await.result(DBCONNECTION.getDb.run(NodeGroupTQ.filter(_.organization === "TestPostNodeGroup").result), AWAITDURATION) + val nodeGroup: Seq[NodeGroupRow] = Await.result(DBCONNECTION.run(NodeGroupTQ.filter(_.organization === "TestPostNodeGroup").result), AWAITDURATION) assert(nodeGroup.length === 1) assert(nodeGroup.head.admin === true) @@ -444,10 +443,10 @@ class TestPostNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAn assert(nodeGroup.head.name === "ng2") assert(nodeGroup.head.organization === TESTORGS.head.orgId) - val nodeAssignments: Seq[NodeGroupAssignmentRow] = Await.result(DBCONNECTION.getDb.run(NodeGroupAssignmentTQ.filter(_.group === nodeGroup.head.group).sortBy(_.node.asc.nullsLast).result), AWAITDURATION) + val nodeAssignments: Seq[NodeGroupAssignmentRow] = Await.result(DBCONNECTION.run(NodeGroupAssignmentTQ.filter(_.group === nodeGroup.head.group).sortBy(_.node.asc.nullsLast).result), AWAITDURATION) assert(nodeAssignments.isEmpty) - val changes: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === "TestPostNodeGroup").sortBy(_.category.asc.nullsLast).sortBy(_.id.asc.nullsLast).result), AWAITDURATION) + val changes: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === "TestPostNodeGroup").sortBy(_.category.asc.nullsLast).sortBy(_.id.asc.nullsLast).result), AWAITDURATION) assert(changes.length === 1) assert(changes.head.category === ResChangeCategory.NODEGROUP.toString) @@ -468,7 +467,7 @@ class TestPostNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAn assert(request.code === HttpCode.POST_OK.intValue) - val nodeGroup: Seq[NodeGroupRow] = Await.result(DBCONNECTION.getDb.run(NodeGroupTQ.filter(_.organization === "TestPostNodeGroup").result), AWAITDURATION) + val nodeGroup: Seq[NodeGroupRow] = Await.result(DBCONNECTION.run(NodeGroupTQ.filter(_.organization === "TestPostNodeGroup").result), AWAITDURATION) assert(nodeGroup.length === 1) assert(nodeGroup.head.admin === true) @@ -477,10 +476,10 @@ class TestPostNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAn assert(nodeGroup.head.name === "ng3") assert(nodeGroup.head.organization === TESTORGS.head.orgId) - val nodeAssignments: Seq[NodeGroupAssignmentRow] = Await.result(DBCONNECTION.getDb.run(NodeGroupAssignmentTQ.filter(_.group === nodeGroup.head.group).sortBy(_.node.asc.nullsLast).result), AWAITDURATION) + val nodeAssignments: Seq[NodeGroupAssignmentRow] = Await.result(DBCONNECTION.run(NodeGroupAssignmentTQ.filter(_.group === nodeGroup.head.group).sortBy(_.node.asc.nullsLast).result), AWAITDURATION) assert(nodeAssignments.isEmpty) - val changes: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === "TestPostNodeGroup").sortBy(_.category.asc.nullsLast).sortBy(_.id.asc.nullsLast).result), AWAITDURATION) + val changes: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === "TestPostNodeGroup").sortBy(_.category.asc.nullsLast).sortBy(_.id.asc.nullsLast).result), AWAITDURATION) assert(changes.length === 1) assert(changes.head.category === ResChangeCategory.NODEGROUP.toString) @@ -504,7 +503,7 @@ class TestPostNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAn assert(request.code === HttpCode.POST_OK.intValue) - val nodeGroup: Seq[NodeGroupRow] = Await.result(DBCONNECTION.getDb.run(NodeGroupTQ.filter(_.organization === "TestPostNodeGroup").result), AWAITDURATION) + val nodeGroup: Seq[NodeGroupRow] = Await.result(DBCONNECTION.run(NodeGroupTQ.filter(_.organization === "TestPostNodeGroup").result), AWAITDURATION) assert(nodeGroup.length === 1) assert(nodeGroup.head.admin === false) @@ -513,10 +512,10 @@ class TestPostNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAn assert(nodeGroup.head.name === "ng4") assert(nodeGroup.head.organization === TESTORGS.head.orgId) - val nodeAssignments: Seq[NodeGroupAssignmentRow] = Await.result(DBCONNECTION.getDb.run(NodeGroupAssignmentTQ.filter(_.group === nodeGroup.head.group).sortBy(_.node.asc.nullsLast).result), AWAITDURATION) + val nodeAssignments: Seq[NodeGroupAssignmentRow] = Await.result(DBCONNECTION.run(NodeGroupAssignmentTQ.filter(_.group === nodeGroup.head.group).sortBy(_.node.asc.nullsLast).result), AWAITDURATION) assert(nodeAssignments.length === members.length) - val changes: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === "TestPostNodeGroup").sortBy(_.category.asc.nullsLast).sortBy(_.id.asc.nullsLast).result), AWAITDURATION) + val changes: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === "TestPostNodeGroup").sortBy(_.category.asc.nullsLast).sortBy(_.id.asc.nullsLast).result), AWAITDURATION) assert(changes.length === 1) assert(changes.head.category === ResChangeCategory.NODEGROUP.toString) diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestPostNodeToNodeGroup.scala b/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestPostNodeToNodeGroup.scala index 3e139e82..788ef556 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestPostNodeToNodeGroup.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestPostNodeToNodeGroup.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.nodegroup -import org.openhorizon.exchangeapi.{TestDBConnection} import org.checkerframework.checker.units.qual.A import org.json4s.DefaultFormats import org.json4s.native.Serialization.write @@ -12,10 +11,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChangeRow, ResourceChangesTQ} import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api.{anyToShapedValue, columnExtensionMethods, columnToOrdered, longColumnType, queryDeleteActionExtensionMethods, queryInsertActionExtensionMethods, streamableQueryActionExtensionMethods, stringColumnExtensionMethods, stringColumnType} import scala.concurrent.Await @@ -24,9 +24,9 @@ import scala.concurrent.duration.{Duration, DurationInt} class TestPostNodeToNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAndAfterEach { private val ACCEPT: (String, String) = ("Content-Type", "application/json") private val CONTENT: (String, String) = ACCEPT - private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val URL: String = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val AWAITDURATION: Duration = 15.seconds implicit val formats: DefaultFormats.type = DefaultFormats // Brings in default date formats etc. @@ -113,40 +113,38 @@ class TestPostNodeToNodeGroup extends AnyFunSuite with BeforeAndAfterAll with Be override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run((OrgsTQ ++= TESTORGS) andThen + Await.ready(DBCONNECTION.run((OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS) andThen (NodesTQ ++= TESTNODES) andThen (NodeGroupTQ ++= TESTNODEGROUPS)), AWAITDURATION) - val nodeGroup: Long = Await.result(DBCONNECTION.getDb.run(NodeGroupTQ.filter(_.name === TESTNODEGROUPS.head.name).map(_.group).result.head), AWAITDURATION) + val nodeGroup: Long = Await.result(DBCONNECTION.run(NodeGroupTQ.filter(_.name === TESTNODEGROUPS.head.name).map(_.group).result.head), AWAITDURATION) val TESTNODEGROUPASSIGNMENTS: Seq[NodeGroupAssignmentRow] = Seq(NodeGroupAssignmentRow(group = nodeGroup, node = TESTNODES.head.id)) - Await.ready(DBCONNECTION.getDb.run(NodeGroupAssignmentTQ ++= TESTNODEGROUPASSIGNMENTS), AWAITDURATION) + Await.ready(DBCONNECTION.run(NodeGroupAssignmentTQ ++= TESTNODEGROUPASSIGNMENTS), AWAITDURATION) //Http(URL + TESTORGS.head.orgId + "/users/u1").postData(write(PostPutUsersRequest("u1pw", admin = true, Option(false), "u1@hotmail.com"))).method("post").headers(CONTENT).headers(ACCEPT).headers(ROOTAUTH).asString } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "TestPostNodeToNodeGroup").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "TestPostNodeToNodeGroup").delete andThen OrgsTQ.filter(_.orgid startsWith "TestPostNodeToNodeGroup").delete), AWAITDURATION) - - DBCONNECTION.getDb.close() } override def afterEach(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "TestPostNodeToNodeGroup").delete), AWAITDURATION) + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "TestPostNodeToNodeGroup").delete), AWAITDURATION) } // Nodes that are dynamically needed, specific to the test case. def fixtureNodes(testCode: Seq[NodeRow] => Any, testData: Seq[NodeRow]): Any = { try { - Await.result(DBCONNECTION.getDb.run(NodesTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(NodesTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id inSet testData.map(_.id)).delete), AWAITDURATION) + Await.result(DBCONNECTION.run(NodesTQ.filter(_.id inSet testData.map(_.id)).delete), AWAITDURATION) } @@ -263,7 +261,7 @@ class TestPostNodeToNodeGroup extends AnyFunSuite with BeforeAndAfterAll with Be assert(response.code === HttpCode.POST_OK.intValue) - val assignedNodes: Seq[(String, String, String)] = Await.result(DBCONNECTION.getDb.run(NodeGroupAssignmentTQ.join(NodeGroupTQ.filter(_.organization === TESTORGS.head.orgId)).on(_.group === _.group).sortBy(_._1.node.asc.nullsLast).map(records => {(records._2.name, records._1.node, records._2.organization)}).result), AWAITDURATION) + val assignedNodes: Seq[(String, String, String)] = Await.result(DBCONNECTION.run(NodeGroupAssignmentTQ.join(NodeGroupTQ.filter(_.organization === TESTORGS.head.orgId)).on(_.group === _.group).sortBy(_._1.node.asc.nullsLast).map(records => {(records._2.name, records._1.node, records._2.organization)}).result), AWAITDURATION) assert(assignedNodes.size === 2) assert(assignedNodes.head._1 === TESTNODEGROUPS.head.name) @@ -274,7 +272,7 @@ class TestPostNodeToNodeGroup extends AnyFunSuite with BeforeAndAfterAll with Be assert(assignedNodes.last._2 === TESTNODES.last.id) assert(assignedNodes.last._3 === TESTNODEGROUPS.head.organization) - val changeRecords: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "TestPostNodeToNodeGroup").sortBy(_.category.asc.nullsLast).result), AWAITDURATION) + val changeRecords: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "TestPostNodeToNodeGroup").sortBy(_.category.asc.nullsLast).result), AWAITDURATION) assert(changeRecords.size === 2) assert(changeRecords.head.category === ResChangeCategory.NODEGROUP.toString) @@ -319,14 +317,14 @@ class TestPostNodeToNodeGroup extends AnyFunSuite with BeforeAndAfterAll with Be assert(response.code === HttpCode.POST_OK.intValue) - val assignedNodes: Seq[(String, String, String)] = Await.result(DBCONNECTION.getDb.run(NodeGroupAssignmentTQ.join(NodeGroupTQ.filter(_.organization === TESTORGS.head.orgId).filter(_.name === TESTNODEGROUPS.last.name)).on(_.group === _.group).sortBy(_._1.node.asc.nullsLast).map(records => {(records._2.name, records._1.node, records._2.organization)}).result), AWAITDURATION) + val assignedNodes: Seq[(String, String, String)] = Await.result(DBCONNECTION.run(NodeGroupAssignmentTQ.join(NodeGroupTQ.filter(_.organization === TESTORGS.head.orgId).filter(_.name === TESTNODEGROUPS.last.name)).on(_.group === _.group).sortBy(_._1.node.asc.nullsLast).map(records => {(records._2.name, records._1.node, records._2.organization)}).result), AWAITDURATION) assert(assignedNodes.size === 1) assert(assignedNodes.head._1 === TESTNODEGROUPS.last.name) assert(assignedNodes.head._2 === TESTNODES.head.id) assert(assignedNodes.head._3 === TESTNODEGROUPS.last.organization) - val changeRecords: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "TestPostNodeToNodeGroup").sortBy(_.category.asc.nullsLast).result), AWAITDURATION) + val changeRecords: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "TestPostNodeToNodeGroup").sortBy(_.category.asc.nullsLast).result), AWAITDURATION) assert(changeRecords.size === 2) assert(changeRecords.head.category === ResChangeCategory.NODEGROUP.toString) diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestPutNodeGroup.scala b/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestPutNodeGroup.scala index 64a1ee4e..639b9abe 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestPutNodeGroup.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/nodegroup/TestPutNodeGroup.scala @@ -1,7 +1,6 @@ package org.openhorizon.exchangeapi.route.nodegroup import org.openhorizon.exchangeapi.utility.ApiTime.fixFormatting -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.DefaultFormats import org.json4s.native.Serialization import org.json4s.native.Serialization.write @@ -13,10 +12,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChangeRow, ResourceChangesTQ} import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import java.sql.Timestamp @@ -29,7 +29,7 @@ class TestPutNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd private val ACCEPT: (String, String) = ("Accept","application/json") private val CONTENT: (String, String) = ("Content-Type", "application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL: String = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private val ROUTE = "/hagroups/" @@ -262,7 +262,7 @@ class TestPutNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd name = "TestPutNodeGroupRoute_other_org", organization = TESTORGS(1).orgId)) - private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS.head.username + ":" + HUBADMINPASSWORD)) private val ORGADMINAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + ORGADMINPASSWORD)) private val USERAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(2).username + ":" + USERPASSWORD)) @@ -270,7 +270,7 @@ class TestPutNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd private val AGBOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(TESTAGBOTS.head.id + ":" + AGBOTTOKEN)) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run((OrgsTQ ++= TESTORGS) andThen + Await.ready(DBCONNECTION.run((OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS) andThen (AgbotsTQ ++= TESTAGBOTS) andThen (NodesTQ ++= TESTNODES) andThen @@ -278,14 +278,12 @@ class TestPutNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "TestPutNodeGroupRoute").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "TestPutNodeGroupRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "TestPutNodeGroupRoute").delete), AWAITDURATION) - - DBCONNECTION.getDb.close() } override def afterEach(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "TestPutNodeGroup").delete), AWAITDURATION) + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "TestPutNodeGroup").delete), AWAITDURATION) } private val normalRequestBody: PutNodeGroupsRequest = @@ -297,11 +295,11 @@ class TestPutNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd def fixtureNodeGroups(testCode: Seq[NodeGroupRow] => Any, testData: Seq[NodeGroupRow]): Any = { var nodeGroups: Seq[NodeGroupRow] = Seq() try { - nodeGroups = Await.result(DBCONNECTION.getDb.run((NodeGroupTQ returning NodeGroupTQ) ++= testData), AWAITDURATION) + nodeGroups = Await.result(DBCONNECTION.run((NodeGroupTQ returning NodeGroupTQ) ++= testData), AWAITDURATION) testCode(nodeGroups) } finally - Await.result(DBCONNECTION.getDb.run(NodeGroupTQ.filter(_.group inSet nodeGroups.map(_.group)).delete), AWAITDURATION) + Await.result(DBCONNECTION.run(NodeGroupTQ.filter(_.group inSet nodeGroups.map(_.group)).delete), AWAITDURATION) } test("PUT /orgs/doesNotExist" + ROUTE + TESTNODEGROUPS(1).name + " -- 404 not found - bad organization - root") { @@ -379,7 +377,7 @@ class TestPutNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd fixtureNodeGroups( assignedTestNodeGroups => { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( NodeGroupAssignmentTQ += NodeGroupAssignmentRow(group = assignedTestNodeGroups.head.group, node = TESTNODES(2).id)), AWAITDURATION) @@ -403,7 +401,7 @@ class TestPutNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd fixtureNodeGroups( assignedTestNodeGroups => { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( NodeGroupAssignmentTQ += NodeGroupAssignmentRow(group = assignedTestNodeGroups.head.group, node = TESTNODES(2).id)), AWAITDURATION) @@ -473,7 +471,7 @@ class TestPutNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd fixtureNodeGroups( assignedTestNodeGroups => { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( NodeGroupAssignmentTQ ++= Seq(NodeGroupAssignmentRow(group = assignedTestNodeGroups.head.group, node = TESTNODES.head.id), NodeGroupAssignmentRow(group = assignedTestNodeGroups.head.group, @@ -506,7 +504,7 @@ class TestPutNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd fixtureNodeGroups( assignedTestNodeGroups => { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( NodeGroupAssignmentTQ += NodeGroupAssignmentRow(group = assignedTestNodeGroups.head.group, node = TESTNODES.head.id)), AWAITDURATION) @@ -532,7 +530,7 @@ class TestPutNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd fixtureNodeGroups( assignedTestNodeGroups => { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( NodeGroupAssignmentTQ += NodeGroupAssignmentRow(group = assignedTestNodeGroups.head.group, node = TESTNODES.head.id)), AWAITDURATION) @@ -542,7 +540,7 @@ class TestPutNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd assert(response.code === HttpCode.PUT_OK.intValue) - val nodeGroup: Seq[NodeGroupRow] = Await.result(DBCONNECTION.getDb.run(NodeGroupTQ.filter(_.organization === TESTORGS.head.orgId).filter(_.name === TESTNODEGROUP.head.name).result), AWAITDURATION) + val nodeGroup: Seq[NodeGroupRow] = Await.result(DBCONNECTION.run(NodeGroupTQ.filter(_.organization === TESTORGS.head.orgId).filter(_.name === TESTNODEGROUP.head.name).result), AWAITDURATION) assert(nodeGroup.sizeIs == 1) //assert(nodeGroup.head.admin === true) @@ -552,13 +550,13 @@ class TestPutNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd assert(nodeGroup.head.name === TESTNODEGROUP.head.name) assert(nodeGroup.head.organization === TESTNODEGROUP.head.organization) - val assignedNodes: Seq[NodeGroupAssignmentRow] = Await.result(DBCONNECTION.getDb.run(NodeGroupAssignmentTQ.filter(_.group === assignedTestNodeGroups.head.group).result), AWAITDURATION) + val assignedNodes: Seq[NodeGroupAssignmentRow] = Await.result(DBCONNECTION.run(NodeGroupAssignmentTQ.filter(_.group === assignedTestNodeGroups.head.group).result), AWAITDURATION) assert(assignedNodes.sizeIs == 1) assert(assignedNodes.head.group === assignedTestNodeGroups.head.group) assert(assignedNodes.head.node === TESTNODES.head.id) - val changes: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === TESTORGS.head.orgId).sortBy(change => (change.category.asc.nullsLast, change.id.asc.nullsLast)).result), AWAITDURATION) + val changes: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === TESTORGS.head.orgId).sortBy(change => (change.category.asc.nullsLast, change.id.asc.nullsLast)).result), AWAITDURATION) assert(changes.sizeIs == 1) assert(changes.head.category === ResChangeCategory.NODEGROUP.toString) @@ -585,7 +583,7 @@ class TestPutNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd TESTNODES(2).id.split("/")(1)))) fixtureNodeGroups(assignedTestNodeGroups => { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( NodeGroupAssignmentTQ ++= Seq(NodeGroupAssignmentRow(group = assignedTestNodeGroups.head.group,node = TESTNODES.head.id), NodeGroupAssignmentRow(group = assignedTestNodeGroups.head.group, node = TESTNODES(1).id))), AWAITDURATION) val response: HttpResponse[String] = Http(URL + TESTORGS.head.orgId + ROUTE + TESTNODEGROUP.head.name).put(write(requestBody)).headers(ACCEPT).headers(CONTENT).headers(ROOTAUTH).asString @@ -594,7 +592,7 @@ class TestPutNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd assert(response.code === HttpCode.PUT_OK.intValue) - val nodeGroup: Seq[NodeGroupRow] = Await.result(DBCONNECTION.getDb.run(NodeGroupTQ.filter(_.organization === TESTORGS.head.orgId).filter(_.name === TESTNODEGROUP.head.name).result), AWAITDURATION) + val nodeGroup: Seq[NodeGroupRow] = Await.result(DBCONNECTION.run(NodeGroupTQ.filter(_.organization === TESTORGS.head.orgId).filter(_.name === TESTNODEGROUP.head.name).result), AWAITDURATION) assert(nodeGroup.sizeIs == 1) //assert(nodeGroup.head.admin === true) @@ -604,7 +602,7 @@ class TestPutNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd assert(nodeGroup.head.name === TESTNODEGROUP.head.name) assert(nodeGroup.head.organization === TESTNODEGROUP.head.organization) - val assignedNodes: Seq[NodeGroupAssignmentRow] = Await.result(DBCONNECTION.getDb.run(NodeGroupAssignmentTQ.filter(_.group === assignedTestNodeGroups.head.group).sortBy(_.node.asc.nullsLast).result), AWAITDURATION) + val assignedNodes: Seq[NodeGroupAssignmentRow] = Await.result(DBCONNECTION.run(NodeGroupAssignmentTQ.filter(_.group === assignedTestNodeGroups.head.group).sortBy(_.node.asc.nullsLast).result), AWAITDURATION) assert(assignedNodes.sizeIs == 2) assert(assignedNodes.head.group === assignedTestNodeGroups.head.group) @@ -613,7 +611,7 @@ class TestPutNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd assert(assignedNodes(1).group === assignedTestNodeGroups.head.group) assert(assignedNodes(1).node === TESTNODES(2).id) - val changes: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === TESTORGS.head.orgId).sortBy(change => (change.category.asc.nullsLast, change.id.asc.nullsLast)).result), AWAITDURATION) + val changes: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === TESTORGS.head.orgId).sortBy(change => (change.category.asc.nullsLast, change.id.asc.nullsLast)).result), AWAITDURATION) changes.map(change => (info("change: " + change.changeId + " " + change.orgId + " " + change.category + " " + change.resource + " " + change.operation + " " + change.id + " " + change.lastUpdated))) assert(changes.sizeIs == 4) @@ -674,7 +672,7 @@ class TestPutNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd assert(response.code === HttpCode.PUT_OK.intValue) - val nodeGroup: Seq[NodeGroupRow] = Await.result(DBCONNECTION.getDb.run(NodeGroupTQ.filter(_.organization === TESTORGS.head.orgId).filter(_.name === TESTNODEGROUP.head.name).result), AWAITDURATION) + val nodeGroup: Seq[NodeGroupRow] = Await.result(DBCONNECTION.run(NodeGroupTQ.filter(_.organization === TESTORGS.head.orgId).filter(_.name === TESTNODEGROUP.head.name).result), AWAITDURATION) assert(nodeGroup.sizeIs == 1) //assert(nodeGroup.head.admin === false) @@ -684,13 +682,13 @@ class TestPutNodeGroup extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd assert(nodeGroup.head.name === TESTNODEGROUP.head.name) assert(nodeGroup.head.organization === TESTNODEGROUP.head.organization) - val assignedNodes: Seq[NodeGroupAssignmentRow] = Await.result(DBCONNECTION.getDb.run(NodeGroupAssignmentTQ.filter(_.group === assignedTestNodeGroups.head.group).result), AWAITDURATION) + val assignedNodes: Seq[NodeGroupAssignmentRow] = Await.result(DBCONNECTION.run(NodeGroupAssignmentTQ.filter(_.group === assignedTestNodeGroups.head.group).result), AWAITDURATION) assert(assignedNodes.sizeIs == 1) assert(assignedNodes.head.group === assignedTestNodeGroups.head.group) assert(assignedNodes.head.node === TESTNODES.head.id) - val changes: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === TESTORGS.head.orgId).sortBy(change => (change.category.asc.nullsLast, change.id.asc.nullsLast)).result), AWAITDURATION) + val changes: Seq[ResourceChangeRow] = Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === TESTORGS.head.orgId).sortBy(change => (change.category.asc.nullsLast, change.id.asc.nullsLast)).result), AWAITDURATION) assert(changes.sizeIs == 2) assert(changes.head.category === ResChangeCategory.NODEGROUP.toString) diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestDeleteOrgRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestDeleteOrgRoute.scala index 27615e8b..867995db 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestDeleteOrgRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestDeleteOrgRoute.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.organization -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods import org.openhorizon.exchangeapi.auth.{Password, Role} @@ -9,11 +8,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChangesTQ} import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} -import org.openhorizon.exchangeapi.TestDBConnection +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import scala.concurrent.Await import scala.concurrent.duration.{Duration, DurationInt} @@ -23,7 +22,7 @@ class TestDeleteOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeA private val ACCEPT = ("Accept","application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private implicit val formats = DefaultFormats @@ -127,18 +126,18 @@ class TestDeleteOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeA token = "", userInput = "")) - private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(0).username + ":" + HUBADMINPASSWORD)) private val USERAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + USERPASSWORD)) private val ORGADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(2).username + ":" + ORGADMINPASSWORD)) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(UsersTQ += TESTUSERS(0)), AWAITDURATION) //add hub admin + Await.ready(DBCONNECTION.run(UsersTQ += TESTUSERS(0)), AWAITDURATION) //add hub admin } override def beforeEach(): Unit = { info("beforeEach running") - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( ResourceChangesTQ.filter(_.orgId startsWith "testDeleteOrgRoute").delete andThen OrgsTQ.insertOrUpdate(TESTORGS(0)) andThen //can't do "insertOrUpdateAll", so do them individually UsersTQ.insertOrUpdate(TESTUSERS(1)) andThen @@ -150,22 +149,21 @@ class TestDeleteOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeA } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "testDeleteOrgRoute").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "testDeleteOrgRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "testDeleteOrgRoute").delete andThen UsersTQ.filter(_.username startsWith "root/TestDeleteOrgRouteHubAdmin").delete), AWAITDURATION) - DBCONNECTION.getDb.close() } def assertDbClear(orgId: String): Unit = { - assert(Await.result(DBCONNECTION.getDb.run(OrgsTQ.filter(_.orgid === orgId).result), AWAITDURATION).isEmpty) //insure org is gone - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.orgid === orgId).result), AWAITDURATION).isEmpty) //insure users are gone - assert(Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.orgid === orgId).result), AWAITDURATION).isEmpty) //insure nodes are gone - assert(Await.result(DBCONNECTION.getDb.run(AgbotsTQ.filter(_.orgid === orgId).result), AWAITDURATION).isEmpty) //insure agbots are gone + assert(Await.result(DBCONNECTION.run(OrgsTQ.filter(_.orgid === orgId).result), AWAITDURATION).isEmpty) //insure org is gone + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.orgid === orgId).result), AWAITDURATION).isEmpty) //insure users are gone + assert(Await.result(DBCONNECTION.run(NodesTQ.filter(_.orgid === orgId).result), AWAITDURATION).isEmpty) //insure nodes are gone + assert(Await.result(DBCONNECTION.run(AgbotsTQ.filter(_.orgid === orgId).result), AWAITDURATION).isEmpty) //insure agbots are gone } def assertDeletedEntryCreated(orgId: String): Unit = { assert( - Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ + Await.result(DBCONNECTION.run(ResourceChangesTQ .filter(_.orgId === orgId) .filter(_.id === orgId) .filter(_.category === ResChangeCategory.ORG.toString) @@ -178,7 +176,7 @@ class TestDeleteOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeA def assertDeletedEntryNotCreated(orgId: String): Unit = { assert( - Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ + Await.result(DBCONNECTION.run(ResourceChangesTQ .filter(_.orgId === orgId) .filter(_.id === orgId) .filter(_.category === ResChangeCategory.ORG.toString) @@ -195,7 +193,7 @@ class TestDeleteOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeA info("Body: " + response.body) assert(response.code === HttpCode.NOT_FOUND.intValue) //insure nothing was added to resource changes table - assert(Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === "doesNotExist").result), AWAITDURATION).isEmpty) + assert(Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === "doesNotExist").result), AWAITDURATION).isEmpty) } test("DELETE /orgs/root -- 403 access denied") { @@ -203,7 +201,7 @@ class TestDeleteOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeA info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.ACCESS_DENIED.intValue) - val numOrgs: Int = Await.result(DBCONNECTION.getDb.run(OrgsTQ.filter(_.orgid === "root").result), AWAITDURATION).length + val numOrgs: Int = Await.result(DBCONNECTION.run(OrgsTQ.filter(_.orgid === "root").result), AWAITDURATION).length assert(numOrgs === 1) //insure root org is still there assertDeletedEntryNotCreated("root") } @@ -214,16 +212,16 @@ class TestDeleteOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeA info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.DELETED.intValue) - val numOrgs: Int = Await.result(DBCONNECTION.getDb.run(OrgsTQ.filter(_.orgid === "IBM").result), AWAITDURATION).length - val numUsers: Int = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.orgid === "IBM").result), AWAITDURATION).length - val numNodes: Int = Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.orgid === "IBM").result), AWAITDURATION).length - val numAgbots: Int = Await.result(DBCONNECTION.getDb.run(AgbotsTQ.filter(_.orgid === "IBM").result), AWAITDURATION).length + val numOrgs: Int = Await.result(DBCONNECTION.run(OrgsTQ.filter(_.orgid === "IBM").result), AWAITDURATION).length + val numUsers: Int = Await.result(DBCONNECTION.run(UsersTQ.filter(_.orgid === "IBM").result), AWAITDURATION).length + val numNodes: Int = Await.result(DBCONNECTION.run(NodesTQ.filter(_.orgid === "IBM").result), AWAITDURATION).length + val numAgbots: Int = Await.result(DBCONNECTION.run(AgbotsTQ.filter(_.orgid === "IBM").result), AWAITDURATION).length assert(numOrgs === 0) //insure ibm org is gone assert(numUsers === 0) //insure users are gone assert(numNodes === 0) //insure nodes are gone assert(numAgbots === 0) //insure agbots are gone //insure entry was created in Resource Changes table - val rcEntryExists: Boolean = Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ + val rcEntryExists: Boolean = Await.result(DBCONNECTION.run(ResourceChangesTQ .filter(_.orgId === "IBM") .filter(_.id === "IBM") .filter(_.category === ResChangeCategory.ORG.toString) @@ -256,7 +254,7 @@ class TestDeleteOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeA info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.ACCESS_DENIED.intValue) - val numOrgs: Int = Await.result(DBCONNECTION.getDb.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).length + val numOrgs: Int = Await.result(DBCONNECTION.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).length assert(numOrgs === 1) //insure org is still there assertDeletedEntryNotCreated(TESTORGS(0).orgId) } @@ -266,7 +264,7 @@ class TestDeleteOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeA info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.ACCESS_DENIED.intValue) - val numOrgs: Int = Await.result(DBCONNECTION.getDb.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).length + val numOrgs: Int = Await.result(DBCONNECTION.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).length assert(numOrgs === 1) //insure org is still there assertDeletedEntryNotCreated(TESTORGS(0).orgId) } diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestGetAllNodeErrorsRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestGetAllNodeErrorsRoute.scala index 1c297fe8..3ee4d1fb 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestGetAllNodeErrorsRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestGetAllNodeErrorsRoute.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.organization -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods import org.openhorizon.exchangeapi.auth.{Password, Role} @@ -10,11 +9,11 @@ import org.openhorizon.exchangeapi.table.node.error.{NodeErrorRow, NodeErrorTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} -import org.openhorizon.exchangeapi.TestDBConnection +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import scala.concurrent.Await import scala.concurrent.duration.{Duration, DurationInt} @@ -24,7 +23,7 @@ class TestGetAllNodeErrorsRoute extends AnyFunSuite with BeforeAndAfterAll { private val ACCEPT = ("Accept","application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private val ROUTE = "/search/nodes/error/all" @@ -225,7 +224,7 @@ class TestGetAllNodeErrorsRoute extends AnyFunSuite with BeforeAndAfterAll { publicKey = "" )) - private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(0).username + ":" + HUBADMINPASSWORD)) private val ORG1USERAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + ORG1USERPASSWORD)) private val ORG1ADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(2).username + ":" + ORG1ADMINPASSWORD)) @@ -250,7 +249,7 @@ class TestGetAllNodeErrorsRoute extends AnyFunSuite with BeforeAndAfterAll { )) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( (OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS) andThen (AgbotsTQ ++= TESTAGBOTS) andThen @@ -260,10 +259,9 @@ class TestGetAllNodeErrorsRoute extends AnyFunSuite with BeforeAndAfterAll { } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "testGetAllNodeErrorsRoute").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "testGetAllNodeErrorsRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "testGetAllNodeErrorsRoute").delete andThen UsersTQ.filter(_.username startsWith "root/TestGetAllNodeErrorsRouteHubAdmin").delete), AWAITDURATION) - DBCONNECTION.getDb.close() } def assertErrorsEqual(error1: NodeErrorsResp, error2: NodeErrorRow): Unit = { diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestGetMaxChangeIDRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestGetMaxChangeIDRoute.scala index 264ccd2d..3f6c4589 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestGetMaxChangeIDRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestGetMaxChangeIDRoute.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.organization -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods import org.openhorizon.exchangeapi.auth.{Password, Role} @@ -9,11 +8,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.{ResourceChangeRow, ResourceChangesTQ} import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} -import org.openhorizon.exchangeapi.TestDBConnection +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import scala.concurrent.Await import scala.concurrent.duration.{Duration, DurationInt} @@ -23,7 +22,7 @@ class TestGetMaxChangeIDRoute extends AnyFunSuite with BeforeAndAfterAll { private val ACCEPT = ("Accept","application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/changes/maxchangeid" private implicit val formats = DefaultFormats @@ -118,7 +117,7 @@ class TestGetMaxChangeIDRoute extends AnyFunSuite with BeforeAndAfterAll { ) ) - private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(0).username + ":" + HUBADMINPASSWORD)) private val USERAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + USERPASSWORD)) private val ADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(2).username + ":" + ADMINPASSWORD)) @@ -139,7 +138,7 @@ class TestGetMaxChangeIDRoute extends AnyFunSuite with BeforeAndAfterAll { ) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( (OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS) andThen (AgbotsTQ ++= TESTAGBOTS) andThen @@ -150,26 +149,25 @@ class TestGetMaxChangeIDRoute extends AnyFunSuite with BeforeAndAfterAll { override def afterAll(): Unit = { Await.ready( - DBCONNECTION.getDb.run( + DBCONNECTION.run( ResourceChangesTQ.filter(_.orgId startsWith "testGetMaxChangeIDRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "testGetMaxChangeIDRoute").delete andThen UsersTQ.filter(_.username startsWith "root/testGetMaxChangeIDRoute").delete ), AWAITDURATION) - DBCONNECTION.getDb.close() } // Resource Changes that are dynamically needed, specific to the test case. def fixtureResourceChanges(testCode: Seq[ResourceChangeRow] => Any, testData: Seq[ResourceChangeRow]): Any = { try{ - Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ ++= testData), AWAITDURATION) + Await.result(DBCONNECTION.run(ResourceChangesTQ ++= testData), AWAITDURATION) testCode(testData) } finally - Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.lastUpdated inSet testData.map(_.lastUpdated)).delete), AWAITDURATION) //hopefully lastUpdated is unique? because don't know change id + Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.lastUpdated inSet testData.map(_.lastUpdated)).delete), AWAITDURATION) //hopefully lastUpdated is unique? because don't know change id } test("GET /changes/maxchangeid -- as root -- success") { - val maxChangeIdBeforeRequest: Long = Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.sortBy(_.changeId.desc).take(1).result), AWAITDURATION).head.changeId + val maxChangeIdBeforeRequest: Long = Await.result(DBCONNECTION.run(ResourceChangesTQ.sortBy(_.changeId.desc).take(1).result), AWAITDURATION).head.changeId val response: HttpResponse[String] = Http(URL).headers(ACCEPT).headers(ROOTAUTH).asString info("Code: " + response.code) info("Body: " + response.body) @@ -215,7 +213,7 @@ class TestGetMaxChangeIDRoute extends AnyFunSuite with BeforeAndAfterAll { } test("GET /changes/maxchangeid -- as org admin -- success") { - val maxChangeIdBeforeRequest: Long = Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.sortBy(_.changeId.desc).take(1).result), AWAITDURATION).head.changeId + val maxChangeIdBeforeRequest: Long = Await.result(DBCONNECTION.run(ResourceChangesTQ.sortBy(_.changeId.desc).take(1).result), AWAITDURATION).head.changeId val response: HttpResponse[String] = Http(URL).headers(ACCEPT).headers(ADMINAUTH).asString info("Code: " + response.code) info("Body: " + response.body) @@ -232,7 +230,7 @@ class TestGetMaxChangeIDRoute extends AnyFunSuite with BeforeAndAfterAll { } test("GET /changes/maxchangeid -- as node -- success") { - val maxChangeIdBeforeRequest: Long = Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.sortBy(_.changeId.desc).take(1).result), AWAITDURATION).head.changeId + val maxChangeIdBeforeRequest: Long = Await.result(DBCONNECTION.run(ResourceChangesTQ.sortBy(_.changeId.desc).take(1).result), AWAITDURATION).head.changeId val response: HttpResponse[String] = Http(URL).headers(ACCEPT).headers(NODEAUTH).asString info("Code: " + response.code) info("Body: " + response.body) @@ -242,7 +240,7 @@ class TestGetMaxChangeIDRoute extends AnyFunSuite with BeforeAndAfterAll { } test("GET /changes/maxchangeid -- as agbot -- success") { - val maxChangeIdBeforeRequest: Long = Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.sortBy(_.changeId.desc).take(1).result), AWAITDURATION).head.changeId + val maxChangeIdBeforeRequest: Long = Await.result(DBCONNECTION.run(ResourceChangesTQ.sortBy(_.changeId.desc).take(1).result), AWAITDURATION).head.changeId val response: HttpResponse[String] = Http(URL).headers(ACCEPT).headers(AGBOTAUTH).asString info("Code: " + response.code) info("Body: " + response.body) diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestGetOrgRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestGetOrgRoute.scala index addb8869..45a9beaa 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestGetOrgRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestGetOrgRoute.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.organization -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods import org.json4s.jackson.JsonMethods.parse @@ -9,11 +8,11 @@ import org.openhorizon.exchangeapi.table.node.NodeHeartbeatIntervals import org.openhorizon.exchangeapi.table.organization.{Org, OrgLimits, OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} -import org.openhorizon.exchangeapi.TestDBConnection +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import scala.concurrent.Await import scala.concurrent.duration.{Duration, DurationInt} @@ -22,7 +21,7 @@ import slick.jdbc.PostgresProfile.api._ class TestGetOrgRoute extends AnyFunSuite with BeforeAndAfterAll { private val ACCEPT = ("Accept","application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private implicit val formats = DefaultFormats @@ -117,23 +116,22 @@ class TestGetOrgRoute extends AnyFunSuite with BeforeAndAfterAll { ) ) - private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(0).username + ":" + HUBADMINPASSWORD)) private val USER1AUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + USER1PASSWORD)) private val USER2AUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(2).username + ":" + USER2PASSWORD)) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( (OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS)), AWAITDURATION ) } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "testGetOrgRoute").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "testGetOrgRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "testGetOrgRoute").delete andThen UsersTQ.filter(_.username startsWith "root/TestGetOrgRouteHubAdmin").delete), AWAITDURATION) - DBCONNECTION.getDb.close() } def assertOrgsEqual(org1: Org, org2: OrgRow): Unit = { diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestGetOrgStatusRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestGetOrgStatusRoute.scala index f15fae93..0aa791b8 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestGetOrgStatusRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestGetOrgStatusRoute.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.organization -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods import org.openhorizon.exchangeapi.auth.{Password, Role} @@ -12,11 +11,11 @@ import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.schema.SchemaTQ import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, ExchMsg, HttpCode} -import org.openhorizon.exchangeapi.TestDBConnection +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, ExchMsg, HttpCode} import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import scala.concurrent.Await import scala.concurrent.duration.{Duration, DurationInt} @@ -26,10 +25,10 @@ class TestGetOrgStatusRoute extends AnyFunSuite with BeforeAndAfterAll { private val ACCEPT = ("Accept","application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private val ROUTE = "/status" - private val SCHEMAVERSION: Int = Await.result(DBCONNECTION.getDb.run(SchemaTQ.getSchemaVersion.result), AWAITDURATION).head + private val SCHEMAVERSION: Int = Await.result(DBCONNECTION.run(SchemaTQ.getSchemaVersion.result), AWAITDURATION).head private implicit val formats = DefaultFormats @@ -192,13 +191,13 @@ class TestGetOrgStatusRoute extends AnyFunSuite with BeforeAndAfterAll { timeSent = ApiTime.nowUTC, timeExpires = ApiTime.futureUTC(120))) - private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(0).username + ":" + HUBADMINPASSWORD)) private val USER1AUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + USER1PASSWORD)) private val USER2AUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(2).username + ":" + USER2PASSWORD)) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( (OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS) andThen (AgbotsTQ ++= TESTAGBOTS) andThen @@ -209,10 +208,9 @@ class TestGetOrgStatusRoute extends AnyFunSuite with BeforeAndAfterAll { } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "testGetOrgStatusRoute").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "testGetOrgStatusRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "testGetOrgStatusRoute").delete andThen UsersTQ.filter(_.username startsWith "root/TestGetOrgStatusRouteHubAdmin").delete), AWAITDURATION) - DBCONNECTION.getDb.close() } //is this intended? I would think this should fail with 404 not found diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestGetOrgsRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestGetOrgsRoute.scala index dd11dd0e..3750507e 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestGetOrgsRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestGetOrgsRoute.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.organization -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.jackson.JsonMethods import org.json4s.jackson.JsonMethods.parse import org.json4s.DefaultFormats @@ -9,10 +8,11 @@ import org.openhorizon.exchangeapi.table.node.NodeHeartbeatIntervals import org.openhorizon.exchangeapi.table.organization.{Org, OrgLimits, OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.concurrent.Await @@ -22,7 +22,7 @@ class TestGetOrgsRoute extends AnyFunSuite with BeforeAndAfterAll { private val ACCEPT = ("Accept","application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs" private implicit val formats = DefaultFormats @@ -106,22 +106,21 @@ class TestGetOrgsRoute extends AnyFunSuite with BeforeAndAfterAll { ) ) - private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(0).username + ":" + HUBADMINPASSWORD)) private val USERAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + USERPASSWORD)) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( (OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS)), AWAITDURATION ) } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "testGetOrgsRoute").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "testGetOrgsRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "testGetOrgsRoute").delete andThen UsersTQ.filter(_.username startsWith "root/TestGetOrgsRouteHubAdmin").delete), AWAITDURATION) - DBCONNECTION.getDb.close() } def assertOrgsEqual(org1: Org, org2: OrgRow): Unit = { diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPatchOrgRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPatchOrgRoute.scala index 6c858abb..7f46e8a9 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPatchOrgRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPatchOrgRoute.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.organization -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods import org.json4s.native.Serialization @@ -9,11 +8,11 @@ import org.openhorizon.exchangeapi.table.node.NodeHeartbeatIntervals import org.openhorizon.exchangeapi.table.organization.{OrgLimits, OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChangesTQ} import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, ExchConfig, HttpCode} -import org.openhorizon.exchangeapi.TestDBConnection +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import scala.concurrent.Await import scala.concurrent.duration.{Duration, DurationInt} @@ -24,7 +23,7 @@ class TestPatchOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn private val ACCEPT = ("Accept","application/json") private val CONTENT: (String, String) = ("Content-Type", "application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private implicit val formats = DefaultFormats @@ -129,7 +128,7 @@ class TestPatchOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn ) ) - private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(0).username + ":" + HUBADMINPASSWORD)) private val ORGADMIN1AUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + ORGADMIN1PASSWORD)) private val ORGADMIN2AUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(2).username + ":" + ORGADMIN2PASSWORD)) @@ -137,29 +136,28 @@ class TestPatchOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn private val USER2AUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(4).username + ":" + USER2PASSWORD)) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( (OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS)), AWAITDURATION) } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( ResourceChangesTQ.filter(_.orgId startsWith "testPatchOrgRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "testPatchOrgRoute").delete andThen UsersTQ.filter(_.username startsWith "root/TestPatchOrgRouteHubAdmin").delete //this guy doesn't get deleted on cascade ), AWAITDURATION) - DBCONNECTION.getDb.close() } override def afterEach(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( TESTORGS(0).update andThen ResourceChangesTQ.filter(_.orgId startsWith "testPatchOrgRoute").delete ), AWAITDURATION) } def assertNoChanges(org: OrgRow): Unit = { - val dbOrg: OrgRow = Await.result(DBCONNECTION.getDb.run(OrgsTQ.filter(_.orgid === org.orgId).result), AWAITDURATION).head + val dbOrg: OrgRow = Await.result(DBCONNECTION.run(OrgsTQ.filter(_.orgid === org.orgId).result), AWAITDURATION).head assert(dbOrg.orgType === org.orgType) assert(dbOrg.tags.get === org.tags.get) assert(dbOrg.orgId === org.orgId) @@ -171,7 +169,7 @@ class TestPatchOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn } def assertPatchedEntryCreated(orgId: String): Unit = { - assert(Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ + assert(Await.result(DBCONNECTION.run(ResourceChangesTQ .filter(_.orgId === orgId) .filter(_.id === orgId) .filter(_.category === ResChangeCategory.ORG.toString) @@ -195,10 +193,10 @@ class TestPatchOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn info("code: " + request.code) info("body: " + request.body) assert(request.code === HttpCode.NOT_FOUND.intValue) - val numOrgs: Int = Await.result(DBCONNECTION.getDb.run(OrgsTQ.filter(_.orgid === "doesNotExist").result), AWAITDURATION).length + val numOrgs: Int = Await.result(DBCONNECTION.run(OrgsTQ.filter(_.orgid === "doesNotExist").result), AWAITDURATION).length assert(numOrgs === 0) //insure org is not added //insure nothing was added to resource changes table - assert(Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === "doesNotExist").result), AWAITDURATION).isEmpty) + assert(Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === "doesNotExist").result), AWAITDURATION).isEmpty) } test("PATCH /orgs/" + TESTORGS(0).orgId + " -- invalid body -- 400 bad input") { @@ -211,7 +209,7 @@ class TestPatchOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn assert(request.code === HttpCode.BAD_INPUT.intValue) assertNoChanges(TESTORGS(0)) //insure nothing was added to resource changes table - assert(Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) + assert(Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) } test("PATCH /orgs/" + TESTORGS(0).orgId + " -- all fields empty -- 400 bad input") { @@ -229,11 +227,11 @@ class TestPatchOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn assert(request.code === HttpCode.BAD_INPUT.intValue) assertNoChanges(TESTORGS(0)) //insure nothing was added to resource changes table - assert(Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) + assert(Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) } test("PATCH /orgs/" + TESTORGS(0).orgId + " -- max nodes too large -- 400 bad input") { - val exchangeMaxNodes: Int = ExchConfig.getInt("api.limits.maxNodes") + val exchangeMaxNodes: Int = Configuration.getConfig.getInt("api.limits.maxNodes") val requestBody: PatchOrgRequest = PatchOrgRequest( orgType = None, label = None, @@ -248,7 +246,7 @@ class TestPatchOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn assert(request.code === HttpCode.BAD_INPUT.intValue) assertNoChanges(TESTORGS(0)) //insure nothing was added to resource changes table - assert(Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) + assert(Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) } // it is undefined what happens when >1 attributes are included in the body, but based on the code it follows this order of preference: @@ -259,7 +257,7 @@ class TestPatchOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn info("code: " + request.code) info("body: " + request.body) assert(request.code === HttpCode.POST_OK.intValue) - val dbOrg: OrgRow = Await.result(DBCONNECTION.getDb.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).head + val dbOrg: OrgRow = Await.result(DBCONNECTION.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).head assert(dbOrg.orgType === "newType") assert(dbOrg.tags.get === TESTORGS(0).tags.get) assert(dbOrg.orgId === TESTORGS(0).orgId) @@ -284,7 +282,7 @@ class TestPatchOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn info("code: " + request.code) info("body: " + request.body) assert(request.code === HttpCode.POST_OK.intValue) - val dbOrg: OrgRow = Await.result(DBCONNECTION.getDb.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).head + val dbOrg: OrgRow = Await.result(DBCONNECTION.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).head assert(dbOrg.orgType === requestBody.orgType.get) assert(dbOrg.tags.get === TESTORGS(0).tags.get) assert(dbOrg.orgId === TESTORGS(0).orgId) @@ -309,7 +307,7 @@ class TestPatchOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn info("code: " + request.code) info("body: " + request.body) assert(request.code === HttpCode.POST_OK.intValue) - val dbOrg: OrgRow = Await.result(DBCONNECTION.getDb.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).head + val dbOrg: OrgRow = Await.result(DBCONNECTION.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).head assert(dbOrg.orgType === TESTORGS(0).orgType) assert(dbOrg.tags.get === TESTORGS(0).tags.get) assert(dbOrg.orgId === TESTORGS(0).orgId) @@ -334,7 +332,7 @@ class TestPatchOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn info("code: " + request.code) info("body: " + request.body) assert(request.code === HttpCode.POST_OK.intValue) - val dbOrg: OrgRow = Await.result(DBCONNECTION.getDb.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).head + val dbOrg: OrgRow = Await.result(DBCONNECTION.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).head assert(dbOrg.orgType === TESTORGS(0).orgType) assert(dbOrg.tags.get === TESTORGS(0).tags.get) assert(dbOrg.orgId === TESTORGS(0).orgId) @@ -359,7 +357,7 @@ class TestPatchOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn info("code: " + request.code) info("body: " + request.body) assert(request.code === HttpCode.POST_OK.intValue) - val dbOrg: OrgRow = Await.result(DBCONNECTION.getDb.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).head + val dbOrg: OrgRow = Await.result(DBCONNECTION.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).head assert(dbOrg.orgType === TESTORGS(0).orgType) assert(dbOrg.tags.get.extract[Map[String, Option[String]]] === requestBody.tags.get) assert(dbOrg.orgId === TESTORGS(0).orgId) @@ -384,7 +382,7 @@ class TestPatchOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn info("code: " + request.code) info("body: " + request.body) assert(request.code === HttpCode.POST_OK.intValue) - val dbOrg: OrgRow = Await.result(DBCONNECTION.getDb.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).head + val dbOrg: OrgRow = Await.result(DBCONNECTION.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).head assert(dbOrg.orgType === TESTORGS(0).orgType) assert(dbOrg.tags.get === TESTORGS(0).tags.get) assert(dbOrg.orgId === TESTORGS(0).orgId) @@ -413,7 +411,7 @@ class TestPatchOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn info("code: " + request.code) info("body: " + request.body) assert(request.code === HttpCode.POST_OK.intValue) - val dbOrg: OrgRow = Await.result(DBCONNECTION.getDb.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).head + val dbOrg: OrgRow = Await.result(DBCONNECTION.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).head assert(dbOrg.orgType === TESTORGS(0).orgType) assert(dbOrg.tags.get === TESTORGS(0).tags.get) assert(dbOrg.orgId === TESTORGS(0).orgId) @@ -430,7 +428,7 @@ class TestPatchOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn info("code: " + request.code) info("body: " + request.body) assert(request.code === HttpCode.POST_OK.intValue) - val dbOrg: OrgRow = Await.result(DBCONNECTION.getDb.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).head + val dbOrg: OrgRow = Await.result(DBCONNECTION.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).head assert(dbOrg.orgType === normalRequestBody.orgType.get) assert(dbOrg.tags.get === TESTORGS(0).tags.get) assert(dbOrg.orgId === TESTORGS(0).orgId) @@ -447,7 +445,7 @@ class TestPatchOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn info("code: " + request.code) info("body: " + request.body) assert(request.code === HttpCode.POST_OK.intValue) - val dbOrg: OrgRow = Await.result(DBCONNECTION.getDb.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).head + val dbOrg: OrgRow = Await.result(DBCONNECTION.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).head assert(dbOrg.orgType === normalRequestBody.orgType.get) assert(dbOrg.tags.get === TESTORGS(0).tags.get) assert(dbOrg.orgId === TESTORGS(0).orgId) @@ -466,7 +464,7 @@ class TestPatchOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn assert(request.code === HttpCode.ACCESS_DENIED.intValue) assertNoChanges(TESTORGS(0)) //insure nothing was added to resource changes table - assert(Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) + assert(Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) } test("PATCH /orgs/" + TESTORGS(0).orgId + " -- as org admin in other org -- 403 access denied") { @@ -476,7 +474,7 @@ class TestPatchOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn assert(request.code === HttpCode.ACCESS_DENIED.intValue) assertNoChanges(TESTORGS(0)) //insure nothing was added to resource changes table - assert(Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) + assert(Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) } test("PATCH /orgs/" + TESTORGS(0).orgId + " -- as regular user in other org -- 403 access denied") { @@ -486,7 +484,7 @@ class TestPatchOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn assert(request.code === HttpCode.ACCESS_DENIED.intValue) assertNoChanges(TESTORGS(0)) //insure nothing was added to resource changes table - assert(Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) + assert(Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) } } diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostAgreementConfirmRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostAgreementConfirmRoute.scala index 96b9f298..6610ac3f 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostAgreementConfirmRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostAgreementConfirmRoute.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.organization -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods import org.json4s.native.Serialization @@ -12,10 +11,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiResponse, ApiTime, ApiUtils, ExchMsg, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiResponse, ApiTime, ApiUtils, Configuration, DatabaseConnection, ExchMsg, HttpCode} import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.concurrent.Await @@ -26,7 +26,7 @@ class TestPostAgreementConfirmRoute extends AnyFunSuite with BeforeAndAfterAll { private val ACCEPT = ("Accept","application/json") private val CONTENT: (String, String) = ("Content-Type", "application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private val ROUTE = "/agreements/confirm" @@ -233,7 +233,7 @@ class TestPostAgreementConfirmRoute extends AnyFunSuite with BeforeAndAfterAll { ) ) - private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(0).username + ":" + HUBADMINPASSWORD)) private val ORG1USERAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + ORG1USERPASSWORD)) private val ORG2USERAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(2).username + ":" + ORG2USERPASSWORD)) @@ -242,7 +242,7 @@ class TestPostAgreementConfirmRoute extends AnyFunSuite with BeforeAndAfterAll { private val AGBOT2AUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTAGBOTS(1).id + ":" + AGBOT2TOKEN)) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( (OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS) andThen (AgbotsTQ ++= TESTAGBOTS) andThen @@ -252,11 +252,10 @@ class TestPostAgreementConfirmRoute extends AnyFunSuite with BeforeAndAfterAll { } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "TestPostAgreementConfirmRouteOrg").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "TestPostAgreementConfirmRouteOrg").delete andThen OrgsTQ.filter(_.orgid startsWith "TestPostAgreementConfirmRouteOrg").delete andThen UsersTQ.filter(_.username startsWith "root/TestPostAgreementConfirmRouteHubAdmin").delete ), AWAITDURATION) - DBCONNECTION.getDb.close() } //I don't think it really makes sense for this to be in the "orgs" route, or even take in the orgId parameter at all. orgId is only used to check if the caller has access to diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostMyOrgsRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostMyOrgsRoute.scala index d9c9a063..557f6e5f 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostMyOrgsRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostMyOrgsRoute.scala @@ -1,18 +1,19 @@ package org.openhorizon.exchangeapi.route.organization -import org.openhorizon.exchangeapi.auth.{IamAccountInfo, Password, Role} -import org.openhorizon.exchangeapi.TestDBConnection +import org.openhorizon.exchangeapi.auth.{Password, Role} import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods import org.json4s.native.Serialization +import org.openhorizon.exchangeapi.auth.cloud.IamAccountInfo import org.openhorizon.exchangeapi.table.node.NodeHeartbeatIntervals import org.openhorizon.exchangeapi.table.organization.{Org, OrgLimits, OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.concurrent.Await @@ -23,7 +24,7 @@ class TestPostMyOrgsRoute extends AnyFunSuite with BeforeAndAfterAll { private val ACCEPT = ("Accept","application/json") private val CONTENT: (String, String) = ("Content-Type", "application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/myorgs" private implicit val formats = DefaultFormats @@ -118,23 +119,22 @@ class TestPostMyOrgsRoute extends AnyFunSuite with BeforeAndAfterAll { ) ) - private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(0).username + ":" + HUBADMINPASSWORD)) private val USERAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + USERPASSWORD)) private val ADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(2).username + ":" + ADMINPASSWORD)) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( (OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS) ), AWAITDURATION) } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "TestPostMyOrgsRouteOrg").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "TestPostMyOrgsRouteOrg").delete andThen OrgsTQ.filter(_.orgid startsWith "TestPostMyOrgsRouteOrg").delete andThen UsersTQ.filter(_.username startsWith "root/TestPostMyOrgsRouteHubAdmin").delete), AWAITDURATION) - DBCONNECTION.getDb.close() } def assertOrgsEqual(org1: Org, org2: OrgRow): Unit = { diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostNodeErrorsRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostNodeErrorsRoute.scala index c2d817bd..837fcda2 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostNodeErrorsRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostNodeErrorsRoute.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.organization -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods import org.openhorizon.exchangeapi.auth.{Password, Role} @@ -11,10 +10,11 @@ import org.openhorizon.exchangeapi.table.node.error.{NodeErrorRow, NodeErrorTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import scala.concurrent.Await import scala.concurrent.duration.{Duration, DurationInt} @@ -25,7 +25,7 @@ class TestPostNodeErrorsRoute extends AnyFunSuite with BeforeAndAfterAll { private val ACCEPT = ("Accept","application/json") private val CONTENT: (String, String) = ("Content-Type", "application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private val ROUTE = "/search/nodes/error" @@ -226,7 +226,7 @@ class TestPostNodeErrorsRoute extends AnyFunSuite with BeforeAndAfterAll { publicKey = "" )) - private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(0).username + ":" + HUBADMINPASSWORD)) private val ORG1USERAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + ORG1USERPASSWORD)) private val ORG1ADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(2).username + ":" + ORG1ADMINPASSWORD)) @@ -251,7 +251,7 @@ class TestPostNodeErrorsRoute extends AnyFunSuite with BeforeAndAfterAll { )) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( (OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS) andThen (AgbotsTQ ++= TESTAGBOTS) andThen @@ -261,10 +261,9 @@ class TestPostNodeErrorsRoute extends AnyFunSuite with BeforeAndAfterAll { } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "testPostNodeErrorsRoute").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "testPostNodeErrorsRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "testPostNodeErrorsRoute").delete andThen UsersTQ.filter(_.username startsWith "root/TestPostNodeErrorsRouteHubAdmin").delete), AWAITDURATION) - DBCONNECTION.getDb.close() } test("POST /orgs/doesNotExist" + ROUTE + " -- 404 not found") { diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostNodeHealthRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostNodeHealthRoute.scala index 64dd0db5..4d3af29b 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostNodeHealthRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostNodeHealthRoute.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.organization -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods import org.json4s.native.Serialization @@ -11,10 +10,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.concurrent.Await @@ -25,7 +25,7 @@ class TestPostNodeHealthRoute extends AnyFunSuite with BeforeAndAfterAll { private val ACCEPT = ("Accept","application/json") private val CONTENT: (String, String) = ("Content-Type", "application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private val ROUTE = "/search/nodehealth" @@ -255,7 +255,7 @@ class TestPostNodeHealthRoute extends AnyFunSuite with BeforeAndAfterAll { lastUpdated = ApiTime.nowUTC) ) - private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(0).username + ":" + HUBADMINPASSWORD)) private val ORG1USERAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + ORG1USERPASSWORD)) private val ORG2USERAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(2).username + ":" + ORG2USERPASSWORD)) @@ -266,7 +266,7 @@ class TestPostNodeHealthRoute extends AnyFunSuite with BeforeAndAfterAll { private val ORG1ADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(3).username + ":" + ORG1ADMINPASSWORD)) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( (OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS) andThen (AgbotsTQ ++= TESTAGBOTS) andThen @@ -276,10 +276,9 @@ class TestPostNodeHealthRoute extends AnyFunSuite with BeforeAndAfterAll { } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "testPostNodeHealthRoute").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "testPostNodeHealthRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "testPostNodeHealthRoute").delete andThen UsersTQ.filter(_.username startsWith "root/TestPostNodeHealthRouteHubAdmin").delete), AWAITDURATION) - DBCONNECTION.getDb.close() } private val oneMinuteAgo: PostNodeHealthRequest = PostNodeHealthRequest(ApiTime.pastUTC(60), None) //not sure what nodeOrgIds is for... it is never referenced in the Route diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostNodesServiceRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostNodesServiceRoute.scala index 80a4489e..b4830718 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostNodesServiceRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostNodesServiceRoute.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.organization -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods import org.json4s.native.Serialization @@ -12,11 +11,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} -import org.openhorizon.exchangeapi.TestDBConnection +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import scala.concurrent.Await import scala.concurrent.duration.{Duration, DurationInt} @@ -27,7 +26,7 @@ class TestPostNodesServiceRoute extends AnyFunSuite with BeforeAndAfterAll { private val ACCEPT = ("Accept","application/json") private val CONTENT: (String, String) = ("Content-Type", "application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private val ROUTE = "/search/nodes/service" @@ -228,7 +227,7 @@ class TestPostNodesServiceRoute extends AnyFunSuite with BeforeAndAfterAll { publicKey = "" )) - private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(0).username + ":" + HUBADMINPASSWORD)) private val ORG1USERAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + ORG1USERPASSWORD)) private val ORG1ADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(2).username + ":" + ORG1ADMINPASSWORD)) @@ -283,7 +282,7 @@ class TestPostNodesServiceRoute extends AnyFunSuite with BeforeAndAfterAll { ) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( (OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS) andThen (AgbotsTQ ++= TESTAGBOTS) andThen @@ -293,10 +292,9 @@ class TestPostNodesServiceRoute extends AnyFunSuite with BeforeAndAfterAll { } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "testPostNodesServiceRoute").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "testPostNodesServiceRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "testPostNodesServiceRoute").delete andThen UsersTQ.filter(_.username startsWith "root/TestPostNodesServiceRouteHubAdmin").delete), AWAITDURATION) - DBCONNECTION.getDb.close() } val normalRequestBody: PostServiceSearchRequest = PostServiceSearchRequest( diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostOrgChangesRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostOrgChangesRoute.scala index c039d7f2..58e02c48 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostOrgChangesRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostOrgChangesRoute.scala @@ -1,7 +1,7 @@ package org.openhorizon.exchangeapi.route.organization import org.apache.pekko.http.scaladsl.model.headers.CacheDirectives.public -import org.openhorizon.exchangeapi.{ExchangeApi, TestDBConnection} +import org.openhorizon.exchangeapi.ExchangeApi import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods import org.json4s.native.Serialization @@ -11,11 +11,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.{ResourceChangeRow, ResourceChangesTQ} import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} -import org.openhorizon.exchangeapi.{ExchangeApi, TestDBConnection} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.concurrent.Await @@ -26,7 +26,7 @@ class TestPostOrgChangesRoute extends AnyFunSuite with BeforeAndAfterAll with Be private val ACCEPT: (String, String) = ("Accept","application/json") private val CONTENT: (String, String) = ("Content-Type", "application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL: String = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private val ROUTE = "/changes" @@ -173,7 +173,7 @@ class TestPostOrgChangesRoute extends AnyFunSuite with BeforeAndAfterAll with Be ) ) - private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(0).username + ":" + HUBADMINPASSWORD)) private val ORG1USERAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + ORG1USERPASSWORD)) private val ORG2USERAUTH: (String, String) = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(2).username + ":" + ORG2USERPASSWORD)) @@ -288,7 +288,7 @@ class TestPostOrgChangesRoute extends AnyFunSuite with BeforeAndAfterAll with Be var lastChangeId: Long = 0L //will be set in beforeAll() override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( (OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS) andThen (ResourceChangesTQ ++= TESTRESOURCECHANGES) andThen @@ -296,7 +296,7 @@ class TestPostOrgChangesRoute extends AnyFunSuite with BeforeAndAfterAll with Be (NodesTQ ++= TESTNODES) ), AWAITDURATION ) - lastChangeId = Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ //get changeId of last RC added to DB + lastChangeId = Await.result(DBCONNECTION.run(ResourceChangesTQ //get changeId of last RC added to DB .filter(_.orgId startsWith "testPostOrgChangesRoute") .sortBy(_.changeId.desc) .take(1) @@ -304,17 +304,15 @@ class TestPostOrgChangesRoute extends AnyFunSuite with BeforeAndAfterAll with Be } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId startsWith "testPostOrgChangesRoute").delete andThen + Await.ready(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId startsWith "testPostOrgChangesRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "testPostOrgChangesRoute").delete andThen UsersTQ.filter(_.username startsWith "root/testPostOrgChangesRouteHubAdmin").delete andThen AgbotsTQ.filter(_.id startsWith "IBM/testPostOrgChangesRouteIBMAgbot").delete), AWAITDURATION) - - DBCONNECTION.getDb.close() } override def afterEach(): Unit = { //need to reset heartbeat each time to ensure it actually gets changed in each test - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( NodesTQ.update(TESTNODES(0)) andThen //can't do "updateAll", so do them individually NodesTQ.update(TESTNODES(1)) andThen AgbotsTQ.update(TESTAGBOTS(0)) andThen @@ -327,10 +325,10 @@ class TestPostOrgChangesRoute extends AnyFunSuite with BeforeAndAfterAll with Be //only does one at a time for ease of deleting when finished def fixtureResourceChange(testCode: ResourceChangeRow => Any, testData: ResourceChangeRow): Any = { try{ - Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ += testData), AWAITDURATION) + Await.result(DBCONNECTION.run(ResourceChangesTQ += testData), AWAITDURATION) testCode(testData) } - //finally Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(x => x.orgId === testData.orgId && x.resource === testData.resource && x.id === testData.id).delete), AWAITDURATION) + //finally Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(x => x.orgId === testData.orgId && x.resource === testData.resource && x.id === testData.id).delete), AWAITDURATION) } def assertResourceChangeExists(rc: ResourceChangeRow, body: ResourceChangesRespObject): Unit = { @@ -442,7 +440,7 @@ class TestPostOrgChangesRoute extends AnyFunSuite with BeforeAndAfterAll with Be assertResourceChangeExists(TESTRESOURCECHANGES(9), responseObj) assert(responseObj.exchangeVersion === EXCHANGEVERSION) //check that heartbeat of caller was updated - assert(Await.result(DBCONNECTION.getDb.run(AgbotsTQ.filter(_.id === TESTAGBOTS(2).id).result), AWAITDURATION).head.lastHeartbeat > TESTAGBOTS(2).lastHeartbeat) + assert(Await.result(DBCONNECTION.run(AgbotsTQ.filter(_.id === TESTAGBOTS(2).id).result), AWAITDURATION).head.lastHeartbeat > TESTAGBOTS(2).lastHeartbeat) } test("POST /orgs/" + TESTORGS(0).orgId + ROUTE + " -- " + TESTORGS(0).orgId + " wildcard '*' -- success") { @@ -465,7 +463,7 @@ class TestPostOrgChangesRoute extends AnyFunSuite with BeforeAndAfterAll with Be assertResourceChangeExists(TESTRESOURCECHANGES(9), responseObj) assert(responseObj.exchangeVersion === EXCHANGEVERSION) //check that heartbeat of caller was updated - assert(Await.result(DBCONNECTION.getDb.run(AgbotsTQ.filter(_.id === TESTAGBOTS(2).id).result), AWAITDURATION).head.lastHeartbeat > TESTAGBOTS(2).lastHeartbeat) + assert(Await.result(DBCONNECTION.run(AgbotsTQ.filter(_.id === TESTAGBOTS(2).id).result), AWAITDURATION).head.lastHeartbeat > TESTAGBOTS(2).lastHeartbeat) } test("POST /orgs/" + TESTORGS(0).orgId + ROUTE + " -- " + TESTORGS(0).orgId + " wildcard '' (empty string) -- success") { @@ -488,7 +486,7 @@ class TestPostOrgChangesRoute extends AnyFunSuite with BeforeAndAfterAll with Be assertResourceChangeExists(TESTRESOURCECHANGES(9), responseObj) assert(responseObj.exchangeVersion === EXCHANGEVERSION) //check that heartbeat of caller was updated - assert(Await.result(DBCONNECTION.getDb.run(AgbotsTQ.filter(_.id === TESTAGBOTS(2).id).result), AWAITDURATION).head.lastHeartbeat > TESTAGBOTS(2).lastHeartbeat) + assert(Await.result(DBCONNECTION.run(AgbotsTQ.filter(_.id === TESTAGBOTS(2).id).result), AWAITDURATION).head.lastHeartbeat > TESTAGBOTS(2).lastHeartbeat) } test("POST /orgs/" + TESTORGS(0).orgId + ROUTE + " -- as agbot in org -- success") { @@ -504,7 +502,7 @@ class TestPostOrgChangesRoute extends AnyFunSuite with BeforeAndAfterAll with Be assertResourceChangeExists(TESTRESOURCECHANGES(9), responseObj) assert(responseObj.exchangeVersion === EXCHANGEVERSION) //check that heartbeat of caller was updated - assert(Await.result(DBCONNECTION.getDb.run(AgbotsTQ.filter(_.id === TESTAGBOTS(0).id).result), AWAITDURATION).head.lastHeartbeat > TESTAGBOTS(0).lastHeartbeat) + assert(Await.result(DBCONNECTION.run(AgbotsTQ.filter(_.id === TESTAGBOTS(0).id).result), AWAITDURATION).head.lastHeartbeat > TESTAGBOTS(0).lastHeartbeat) } test("POST /orgs/" + TESTORGS(0).orgId + ROUTE + " -- as agbot in other org -- 403 access denied") { @@ -513,7 +511,7 @@ class TestPostOrgChangesRoute extends AnyFunSuite with BeforeAndAfterAll with Be info("Body: " + response.body) assert(response.code === HttpCode.ACCESS_DENIED.intValue) //insure heartbeat hasn't changed - assert(Await.result(DBCONNECTION.getDb.run(AgbotsTQ.filter(_.id === TESTAGBOTS(1).id).result), AWAITDURATION).head.lastHeartbeat === TESTAGBOTS(1).lastHeartbeat) + assert(Await.result(DBCONNECTION.run(AgbotsTQ.filter(_.id === TESTAGBOTS(1).id).result), AWAITDURATION).head.lastHeartbeat === TESTAGBOTS(1).lastHeartbeat) } test("POST /orgs/" + TESTORGS(0).orgId + ROUTE + " -- as node in org -- success") { @@ -531,7 +529,7 @@ class TestPostOrgChangesRoute extends AnyFunSuite with BeforeAndAfterAll with Be assertResourceChangeExists(TESTRESOURCECHANGES(9), responseObj) assert(responseObj.exchangeVersion === EXCHANGEVERSION) //check that heartbeat of caller was updated - assert(Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === TESTNODES(0).id).result), AWAITDURATION).head.lastHeartbeat.get > TESTNODES(0).lastHeartbeat.get) + assert(Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === TESTNODES(0).id).result), AWAITDURATION).head.lastHeartbeat.get > TESTNODES(0).lastHeartbeat.get) } test("POST /orgs/" + TESTORGS(0).orgId + ROUTE + " -- as node in other org -- 403 access denied") { @@ -540,7 +538,7 @@ class TestPostOrgChangesRoute extends AnyFunSuite with BeforeAndAfterAll with Be info("Body: " + response.body) assert(response.code === HttpCode.ACCESS_DENIED.intValue) //insure heartbeat hasn't changed - assert(Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.id === TESTNODES(1).id).result), AWAITDURATION).head.lastHeartbeat === TESTNODES(1).lastHeartbeat) + assert(Await.result(DBCONNECTION.run(NodesTQ.filter(_.id === TESTNODES(1).id).result), AWAITDURATION).head.lastHeartbeat === TESTNODES(1).lastHeartbeat) } test("POST /orgs/" + TESTORGS(0).orgId + ROUTE + " -- as root -- success") { diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostOrgRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostOrgRoute.scala index 05c8c62b..c7c1ca58 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostOrgRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPostOrgRoute.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.organization -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods import org.json4s.native.Serialization @@ -9,10 +8,11 @@ import org.openhorizon.exchangeapi.table.node.NodeHeartbeatIntervals import org.openhorizon.exchangeapi.table.organization.{Org, OrgLimits, OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChangesTQ} import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, ExchConfig, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.concurrent.Await @@ -23,7 +23,7 @@ class TestPostOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd private val ACCEPT = ("Accept","application/json") private val CONTENT: (String, String) = ("Content-Type", "application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private implicit val formats = DefaultFormats @@ -67,23 +67,22 @@ class TestPostOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd ) ) - private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(0).username + ":" + HUBADMINPASSWORD)) private val USER1AUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + USER1PASSWORD)) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( (OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS)), AWAITDURATION) } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( ResourceChangesTQ.filter(_.orgId startsWith "TEMPtestPostOrgRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "TEMPtestPostOrgRoute").delete andThen UsersTQ.filter(_.username startsWith "root/TestPostOrgRouteHubAdmin").delete //this guy doesn't get deleted on cascade ), AWAITDURATION) - DBCONNECTION.getDb.close() } private val orgId: String = "testPostOrgRoute1" @@ -103,7 +102,7 @@ class TestPostOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd )) override def afterEach(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( ResourceChangesTQ.filter(_.orgId startsWith "testPostOrgRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "testPostOrgRoute").delete), AWAITDURATION) } @@ -119,7 +118,7 @@ class TestPostOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd def assertCreatedEntryExists(orgId: String): Unit = { assert( - Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ + Await.result(DBCONNECTION.run(ResourceChangesTQ .filter(_.orgId === orgId) .filter(_.id === orgId) .filter(_.category === ResChangeCategory.ORG.toString) @@ -136,9 +135,9 @@ class TestPostOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd info("code: " + request.code) info("body: " + request.body) assert(request.code === HttpCode.BAD_INPUT.intValue) - assert(Await.result(DBCONNECTION.getDb.run(OrgsTQ.getOrgid(orgId).result), AWAITDURATION).isEmpty) //make sure org didn't actually get added to DB + assert(Await.result(DBCONNECTION.run(OrgsTQ.getOrgid(orgId).result), AWAITDURATION).isEmpty) //make sure org didn't actually get added to DB //insure nothing was added to resource changes table - assert(Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === orgId).result), AWAITDURATION).isEmpty) + assert(Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === orgId).result), AWAITDURATION).isEmpty) } test("POST /orgs/" + orgId + " -- null label -- 400 bad input") { @@ -154,9 +153,9 @@ class TestPostOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd info("code: " + request.code) info("body: " + request.body) assert(request.code === HttpCode.BAD_INPUT.intValue) - assert(Await.result(DBCONNECTION.getDb.run(OrgsTQ.getOrgid(orgId).result), AWAITDURATION).isEmpty) //make sure org didn't actually get added to DB + assert(Await.result(DBCONNECTION.run(OrgsTQ.getOrgid(orgId).result), AWAITDURATION).isEmpty) //make sure org didn't actually get added to DB //insure nothing was added to resource changes table - assert(Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === orgId).result), AWAITDURATION).isEmpty) + assert(Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === orgId).result), AWAITDURATION).isEmpty) } //error message "requirement failed" isn't very descriptive here @@ -173,13 +172,13 @@ class TestPostOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd info("code: " + request.code) info("body: " + request.body) assert(request.code === HttpCode.BAD_INPUT.intValue) - assert(Await.result(DBCONNECTION.getDb.run(OrgsTQ.getOrgid(orgId).result), AWAITDURATION).isEmpty) //make sure org didn't actually get added to DB + assert(Await.result(DBCONNECTION.run(OrgsTQ.getOrgid(orgId).result), AWAITDURATION).isEmpty) //make sure org didn't actually get added to DB //insure nothing was added to resource changes table - assert(Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === orgId).result), AWAITDURATION).isEmpty) + assert(Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === orgId).result), AWAITDURATION).isEmpty) } test("POST /orgs/" + orgId + " -- max nodes too large -- 400 bad input") { - val exchangeMaxNodes: Int = ExchConfig.getInt("api.limits.maxNodes") + val exchangeMaxNodes: Int = Configuration.getConfig.getInt("api.limits.maxNodes") val requestBody: PostPutOrgRequest = PostPutOrgRequest( orgType = None, label = "label", @@ -192,9 +191,9 @@ class TestPostOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd info("code: " + request.code) info("body: " + request.body) assert(request.code === HttpCode.BAD_INPUT.intValue) - assert(Await.result(DBCONNECTION.getDb.run(OrgsTQ.getOrgid(orgId).result), AWAITDURATION).isEmpty) //make sure org didn't actually get added to DB + assert(Await.result(DBCONNECTION.run(OrgsTQ.getOrgid(orgId).result), AWAITDURATION).isEmpty) //make sure org didn't actually get added to DB //insure nothing was added to resource changes table - assert(Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === orgId).result), AWAITDURATION).isEmpty) + assert(Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === orgId).result), AWAITDURATION).isEmpty) } test("POST /orgs/" + orgId + " as root -- normal success") { @@ -202,7 +201,7 @@ class TestPostOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd info("code: " + request.code) info("body: " + request.body) assert(request.code === HttpCode.POST_OK.intValue) - val newOrg: OrgRow = Await.result(DBCONNECTION.getDb.run(OrgsTQ.filter(_.orgid === orgId).take(1).result), AWAITDURATION).head + val newOrg: OrgRow = Await.result(DBCONNECTION.run(OrgsTQ.filter(_.orgid === orgId).take(1).result), AWAITDURATION).head assert(newOrg.orgId === orgId) assertOrgsEqual(normalRequestBody, newOrg) assertCreatedEntryExists(orgId) @@ -213,7 +212,7 @@ class TestPostOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd info("code: " + request.code) info("body: " + request.body) assert(request.code === HttpCode.POST_OK.intValue) - val newOrg: OrgRow = Await.result(DBCONNECTION.getDb.run(OrgsTQ.filter(_.orgid === orgId).take(1).result), AWAITDURATION).head + val newOrg: OrgRow = Await.result(DBCONNECTION.run(OrgsTQ.filter(_.orgid === orgId).take(1).result), AWAITDURATION).head assert(newOrg.orgId === orgId) assertOrgsEqual(normalRequestBody, newOrg) assertCreatedEntryExists(orgId) @@ -224,10 +223,10 @@ class TestPostOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd info("code: " + request.code) info("body: " + request.body) assert(request.code === HttpCode.ACCESS_DENIED.intValue) - val numOrgs: Int = Await.result(DBCONNECTION.getDb.run(OrgsTQ.getOrgid(orgId).result), AWAITDURATION).length + val numOrgs: Int = Await.result(DBCONNECTION.run(OrgsTQ.getOrgid(orgId).result), AWAITDURATION).length assert(numOrgs === 0) //make sure org didn't actually get added to DB //insure nothing was added to resource changes table - assert(Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === orgId).result), AWAITDURATION).isEmpty) + assert(Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === orgId).result), AWAITDURATION).isEmpty) } test("POST /orgs/" + TESTORGS(0).orgId + " -- 409 conflict (already exists)") { @@ -235,10 +234,10 @@ class TestPostOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd info("code: " + request.code) info("body: " + request.body) assert(request.code === HttpCode.ALREADY_EXISTS2.intValue) - val numOrgs: Int = Await.result(DBCONNECTION.getDb.run(OrgsTQ.getOrgid(TESTORGS(0).orgId).result), AWAITDURATION).length + val numOrgs: Int = Await.result(DBCONNECTION.run(OrgsTQ.getOrgid(TESTORGS(0).orgId).result), AWAITDURATION).length assert(numOrgs === 1) //insure nothing was added to resource changes table - assert(Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) + assert(Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) } } diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPutOrgRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPutOrgRoute.scala index 12e22344..83d13fe4 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPutOrgRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/organization/TestPutOrgRoute.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.organization -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods import org.json4s.native.Serialization @@ -9,11 +8,11 @@ import org.openhorizon.exchangeapi.table.node.NodeHeartbeatIntervals import org.openhorizon.exchangeapi.table.organization.{OrgLimits, OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.{ResChangeCategory, ResChangeOperation, ResChangeResource, ResourceChangesTQ} import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, ExchConfig, HttpCode} -import org.openhorizon.exchangeapi.TestDBConnection +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import scala.concurrent.Await import scala.concurrent.duration.{Duration, DurationInt} @@ -24,7 +23,7 @@ class TestPutOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAndA private val ACCEPT = ("Accept","application/json") private val CONTENT: (String, String) = ("Content-Type", "application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private implicit val formats = DefaultFormats @@ -129,7 +128,7 @@ class TestPutOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAndA ) ) - private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(0).username + ":" + HUBADMINPASSWORD)) private val ORGADMIN1AUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + ORGADMIN1PASSWORD)) private val ORGADMIN2AUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(2).username + ":" + ORGADMIN2PASSWORD)) @@ -137,29 +136,28 @@ class TestPutOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAndA private val USER2AUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(4).username + ":" + USER2PASSWORD)) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( (OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS)), AWAITDURATION) } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( ResourceChangesTQ.filter(_.orgId startsWith "testPutOrgRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "testPutOrgRoute").delete andThen UsersTQ.filter(_.username startsWith "root/TestPutOrgRouteHubAdmin").delete //this guy doesn't get deleted on cascade ), AWAITDURATION) - DBCONNECTION.getDb.close() } override def afterEach(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( TESTORGS(0).update andThen //reset testPutOrgRoute1 each time ResourceChangesTQ.filter(_.orgId startsWith "testPutOrgRoute").delete ), AWAITDURATION) } def assertNoChanges(org: OrgRow): Unit = { - val dbOrg: OrgRow = Await.result(DBCONNECTION.getDb.run(OrgsTQ.filter(_.orgid === org.orgId).result), AWAITDURATION).head + val dbOrg: OrgRow = Await.result(DBCONNECTION.run(OrgsTQ.filter(_.orgid === org.orgId).result), AWAITDURATION).head assert(dbOrg.orgType === org.orgType) assert(dbOrg.tags.get === org.tags.get) assert(dbOrg.orgId === org.orgId) @@ -171,7 +169,7 @@ class TestPutOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAndA } def assertOrg1Updated(request: PostPutOrgRequest): Unit = { - val dbOrg: OrgRow = Await.result(DBCONNECTION.getDb.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).head + val dbOrg: OrgRow = Await.result(DBCONNECTION.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).head assert(dbOrg.orgType === request.orgType.getOrElse("")) assert(dbOrg.tags.get.extract[Map[String, String]] === request.tags.get) assert(dbOrg.orgId === TESTORGS(0).orgId) @@ -184,7 +182,7 @@ class TestPutOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAndA def assertCreatedModifiedEntryExists(orgId: String): Unit = { assert( - Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ + Await.result(DBCONNECTION.run(ResourceChangesTQ .filter(_.orgId === orgId) .filter(_.id === orgId) .filter(_.category === ResChangeCategory.ORG.toString) @@ -213,10 +211,10 @@ class TestPutOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAndA info("code: " + request.code) info("body: " + request.body) assert(request.code === HttpCode.NOT_FOUND.intValue) - val numOrgs: Int = Await.result(DBCONNECTION.getDb.run(OrgsTQ.filter(_.orgid === "doesNotExist").result), AWAITDURATION).length + val numOrgs: Int = Await.result(DBCONNECTION.run(OrgsTQ.filter(_.orgid === "doesNotExist").result), AWAITDURATION).length assert(numOrgs === 0) //insure org is not added //insure nothing was added to resource changes table - assert(Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === "doesNotExist").result), AWAITDURATION).isEmpty) + assert(Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === "doesNotExist").result), AWAITDURATION).isEmpty) } test("PUT /orgs/" + TESTORGS(0).orgId + " -- invalid body -- 400 bad input") { @@ -227,7 +225,7 @@ class TestPutOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAndA assert(request.code === HttpCode.BAD_INPUT.intValue) assertNoChanges(TESTORGS(0)) //insure nothing was added to resource changes table - assert(Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) + assert(Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) } test("PUT /orgs/" + TESTORGS(0).orgId + " -- null label -- 400 bad input") { @@ -245,7 +243,7 @@ class TestPutOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAndA assert(request.code === HttpCode.BAD_INPUT.intValue) assertNoChanges(TESTORGS(0)) //insure nothing was added to resource changes table - assert(Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) + assert(Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) } test("PUT /orgs/" + TESTORGS(0).orgId + " -- null description -- 400 bad input") { @@ -263,11 +261,11 @@ class TestPutOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAndA assert(request.code === HttpCode.BAD_INPUT.intValue) assertNoChanges(TESTORGS(0)) //insure nothing was added to resource changes table - assert(Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) + assert(Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) } test("PUT /orgs/" + TESTORGS(0).orgId + " -- max nodes too large -- 400 bad input") { - val exchangeMaxNodes: Int = ExchConfig.getInt("api.limits.maxNodes") + val exchangeMaxNodes: Int = Configuration.getConfig.getInt("api.limits.maxNodes") val requestBody: PostPutOrgRequest = PostPutOrgRequest( orgType = None, label = "label", @@ -282,7 +280,7 @@ class TestPutOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAndA assert(request.code === HttpCode.BAD_INPUT.intValue) assertNoChanges(TESTORGS(0)) //insure nothing was added to resource changes table - assert(Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) + assert(Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) } test("PUT /orgs/" + TESTORGS(0).orgId + " as root -- normal success") { @@ -303,7 +301,7 @@ class TestPutOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAndA info("code: " + request.code) info("body: " + request.body) assert(request.code === HttpCode.PUT_OK.intValue) - val dbOrg: OrgRow = Await.result(DBCONNECTION.getDb.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).head + val dbOrg: OrgRow = Await.result(DBCONNECTION.run(OrgsTQ.filter(_.orgid === TESTORGS(0).orgId).result), AWAITDURATION).head assert(dbOrg.orgType === "") assert(dbOrg.tags.isEmpty) assert(dbOrg.orgId === TESTORGS(0).orgId) @@ -340,7 +338,7 @@ class TestPutOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAndA assert(request.code === HttpCode.ACCESS_DENIED.intValue) assertNoChanges(TESTORGS(0)) //insure nothing was added to resource changes table - assert(Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) + assert(Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) } test("PUT /orgs/" + TESTORGS(0).orgId + " as org admin in other org -- 403 access denied") { @@ -350,7 +348,7 @@ class TestPutOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAndA assert(request.code === HttpCode.ACCESS_DENIED.intValue) assertNoChanges(TESTORGS(0)) //insure nothing was added to resource changes table - assert(Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) + assert(Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) } test("PUT /orgs/" + TESTORGS(0).orgId + " as regular user in other org -- 403 access denied") { @@ -360,7 +358,7 @@ class TestPutOrgRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAndA assert(request.code === HttpCode.ACCESS_DENIED.intValue) assertNoChanges(TESTORGS(0)) //insure nothing was added to resource changes table - assert(Await.result(DBCONNECTION.getDb.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) + assert(Await.result(DBCONNECTION.run(ResourceChangesTQ.filter(_.orgId === TESTORGS(0).orgId).result), AWAITDURATION).isEmpty) } } diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/user/TestDeleteUserRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/user/TestDeleteUserRoute.scala index 11c48d1b..83cd2760 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/user/TestDeleteUserRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/user/TestDeleteUserRoute.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.user -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.DefaultFormats import org.openhorizon.exchangeapi.auth.{Password, Role} import org.openhorizon.exchangeapi.table.agreementbot.{AgbotRow, AgbotsTQ} @@ -8,10 +7,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.concurrent.Await @@ -21,7 +21,7 @@ class TestDeleteUserRoute extends AnyFunSuite with BeforeAndAfterAll with Before private val ACCEPT = ("Accept","application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private val ROUTE = "/users/" @@ -157,7 +157,7 @@ class TestDeleteUserRoute extends AnyFunSuite with BeforeAndAfterAll with Before ) ) - private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(0).username + ":" + HUBADMINPASSWORD)) private val ORG1ADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + ORG1ADMINPASSWORD)) private val ORG1USERAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(2).username + ":" + ORG1USERPASSWORD)) @@ -165,7 +165,7 @@ class TestDeleteUserRoute extends AnyFunSuite with BeforeAndAfterAll with Before private val NODEAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTNODES(0).id + ":" + NODETOKEN)) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( (OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS) andThen (AgbotsTQ ++= TESTAGBOTS) andThen @@ -174,16 +174,15 @@ class TestDeleteUserRoute extends AnyFunSuite with BeforeAndAfterAll with Before } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( ResourceChangesTQ.filter(_.orgId startsWith "testDeleteUserRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "testDeleteUserRoute").delete andThen UsersTQ.filter(_.username startsWith "root/TestDeleteUserRouteHubAdmin").delete ), AWAITDURATION) - DBCONNECTION.getDb.close() } override def afterEach(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( TESTUSERS(2).upsertUser andThen TESTUSERS(0).upsertUser andThen TESTUSERS(1).upsertUser andThen @@ -194,9 +193,9 @@ class TestDeleteUserRoute extends AnyFunSuite with BeforeAndAfterAll with Before } def assertDbClear(username: String): Unit = { - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === username).result), AWAITDURATION).isEmpty) //insure users are gone - assert(Await.result(DBCONNECTION.getDb.run(NodesTQ.filter(_.owner === username).result), AWAITDURATION).isEmpty) //insure nodes are gone - assert(Await.result(DBCONNECTION.getDb.run(AgbotsTQ.filter(_.owner === username).result), AWAITDURATION).isEmpty) //insure agbots are gone + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === username).result), AWAITDURATION).isEmpty) //insure users are gone + assert(Await.result(DBCONNECTION.run(NodesTQ.filter(_.owner === username).result), AWAITDURATION).isEmpty) //insure nodes are gone + assert(Await.result(DBCONNECTION.run(AgbotsTQ.filter(_.owner === username).result), AWAITDURATION).isEmpty) //insure agbots are gone } private val normalUsernameToDelete = TESTUSERS(2).username.split("/")(1) @@ -220,7 +219,7 @@ class TestDeleteUserRoute extends AnyFunSuite with BeforeAndAfterAll with Before info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.BAD_INPUT.intValue) - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === "root/root").result), AWAITDURATION).nonEmpty) //insure user still exists + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === "root/root").result), AWAITDURATION).nonEmpty) //insure user still exists } test("DELETE /orgs/" + TESTORGS(0).orgId + ROUTE + normalUsernameToDelete + " -- user deletes self -- 204 DELETED") { @@ -236,7 +235,7 @@ class TestDeleteUserRoute extends AnyFunSuite with BeforeAndAfterAll with Before info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.ACCESS_DENIED.intValue) - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(4).username).result), AWAITDURATION).nonEmpty) //insure user still exists + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(4).username).result), AWAITDURATION).nonEmpty) //insure user still exists } test("DELETE /orgs/" + TESTORGS(0).orgId + ROUTE + normalUsernameToDelete + " -- org admin deletes user -- 204 DELETED") { @@ -252,7 +251,7 @@ class TestDeleteUserRoute extends AnyFunSuite with BeforeAndAfterAll with Before info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.ACCESS_DENIED.intValue) - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(3).username).result), AWAITDURATION).nonEmpty) //insure user still exists + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(3).username).result), AWAITDURATION).nonEmpty) //insure user still exists } test("DELETE /orgs/root" + ROUTE + "TestDeleteUserRouteHubAdmin -- org admin tries to delete hub admin -- 403 ACCESS DENIED") { @@ -260,7 +259,7 @@ class TestDeleteUserRoute extends AnyFunSuite with BeforeAndAfterAll with Before info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.ACCESS_DENIED.intValue) - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(0).username).result), AWAITDURATION).nonEmpty) //insure user still exists + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(0).username).result), AWAITDURATION).nonEmpty) //insure user still exists } test("DELETE /orgs/root" + ROUTE + "TestDeleteUserRouteHubAdmin -- hub admin deletes self -- 204 DELETED") { @@ -268,7 +267,7 @@ class TestDeleteUserRoute extends AnyFunSuite with BeforeAndAfterAll with Before info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.DELETED.intValue) - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(0).username).result), AWAITDURATION).isEmpty) //insure user is deleted + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(0).username).result), AWAITDURATION).isEmpty) //insure user is deleted } test("DELETE /orgs/root" + ROUTE + "TestDeleteUserRouteHubAdmin2 -- hub admin deletes other hub admin -- 204 DELETED") { @@ -276,7 +275,7 @@ class TestDeleteUserRoute extends AnyFunSuite with BeforeAndAfterAll with Before info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.DELETED.intValue) - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(5).username).result), AWAITDURATION).isEmpty) //insure user is deleted + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(5).username).result), AWAITDURATION).isEmpty) //insure user is deleted } //currently a hub admin is able to delete any user (other than root). However, a hub admin is only supposed to be able to delete admins @@ -285,7 +284,7 @@ class TestDeleteUserRoute extends AnyFunSuite with BeforeAndAfterAll with Before info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.ACCESS_DENIED.intValue) - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).nonEmpty) //insure user still exists + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).nonEmpty) //insure user still exists } test("DELETE /orgs/" + TESTORGS(0).orgId + ROUTE + "orgAdmin -- hub admin deletes org admin -- 204 DELETED") { @@ -293,7 +292,7 @@ class TestDeleteUserRoute extends AnyFunSuite with BeforeAndAfterAll with Before info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.DELETED.intValue) - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(1).username).result), AWAITDURATION).isEmpty) //insure user is deleted + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(1).username).result), AWAITDURATION).isEmpty) //insure user is deleted } test("DELETE /orgs/" + TESTORGS(0).orgId + ROUTE + normalUsernameToDelete + " -- node tries to delete user -- 204 DELETED") { @@ -301,7 +300,7 @@ class TestDeleteUserRoute extends AnyFunSuite with BeforeAndAfterAll with Before info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.ACCESS_DENIED.intValue) - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).nonEmpty) //insure user still exists + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).nonEmpty) //insure user still exists } test("DELETE /orgs/" + TESTORGS(0).orgId + ROUTE + normalUsernameToDelete + " -- agbot tries to delete user -- 204 DELETED") { @@ -309,7 +308,7 @@ class TestDeleteUserRoute extends AnyFunSuite with BeforeAndAfterAll with Before info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.ACCESS_DENIED.intValue) - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).nonEmpty) //insure user still exists + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).nonEmpty) //insure user still exists } } diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/user/TestGetUserRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/user/TestGetUserRoute.scala index 47dc54d6..24e98a7e 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/user/TestGetUserRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/user/TestGetUserRoute.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.user -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods import org.openhorizon.exchangeapi.auth.{Password, Role} @@ -9,10 +8,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.user.{User, UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode, StrConstants} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode, StrConstants} import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.concurrent.Await @@ -22,7 +22,7 @@ class TestGetUserRoute extends AnyFunSuite with BeforeAndAfterAll { private val ACCEPT = ("Accept","application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private val ROUTE = "/users/" @@ -138,7 +138,7 @@ class TestGetUserRoute extends AnyFunSuite with BeforeAndAfterAll { ) ) - private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(0).username + ":" + HUBADMINPASSWORD)) private val ORG1ADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + ORG1ADMINPASSWORD)) private val ORG1USERAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(2).username + ":" + ORG1USERPASSWORD)) @@ -146,7 +146,7 @@ class TestGetUserRoute extends AnyFunSuite with BeforeAndAfterAll { private val NODEAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTNODES(0).id + ":" + NODETOKEN)) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( (OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS) andThen (AgbotsTQ ++= TESTAGBOTS) andThen @@ -155,12 +155,11 @@ class TestGetUserRoute extends AnyFunSuite with BeforeAndAfterAll { } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( ResourceChangesTQ.filter(_.orgId startsWith "testGetUserRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "testGetUserRoute").delete andThen UsersTQ.filter(_.username startsWith "root/TestGetUserRouteHubAdmin").delete ), AWAITDURATION) - DBCONNECTION.getDb.close() } def assertUsersEqual(user1: User, user2: UserRow): Unit = { diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/user/TestGetUsersRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/user/TestGetUsersRoute.scala index 1b533e67..45dd473e 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/user/TestGetUsersRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/user/TestGetUsersRoute.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.user -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods import org.openhorizon.exchangeapi.auth.{Password, Role} @@ -9,10 +8,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.user.{User, UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode, StrConstants} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode, StrConstants} import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.concurrent.Await @@ -22,7 +22,7 @@ class TestGetUsersRoute extends AnyFunSuite with BeforeAndAfterAll { private val ACCEPT = ("Accept","application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private val ROUTE = "/users" @@ -149,7 +149,7 @@ class TestGetUsersRoute extends AnyFunSuite with BeforeAndAfterAll { ) ) - private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(0).username + ":" + HUBADMINPASSWORD)) private val ORG1ADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + ORG1ADMINPASSWORD)) private val ORG1USERAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(2).username + ":" + ORG1USERPASSWORD)) @@ -158,7 +158,7 @@ class TestGetUsersRoute extends AnyFunSuite with BeforeAndAfterAll { private val NODEAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTNODES(0).id + ":" + NODETOKEN)) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( (OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS) andThen (AgbotsTQ ++= TESTAGBOTS) andThen @@ -167,12 +167,11 @@ class TestGetUsersRoute extends AnyFunSuite with BeforeAndAfterAll { } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( ResourceChangesTQ.filter(_.orgId startsWith "testGetUsersRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "testGetUsersRoute").delete andThen UsersTQ.filter(_.username startsWith "root/TestGetUsersRouteHubAdmin").delete ), AWAITDURATION) - DBCONNECTION.getDb.close() } def assertUsersEqual(user1: User, user2: UserRow): Unit = { diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/user/TestPatchUserRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/user/TestPatchUserRoute.scala index 6154d24a..781aa19a 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/user/TestPatchUserRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/user/TestPatchUserRoute.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.user -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods import org.json4s.native.Serialization @@ -11,10 +10,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiResponse, ApiTime, ApiUtils, ExchMsg, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiResponse, ApiTime, ApiUtils, Configuration, DatabaseConnection, ExchMsg, HttpCode} import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.concurrent.Await @@ -25,7 +25,7 @@ class TestPatchUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeA private val ACCEPT = ("Accept","application/json") private val CONTENT: (String, String) = ("Content-Type", "application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private val ROUTE = "/users/" @@ -151,7 +151,7 @@ class TestPatchUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeA ) ) - private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(0).username + ":" + HUBADMINPASSWORD)) private val ORG1ADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + ORG1ADMINPASSWORD)) private val ORG1USERAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(2).username + ":" + ORG1USERPASSWORD)) @@ -159,7 +159,7 @@ class TestPatchUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeA private val NODEAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTNODES(0).id + ":" + NODETOKEN)) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( (OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS) andThen (AgbotsTQ ++= TESTAGBOTS) andThen @@ -168,16 +168,15 @@ class TestPatchUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeA } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( ResourceChangesTQ.filter(_.orgId startsWith "testPatchUserRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "testPatchUserRoute").delete andThen UsersTQ.filter(_.username startsWith "root/TestPatchUserRouteHubAdmin").delete ), AWAITDURATION) - DBCONNECTION.getDb.close() } override def afterEach(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( TESTUSERS(0).updateUser() andThen TESTUSERS(2).updateUser() andThen TESTUSERS(4).updateUser @@ -185,7 +184,7 @@ class TestPatchUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeA } def assertNoChanges(user: UserRow): Unit = { - val dbUser: UserRow = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === user.username).result), AWAITDURATION).head + val dbUser: UserRow = Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === user.username).result), AWAITDURATION).head assert(dbUser.username === user.username) assert(dbUser.orgid === user.orgid) assert(dbUser.hashedPw === user.hashedPw) @@ -263,7 +262,7 @@ class TestPatchUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeA info("Body: " + response.body) assert(response.code === HttpCode.POST_OK.intValue) //insure new user is in DB correctly - val newUser: UserRow = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head + val newUser: UserRow = Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head assert(newUser.username === TESTUSERS(2).username) assert(newUser.orgid === TESTORGS(0).orgId) assert(newUser.updatedBy === "root/root") //updated by root @@ -334,7 +333,7 @@ class TestPatchUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeA info("Body: " + response.body) assert(response.code === HttpCode.POST_OK.intValue) //insure new user is in DB correctly - val newUser: UserRow = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(0).username).result), AWAITDURATION).head + val newUser: UserRow = Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(0).username).result), AWAITDURATION).head assert(newUser.username === TESTUSERS(0).username) assert(newUser.orgid === TESTUSERS(0).orgid) assert(newUser.updatedBy === "root/root") //updated by root @@ -389,7 +388,7 @@ class TestPatchUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeA info("Body: " + response.body) assert(response.code === HttpCode.POST_OK.intValue) //insure new user is in DB correctly - val newUser: UserRow = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(4).username).result), AWAITDURATION).head + val newUser: UserRow = Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(4).username).result), AWAITDURATION).head assert(newUser.username === TESTUSERS(4).username) assert(newUser.orgid === TESTUSERS(4).orgid) assert(newUser.updatedBy === TESTUSERS(1).username) //updated by org admin @@ -414,7 +413,7 @@ class TestPatchUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeA info("Body: " + response.body) assert(response.code === HttpCode.POST_OK.intValue) //insure new user is in DB correctly - val newUser: UserRow = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head + val newUser: UserRow = Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head assert(newUser.username === TESTUSERS(2).username) assert(newUser.orgid === TESTUSERS(2).orgid) assert(newUser.updatedBy === TESTUSERS(2).username) //updated by self @@ -462,7 +461,7 @@ class TestPatchUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeA info("Body: " + response.body) assert(response.code === HttpCode.POST_OK.intValue) //insure new user is in DB correctly - val newUser: UserRow = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head + val newUser: UserRow = Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head assert(newUser.username === TESTUSERS(2).username) assert(newUser.orgid === TESTUSERS(2).orgid) assert(newUser.updatedBy === "root/root") //updated by root @@ -485,7 +484,7 @@ class TestPatchUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeA info("Body: " + response.body) assert(response.code === HttpCode.POST_OK.intValue) //insure new user is in DB correctly - val newUser: UserRow = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head + val newUser: UserRow = Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head assert(newUser.username === TESTUSERS(2).username) assert(newUser.orgid === TESTUSERS(2).orgid) assert(newUser.updatedBy === "root/root") //updated by root diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/user/TestPostChangeUserPasswordRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/user/TestPostChangeUserPasswordRoute.scala index 70f3851d..8b12c40b 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/user/TestPostChangeUserPasswordRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/user/TestPostChangeUserPasswordRoute.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.user -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.DefaultFormats import org.json4s.native.Serialization import org.mindrot.jbcrypt.BCrypt @@ -10,10 +9,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.concurrent.Await @@ -24,7 +24,7 @@ class TestPostChangeUserPasswordRoute extends AnyFunSuite with BeforeAndAfterAll private val ACCEPT = ("Accept","application/json") private val CONTENT: (String, String) = ("Content-Type", "application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private val ROUTE1 = "/users/" private val ROUTE2 = "/changepw" @@ -151,7 +151,7 @@ class TestPostChangeUserPasswordRoute extends AnyFunSuite with BeforeAndAfterAll ) ) - private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(0).username + ":" + HUBADMINPASSWORD)) private val ORG1ADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + ORG1ADMINPASSWORD)) private val ORG1USERAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(2).username + ":" + ORG1USERPASSWORD)) @@ -159,7 +159,7 @@ class TestPostChangeUserPasswordRoute extends AnyFunSuite with BeforeAndAfterAll private val NODEAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTNODES(0).id + ":" + NODETOKEN)) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( (OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS) andThen (AgbotsTQ ++= TESTAGBOTS) andThen @@ -168,16 +168,15 @@ class TestPostChangeUserPasswordRoute extends AnyFunSuite with BeforeAndAfterAll } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( ResourceChangesTQ.filter(_.orgId startsWith "testPostChangeUserPasswordRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "testPostChangeUserPasswordRoute").delete andThen UsersTQ.filter(_.username startsWith "root/TestPostChangeUserPasswordRouteHubAdmin").delete ), AWAITDURATION) - DBCONNECTION.getDb.close() } override def afterEach(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( TESTUSERS(2).updateUser() andThen TESTUSERS(1).updateUser() ), AWAITDURATION) @@ -206,7 +205,7 @@ class TestPostChangeUserPasswordRoute extends AnyFunSuite with BeforeAndAfterAll info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.BAD_INPUT.intValue) - val dbPass = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head.hashedPw + val dbPass = Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head.hashedPw assert(dbPass === TESTUSERS(2).hashedPw) //assert password hasn't changed } @@ -216,7 +215,7 @@ class TestPostChangeUserPasswordRoute extends AnyFunSuite with BeforeAndAfterAll info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.BAD_INPUT.intValue) - val dbPass = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head.hashedPw + val dbPass = Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head.hashedPw assert(dbPass === TESTUSERS(2).hashedPw) //assert password hasn't changed } @@ -225,7 +224,7 @@ class TestPostChangeUserPasswordRoute extends AnyFunSuite with BeforeAndAfterAll info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.POST_OK.intValue) - val dbPass = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head.hashedPw + val dbPass = Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head.hashedPw assert(BCrypt.checkpw(normalRequestBody.newPassword, dbPass)) //assert password was updated } @@ -235,7 +234,7 @@ class TestPostChangeUserPasswordRoute extends AnyFunSuite with BeforeAndAfterAll info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.ACCESS_DENIED.intValue) - val dbPass = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head.hashedPw + val dbPass = Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head.hashedPw assert(dbPass === TESTUSERS(2).hashedPw) //assert password hasn't changed } @@ -244,7 +243,7 @@ class TestPostChangeUserPasswordRoute extends AnyFunSuite with BeforeAndAfterAll info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.POST_OK.intValue) - val dbPass = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(1).username).result), AWAITDURATION).head.hashedPw + val dbPass = Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(1).username).result), AWAITDURATION).head.hashedPw assert(BCrypt.checkpw(normalRequestBody.newPassword, dbPass)) //assert password was updated } @@ -253,7 +252,7 @@ class TestPostChangeUserPasswordRoute extends AnyFunSuite with BeforeAndAfterAll info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.POST_OK.intValue) - val dbPass = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head.hashedPw + val dbPass = Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head.hashedPw assert(BCrypt.checkpw(normalRequestBody.newPassword, dbPass)) //assert password was updated } @@ -262,7 +261,7 @@ class TestPostChangeUserPasswordRoute extends AnyFunSuite with BeforeAndAfterAll info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.ACCESS_DENIED.intValue) - val dbPass = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(3).username).result), AWAITDURATION).head.hashedPw + val dbPass = Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(3).username).result), AWAITDURATION).head.hashedPw assert(dbPass === TESTUSERS(3).hashedPw) //assert password hasn't changed } @@ -271,7 +270,7 @@ class TestPostChangeUserPasswordRoute extends AnyFunSuite with BeforeAndAfterAll info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.POST_OK.intValue) - val dbPass = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head.hashedPw + val dbPass = Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head.hashedPw assert(BCrypt.checkpw(normalRequestBody.newPassword, dbPass)) //assert password was updated } @@ -280,7 +279,7 @@ class TestPostChangeUserPasswordRoute extends AnyFunSuite with BeforeAndAfterAll info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.ACCESS_DENIED.intValue) - val dbPass = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(4).username).result), AWAITDURATION).head.hashedPw + val dbPass = Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(4).username).result), AWAITDURATION).head.hashedPw assert(dbPass === TESTUSERS(4).hashedPw) //assert password hasn't changed } @@ -289,7 +288,7 @@ class TestPostChangeUserPasswordRoute extends AnyFunSuite with BeforeAndAfterAll info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.ACCESS_DENIED.intValue) - val dbPass = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head.hashedPw + val dbPass = Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head.hashedPw assert(dbPass === TESTUSERS(2).hashedPw) //assert password hasn't changed } @@ -298,7 +297,7 @@ class TestPostChangeUserPasswordRoute extends AnyFunSuite with BeforeAndAfterAll info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.ACCESS_DENIED.intValue) - val dbPass = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head.hashedPw + val dbPass = Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head.hashedPw assert(dbPass === TESTUSERS(2).hashedPw) //assert password hasn't changed } diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/user/TestPostConfirmUserRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/user/TestPostConfirmUserRoute.scala index a942beba..ecf8f868 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/user/TestPostConfirmUserRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/user/TestPostConfirmUserRoute.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.user -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.DefaultFormats import org.openhorizon.exchangeapi.auth.{Password, Role} import org.openhorizon.exchangeapi.table.agreementbot.{AgbotRow, AgbotsTQ} @@ -8,10 +7,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiTime, ApiUtils, Configuration, DatabaseConnection, HttpCode} import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.concurrent.Await @@ -21,7 +21,7 @@ class TestPostConfirmUserRoute extends AnyFunSuite with BeforeAndAfterAll { private val ACCEPT = ("Accept","application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private val ROUTE1 = "/users/" private val ROUTE2 = "/confirm" @@ -148,7 +148,7 @@ class TestPostConfirmUserRoute extends AnyFunSuite with BeforeAndAfterAll { ) ) - private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(0).username + ":" + HUBADMINPASSWORD)) private val ORG1ADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + ORGADMINPASSWORD)) private val ORG1USERAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(2).username + ":" + ORGUSERPASSWORD)) @@ -156,7 +156,7 @@ class TestPostConfirmUserRoute extends AnyFunSuite with BeforeAndAfterAll { private val NODEAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTNODES(0).id + ":" + NODETOKEN)) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( (OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS) andThen (AgbotsTQ ++= TESTAGBOTS) andThen @@ -165,12 +165,11 @@ class TestPostConfirmUserRoute extends AnyFunSuite with BeforeAndAfterAll { } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( ResourceChangesTQ.filter(_.orgId startsWith "testPostConfirmUserRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "testPostConfirmUserRoute").delete andThen UsersTQ.filter(_.username startsWith "root/TestPostConfirmUserRouteHubAdmin").delete ), AWAITDURATION) - DBCONNECTION.getDb.close() } private val defaultUsername = TESTUSERS(2).username.split("/")(1) diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/user/TestPostUserRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/user/TestPostUserRoute.scala index 9f6b8cf7..f983a04e 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/user/TestPostUserRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/user/TestPostUserRoute.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.user -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods import org.json4s.native.Serialization @@ -11,10 +10,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiResponse, ApiTime, ApiUtils, ExchMsg, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiResponse, ApiTime, ApiUtils, Configuration, DatabaseConnection, ExchMsg, HttpCode} import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.concurrent.Await @@ -25,7 +25,7 @@ class TestPostUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn private val ACCEPT = ("Accept","application/json") private val CONTENT: (String, String) = ("Content-Type", "application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private val ROUTE = "/users/" @@ -131,7 +131,7 @@ class TestPostUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn ) ) - private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(0).username + ":" + HUBADMINPASSWORD)) private val ORG1ADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + ORGADMINPASSWORD)) private val ORG1USERAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(2).username + ":" + ORGUSERPASSWORD)) @@ -139,7 +139,7 @@ class TestPostUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn private val NODEAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTNODES(0).id + ":" + NODETOKEN)) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( (OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS) andThen (AgbotsTQ ++= TESTAGBOTS) andThen @@ -148,16 +148,15 @@ class TestPostUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( ResourceChangesTQ.filter(_.orgId startsWith "testPostUserRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "testPostUserRoute").delete andThen UsersTQ.filter(_.username startsWith "root/TestPostUserRouteHubAdmin").delete ), AWAITDURATION) - DBCONNECTION.getDb.close() } override def afterEach(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( UsersTQ.filter(_.username startsWith (TESTORGS(0).orgId + "/newUser")).delete andThen UsersTQ.filter(_.username startsWith "root/TestPostUserRouteNewUser").delete ), AWAITDURATION) @@ -183,7 +182,7 @@ class TestPostUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.INTERNAL_ERROR.intValue) - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === "doesNotExist/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === "doesNotExist/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added } test("POST /orgs/" + TESTORGS(0).orgId + ROUTE + "newUser -- empty body -- 400 bad input") { @@ -191,7 +190,7 @@ class TestPostUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.BAD_INPUT.intValue) - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added } test("POST /orgs/" + TESTORGS(0).orgId + ROUTE + "newUser -- null password -- 400 bad input") { @@ -205,7 +204,7 @@ class TestPostUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.BAD_INPUT.intValue) - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added } test("POST /orgs/" + TESTORGS(0).orgId + ROUTE + "newUser -- null email -- 400 bad input") { @@ -219,7 +218,7 @@ class TestPostUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.BAD_INPUT.intValue) - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added } test("POST /orgs/" + TESTORGS(0).orgId + ROUTE + "newUser -- blank password -- as org admin -- 400 bad input") { @@ -235,7 +234,7 @@ class TestPostUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn assert(response.code === HttpCode.BAD_INPUT.intValue) val responseBody: ApiResponse = JsonMethods.parse(response.body).extract[ApiResponse] assert(responseBody.msg === ExchMsg.translate("password.must.be.non.blank.when.creating.user")) - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added } test("POST /orgs/" + TESTORGS(0).orgId + ROUTE + "newUser -- blank password -- as root -- 201 OK") { @@ -250,7 +249,7 @@ class TestPostUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn info("Body: " + response.body) assert(response.code === HttpCode.POST_OK.intValue) //insure new user is in DB correctly - val newUser: UserRow = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/newUser").result), AWAITDURATION).head + val newUser: UserRow = Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/newUser").result), AWAITDURATION).head assert(newUser.username === TESTORGS(0).orgId + "/newUser") assert(newUser.orgid === TESTORGS(0).orgId) assert(newUser.updatedBy === "root/root") //updated by root @@ -270,7 +269,7 @@ class TestPostUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn assert(response.code === HttpCode.BAD_INPUT.intValue) val responseBody: ApiResponse = JsonMethods.parse(response.body).extract[ApiResponse] assert(responseBody.msg === ExchMsg.translate("only.super.users.make.hub.admins")) - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added } test("POST /orgs/root" + ROUTE + "TestPostUserRouteNewUser -- hub admin creates new hub admin -- 201 OK") { @@ -285,7 +284,7 @@ class TestPostUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn info("Body: " + response.body) assert(response.code === HttpCode.POST_OK.intValue) //insure new user is in DB correctly - val newUser: UserRow = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === "root/TestPostUserRouteNewUser").result), AWAITDURATION).head + val newUser: UserRow = Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === "root/TestPostUserRouteNewUser").result), AWAITDURATION).head assert(newUser.username === "root/TestPostUserRouteNewUser") assert(newUser.orgid === "root") assert(newUser.updatedBy === TESTUSERS(0).username) //updated by hub admin @@ -305,7 +304,7 @@ class TestPostUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn assert(response.code === HttpCode.BAD_INPUT.intValue) val responseBody: ApiResponse = JsonMethods.parse(response.body).extract[ApiResponse] assert(responseBody.msg === ExchMsg.translate("hub.admins.in.root.org")) - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added } test("POST /orgs/root" + ROUTE + "newUser -- try to create regular user in root org -- 400 bad input") { @@ -315,7 +314,7 @@ class TestPostUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn assert(response.code === HttpCode.BAD_INPUT.intValue) val responseBody: ApiResponse = JsonMethods.parse(response.body).extract[ApiResponse] assert(responseBody.msg === ExchMsg.translate("user.cannot.be.in.root.org")) - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === "root/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === "root/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added } test("POST /orgs/" + TESTORGS(0).orgId + ROUTE + "newUser -- hub admin tries to create regular user -- 400 bad input") { @@ -325,7 +324,7 @@ class TestPostUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn assert(response.code === HttpCode.BAD_INPUT.intValue) val responseBody: ApiResponse = JsonMethods.parse(response.body).extract[ApiResponse] assert(responseBody.msg === ExchMsg.translate("hub.admins.only.write.admins")) - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added } test("POST /orgs/root" + ROUTE + "newUser -- try to make a user who is both admin and hub admin -- 400 bad input") { @@ -341,7 +340,7 @@ class TestPostUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn assert(response.code === HttpCode.BAD_INPUT.intValue) val responseBody: ApiResponse = JsonMethods.parse(response.body).extract[ApiResponse] assert(responseBody.msg === "User cannot be admin and hubAdmin at the same time") - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === "root/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === "root/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added } test("POST /orgs/" + TESTORGS(0).orgId + ROUTE + "orgUser -- try to create user with existing username -- 400 bad input") { @@ -349,7 +348,7 @@ class TestPostUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.BAD_INPUT.intValue) - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/orgUser").result), AWAITDURATION).length === 1) //insure only one exists + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/orgUser").result), AWAITDURATION).length === 1) //insure only one exists } test("POST /orgs/" + TESTORGS(0).orgId + ROUTE + "newUser -- as org admin -- 201 OK") { @@ -364,7 +363,7 @@ class TestPostUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn info("Body: " + response.body) assert(response.code === HttpCode.POST_OK.intValue) //insure new user is in DB correctly - val newUser: UserRow = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/newUser").result), AWAITDURATION).head + val newUser: UserRow = Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/newUser").result), AWAITDURATION).head assert(newUser.username === TESTORGS(0).orgId + "/newUser") assert(newUser.orgid === TESTORGS(0).orgId) assert(newUser.updatedBy === TESTUSERS(1).username) //updated by org admin @@ -376,7 +375,7 @@ class TestPostUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.ACCESS_DENIED.intValue) - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTORGS(1).orgId + "/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTORGS(1).orgId + "/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added } test("POST /orgs/" + TESTORGS(0).orgId + ROUTE + "newUser -- as regular user -- 403 access denied") { @@ -384,7 +383,7 @@ class TestPostUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.ACCESS_DENIED.intValue) - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added } test("POST /orgs/" + TESTORGS(0).orgId + ROUTE + "newUser -- as agbot -- 403 access denied") { @@ -392,7 +391,7 @@ class TestPostUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.ACCESS_DENIED.intValue) - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added } test("POST /orgs/" + TESTORGS(0).orgId + ROUTE + "newUser -- as node -- 403 access denied") { @@ -400,7 +399,7 @@ class TestPostUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAn info("Code: " + response.code) info("Body: " + response.body) assert(response.code === HttpCode.ACCESS_DENIED.intValue) - assert(Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added + assert(Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTORGS(0).orgId + "/newUser").result), AWAITDURATION).isEmpty) //insure new user wasn't added } } diff --git a/src/test/scala/org/openhorizon/exchangeapi/route/user/TestPutUserRoute.scala b/src/test/scala/org/openhorizon/exchangeapi/route/user/TestPutUserRoute.scala index 0c164d9d..a3e303a5 100644 --- a/src/test/scala/org/openhorizon/exchangeapi/route/user/TestPutUserRoute.scala +++ b/src/test/scala/org/openhorizon/exchangeapi/route/user/TestPutUserRoute.scala @@ -1,6 +1,5 @@ package org.openhorizon.exchangeapi.route.user -import org.openhorizon.exchangeapi.TestDBConnection import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods import org.json4s.native.Serialization @@ -11,10 +10,11 @@ import org.openhorizon.exchangeapi.table.node.{NodeRow, NodesTQ} import org.openhorizon.exchangeapi.table.organization.{OrgRow, OrgsTQ} import org.openhorizon.exchangeapi.table.resourcechange.ResourceChangesTQ import org.openhorizon.exchangeapi.table.user.{UserRow, UsersTQ} -import org.openhorizon.exchangeapi.utility.{ApiResponse, ApiTime, ApiUtils, ExchMsg, HttpCode} +import org.openhorizon.exchangeapi.utility.{ApiResponse, ApiTime, ApiUtils, Configuration, DatabaseConnection, ExchMsg, HttpCode} import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} import org.scalatest.funsuite.AnyFunSuite import scalaj.http.{Http, HttpResponse} +import slick.jdbc import slick.jdbc.PostgresProfile.api._ import scala.concurrent.Await @@ -25,7 +25,7 @@ class TestPutUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd private val ACCEPT = ("Accept","application/json") private val CONTENT: (String, String) = ("Content-Type", "application/json") private val AWAITDURATION: Duration = 15.seconds - private val DBCONNECTION: TestDBConnection = new TestDBConnection + private val DBCONNECTION: jdbc.PostgresProfile.api.Database = DatabaseConnection.getDatabase private val URL = sys.env.getOrElse("EXCHANGE_URL_ROOT", "http://localhost:8080") + "/v1/orgs/" private val ROUTE = "/users/" @@ -151,7 +151,7 @@ class TestPutUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd ) ) - private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + sys.env.getOrElse("EXCHANGE_ROOTPW", ""))) + private val ROOTAUTH = ("Authorization","Basic " + ApiUtils.encode(Role.superUser + ":" + (try Configuration.getConfig.getString("api.root.password") catch { case _: Exception => "" }))) private val HUBADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(0).username + ":" + HUBADMINPASSWORD)) private val ORG1ADMINAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(1).username + ":" + ORG1ADMINPASSWORD)) private val ORG1USERAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTUSERS(2).username + ":" + ORG1USERPASSWORD)) @@ -159,7 +159,7 @@ class TestPutUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd private val NODEAUTH = ("Authorization", "Basic " + ApiUtils.encode(TESTNODES(0).id + ":" + NODETOKEN)) override def beforeAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( (OrgsTQ ++= TESTORGS) andThen (UsersTQ ++= TESTUSERS) andThen (AgbotsTQ ++= TESTAGBOTS) andThen @@ -168,16 +168,15 @@ class TestPutUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd } override def afterAll(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( ResourceChangesTQ.filter(_.orgId startsWith "testPutUserRoute").delete andThen OrgsTQ.filter(_.orgid startsWith "testPutUserRoute").delete andThen UsersTQ.filter(_.username startsWith "root/TestPutUserRouteHubAdmin").delete ), AWAITDURATION) - DBCONNECTION.getDb.close() } override def afterEach(): Unit = { - Await.ready(DBCONNECTION.getDb.run( + Await.ready(DBCONNECTION.run( TESTUSERS(2).updateUser() ), AWAITDURATION) } @@ -190,7 +189,7 @@ class TestPutUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd } def assertNoChanges(user: UserRow): Unit = { - val dbUser: UserRow = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === user.username).result), AWAITDURATION).head + val dbUser: UserRow = Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === user.username).result), AWAITDURATION).head assert(dbUser.username === user.username) assert(dbUser.orgid === user.orgid) assert(dbUser.hashedPw === user.hashedPw) @@ -288,7 +287,7 @@ class TestPutUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd info("Body: " + response.body) assert(response.code === HttpCode.POST_OK.intValue) //insure new user is in DB correctly - val newUser: UserRow = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head + val newUser: UserRow = Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head assert(newUser.username === TESTUSERS(2).username) assert(newUser.orgid === TESTORGS(0).orgId) assert(newUser.updatedBy === "root/root") //updated by root @@ -392,7 +391,7 @@ class TestPutUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd info("Body: " + response.body) assert(response.code === HttpCode.POST_OK.intValue) //insure new user is in DB correctly - val newUser: UserRow = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head + val newUser: UserRow = Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head assert(newUser.username === TESTUSERS(2).username) assert(newUser.orgid === TESTORGS(0).orgId) assert(newUser.updatedBy === TESTUSERS(1).username) //updated by org admin @@ -414,7 +413,7 @@ class TestPutUserRoute extends AnyFunSuite with BeforeAndAfterAll with BeforeAnd info("Body: " + response.body) assert(response.code === HttpCode.POST_OK.intValue) //insure new user is in DB correctly - val newUser: UserRow = Await.result(DBCONNECTION.getDb.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head + val newUser: UserRow = Await.result(DBCONNECTION.run(UsersTQ.filter(_.username === TESTUSERS(2).username).result), AWAITDURATION).head assert(newUser.username === TESTUSERS(2).username) assert(newUser.orgid === TESTORGS(0).orgId) assert(newUser.updatedBy === TESTUSERS(2).username) //updated by self