diff --git a/.travis.yml b/.travis.yml index ce6a3e19..0dbb3bdf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ sudo: true services: docker language: scala jdk: openjdk8 -scala: 2.12.4 +scala: 2.12.7 notifications: slack: secure: vSzVllY4Vmt6Nf6Yl+5QDMKTEQ4jIbP/bC/LSzZFmQNlYWlDEzG1gQcPb4prh7/efjUFt+vM2lTFP7jLEkib+XiR8n4LShCl+IeRyBpiONXII4KN7O9IPTMMOpGvN/FhZFgyzRFwEvkCSuSBpOYoRGGij/wzo/mPzfJYEyGxpaRmQKEmD7xmtxXMAVWpK2L5fL6XbglEPKD7pMYUvM/WyzAMJg4JgDxOGeX7QLGdSQrTOOkUtJjiEs8WtB/fR9Gev+vwX22KrnMwZpmT9KPPi+kzwFYnfzKxHdjdsmydmBBlBS4XM8rvJL1eMyrQB2LOOW8xWUeOF03t5PfKAC6cY2h01Jdl/rx3L6Pc99ABItS3OvYcwL0HY20bz+hNI0seRMVftz7eRzMHGdQsfYXfU6hOxkovGutRoyVkS7f+m66nBaOfRh6DAlOt/H6us4WEq1zJb8SyP+S7Xm38grh+xEDIMaz8AeavhDRWyH6RQfrpxvcGbcWhURXiY7clJLgjmauGJw9H/jxkuuoIINIFjkrziRHDGj9ZxNp3JbtJfprrramMCLtHl5Ziz5Pjmcf46iZxFzNlRlW/aC5ZqQbHm5EgcNuHXHMHvzTQcnkFDrQBGaXI3sLXfaYt+1LeRwfQXGOsWSgc8lmgfcGn1Fvbj/8ykq/q1ReQFAbCvT62ZR8= diff --git a/Dockerfile-bld b/Dockerfile-bld index 14496013..c862c9a4 100644 --- a/Dockerfile-bld +++ b/Dockerfile-bld @@ -4,12 +4,15 @@ MAINTAINER bruce potter ARG SCALA_VERSION -# install build tools: curl, java -RUN apt-get update && apt-get install -y curl openjdk-8-jdk +# install build tools: curl, java, sbt +# https is needed to get the sbt deb from its apt repo +RUN apt-get update && apt-get install -y apt-transport-https gnupg ca-certificates +RUN echo "deb https://dl.bintray.com/sbt/debian /" > /etc/apt/sources.list.d/sbt.list && apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2EE0EA64E40A89B84B2DF73499E82A75642AC823 +RUN apt-get update && apt-get install -y curl openjdk-8-jdk sbt # install scala RUN curl -s http://downloads.lightbend.com/scala/$SCALA_VERSION/scala-$SCALA_VERSION.tgz | tar -zx -C /usr/local/share RUN ln -s `ls -1dr /usr/local/share/scala-2* | head -1` /usr/local/share/scala ENV PATH="/usr/local/share/scala/bin:$PATH" -# the exchange api war file is built from the Makefile using a cmd like: cd $EXCHANGE_API_DIR && ./sbt package +# the exchange api war file is built from the Makefile using a cmd like: cd $EXCHANGE_API_DIR && sbt package diff --git a/Makefile b/Makefile index 56070010..0985fd05 100644 --- a/Makefile +++ b/Makefile @@ -61,7 +61,7 @@ docker: .docker-exec # - the create because the network already exists because of the build step .docker-network: -docker network remove $(DOCKER_NETWORK) 2> /dev/null || : - -docker network create $(DOCKER_NETWORK) + docker network create $(DOCKER_NETWORK) @touch $@ # Using dot files to hold the modification time the docker image and container were built @@ -72,7 +72,7 @@ docker: .docker-exec @touch $@ .docker-compile: $(wildcard src/main/scala/com/horizon/exchangeapi/*) $(wildcard src/main/resources/*) .docker-bld - docker exec -t $(DOCKER_NAME)_bld /bin/bash -c "cd $(EXCHANGE_API_DIR) && ./sbt package" + docker exec -t $(DOCKER_NAME)_bld /bin/bash -c "cd $(EXCHANGE_API_DIR) && sbt package" # war file ends up in: ./target/scala-$SCALA_VERSION_SHORT/exchange-api_$SCALA_VERSION_SHORT-$EXCHANGE_API_WAR_VERSION.war @touch $@ @@ -90,10 +90,9 @@ docker: .docker-exec @touch $@ # Run the automated tests in the bld container against the exchange svr running in the exec container -test: -docker-test: .docker-bld +test: .docker-bld : $${EXCHANGE_ROOTPW:?} # this verifies these env vars are set - docker exec -t -e EXCHANGE_URL_ROOT=http://$(DOCKER_NAME):8080 -e "EXCHANGE_ROOTPW=$$EXCHANGE_ROOTPW" $(DOCKER_NAME)_bld /bin/bash -c 'cd $(EXCHANGE_API_DIR) && ./sbt test' + docker exec -t -e EXCHANGE_URL_ROOT=http://$(DOCKER_NAME):8080 -e "EXCHANGE_ROOTPW=$$EXCHANGE_ROOTPW" $(DOCKER_NAME)_bld /bin/bash -c 'cd $(EXCHANGE_API_DIR) && sbt test' # Push the docker images to the registry w/o rebuilding them docker-push-only: @@ -132,4 +131,4 @@ version: .SECONDARY: -.PHONY: default clean clean-exec-image clean-all docker docker-test docker-push-only docker-push sync-swagger-ui testmake +.PHONY: default clean clean-exec-image clean-all docker test docker-push-only docker-push-version-only docker-push docker-push-to-prod sync-swagger-ui testmake version diff --git a/README.md b/README.md index 807cecd0..10aeb56f 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ services in the exchange. ## Preconditions - [Install scala](http://www.scala-lang.org/download/install.html) +- [Install sbt](https://www.scala-sbt.org/1.x/docs/Setup.html) - (optional) Install conscript and giter8 if you want to get example code from scalatra.org - Install postgresql locally (unless you have a remote instance you are using). Instructions for installing on Mac OS X: - `brew install postgresql` @@ -44,13 +45,13 @@ services in the exchange. ## Building in Local Sandbox -- `./sbt` +- `sbt` - `jetty:start` - Or to have the server restart automatically when code changes: `~;jetty:stop;jetty:start` - Once the server starts, to try a simple rest method browse: [http://localhost:8080/v1/nodes?id=a&token=b](http://localhost:8080/v1/orgs/IBM/nodes?id=a&token=b) - To see the swagger output, browse: [http://localhost:8080/api](http://localhost:8080/api) -- Run the automated tests (with the exchange server still running): `./sbt test` -- Run just 1 of the the automated test suites (with the exchange server still running): `./sbt "test-only exchangeapi.AgbotsSuite"` +- Run the automated tests (with the exchange server still running): `sbt test` +- Run just 1 of the the automated test suites (with the exchange server still running): `sbt "testOnly exchangeapi.AgbotsSuite"` - Run the performance tests: `src/test/bash/scale/test.sh` or `src/test/bash/scale/wrapper.sh 8` ## Building and Running the Container @@ -62,7 +63,7 @@ services in the exchange. - Build the exchange api container and run it locally: `make .docker-exec-run` - Manually test container locally: `curl -sS -w %{http_code} http://localhost:8080/v1/admin/version` - Note: the container can not access a postgres db running locally on the docker host if the db is only listening for unix domain sockets or 127.0.0.1. -- Run the automated tests: `./sbt test` +- Run the automated tests: `sbt test` - Push container to our docker registry: `make docker-push-only` - Deploy the new container to a docker host - Ensure that no changes are needed to the /etc/horizon/exchange/config.json file @@ -86,6 +87,11 @@ services in the exchange. - Allow random PW creation for user creation - Consider changing all creates to POST, and update (via put/patch) return codes to 200 +## Changes in 1.62.0 + +- Remove microservice, workload, and blockchain support (leaving just service support) +- Remove deprecated `PUT /orgs/{orgid}/agbots/{id}/patterns/{patid}` method (use POST instead) + ## Changes in 1.61.0 - Support IAM API keys for bx cr access by adding `username` field to service dockauths (issue anax 651) diff --git a/build.sbt b/build.sbt index f177d428..68c7f121 100644 --- a/build.sbt +++ b/build.sbt @@ -1,11 +1,11 @@ -lazy val scalatraVersion = "2.6.2" // can see the latest version at https://github.com/scalatra/scalatra/releases +lazy val scalatraVersion = "2.6.3" // can see the latest version at https://github.com/scalatra/scalatra/releases lazy val root = (project in file(".")). settings( organization := "com.horizon", name := "Exchange API", version := "0.1.0", - scalaVersion := "2.12.4", + scalaVersion := "2.12.7", resolvers += Classpaths.typesafeReleases, libraryDependencies ++= Seq( "org.scalatra" %% "scalatra" % "latest.release", @@ -14,8 +14,8 @@ lazy val root = (project in file(".")). "com.typesafe.slick" %% "slick" % "latest.release", "com.typesafe.slick" %% "slick-hikaricp" % "latest.release", "org.postgresql" % "postgresql" % "latest.release", - //"com.zaxxer" % "HikariCP" % "latest.release", // was grabbing 3.0.0 too early - "com.zaxxer" % "HikariCP" % "2.5.1", + "com.zaxxer" % "HikariCP" % "latest.release", + //"com.zaxxer" % "HikariCP" % "2.5.1", //"org.slf4j" % "slf4j-api" % "latest.release", // they put version 1.8.0-alpha2 prematurely into latest.release "org.slf4j" % "slf4j-api" % "1.7.25", //"ch.qos.logback" % "logback-classic" % "latest.release", // they put version 1.3.0-alpha2 prematurely into latest.release @@ -25,10 +25,10 @@ lazy val root = (project in file(".")). "org.eclipse.jetty" % "jetty-webapp" % "latest.release" % "container", "org.eclipse.jetty" % "jetty-plus" % "latest.release" % "container", "org.scalatra" %% "scalatra-json" % "latest.release", - //"org.json4s" %% "json4s-native" % "latest.release", // this requires 3.6.0-M2 of some libraries, which cause conflicts with other libraries - //"org.json4s" %% "json4s-jackson" % "latest.release", - "org.json4s" %% "json4s-native" % "3.5.3", - "org.json4s" %% "json4s-jackson" % "3.5.3", + "org.json4s" %% "json4s-native" % "latest.release", + "org.json4s" %% "json4s-jackson" % "latest.release", + //"org.json4s" %% "json4s-native" % "3.5.3", + //"org.json4s" %% "json4s-jackson" % "3.5.3", "org.scalatra" %% "scalatra-swagger" % "latest.release", "org.scalatest" %% "scalatest" % "latest.release" % "test", "org.scalacheck" %% "scalacheck" % "latest.release" % "test", diff --git a/project/build.properties b/project/build.properties index 133a8f19..7c58a83a 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.17 +sbt.version=1.2.6 diff --git a/sbt b/sbt deleted file mode 100755 index 416117ff..00000000 --- a/sbt +++ /dev/null @@ -1,462 +0,0 @@ -#!/usr/bin/env bash -# -# A more capable sbt runner, coincidentally also called sbt. -# Author: Paul Phillips - -# todo - make this dynamic -declare -r sbt_release_version=0.12.0 -declare -r sbt_snapshot_version=0.13.0-SNAPSHOT - -unset sbt_jar sbt_dir sbt_create sbt_snapshot sbt_launch_dir -unset scala_version java_home sbt_explicit_version -unset verbose debug quiet - -for arg in "$@"; do - case $arg in - -q|-quiet) quiet=1 ;; - *) ;; - esac -done - -build_props_sbt () { - if [[ -f project/build.properties ]]; then - versionLine=$(grep ^sbt.version project/build.properties) - versionString=${versionLine##sbt.version=} - echo "$versionString" - fi -} - -update_build_props_sbt () { - local ver="$1" - local old=$(build_props_sbt) - - if [[ $ver == $old ]]; then - return - elif [[ -f project/build.properties ]]; then - perl -pi -e "s/^sbt\.version=.*\$/sbt.version=${ver}/" project/build.properties - grep -q '^sbt.version=' project/build.properties || echo "sbt.version=${ver}" >> project/build.properties - - echo !!! - echo !!! Updated file project/build.properties setting sbt.version to: $ver - echo !!! Previous value was: $old - echo !!! - fi -} - -sbt_version () { - if [[ -n $sbt_explicit_version ]]; then - echo $sbt_explicit_version - else - local v=$(build_props_sbt) - if [[ -n $v ]]; then - echo $v - else - echo $sbt_release_version - fi - fi -} - -echoerr () { - [[ -z $quiet ]] && echo 1>&2 "$@" -} -vlog () { - [[ $verbose || $debug ]] && echoerr "$@" -} -dlog () { - [[ $debug ]] && echoerr "$@" -} - -# this seems to cover the bases on OSX, and someone will -# have to tell me about the others. -get_script_path () { - local path="$1" - [[ -L "$path" ]] || { echo "$path" ; return; } - - local target=$(readlink "$path") - if [[ "${target:0:1}" == "/" ]]; then - echo "$target" - else - echo "$(dirname $path)/$target" - fi -} - -# a ham-fisted attempt to move some memory settings in concert -# so they need not be dicked around with individually. -get_mem_opts () { - local mem=${1:-1536} - local perm=$(( $mem / 4 )) - (( $perm > 256 )) || perm=256 - (( $perm < 1024 )) || perm=1024 - local codecache=$(( $perm / 2 )) - - # echo "-Xms${mem}m -Xmx${mem}m -XX:MaxPermSize=${perm}m -XX:ReservedCodeCacheSize=${codecache}m" - echo "-Xms${mem}m -Xmx${mem}m -XX:ReservedCodeCacheSize=${codecache}m" -} - -die() { - echo "Aborting: $@" - exit 1 -} - -make_url () { - groupid="$1" - category="$2" - version="$3" - - echo "http://repo.typesafe.com/typesafe/ivy-$category/$groupid/sbt-launch/$version/sbt-launch.jar" -} - -declare -r default_jvm_opts="-Dfile.encoding=UTF8" -declare -r default_sbt_opts="-XX:+CMSClassUnloadingEnabled" -declare -r default_sbt_mem=1536 -declare -r noshare_opts="-Dsbt.global.base=project/.sbtboot -Dsbt.boot.directory=project/.boot -Dsbt.ivy.home=project/.ivy" -declare -r sbt_opts_file=".sbtopts" -declare -r jvm_opts_file=".jvmopts" -declare -r latest_28="2.8.2" -declare -r latest_29="2.9.2" -declare -r latest_210="2.10.0-SNAPSHOT" - -declare -r script_path=$(get_script_path "$BASH_SOURCE") -declare -r script_dir="$(dirname $script_path)" -declare -r script_name="$(basename $script_path)" - -# some non-read-onlies set with defaults -declare java_cmd=java -declare sbt_launch_dir="$script_dir/.lib" -declare sbt_universal_launcher="$script_dir/lib/sbt-launch.jar" -declare sbt_mem=$default_sbt_mem -declare sbt_jar=$sbt_universal_launcher - -# pull -J and -D options to give to java. -declare -a residual_args -declare -a java_args -declare -a scalac_args -declare -a sbt_commands - -build_props_scala () { - if [[ -f project/build.properties ]]; then - versionLine=$(grep ^build.scala.versions project/build.properties) - versionString=${versionLine##build.scala.versions=} - echo ${versionString%% .*} - fi -} - -execRunner () { - # print the arguments one to a line, quoting any containing spaces - [[ $verbose || $debug ]] && echo "# Executing command line:" && { - for arg; do - if printf "%s\n" "$arg" | grep -q ' '; then - printf "\"%s\"\n" "$arg" - else - printf "%s\n" "$arg" - fi - done - echo "" - } - - exec "$@" -} - -sbt_groupid () { - case $(sbt_version) in - 0.7.*) echo org.scala-tools.sbt ;; - 0.10.*) echo org.scala-tools.sbt ;; - 0.11.[12]) echo org.scala-tools.sbt ;; - *) echo org.scala-sbt ;; - esac -} - -sbt_artifactory_list () { - local version0=$(sbt_version) - local version=${version0%-SNAPSHOT} - local url="http://typesafe.artifactoryonline.com/typesafe/ivy-snapshots/$(sbt_groupid)/sbt-launch/" - dlog "Looking for snapshot list at: $url " - - curl -s --list-only "$url" | \ - grep -F $version | \ - perl -e 'print reverse <>' | \ - perl -pe 's#^/dev/null - dlog "curl returned: $?" - echo "$url" - return - done -} - -jar_url () { - case $(sbt_version) in - 0.7.*) echo "http://simple-build-tool.googlecode.com/files/sbt-launch-0.7.7.jar" ;; - *-SNAPSHOT) make_snapshot_url ;; - *) make_release_url ;; - esac -} - -jar_file () { - echo "$sbt_launch_dir/$1/sbt-launch.jar" -} - -download_url () { - local url="$1" - local jar="$2" - - echo "Downloading sbt launcher $(sbt_version):" - echo " From $url" - echo " To $jar" - - mkdir -p $(dirname "$jar") && { - if which curl >/dev/null; then - curl -L --fail --silent "$url" --output "$jar" - elif which wget >/dev/null; then - wget --quiet -O "$jar" "$url" - fi - } && [[ -f "$jar" ]] -} - -acquire_sbt_jar () { - sbt_url="$(jar_url)" - sbt_jar="$(jar_file $(sbt_version))" - - [[ -f "$sbt_jar" ]] || download_url "$sbt_url" "$sbt_jar" -} - -usage () { - cat < path to global settings/plugins directory (default: ~/.sbt/) - -sbt-boot path to shared boot directory (default: ~/.sbt/boot in 0.11+) - -ivy path to local Ivy repository (default: ~/.ivy2) - -mem set memory options (default: $sbt_mem, which is - $(get_mem_opts $sbt_mem) ) - -no-share use all local caches; no sharing - -offline put sbt in offline mode - -jvm-debug Turn on JVM debugging, open at the given port. - -batch Disable interactive mode - - # sbt version (default: from project/build.properties if present, else latest release) - !!! The only way to accomplish this pre-0.12.0 if there is a build.properties file which - !!! contains an sbt.version property is to update the file on disk. That's what this does. - -sbt-version use the specified version of sbt - -sbt-jar use the specified jar as the sbt launcher - -sbt-snapshot use a snapshot version of sbt - -sbt-launch-dir directory to hold sbt launchers (default: $sbt_launch_dir) - - # scala version (default: as chosen by sbt) - -28 use $latest_28 - -29 use $latest_29 - -210 use $latest_210 - -scala-home use the scala build at the specified directory - -scala-version use the specified version of scala - - # java version (default: java from PATH, currently $(java -version |& grep version)) - -java-home alternate JAVA_HOME - - # jvm options and output control - JAVA_OPTS environment variable holding jvm args, if unset uses "$default_jvm_opts" - SBT_OPTS environment variable holding jvm args, if unset uses "$default_sbt_opts" - .jvmopts if file is in sbt root, it is prepended to the args given to the jvm - .sbtopts if file is in sbt root, it is prepended to the args given to **sbt** - -Dkey=val pass -Dkey=val directly to the jvm - -J-X pass option -X directly to the jvm (-J is stripped) - -S-X add -X to sbt's scalacOptions (-J is stripped) - -In the case of duplicated or conflicting options, the order above -shows precedence: JAVA_OPTS lowest, command line options highest. -EOM -} - -addJava () { - dlog "[addJava] arg = '$1'" - java_args=( "${java_args[@]}" "$1" ) -} -addSbt () { - dlog "[addSbt] arg = '$1'" - sbt_commands=( "${sbt_commands[@]}" "$1" ) -} -addScalac () { - dlog "[addScalac] arg = '$1'" - scalac_args=( "${scalac_args[@]}" "$1" ) -} -addResidual () { - dlog "[residual] arg = '$1'" - residual_args=( "${residual_args[@]}" "$1" ) -} -addResolver () { - addSbt "set resolvers in ThisBuild += $1" -} -addDebugger () { - addJava "-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=$1" -} - -jrebelAgent () { - SCALATRA_PROJECT_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - if [ -z "$SCALATRA_JREBEL" ]; - then echo -n ''; - else echo -n "-javaagent:$SCALATRA_JREBEL -Dscalatra_project_root=${SCALATRA_PROJECT_ROOT}"; - fi -} - -get_jvm_opts () { - echo "${JAVA_OPTS:-$default_jvm_opts}" - echo "`jrebelAgent` ${SBT_OPTS:-$default_sbt_opts}" - - [[ -f "$jvm_opts_file" ]] && cat "$jvm_opts_file" -} - -process_args () -{ - require_arg () { - local type="$1" - local opt="$2" - local arg="$3" - - if [[ -z "$arg" ]] || [[ "${arg:0:1}" == "-" ]]; then - die "$opt requires <$type> argument" - fi - } - while [[ $# -gt 0 ]]; do - case "$1" in - -h|-help) usage; exit 1 ;; - -v|-verbose) verbose=1 && shift ;; - -d|-debug) debug=1 && shift ;; - -q|-quiet) quiet=1 && shift ;; - - -ivy) require_arg path "$1" "$2" && addJava "-Dsbt.ivy.home=$2" && shift 2 ;; - -mem) require_arg integer "$1" "$2" && sbt_mem="$2" && shift 2 ;; - -no-colors) addJava "-Dsbt.log.noformat=true" && shift ;; - -no-share) addJava "$noshare_opts" && shift ;; - -sbt-boot) require_arg path "$1" "$2" && addJava "-Dsbt.boot.directory=$2" && shift 2 ;; - -sbt-dir) require_arg path "$1" "$2" && sbt_dir="$2" && shift 2 ;; - -debug-inc) addJava "-Dxsbt.inc.debug=true" && shift ;; - -offline) addSbt "set offline := true" && shift ;; - -jvm-debug) require_arg port "$1" "$2" && addDebugger $2 && shift 2 ;; - -batch) exec 0 )) || echo "Starting $script_name: invoke with -help for other options" - -# verify this is an sbt dir or -create was given -[[ -f ./build.sbt || -d ./project || -n "$sbt_create" ]] || { - cat < */ -case class PutAgbotPatternRequest(patternOrgid: String, pattern: String) { - def toAgbotPattern = AgbotPattern(patternOrgid, pattern, "", ApiTime.nowUTC) - def toAgbotPatternRow(agbotId: String, patId: String) = AgbotPatternRow(patId, agbotId, patternOrgid, pattern, "", ApiTime.nowUTC) - def formId = patternOrgid + "_" + pattern - def validate(patId: String) = { - if (patId != formId) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "the pattern id should be in the form patternOrgid_pattern")) - } -} - - /** Output format for GET /orgs/{orgid}/agbots/{id}/agreements */ case class GetAgbotAgreementsResponse(agreements: Map[String,AgbotAgreement], lastIndex: Int) /** Input format for PUT /orgs/{orgid}/agbots/{id}/agreements/ */ -case class PutAgbotAgreementRequest(workload: Option[AAWorkload], service: Option[AAService], state: String) { - def validate() = { - if ( service.isDefined && workload.isDefined ) { - halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "you can not specify both the 'service' and 'workload' fields.")) - } else if (service.isEmpty && workload.isEmpty) { - halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "you must specify either the 'service' or 'workload' field.")) - } - } +case class PutAgbotAgreementRequest(service: AAService, state: String) { + def validate() = { } - //def toAgbotAgreement = AgbotAgreement(workload, state, ApiTime.nowUTC, "") def toAgbotAgreementRow(agbotId: String, agrId: String) = { - if (service.isDefined) AgbotAgreementRow(agrId, agbotId, "", "", "", service.get.orgid, service.get.pattern, service.get.url, state, ApiTime.nowUTC, "") - else AgbotAgreementRow(agrId, agbotId, workload.get.orgid, workload.get.pattern, workload.get.url, "", "", "", state, ApiTime.nowUTC, "") + AgbotAgreementRow(agrId, agbotId, service.orgid, service.pattern, service.url, state, ApiTime.nowUTC, "") } } @@ -169,7 +150,7 @@ trait AgbotsRoutes extends ScalatraBase with FutureSupport with SwaggerSupport w Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), Parameter("id", DataType.String, Option[String](" ID (orgid/agbotid) of the agbot."), paramType=ParamType.Path), Parameter("token", DataType.String, Option[String]("Token of the agbot. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("attribute", DataType.String, Option[String]("Which attribute value should be returned. Only 1 attribute can be specified. If not specified, the entire node resource (including microservices) will be returned."), paramType=ParamType.Query, required=false) + Parameter("attribute", DataType.String, Option[String]("Which attribute value should be returned. Only 1 attribute can be specified. If not specified, the entire node resource (including services) will be returned."), paramType=ParamType.Query, required=false) ) responseMessages(ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.BAD_INPUT,"bad input"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) ) @@ -196,7 +177,7 @@ trait AgbotsRoutes extends ScalatraBase with FutureSupport with SwaggerSupport w } }) - case None => ; // Return the whole agbot, including the microservices + case None => ; // Return the whole agbot, including the services db.run(AgbotsTQ.getAgbot(compositeId).result).map({ list => logger.debug("GET /orgs/"+orgid+"/agbots/"+id+" result: "+list.toString) val agbots = new MutableHashMap[String,Agbot] @@ -506,57 +487,6 @@ trait AgbotsRoutes extends ScalatraBase with FutureSupport with SwaggerSupport w }) }) - // =========== Deprecated!! PUT /orgs/{orgid}/agbots/{id}/patterns/{patid} =============================== - val putAgbotPattern = - (apiOperation[ApiResponse]("putAgbotPattern") - summary "Deprecated!! Adds/updates a pattern that the agbot should serve" - description """Adds a new pattern, or updates an existing pattern, that this agbot should find nodes for to make agreements with them. This is called by the owning user or the agbot to give their information about the pattern.""" - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("id", DataType.String, Option[String](" ID (orgid/agbotid) of the agbot wanting to add/update this pattern."), paramType = ParamType.Path), - Parameter("patid", DataType.String, Option[String]("ID of the pattern to be added/updated."), paramType = ParamType.Path), - Parameter("token", DataType.String, Option[String]("Token of the agbot. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("body", DataType[PutAgbotPatternRequest], - Option[String]("Pattern object that needs to be added to, or updated in, the exchange. See details in the Implementation Notes above."), - paramType = ParamType.Body) - ) - responseMessages(ResponseMessage(HttpCode.POST_OK,"created/updated"), ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.BAD_INPUT,"bad input"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - val putAgbotPattern2 = (apiOperation[PutAgbotPatternRequest]("putPattern2") summary("a") description("a")) // for some bizarre reason, the PutPatternsRequest class has to be used in apiOperation() for it to be recognized in the body Parameter above - - put("/orgs/:orgid/agbots/:id/patterns/:patid", operation(putAgbotPattern)) ({ - val orgid = params("orgid") - val id = params("id") - val compositeId = OrgAndId(orgid,id).toString - val patId = params("patid") - credsAndLog().authenticate().authorizeTo(TAgbot(compositeId),Access.WRITE) - val pattern = try { parse(request.body).extract[PutAgbotPatternRequest] } - catch { case e: Exception => halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "Error parsing the input body json: "+e)) } // the specific exception is MappingException - pattern.validate(patId) - val resp = response - db.run(PatternsTQ.getPattern(OrgAndId(pattern.patternOrgid,pattern.pattern).toString).length.result.asTry.flatMap({ xs => - logger.debug("PUT /orgs/"+orgid+"/agbots/"+id+"/patterns"+patId+" pattern validation: "+xs.toString) - xs match { - case Success(num) => if (num > 0) pattern.toAgbotPatternRow(compositeId, patId).upsert.asTry - else DBIO.failed(new Throwable("the referenced pattern does not exist in the exchange")).asTry - case Failure(t) => DBIO.failed(new Throwable(t.getMessage)).asTry - } - })).map({ xs => - logger.debug("PUT /orgs/"+orgid+"/agbots/"+id+"/patterns/"+patId+" result: "+xs.toString) - xs match { - case Success(_) => resp.setStatus(HttpCode.PUT_OK) - ApiResponse(ApiResponseType.OK, "pattern added or updated") - case Failure(t) => if (t.getMessage.startsWith("Access Denied:")) { - resp.setStatus(HttpCode.ACCESS_DENIED) - ApiResponse(ApiResponseType.ACCESS_DENIED, "pattern '"+patId+"' for agbot '"+compositeId+"' not inserted or updated: "+t.getMessage) - } else { - resp.setStatus(HttpCode.BAD_INPUT) - ApiResponse(ApiResponseType.BAD_INPUT, "pattern '"+patId+"' for agbot '"+compositeId+"' not inserted or updated: "+t.getMessage) - } - } - }) - }) - // =========== DELETE /orgs/{orgid}/agbots/{id}/patterns =============================== val deleteAgbotAllPattern = (apiOperation[ApiResponse]("deleteAgbotAllPattern") diff --git a/src/main/scala/com/horizon/exchangeapi/AuthenticationSupport.scala b/src/main/scala/com/horizon/exchangeapi/AuthenticationSupport.scala index b8703bf9..34c84fad 100644 --- a/src/main/scala/com/horizon/exchangeapi/AuthenticationSupport.scala +++ b/src/main/scala/com/horizon/exchangeapi/AuthenticationSupport.scala @@ -43,26 +43,6 @@ object Access extends Enumeration { val READ_ALL_USERS = Value("READ_ALL_USERS") val WRITE_ALL_USERS = Value("WRITE_ALL_USERS") val RESET_USER_PW = Value("RESET_USER_PW") - val READ_MY_BLOCKCHAINS = Value("READ_MY_BLOCKCHAINS") - val WRITE_MY_BLOCKCHAINS = Value("WRITE_MY_BLOCKCHAINS") - val READ_ALL_BLOCKCHAINS = Value("READ_ALL_BLOCKCHAINS") - val WRITE_ALL_BLOCKCHAINS = Value("WRITE_ALL_BLOCKCHAINS") - val CREATE_BLOCKCHAINS = Value("CREATE_BLOCKCHAINS") // we use WRITE_MY_BLOCKCHAINS instead of this - val READ_MY_BCTYPES = Value("READ_MY_BCTYPES") - val WRITE_MY_BCTYPES = Value("WRITE_MY_BCTYPES") - val READ_ALL_BCTYPES = Value("READ_ALL_BCTYPES") - val WRITE_ALL_BCTYPES = Value("WRITE_ALL_BCTYPES") - val CREATE_BCTYPES = Value("CREATE_BCTYPES") // we use WRITE_MY_BCTYPES instead of this - val READ_MY_MICROSERVICES = Value("READ_MY_MICROSERVICES") - val WRITE_MY_MICROSERVICES = Value("WRITE_MY_MICROSERVICES") - val READ_ALL_MICROSERVICES = Value("READ_ALL_MICROSERVICES") - val WRITE_ALL_MICROSERVICES = Value("WRITE_ALL_MICROSERVICES") - val CREATE_MICROSERVICES = Value("CREATE_MICROSERVICES") - val READ_MY_WORKLOADS = Value("READ_MY_WORKLOADS") - val WRITE_MY_WORKLOADS = Value("WRITE_MY_WORKLOADS") - val READ_ALL_WORKLOADS = Value("READ_ALL_WORKLOADS") - val WRITE_ALL_WORKLOADS = Value("WRITE_ALL_WORKLOADS") - val CREATE_WORKLOADS = Value("CREATE_WORKLOADS") val READ_MY_SERVICES = Value("READ_MY_SERVICES") val WRITE_MY_SERVICES = Value("WRITE_MY_SERVICES") val READ_ALL_SERVICES = Value("READ_ALL_SERVICES") @@ -91,12 +71,12 @@ import com.horizon.exchangeapi.Access._ /** Who is allowed to do what. */ object Role { - /* + /* this is now in config.json val ANONYMOUS = Set(Access.CREATE_USER, Access.RESET_USER_PW) - val USER = Set(Access.READ_MYSELF, Access.WRITE_MYSELF, Access.RESET_USER_PW, Access.CREATE_NODE, Access.READ_MY_NODES, Access.WRITE_MY_NODES, Access.READ_ALL_NODES, Access.CREATE_AGBOT, Access.READ_MY_AGBOTS, Access.WRITE_MY_AGBOTS, Access.DATA_HEARTBEAT_MY_AGBOTS, Access.READ_ALL_AGBOTS, Access.STATUS, Access.READ_MY_BLOCKCHAINS, Access.READ_ALL_BLOCKCHAINS, Access.WRITE_MY_BLOCKCHAINS, Access.CREATE_BLOCKCHAINS, Access.READ_MY_BCTYPES, Access.READ_ALL_BCTYPES, Access.WRITE_MY_BCTYPES, Access.CREATE_BCTYPES) + val USER = Set(Access.READ_MYSELF, Access.WRITE_MYSELF, Access.RESET_USER_PW, Access.CREATE_NODE, Access.READ_MY_NODES, Access.WRITE_MY_NODES, Access.READ_ALL_NODES, Access.CREATE_AGBOT, Access.READ_MY_AGBOTS, Access.WRITE_MY_AGBOTS, Access.DATA_HEARTBEAT_MY_AGBOTS, Access.READ_ALL_AGBOTS, Access.STATUS) val SUPERUSER = Set(Access.ALL) - val NODE = Set(Access.READ_MYSELF, Access.WRITE_MYSELF, Access.READ_MY_NODES, Access.SEND_MSG_TO_AGBOT, Access.READ_ALL_BLOCKCHAINS, Access.READ_ALL_BCTYPES) - val AGBOT = Set(Access.READ_MYSELF, Access.WRITE_MYSELF, Access.DATA_HEARTBEAT_MY_AGBOTS, Access.READ_MY_AGBOTS, Access.READ_ALL_NODES, Access.SEND_MSG_TO_NODE, Access.READ_ALL_BLOCKCHAINS, Access.READ_ALL_BCTYPES) + val NODE = Set(Access.READ_MYSELF, Access.WRITE_MYSELF, Access.READ_MY_NODES, Access.SEND_MSG_TO_AGBOT) + val AGBOT = Set(Access.READ_MYSELF, Access.WRITE_MYSELF, Access.DATA_HEARTBEAT_MY_AGBOTS, Access.READ_MY_AGBOTS, Access.READ_ALL_NODES, Access.SEND_MSG_TO_NODE) def hasAuthorization(role: Set[Access], access: Access): Boolean = { role.contains(Access.ALL) || role.contains(access) } */ @@ -196,7 +176,7 @@ case class CompositeId(compositeId: String) { object AuthCache { val logger = LoggerFactory.getLogger(ExchConfig.LOGGER) - /** 1 set of things (user/pw, node id/token, agbot id/token, bctype/owner, bc/owner, microservice/owner, workload/owner) */ + /** 1 set of things (user/pw, node id/token, agbot id/token, service/owner, pattern/owner) */ class Cache(val whichTab: String) { //TODO: i am sure there is a better way to handle the different tables // Throughout the implementation of this class, id and token are used generically, meaning in the case of users they are user and pw. // Our goal is for the token to be unhashed, but we have to handle the case where the user gives us an already hashed token. @@ -221,10 +201,6 @@ object AuthCache { case "users" => db.run(UsersTQ.rows.map(x => (x.username, x.password, x.admin)).result).map({ list => this._initUsers(list, skipRoot = true) }) case "nodes" => db.run(NodesTQ.rows.map(x => (x.id, x.token, x.owner)).result).map({ list => this._initIds(list) }) case "agbots" => db.run(AgbotsTQ.rows.map(x => (x.id, x.token, x.owner)).result).map({ list => this._initIds(list) }) - case "bctypes" => db.run(BctypesTQ.rows.map(x => (x.bctype, x.definedBy)).result).map({ list => this._initBctypes(list) }) - case "blockchains" => db.run(BlockchainsTQ.rows.map(x => (x.name, x.bctype, x.definedBy, x.public)).result).map({ list => this._initBCs(list) }) - case "microservices" => db.run(MicroservicesTQ.rows.map(x => (x.microservice, x.owner, x.public)).result).map({ list => this._initMicroservices(list) }) - case "workloads" => db.run(WorkloadsTQ.rows.map(x => (x.workload, x.owner, x.public)).result).map({ list => this._initWorkloads(list) }) case "services" => db.run(ServicesTQ.rows.map(x => (x.service, x.owner, x.public)).result).map({ list => this._initServices(list) }) case "patterns" => db.run(PatternsTQ.rows.map(x => (x.pattern, x.owner, x.public)).result).map({ list => this._initPatterns(list) }) } @@ -248,40 +224,7 @@ object AuthCache { } } - /** Put owners of bctypes in the cache */ - def _initBctypes(credList: Seq[(String,String)]): Unit = { - for ((bctype,definedBy) <- credList) { - if (definedBy != "") _putOwner(bctype, definedBy) - } - } - - /** Put owners of bc instances in the cache */ - def _initBCs(credList: Seq[(String,String,String,Boolean)]): Unit = { - for ((name,bctype,definedBy,isPub) <- credList) { - //val key = name+"|"+bctype - val key = bctype+"|"+name - if (definedBy != "") _putOwner(key, definedBy) - _putIsPublic(key, isPub) - } - } - - /** Put owners of microservices in the cache */ - def _initMicroservices(credList: Seq[(String,String,Boolean)]): Unit = { - for ((microservice,owner,isPub) <- credList) { - if (owner != "") _putOwner(microservice, owner) - _putIsPublic(microservice, isPub) - } - } - - /** Put owners of workloads in the cache */ - def _initWorkloads(credList: Seq[(String,String,Boolean)]): Unit = { - for ((workload,owner,isPub) <- credList) { - if (owner != "") _putOwner(workload, owner) - _putIsPublic(workload, isPub) - } - } - - /** Put owners of workloads in the cache */ + /** Put owners of services in the cache */ def _initServices(credList: Seq[(String,String,Boolean)]): Unit = { for ((service,owner,isPub) <- credList) { if (owner != "") _putOwner(service, owner) @@ -391,10 +334,6 @@ object AuthCache { //case "users" => UsersTQ.getAdminAsString(id).result case "nodes" => NodesTQ.getOwner(id).result case "agbots" => AgbotsTQ.getOwner(id).result - case "bctypes" => BctypesTQ.getOwner(id).result - case "blockchains" => BlockchainsTQ.getOwner2(id).result - case "microservices" => MicroservicesTQ.getOwner(id).result - case "workloads" => WorkloadsTQ.getOwner(id).result case "services" => ServicesTQ.getOwner(id).result case "patterns" => PatternsTQ.getOwner(id).result } @@ -415,9 +354,6 @@ object AuthCache { try { // For the all the others, we are looking for the traditional owner val a = whichTable match { - case "blockchains" => BlockchainsTQ.getPublic2(id).result - case "microservices" => MicroservicesTQ.getPublic(id).result - case "workloads" => WorkloadsTQ.getPublic(id).result case "services" => ServicesTQ.getPublic(id).result case "patterns" => PatternsTQ.getPublic(id).result case _ => return Some(false) // should never get here @@ -496,10 +432,6 @@ object AuthCache { val users = new Cache("users") val nodes = new Cache("nodes") val agbots = new Cache("agbots") - val bctypes = new Cache("bctypes") - val blockchains = new Cache("blockchains") - val microservices = new Cache("microservices") - val workloads = new Cache("workloads") val services = new Cache("services") val patterns = new Cache("patterns") } @@ -619,30 +551,6 @@ trait AuthenticationSupport extends ScalatraBase { case Access.CREATE => Access.CREATE_AGBOT case _ => access } - case TBctype(_) => access match { // a user accessing a bctype - case Access.READ => if (iOwnTarget(target)) Access.READ_MY_BCTYPES else Access.READ_ALL_BCTYPES - case Access.WRITE => if (iOwnTarget(target)) Access.WRITE_MY_BCTYPES else Access.WRITE_ALL_BCTYPES - case Access.CREATE => Access.CREATE_BCTYPES - case _ => access - } - case TBlockchain(_) => access match { // a user accessing a blockchain - case Access.READ => if (iOwnTarget(target)) Access.READ_MY_BLOCKCHAINS else Access.READ_ALL_BLOCKCHAINS - case Access.WRITE => if (iOwnTarget(target)) Access.WRITE_MY_BLOCKCHAINS else Access.WRITE_ALL_BLOCKCHAINS - case Access.CREATE => Access.CREATE_BLOCKCHAINS - case _ => access - } - case TMicroservice(_) => access match { // a user accessing a microservice - case Access.READ => if (iOwnTarget(target)) Access.READ_MY_MICROSERVICES else Access.READ_ALL_MICROSERVICES - case Access.WRITE => if (iOwnTarget(target)) Access.WRITE_MY_MICROSERVICES else Access.WRITE_ALL_MICROSERVICES - case Access.CREATE => Access.CREATE_MICROSERVICES - case _ => access - } - case TWorkload(_) => access match { // a user accessing a workload - case Access.READ => if (iOwnTarget(target)) Access.READ_MY_WORKLOADS else Access.READ_ALL_WORKLOADS - case Access.WRITE => if (iOwnTarget(target)) Access.WRITE_MY_WORKLOADS else Access.WRITE_ALL_WORKLOADS - case Access.CREATE => Access.CREATE_WORKLOADS - case _ => access - } case TService(_) => access match { // a user accessing a service case Access.READ => if (iOwnTarget(target)) Access.READ_MY_SERVICES else Access.READ_ALL_SERVICES case Access.WRITE => if (iOwnTarget(target)) Access.WRITE_MY_SERVICES else Access.WRITE_ALL_SERVICES @@ -687,10 +595,6 @@ trait AuthenticationSupport extends ScalatraBase { case TUser(id) => return id == creds.id case TNode(id) => AuthCache.nodes.getOwner(id) case TAgbot(id) => AuthCache.agbots.getOwner(id) - case TBctype(id) => AuthCache.bctypes.getOwner(id) - case TBlockchain(id) => AuthCache.blockchains.getOwner(id) - case TMicroservice(id) => AuthCache.microservices.getOwner(id) - case TWorkload(id) => AuthCache.workloads.getOwner(id) case TService(id) => AuthCache.services.getOwner(id) case TPattern(id) => AuthCache.patterns.getOwner(id) case _ => return false @@ -735,30 +639,6 @@ trait AuthenticationSupport extends ScalatraBase { case Access.CREATE => Access.CREATE_AGBOT case _ => access } - case TBctype(_) => access match { // a node accessing a bctype - case Access.READ => Access.READ_ALL_BCTYPES - case Access.WRITE => Access.WRITE_ALL_BCTYPES - case Access.CREATE => Access.CREATE_BCTYPES - case _ => access - } - case TBlockchain(_) => access match { // a node accessing a blockchain - case Access.READ => Access.READ_ALL_BLOCKCHAINS - case Access.WRITE => Access.WRITE_ALL_BLOCKCHAINS - case Access.CREATE => Access.CREATE_BLOCKCHAINS - case _ => access - } - case TMicroservice(_) => access match { // a node accessing a microservice - case Access.READ => Access.READ_ALL_MICROSERVICES - case Access.WRITE => Access.WRITE_ALL_MICROSERVICES - case Access.CREATE => Access.CREATE_MICROSERVICES - case _ => access - } - case TWorkload(_) => access match { // a node accessing a workload - case Access.READ => Access.READ_ALL_WORKLOADS - case Access.WRITE => Access.WRITE_ALL_WORKLOADS - case Access.CREATE => Access.CREATE_WORKLOADS - case _ => access - } case TService(_) => access match { // a node accessing a service case Access.READ => Access.READ_ALL_SERVICES case Access.WRITE => Access.WRITE_ALL_SERVICES @@ -820,30 +700,6 @@ trait AuthenticationSupport extends ScalatraBase { case Access.CREATE => Access.CREATE_AGBOT case _ => access } - case TBctype(_) => access match { // a agbot accessing a bctype - case Access.READ => Access.READ_ALL_BCTYPES - case Access.WRITE => Access.WRITE_ALL_BCTYPES - case Access.CREATE => Access.CREATE_BCTYPES - case _ => access - } - case TBlockchain(_) => access match { // a agbot accessing a blockchain - case Access.READ => Access.READ_ALL_BLOCKCHAINS - case Access.WRITE => Access.WRITE_ALL_BLOCKCHAINS - case Access.CREATE => Access.CREATE_BLOCKCHAINS - case _ => access - } - case TMicroservice(_) => access match { // a agbot accessing a microservice - case Access.READ => Access.READ_ALL_MICROSERVICES - case Access.WRITE => Access.WRITE_ALL_MICROSERVICES - case Access.CREATE => Access.CREATE_MICROSERVICES - case _ => access - } - case TWorkload(_) => access match { // a agbot accessing a workload - case Access.READ => Access.READ_ALL_WORKLOADS - case Access.WRITE => Access.WRITE_ALL_WORKLOADS - case Access.CREATE => Access.CREATE_WORKLOADS - case _ => access - } case TService(_) => access match { // a agbot accessing a service case Access.READ => Access.READ_ALL_SERVICES case Access.WRITE => Access.WRITE_ALL_SERVICES @@ -912,30 +768,6 @@ trait AuthenticationSupport extends ScalatraBase { case Access.CREATE => Access.CREATE_AGBOT case _ => access } - case TBctype(_) => access match { // a anonymous accessing a bctype - case Access.READ => Access.READ_ALL_BCTYPES - case Access.WRITE => Access.WRITE_ALL_BCTYPES - case Access.CREATE => Access.CREATE_BCTYPES - case _ => access - } - case TBlockchain(_) => access match { // a anonymous accessing a blockchain - case Access.READ => Access.READ_ALL_BLOCKCHAINS - case Access.WRITE => Access.WRITE_ALL_BLOCKCHAINS - case Access.CREATE => Access.CREATE_BLOCKCHAINS - case _ => access - } - case TMicroservice(_) => access match { // a anonymous accessing a microservice - case Access.READ => Access.READ_ALL_MICROSERVICES - case Access.WRITE => Access.WRITE_ALL_MICROSERVICES - case Access.CREATE => Access.CREATE_MICROSERVICES - case _ => access - } - case TWorkload(_) => access match { // a anonymous accessing a workload - case Access.READ => Access.READ_ALL_WORKLOADS - case Access.WRITE => Access.WRITE_ALL_WORKLOADS - case Access.CREATE => Access.CREATE_WORKLOADS - case _ => access - } case TService(_) => access match { // a anonymous accessing a service case Access.READ => Access.READ_ALL_SERVICES case Access.WRITE => Access.WRITE_ALL_SERVICES @@ -994,17 +826,6 @@ trait AuthenticationSupport extends ScalatraBase { case class TUser(id: String) extends Target case class TNode(id: String) extends Target case class TAgbot(id: String) extends Target - case class TBctype(id: String) extends Target // for bctypes and blockchains only the user that created it can update/delete it - case class TBlockchain(id: String) extends Target { // this id is a composite of the bc name and bctype - override def isPublic: Boolean = if (all) return true else return AuthCache.blockchains.getIsPublic(id).getOrElse(false) - } - case class TMicroservice(id: String) extends Target { // for microservices only the user that created it can update/delete it - // If getting all microservices, let it thru here, it will be up to the code processing the results to only return the public resources - override def isPublic: Boolean = if (all) return true else return AuthCache.microservices.getIsPublic(id).getOrElse(false) - } - case class TWorkload(id: String) extends Target { // for workloads only the user that created it can update/delete it - override def isPublic: Boolean = if (all) return true else return AuthCache.workloads.getIsPublic(id).getOrElse(false) - } case class TService(id: String) extends Target { // for services only the user that created it can update/delete it override def isPublic: Boolean = if (all) return true else return AuthCache.services.getIsPublic(id).getOrElse(false) } diff --git a/src/main/scala/com/horizon/exchangeapi/BlockchainsRoutes.scala b/src/main/scala/com/horizon/exchangeapi/BlockchainsRoutes.scala deleted file mode 100644 index 68e02665..00000000 --- a/src/main/scala/com/horizon/exchangeapi/BlockchainsRoutes.scala +++ /dev/null @@ -1,559 +0,0 @@ -/** Services routes for all of the /orgs/{orgid}/bctypes api methods. */ -package com.horizon.exchangeapi - -import org.json4s._ -import org.json4s.jackson.JsonMethods._ -import org.scalatra._ -import org.scalatra.swagger._ -import org.slf4j._ -import slick.jdbc.PostgresProfile.api._ -import com.horizon.exchangeapi.tables._ -import scala.collection.immutable._ -import scala.collection.mutable.{HashMap => MutableHashMap} -import scala.util._ - -//====== These are the input and output structures for /orgs/{orgid}/bctypes routes. Swagger and/or json seem to require they be outside the trait. - -/** Output format for GET /orgs/{orgid}/bctypes */ -case class GetBctypesResponse(bctypes: Map[String,Bctype], lastIndex: Int) -case class GetBctypeAttributeResponse(attribute: String, value: String) - -/** Input format for PUT /orgs/{orgid}/bctypes/ */ -// case class PutBctypeRequest(description: String, containerInfo: Map[String,String]) { -case class PutBctypeRequest(description: String, details: String) { - // protected implicit val jsonFormats: Formats = DefaultFormats - def validate() = { - // if (msgEndPoint == "" && publicKey == "") halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "either msgEndPoint or publicKey must be specified.")) <-- skipping this check because POST /nodes/{id}/msgs checks for the publicKey - } - - // def toBctypeRow(bctype: String, owner: String) = BctypeRow(bctype, description, owner, write(containerInfo), ApiTime.nowUTC) - def toBctypeRow(bctype: String, orgid: String, owner: String) = BctypeRow(bctype, orgid, description, owner, details, ApiTime.nowUTC) -} - -case class PatchBctypeRequest(description: Option[String], details: Option[String]) { - // protected implicit val jsonFormats: Formats = DefaultFormats - - /** Returns a tuple of the db action to update parts of the bctype, and the attribute name being updated. */ - def getDbUpdate(bctype: String, orgid: String): (DBIO[_],String) = { - val lastUpdated = ApiTime.nowUTC - //todo: support updating more than 1 attribute - // find the 1st attribute that was specified in the body and create a db action to update it for this bctype - description match { case Some(description2) => return ((for { d <- BctypesTQ.rows if d.bctype === bctype } yield (d.bctype,d.description,d.lastUpdated)).update((bctype, description2, lastUpdated)), "description"); case _ => ; } - details match { case Some(det) => return ((for { d <- BctypesTQ.rows if d.bctype === bctype } yield (d.bctype,d.details,d.lastUpdated)).update((bctype, det, lastUpdated)), "details"); case _ => ; } - // containerInfo match { - // case Some(ci) => val cInfo = if (ci != "") write(containerInfo) else "" - // return ((for { d <- BctypesTQ.rows if d.bctype === bctype } yield (d.bctype,d.containerInfo,d.lastUpdated)).update((bctype, cInfo, lastUpdated)), "containerInfo") - // case _ => ; - // } - return (null, null) - } -} - - -/** Output format for GET /orgs/{orgid}/bctypes/{bctype}/blockchains */ -case class GetBlockchainsResponse(blockchains: Map[String,Blockchain], lastIndex: Int) -case class GetBlockchainAttributeResponse(attribute: String, value: String) - -/** Input format for PUT /orgs/{orgid}/bctypes/{bctype}/blockchains/ */ -// case class PutBlockchainRequest(description: String, bootNodes: List[String], genesis: List[String], networkId: List[String]) { -case class PutBlockchainRequest(description: String, public: Boolean, details: String) { - // protected implicit val jsonFormats: Formats = DefaultFormats - // def toBlockchainRow(bctype: String, name: String, owner: String) = BlockchainRow(name, bctype, description, owner, write(bootNodes), write(genesis), write(networkId), ApiTime.nowUTC) - def toBlockchainRow(bctype: String, name: String, orgid: String, owner: String) = BlockchainRow(name, bctype, orgid, description, owner, public, details, ApiTime.nowUTC) -} - -// case class PatchBlockchainRequest(description: Option[String], bootNodes: Option[List[String]], genesis: Option[List[String]], networkId: Option[List[String]]) { -case class PatchBlockchainRequest(description: Option[String], details: Option[String]) { - // protected implicit val jsonFormats: Formats = DefaultFormats - - /** Returns a tuple of the db action to update parts of the blockchain, and the attribute name being updated. */ - def getDbUpdate(bctype: String, name: String, orgid: String): (DBIO[_],String) = { - val lastUpdated = ApiTime.nowUTC - //todo: support updating more than 1 attribute - // find the 1st attribute that was specified in the body and create a db action to update it for this bctype - description match { case Some(description2) => return ((for { d <- BlockchainsTQ.rows if d.bctype === bctype && d.name === name } yield (d.name, d.bctype,d.description,d.lastUpdated)).update((name, bctype, description2, lastUpdated)), "description"); case _ => ; } - details match { case Some(det) => return ((for { d <- BlockchainsTQ.rows if d.bctype === bctype && d.name === name } yield (d.name,d.bctype,d.details,d.lastUpdated)).update((name, bctype, det, lastUpdated)), "details") - case _ => ; - } - return (null, null) - } -} - - - -/** Implementation for all of the /orgs/{orgid}/bctypes routes */ -trait BlockchainsRoutes extends ScalatraBase with FutureSupport with SwaggerSupport with AuthenticationSupport { - def db: Database // get access to the db object in ExchangeApiApp - def logger: Logger // get access to the logger object in ExchangeApiApp - protected implicit def jsonFormats: Formats - - /* ====== GET /orgs/{orgid}/bctypes ================================ */ - val getBctypes = - (apiOperation[GetBctypesResponse]("getBctypes") - summary("Returns all blockchain types") - description("""Returns all Blockchain type definitions in the exchange DB. Can be run by any user, node, or agbot.""") - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("id", DataType.String, Option[String]("Username of exchange user, or ID of the node or agbot. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("token", DataType.String, Option[String]("Password of exchange user, or token of the node or agbot. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("bctype", DataType.String, Option[String]("Filter results to only include bctypes with this bctype (can include % for wildcard - the URL encoding for % is %25)"), paramType=ParamType.Query, required=false), - Parameter("description", DataType.String, Option[String]("Filter results to only include bctypes with this description (can include % for wildcard - the URL encoding for % is %25)"), paramType=ParamType.Query, required=false), - Parameter("definedBy", DataType.String, Option[String]("Filter results to only include bctypes defined by this user (can include % for wildcard - the URL encoding for % is %25)"), paramType=ParamType.Query, required=false) - ) - responseMessages(ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - - get("/orgs/:orgid/bctypes", operation(getBctypes)) ({ - val orgid = params("orgid") - credsAndLog().authenticate().authorizeTo(TBctype(OrgAndId(orgid,"*").toString),Access.READ) - val resp = response - //var q = BctypesTQ.rows.subquery - var q = BctypesTQ.getAllBctypes(orgid) - params.get("bctype").foreach(bctype => { if (bctype.contains("%")) q = q.filter(_.bctype like bctype) else q = q.filter(_.bctype === bctype) }) - params.get("description").foreach(description => { if (description.contains("%")) q = q.filter(_.description like description) else q = q.filter(_.description === description) }) - params.get("definedBy").foreach(definedBy => { if (definedBy.contains("%")) q = q.filter(_.definedBy like definedBy) else q = q.filter(_.definedBy === definedBy) }) - - db.run(q.result).map({ list => - logger.debug("GET /orgs/"+orgid+"/bctypes result size: "+list.size) - val bctypes = new MutableHashMap[String,Bctype] - if (list.nonEmpty) for (a <- list) bctypes.put(a.bctype, a.toBctype) - if (bctypes.nonEmpty) resp.setStatus(HttpCode.OK) - else resp.setStatus(HttpCode.NOT_FOUND) - GetBctypesResponse(bctypes.toMap, 0) - }) - }) - - /* ====== GET /orgs/{orgid}/bctypes/{bctype} ================================ */ - val getOneBctype = - (apiOperation[GetBctypesResponse]("getOneBctype") - summary("Returns a blockchain type") - description("""Returns the blockchain type with the specified type name in the exchange DB. Can be run by a user, node, or agbot.""") - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("bctype", DataType.String, Option[String]("Blockchain type."), paramType=ParamType.Path), - Parameter("id", DataType.String, Option[String]("Username of exchange user, or ID of the node or agbot. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("token", DataType.String, Option[String]("Password of exchange user, or token of the node or agbot. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("attribute", DataType.String, Option[String]("Which attribute value should be returned. Only 1 attribute can be specified. If not specified, the entire bctype resource will be returned."), paramType=ParamType.Query, required=false) - ) - responseMessages(ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.BAD_INPUT,"bad input"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - - get("/orgs/:orgid/bctypes/:bctype", operation(getOneBctype)) ({ - val orgid = params("orgid") - val bareBctype = params("bctype") - val bctype = OrgAndId(orgid,bareBctype).toString - credsAndLog().authenticate().authorizeTo(TBctype(bctype),Access.READ) - val resp = response - params.get("attribute") match { - case Some(attribute) => ; // Only returning 1 attr of the bctype - val q = BctypesTQ.getAttribute(bctype, attribute) // get the proper db query for this attribute - if (q == null) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "Bctype attribute name '"+attribute+"' is not an attribute of the bctype resource.")) - db.run(q.result).map({ list => - logger.trace("GET /orgs/"+orgid+"/bctypes/"+bareBctype+" attribute result: "+list.toString) - if (list.nonEmpty) { - resp.setStatus(HttpCode.OK) - GetBctypeAttributeResponse(attribute, list.head.toString) - } else { - resp.setStatus(HttpCode.NOT_FOUND) - ApiResponse(ApiResponseType.NOT_FOUND, "not found") - } - }) - - case None => ; // Return the whole bctype resource - db.run(BctypesTQ.getBctype(bctype).result).map({ list => - logger.debug("GET /orgs/"+orgid+"/bctypes/"+bareBctype+" result: "+list.toString) - val bctypes = new MutableHashMap[String,Bctype] - if (list.nonEmpty) for (a <- list) bctypes.put(a.bctype, a.toBctype) - if (bctypes.nonEmpty) resp.setStatus(HttpCode.OK) - else resp.setStatus(HttpCode.NOT_FOUND) - GetBctypesResponse(bctypes.toMap, 0) - }) - } - }) - - // =========== PUT /orgs/{orgid}/bctypes/{bctype} =============================== - val putBctypes = - (apiOperation[ApiResponse]("putBctypes") - summary "Adds/updates a blockchain type" - description """Adds a new blockchain type to the exchange DB, or updates an existing blockchain type. This can only be called by a user to create, and then only by that user to update.""" - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("bctype", DataType.String, Option[String]("Blockchain type."), paramType=ParamType.Path), - Parameter("username", DataType.String, Option[String]("Username of exchange user. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), - Parameter("password", DataType.String, Option[String]("Password of the user. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("body", DataType[PutBctypeRequest], - Option[String]("Bctype object that needs to be added to, or updated in, the exchange. See details in the Implementation Notes above."), - paramType = ParamType.Body) - ) - responseMessages(ResponseMessage(HttpCode.POST_OK,"created/updated"), ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.BAD_INPUT,"bad input"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - val putBctypes2 = (apiOperation[PutBctypeRequest]("putBctypes2") summary("a") description("a")) // for some bizarre reason, the PutBctypeRequest class has to be used in apiOperation() for it to be recognized in the body Parameter above - - /** Handles PUT /bctype/{bctype}. Called by a user to create, must be called by same user to update. */ - put("/orgs/:orgid/bctypes/:bctype", operation(putBctypes)) ({ - val orgid = params("orgid") - val bareBctype = params("bctype") - val bctype = OrgAndId(orgid,bareBctype).toString - val ident = credsAndLog().authenticate().authorizeTo(TBctype(bctype),Access.WRITE) - val bctypeReq = try { parse(request.body).extract[PutBctypeRequest] } - catch { case e: Exception => halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "Error parsing the input body json: "+e)) } - bctypeReq.validate() - // val owner = if (isAuthenticatedUser(creds)) creds.id else "" - val owner = ident match { case IUser(creds) => creds.id; case _ => "" } - val resp = response - db.run(BctypesTQ.getNumOwned(owner).result.flatMap({ xs => - logger.debug("PUT /orgs/"+orgid+"/bctypes/"+bareBctype+" num owned: "+xs) - val numOwned = xs - val maxBlockchains = ExchConfig.getInt("api.limits.maxBlockchains") - if (maxBlockchains == 0 || numOwned <= maxBlockchains) { // we are not sure if this is a create of update, but if they are already over the limit, stop them anyway - bctypeReq.toBctypeRow(bctype, orgid, owner).upsert.asTry - } - else DBIO.failed(new Throwable("Access Denied: you are over the limit of "+maxBlockchains+ " bctypes")).asTry - })).map({ xs => - logger.debug("PUT /orgs/"+orgid+"/bctypes/"+bareBctype+" result: "+xs.toString) - xs match { - case Success(_) => if (owner != "") AuthCache.bctypes.putOwner(bctype, owner) // currently only users are allowed to create/update bc resources, so owner should never be blank - resp.setStatus(HttpCode.PUT_OK) - ApiResponse(ApiResponseType.OK, "bctype added or updated") - case Failure(t) => if (t.getMessage.startsWith("Access Denied:")) { - resp.setStatus(HttpCode.ACCESS_DENIED) - ApiResponse(ApiResponseType.ACCESS_DENIED, "bctype '"+bctype+"' not inserted or updated: "+t.getMessage) - } else { - resp.setStatus(HttpCode.INTERNAL_ERROR) - ApiResponse(ApiResponseType.INTERNAL_ERROR, "bctype '"+bctype+"' not inserted or updated: "+t.toString) - } - } - }) - }) - - // =========== PATCH /orgs/{orgid}/bctypes/{bctype} =============================== - val patchBctypes = - (apiOperation[Map[String,String]]("patchBctypes") - summary "Updates 1 attribute of a blockchain type" - description """Updates one attribute of a blockchain type in the exchange DB. This can only be called by the user that originally created this bctype resource.""" - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("bctype", DataType.String, Option[String]("Blockchain type."), paramType=ParamType.Path), - Parameter("username", DataType.String, Option[String]("Username of owning user. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), - Parameter("password", DataType.String, Option[String]("Password of the user. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("body", DataType[PatchBctypeRequest], - Option[String]("Partial bctype object that contains an attribute to be updated in this bctype. See details in the Implementation Notes above."), - paramType = ParamType.Body) - ) - responseMessages(ResponseMessage(HttpCode.POST_OK,"created/updated"), ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.BAD_INPUT,"bad input"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - val patchBctypes2 = (apiOperation[PatchBctypeRequest]("patchBctypes2") summary("a") description("a")) // for some bizarre reason, the PatchBctypeRequest class has to be used in apiOperation() for it to be recognized in the body Parameter above - - patch("/orgs/:orgid/bctypes/:bctype", operation(patchBctypes)) ({ - val orgid = params("orgid") - val bareBctype = params("bctype") - val bctype = OrgAndId(orgid,bareBctype).toString - credsAndLog().authenticate().authorizeTo(TBctype(bctype),Access.WRITE) - val bctypeReq = try { parse(request.body).extract[PatchBctypeRequest] } - catch { case e: Exception => halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "Error parsing the input body json: "+e)) } // the specific exception is MappingException - logger.trace("PATCH /orgs/"+orgid+"/bctypes/"+bareBctype+" input: "+bctypeReq.toString) - val resp = response - val (action, attrName) = bctypeReq.getDbUpdate(bctype, orgid) - if (action == null) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "no valid bctype attribute specified")) - db.run(action.transactionally.asTry).map({ xs => - logger.debug("PATCH /orgs/"+orgid+"/bctypes/"+bareBctype+" result: "+xs.toString) - xs match { - case Success(v) => try { - val numUpdated = v.toString.toInt // v comes to us as type Any - if (numUpdated > 0) { // there were no db errors, but determine if it actually found it or not - resp.setStatus(HttpCode.PUT_OK) - ApiResponse(ApiResponseType.OK, "attribute '"+attrName+"' of bctype '"+bctype+"' updated") - } else { - resp.setStatus(HttpCode.NOT_FOUND) - ApiResponse(ApiResponseType.NOT_FOUND, "bctype '"+bctype+"' not found") - } - } catch { case e: Exception => resp.setStatus(HttpCode.INTERNAL_ERROR); ApiResponse(ApiResponseType.INTERNAL_ERROR, "Unexpected result from update: "+e) } - case Failure(t) => resp.setStatus(HttpCode.INTERNAL_ERROR) - ApiResponse(ApiResponseType.INTERNAL_ERROR, "bctype '"+bctype+"' not inserted or updated: "+t.toString) - } - }) - }) - - // =========== DELETE /orgs/{orgid}/bctypes/{bctype} =============================== - val deleteBctypes = - (apiOperation[ApiResponse]("deleteBctypes") - summary "Deletes a blockchain type" - description "Deletes a blockchain type from the exchange DB, and deletes the blockchain definitions stored for this blockchain type. Can only be run by the owning user." - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("bctype", DataType.String, Option[String]("Blockchain type."), paramType=ParamType.Path), - Parameter("username", DataType.String, Option[String]("Username of owning user. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), - Parameter("password", DataType.String, Option[String]("Password of the user. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false) - ) - responseMessages(ResponseMessage(HttpCode.DELETED,"deleted"), ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - - delete("/orgs/:orgid/bctypes/:bctype", operation(deleteBctypes)) ({ - val orgid = params("orgid") - val bareBctype = params("bctype") - val bctype = OrgAndId(orgid,bareBctype).toString - credsAndLog().authenticate().authorizeTo(TBctype(bctype),Access.WRITE) - // remove does *not* throw an exception if the key does not exist - val resp = response - db.run(BctypesTQ.getBctype(bctype).delete.transactionally.asTry).map({ xs => - logger.debug("DELETE /orgs/"+orgid+"/bctypes/"+bareBctype+" result: "+xs.toString) - xs match { - case Success(v) => if (v > 0) { // there were no db errors, but determine if it actually found it or not - AuthCache.bctypes.removeOwner(bctype) - resp.setStatus(HttpCode.DELETED) - ApiResponse(ApiResponseType.OK, "bctype deleted") - } else { - resp.setStatus(HttpCode.NOT_FOUND) - ApiResponse(ApiResponseType.NOT_FOUND, "bctype '"+bctype+"' not found") - } - case Failure(t) => resp.setStatus(HttpCode.INTERNAL_ERROR) - ApiResponse(ApiResponseType.INTERNAL_ERROR, "bctype '"+bctype+"' not deleted: "+t.toString) - } - }) - }) - - /* ====== GET /orgs/{orgid}/bctypes/{bctype}/blockchains ================================ */ - val getBlockchains = - (apiOperation[GetBlockchainsResponse]("getBlockchains") - summary("Returns all blockchains of this blockchain type") - description("""Returns all blockchain instances that are this blockchain type. Can be run by any user, node, or agbot.""") - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("bctype", DataType.String, Option[String]("Blockchain type."), paramType=ParamType.Path), - Parameter("id", DataType.String, Option[String]("Username of exchange user, or ID of the node or agbot. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("token", DataType.String, Option[String]("Token of the bctype. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false) - ) - responseMessages(ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.BAD_INPUT,"bad input"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - - get("/orgs/:orgid/bctypes/:bctype/blockchains", operation(getBlockchains)) ({ - val orgid = params("orgid") - val bareBctype = params("bctype") - val bctype = OrgAndId(orgid,bareBctype).toString - val ident = credsAndLog().authenticate().authorizeTo(TBlockchain(OrgAndId(orgid,"*").toString),Access.READ) - val resp = response - db.run(BlockchainsTQ.getBlockchains(bctype).result).map({ list => - logger.debug("GET /orgs/"+orgid+"/bctypes/"+bareBctype+"/blockchains result size: "+list.size) - logger.trace("GET /orgs/"+orgid+"/bctypes/"+bareBctype+"/blockchains result: "+list.toString) - val blockchains = new MutableHashMap[String, Blockchain] - if (list.nonEmpty) for (e <- list) { if (ident.getOrg == e.orgid || e.public) blockchains.put(e.name, e.toBlockchain) } - if (blockchains.nonEmpty) resp.setStatus(HttpCode.OK) - else resp.setStatus(HttpCode.NOT_FOUND) - GetBlockchainsResponse(blockchains.toMap, 0) - }) - }) - - /* ====== GET /orgs/{orgid}/bctypes/{bctype}/blockchains/{name} ================================ */ - val getOneBlockchain = - (apiOperation[GetBlockchainsResponse]("getOneBlockchain") - summary("Returns a blockchain for a blockchain type") - description("""Returns the blockchain definition with the specified name for the specified blockchain type in the exchange DB. Can be run by any user, node, or agbot.""") - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("bctype", DataType.String, Option[String]("Blockchain type."), paramType=ParamType.Path), - Parameter("name", DataType.String, Option[String]("Name of the blockchain."), paramType=ParamType.Query), - Parameter("id", DataType.String, Option[String]("Username of exchange user, or ID of the node or agbot. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("token", DataType.String, Option[String]("Token of the bctype. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("attribute", DataType.String, Option[String]("Which attribute value should be returned. Only 1 attribute can be specified. If not specified, the entire blockchain resource will be returned."), paramType=ParamType.Query, required=false) - ) - responseMessages(ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.BAD_INPUT,"bad input"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - - get("/orgs/:orgid/bctypes/:bctype/blockchains/:name", operation(getOneBlockchain)) ({ - val orgid = params("orgid") - val bareBctype = params("bctype") - val bctype = OrgAndId(orgid,bareBctype).toString - val name = params("name") - //val compositeId = name+"|"+bctype - val compositeId = bctype+"|"+name - credsAndLog().authenticate().authorizeTo(TBlockchain(compositeId),Access.READ) - val resp = response - params.get("attribute") match { - case Some(attribute) => ; // Only returning 1 attr of the blockchain - val q = BlockchainsTQ.getAttribute(bctype, name, attribute) // get the proper db query for this attribute - if (q == null) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "Blockchain attribute name '"+attribute+"' is not an attribute of the blockchain resource.")) - db.run(q.result).map({ list => - logger.trace("GET /orgs/"+orgid+"/bctypes/"+bareBctype+"/blockchains/"+name+" attribute result: "+list.toString) - if (list.nonEmpty) { - resp.setStatus(HttpCode.OK) - GetBlockchainAttributeResponse(attribute, list.head.toString) - } else { - resp.setStatus(HttpCode.NOT_FOUND) - ApiResponse(ApiResponseType.NOT_FOUND, "not found") - } - }) - - case None => ; // Return the whole blockchain resource - db.run(BlockchainsTQ.getBlockchain(bctype, name).result).map({ list => - logger.debug("GET /orgs/"+orgid+"/bctypes/"+bareBctype+"/blockchains/"+name+" result: "+list.toString) - val blockchains = new MutableHashMap[String, Blockchain] - if (list.nonEmpty) for (e <- list) { blockchains.put(e.name, e.toBlockchain) } - if (blockchains.nonEmpty) resp.setStatus(HttpCode.OK) - else resp.setStatus(HttpCode.NOT_FOUND) - GetBlockchainsResponse(blockchains.toMap, 0) - }) - } - }) - - // =========== PUT /orgs/{orgid}/bctypes/{bctype}/blockchains/{name} =============================== - val putBlockchain = - (apiOperation[ApiResponse]("putBlockchain") - summary "Adds/updates a blockchain of a blockchain type" - description """Adds a new blockchain definition of a blockchain type to the exchange DB, or updates an existing blockchain definition. This can only be called by a user to create, and then only by that user to update.""" - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("bctype", DataType.String, Option[String]("Blockchain type."), paramType=ParamType.Path), - Parameter("name", DataType.String, Option[String]("Name of the blockchain to be added/updated."), paramType = ParamType.Path), - Parameter("username", DataType.String, Option[String]("Username of owning user. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), - Parameter("password", DataType.String, Option[String]("Password of the user. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("body", DataType[PutBlockchainRequest], - Option[String]("Blockchain object that needs to be added to, or updated in, the exchange. See details in the Implementation Notes above."), - paramType = ParamType.Body) - ) - responseMessages(ResponseMessage(HttpCode.POST_OK,"created/updated"), ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.BAD_INPUT,"bad input"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - val putBlockchain2 = (apiOperation[PutBlockchainRequest]("putBlockchain2") summary("a") description("a")) // for some bizarre reason, the PutBlockchainsRequest class has to be used in apiOperation() for it to be recognized in the body Parameter above - - put("/orgs/:orgid/bctypes/:bctype/blockchains/:name", operation(putBlockchain)) ({ - val orgid = params("orgid") - val bareBctype = params("bctype") - val bctype = OrgAndId(orgid,bareBctype).toString - val name = params("name") - //val compositeId = name+"|"+bctype - val compositeId = bctype+"|"+name - val ident = credsAndLog().authenticate().authorizeTo(TBlockchain(compositeId),Access.WRITE) - val blockchain = try { parse(request.body).extract[PutBlockchainRequest] } - catch { case e: Exception => halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "Error parsing the input body json: "+e)) } // the specific exception is MappingException - val resp = response - val owner = ident match { case IUser(creds) => creds.id; case _ => "" } - db.run(BlockchainsTQ.getNumOwned(owner).result.flatMap({ xs => - logger.debug("PUT /orgs/"+orgid+"/bctypes/"+bareBctype+"/blockchains/"+name+" num owned: "+xs) - val numOwned = xs - val maxBlockchains = ExchConfig.getInt("api.limits.maxBlockchains") - if (maxBlockchains == 0 || numOwned <= maxBlockchains) { // we are not sure if this is create or update, but if they are already over the limit, stop them anyway - //todo: upsert does not work for this table due to this slick bug: https://github.com/slick/slick/issues/966. So we have to emulate it. - // blockchain.toBlockchainRow(bctype, name, owner).upsert.asTry - BlockchainsTQ.getBlockchain(bctype, name).result.asTry - } - else DBIO.failed(new Throwable("Access Denied: you are over the limit of "+maxBlockchains+ " blockchains for this bctype")).asTry - }).flatMap({ xs => - logger.debug("PUT /orgs/"+orgid+"/bctypes/"+bareBctype+"/blockchains/"+name+" get existing: "+xs.toString) - xs match { - case Success(v) => val bcExists = v.nonEmpty - if (bcExists) { logger.debug("PUT /orgs/"+orgid+"/bctypes/"+bareBctype+"/blockchains/"+name+" updating existing row"); blockchain.toBlockchainRow(bctype, name, orgid, owner).update.asTry } - else { logger.debug("PUT /orgs/"+orgid+"/bctypes/"+bareBctype+"/blockchains/"+name+" inserting new row"); blockchain.toBlockchainRow(bctype, name, orgid, owner).insert.asTry } - case Failure(t) => DBIO.failed(t).asTry // rethrow the error to the next step - } - })).map({ xs => - logger.debug("PUT /orgs/"+orgid+"/bctypes/"+bareBctype+"/blockchains/"+name+" result: "+xs.toString) - xs match { - case Success(_) => AuthCache.blockchains.putOwner(compositeId, owner) - AuthCache.blockchains.putIsPublic(compositeId, blockchain.public) - resp.setStatus(HttpCode.PUT_OK) - ApiResponse(ApiResponseType.OK, "blockchain added or updated") - case Failure(t) => if (t.getMessage.startsWith("Access Denied:")) { - resp.setStatus(HttpCode.ACCESS_DENIED) - ApiResponse(ApiResponseType.ACCESS_DENIED, "blockchain '"+name+"' for bctype '"+bctype+"' not inserted or updated: "+t.getMessage) - } else { - resp.setStatus(HttpCode.INTERNAL_ERROR) - ApiResponse(ApiResponseType.INTERNAL_ERROR, "blockchain '"+name+"' for bctype '"+bctype+"' not inserted or updated: "+t.toString) - } - } - }) - }) - - // =========== PATCH /orgs/{orgid}/bctypes/{bctype}/blockchains/{name} =============================== - val patchBlockchain = - (apiOperation[Map[String,String]]("patchBlockchain") - summary "Updates 1 attribute of a blockchain definition" - description """Updates one attribute of a blockchain instance in the exchange DB. This can only be called by the user that originally created this blockchain resource.""" - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("bctype", DataType.String, Option[String]("Blockchain type."), paramType=ParamType.Path), - Parameter("name", DataType.String, Option[String]("Blockchain instance name."), paramType=ParamType.Path), - Parameter("username", DataType.String, Option[String]("Username of owning user. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), - Parameter("password", DataType.String, Option[String]("Password of the user. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("body", DataType[PatchBlockchainRequest], - Option[String]("Partial blockchain object that contains an attribute to be updated in this blockchain. See details in the Implementation Notes above."), - paramType = ParamType.Body) - ) - responseMessages(ResponseMessage(HttpCode.POST_OK,"created/updated"), ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.BAD_INPUT,"bad input"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - val patchBlockchain2 = (apiOperation[PatchBlockchainRequest]("patchBlockchain2") summary("a") description("a")) // for some bizarre reason, the PatchBlockchainRequest class has to be used in apiOperation() for it to be recognized in the body Parameter above - - patch("/orgs/:orgid/bctypes/:bctype/blockchains/:name", operation(patchBlockchain)) ({ - val orgid = params("orgid") - val bareBctype = params("bctype") - val bctype = OrgAndId(orgid,bareBctype).toString - val name = params("name") - //val compositeId = name+"|"+bctype - val compositeId = bctype+"|"+name - credsAndLog().authenticate().authorizeTo(TBlockchain(compositeId),Access.WRITE) - val bcReq = try { parse(request.body).extract[PatchBlockchainRequest] } - catch { case e: Exception => halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "Error parsing the input body json: "+e)) } // the specific exception is MappingException - logger.trace("PATCH /orgs/"+orgid+"/bctypes/"+bareBctype+"/blockchains/"+name+" input: "+bcReq.toString) - val resp = response - val (action, attrName) = bcReq.getDbUpdate(bctype, name, orgid) - if (action == null) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "no valid bctype attribute specified")) - db.run(action.transactionally.asTry).map({ xs => - logger.debug("PATCH /orgs/"+orgid+"/bctypes/"+bareBctype+"/blockchains/"+name+" result: "+xs.toString) - xs match { - case Success(v) => try { - val numUpdated = v.toString.toInt // v comes to us as type Any - if (numUpdated > 0) { // there were no db errors, but determine if it actually found it or not - resp.setStatus(HttpCode.PUT_OK) - ApiResponse(ApiResponseType.OK, "attribute '"+attrName+"' of blockchain '"+name+"' updated") - } else { - resp.setStatus(HttpCode.NOT_FOUND) - ApiResponse(ApiResponseType.NOT_FOUND, "blockchain '"+name+"' for bctype '"+bctype+"' not found") - } - } catch { case e: Exception => resp.setStatus(HttpCode.INTERNAL_ERROR); ApiResponse(ApiResponseType.INTERNAL_ERROR, "Unexpected result from update: "+e) } - case Failure(t) => resp.setStatus(HttpCode.INTERNAL_ERROR) - ApiResponse(ApiResponseType.INTERNAL_ERROR, "blockchain '"+name+"' not inserted or updated: "+t.toString) - } - }) - }) - - // =========== DELETE /orgs/{orgid}/bctypes/{bctype}/blockchains/{name} =============================== - val deleteBlockchain = - (apiOperation[ApiResponse]("deleteBlockchain") - summary "Deletes a blockchain of a blockchain type" - description "Deletes a blockchain definition of a blockchain type from the exchange DB. Can only be run by the owning user." - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("bctype", DataType.String, Option[String]("Blockchain type."), paramType=ParamType.Path), - Parameter("name", DataType.String, Option[String]("Name of the blockchain to be deleted."), paramType = ParamType.Path), - Parameter("username", DataType.String, Option[String]("Username of owning user. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), - Parameter("password", DataType.String, Option[String]("Password of the user. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false) - ) - responseMessages(ResponseMessage(HttpCode.DELETED,"deleted"), ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - - delete("/orgs/:orgid/bctypes/:bctype/blockchains/:name", operation(deleteBlockchain)) ({ - val orgid = params("orgid") - val bareBctype = params("bctype") - val bctype = OrgAndId(orgid,bareBctype).toString - val name = params("name") - //val compositeId = name+"|"+bctype - val compositeId = bctype+"|"+name - credsAndLog().authenticate().authorizeTo(TBlockchain(compositeId),Access.WRITE) - val resp = response - db.run(BlockchainsTQ.getBlockchain(bctype,name).delete.asTry).map({ xs => - logger.debug("DELETE /orgs/"+orgid+"/bctypes/"+bareBctype+"/blockchains/"+name+" result: "+xs.toString) - xs match { - case Success(v) => if (v > 0) { // there were no db errors, but determine if it actually found it or not - AuthCache.blockchains.removeOwner(compositeId) - AuthCache.blockchains.removeIsPublic(compositeId) - resp.setStatus(HttpCode.DELETED) - ApiResponse(ApiResponseType.OK, "bctype blockchain deleted") - } else { - resp.setStatus(HttpCode.NOT_FOUND) - ApiResponse(ApiResponseType.NOT_FOUND, "blockchain '"+name+"' for bctype '"+bctype+"' not found") - } - case Failure(t) => resp.setStatus(HttpCode.INTERNAL_ERROR) - ApiResponse(ApiResponseType.INTERNAL_ERROR, "blockchain '"+name+"' for bctype '"+bctype+"' not deleted: "+t.toString) - } - }) - }) - -} \ No newline at end of file diff --git a/src/main/scala/com/horizon/exchangeapi/ExchangeApiApp.scala b/src/main/scala/com/horizon/exchangeapi/ExchangeApiApp.scala index 019e5aad..6aa1d4ce 100644 --- a/src/main/scala/com/horizon/exchangeapi/ExchangeApiApp.scala +++ b/src/main/scala/com/horizon/exchangeapi/ExchangeApiApp.scala @@ -21,7 +21,7 @@ import org.slf4j.LoggerFactory * @param swagger the ExchangeApiSwagger instance, created in ScalatraBootstrap */ class ExchangeApiApp(val db: Database)(implicit val swagger: Swagger) extends ScalatraServlet - with FutureSupport with NativeJsonSupport with SwaggerSupport with CorsSupport with AuthenticationSupport with NodesRoutes with AgbotsRoutes with UsersRoutes with AdminRoutes with BlockchainsRoutes with ServiceRoutes with MicroserviceRoutes with WorkloadRoutes with PatternRoutes with OrgRoutes { + with FutureSupport with NativeJsonSupport with SwaggerSupport with CorsSupport with AuthenticationSupport with NodesRoutes with AgbotsRoutes with UsersRoutes with AdminRoutes with ServiceRoutes with PatternRoutes with OrgRoutes { /** Sets up automatic case class to JSON output serialization, required by the JValueResult trait. */ protected implicit val jsonFormats: Formats = DefaultFormats @@ -62,11 +62,7 @@ class ExchangeApiApp(val db: Database)(implicit val swagger: Swagger) extends Sc AuthCache.users.init(db) AuthCache.nodes.init(db) AuthCache.agbots.init(db) - AuthCache.bctypes.init(db) - AuthCache.blockchains.init(db) AuthCache.services.init(db) - AuthCache.microservices.init(db) - AuthCache.workloads.init(db) AuthCache.patterns.init(db) // All of the route implementations are in traits called *Routes diff --git a/src/main/scala/com/horizon/exchangeapi/ExchangeApiTables.scala b/src/main/scala/com/horizon/exchangeapi/ExchangeApiTables.scala index 7c29921c..0897a298 100644 --- a/src/main/scala/com/horizon/exchangeapi/ExchangeApiTables.scala +++ b/src/main/scala/com/horizon/exchangeapi/ExchangeApiTables.scala @@ -21,10 +21,10 @@ object ExchangeApiTables { // Create all of the current version's tables - used in /admin/initdb val initDB = DBIO.seq(( SchemaTQ.rows.schema ++ OrgsTQ.rows.schema ++ UsersTQ.rows.schema - ++ NodesTQ.rows.schema ++ RegMicroservicesTQ.rows.schema ++ PropsTQ.rows.schema ++ NodeAgreementsTQ.rows.schema ++ NodeStatusTQ.rows.schema + ++ NodesTQ.rows.schema ++ NodeAgreementsTQ.rows.schema ++ NodeStatusTQ.rows.schema ++ AgbotsTQ.rows.schema ++ AgbotAgreementsTQ.rows.schema ++ AgbotPatternsTQ.rows.schema ++ NodeMsgsTQ.rows.schema ++ AgbotMsgsTQ.rows.schema - ++ BctypesTQ.rows.schema ++ BlockchainsTQ.rows.schema ++ ServicesTQ.rows.schema ++ ServiceKeysTQ.rows.schema ++ ServiceDockAuthsTQ.rows.schema ++ MicroservicesTQ.rows.schema ++ MicroserviceKeysTQ.rows.schema ++ WorkloadsTQ.rows.schema ++ WorkloadKeysTQ.rows.schema ++ PatternsTQ.rows.schema ++ PatternKeysTQ.rows.schema + ++ ServicesTQ.rows.schema ++ ServiceKeysTQ.rows.schema ++ ServiceDockAuthsTQ.rows.schema ++ PatternsTQ.rows.schema ++ PatternKeysTQ.rows.schema ).create, SchemaTQ.getSetVersionAction) @@ -40,17 +40,15 @@ object ExchangeApiTables { // Note: doing this with raw sql stmts because a foreign key constraint not existing was causing slick's drops to fail. As long as we are not removing contraints (only adding), we should be ok with the drops below? //val delete = DBIO.seq(sqlu"drop table orgs", sqlu"drop table workloads", sqlu"drop table mmicroservices", sqlu"drop table blockchains", sqlu"drop table bctypes", sqlu"drop table devmsgs", sqlu"drop table agbotmsgs", sqlu"drop table agbotagreements", sqlu"drop table agbots", sqlu"drop table devagreements", sqlu"drop table properties", sqlu"drop table microservices", sqlu"drop table nodes", sqlu"drop table users") val dropDB = DBIO.seq( - sqlu"drop table if exists patternkeys", sqlu"drop table if exists patterns", sqlu"drop table if exists servicedockauths", sqlu"drop table if exists servicekeys", sqlu"drop table if exists services", sqlu"drop table if exists workloadkeys", sqlu"drop table if exists workloads", sqlu"drop table if exists blockchains", sqlu"drop table if exists bctypes", // no table depends on these - sqlu"drop table if exists mmicroservices", // from older schema - sqlu"drop table if exists devmsgs", // from older schema + sqlu"drop table if exists patternkeys", sqlu"drop table if exists patterns", sqlu"drop table if exists servicedockauths", sqlu"drop table if exists servicekeys", sqlu"drop table if exists services", // no table depends on these sqlu"drop table if exists nodemsgs", sqlu"drop table if exists agbotmsgs", // these depend on both nodes and agbots sqlu"drop table if exists agbotpatterns", sqlu"drop table if exists agbotagreements", sqlu"drop table if exists agbots", - sqlu"drop table if exists devagreements", // from older schema sqlu"drop table if exists nodeagreements", sqlu"drop table if exists nodestatus", sqlu"drop table if exists properties", - sqlu"drop table if exists microservicekeys", sqlu"drop table if exists microservices", sqlu"drop table if exists devmicros", sqlu"drop table if exists devices", // from older schema sqlu"drop table if exists nodemicros", sqlu"drop table if exists nodes", - sqlu"drop table if exists users", sqlu"drop table if exists orgs", sqlu"drop table if exists schema" + sqlu"drop table if exists users", sqlu"drop table if exists orgs", sqlu"drop table if exists schema", + // these are no longer used, but here just in case they are still hanging around + sqlu"drop table if exists microservicekeys", sqlu"drop table if exists microservices", sqlu"drop table if exists workloadkeys", sqlu"drop table if exists workloads", sqlu"drop table if exists blockchains", sqlu"drop table if exists bctypes" ) // Delete the previous version's tables - used to be used by /admin/migratedb @@ -115,16 +113,6 @@ object ExchangeApiTables { val filename = dumpDir+"/nodes"+dumpSuffix logger.info("dumping "+xs.size+" rows to "+filename) new TableIo[NodeRow](filename).dump(xs) - RegMicroservicesTQ.rows.result - }).flatMap({ xs => - val filename = dumpDir+"/nodemicros"+dumpSuffix - logger.info("dumping "+xs.size+" rows to "+filename) - new TableIo[RegMicroserviceRow](filename).dump(xs) - PropsTQ.rows.result - }).flatMap({ xs => - val filename = dumpDir+"/properties"+dumpSuffix - logger.info("dumping "+xs.size+" rows to "+filename) - new TableIo[PropRow](filename).dump(xs) NodeAgreementsTQ.rows.result }).flatMap({ xs => val filename = dumpDir+"/nodeagreements"+dumpSuffix @@ -150,16 +138,6 @@ object ExchangeApiTables { val filename = dumpDir+"/agbotmsgs"+dumpSuffix logger.info("dumping "+xs.size+" rows to "+filename) new TableIo[AgbotMsgRow](filename).dump(xs) - BctypesTQ.rows.result - }).flatMap({ xs => - val filename = dumpDir+"/bctypes"+dumpSuffix - logger.info("dumping "+xs.size+" rows to "+filename) - new TableIo[BctypeRow](filename).dump(xs) - BlockchainsTQ.rows.result - }).flatMap({ xs => - val filename = dumpDir+"/blockchains"+dumpSuffix - logger.info("dumping "+xs.size+" rows to "+filename) - new TableIo[BlockchainRow](filename).dump(xs) ServicesTQ.rows.result }).flatMap({ xs => val filename = dumpDir+"/services"+dumpSuffix @@ -175,26 +153,6 @@ object ExchangeApiTables { val filename = dumpDir+"/servicedockauths"+dumpSuffix logger.info("dumping "+xs.size+" rows to "+filename) new TableIo[ServiceDockAuthRow](filename).dump(xs) - MicroservicesTQ.rows.result - }).flatMap({ xs => - val filename = dumpDir+"/microservices"+dumpSuffix - logger.info("dumping "+xs.size+" rows to "+filename) - new TableIo[MicroserviceRow](filename).dump(xs) - MicroserviceKeysTQ.rows.result - }).flatMap({ xs => - val filename = dumpDir+"/microservicekeys"+dumpSuffix - logger.info("dumping "+xs.size+" rows to "+filename) - new TableIo[MicroserviceKeyRow](filename).dump(xs) - WorkloadsTQ.rows.result - }).flatMap({ xs => - val filename = dumpDir+"/workloads"+dumpSuffix - logger.info("dumping "+xs.size+" rows to "+filename) - new TableIo[WorkloadRow](filename).dump(xs) - WorkloadKeysTQ.rows.result - }).flatMap({ xs => - val filename = dumpDir+"/workloadkeys"+dumpSuffix - logger.info("dumping "+xs.size+" rows to "+filename) - new TableIo[WorkloadKeyRow](filename).dump(xs) PatternsTQ.rows.result }).flatMap({ xs => val filename = dumpDir+"/patterns"+dumpSuffix @@ -255,12 +213,6 @@ object ExchangeApiTables { val nodes = new TableIo[NodeRow](dumpDir+"/nodes"+dumpSuffix).load if (nodes.nonEmpty) actions += (NodesTQ.rows ++= nodes) - val nodemicros = new TableIo[RegMicroserviceRow](dumpDir+"/nodemicros"+dumpSuffix).load - if (nodemicros.nonEmpty) actions += (RegMicroservicesTQ.rows ++= nodemicros) - - val properties = new TableIo[PropRow](dumpDir+"/properties"+dumpSuffix).load - if (properties.nonEmpty) actions += (PropsTQ.rows ++= properties) - val nodeagreements = new TableIo[NodeAgreementRow](dumpDir+"/nodeagreements"+dumpSuffix).load if (nodeagreements.nonEmpty) actions += (NodeAgreementsTQ.rows ++= nodeagreements) @@ -276,12 +228,6 @@ object ExchangeApiTables { val agbotmsgs = new TableIo[AgbotMsgRow](dumpDir+"/agbotmsgs"+dumpSuffix).load if (agbotmsgs.nonEmpty) actions += (AgbotMsgsTQ.rows ++= agbotmsgs) - val bctypes = new TableIo[BctypeRow](dumpDir+"/bctypes"+dumpSuffix).load - if (bctypes.nonEmpty) actions += (BctypesTQ.rows ++= bctypes) - - val blockchains = new TableIo[BlockchainRow](dumpDir+"/blockchains"+dumpSuffix).load - if (blockchains.nonEmpty) actions += (BlockchainsTQ.rows ++= blockchains) - val services = new TableIo[ServiceRow](dumpDir+"/services"+dumpSuffix).load if (services.nonEmpty) actions += (ServicesTQ.rows ++= services) @@ -291,18 +237,6 @@ object ExchangeApiTables { val servicedockauths = new TableIo[ServiceDockAuthRow](dumpDir+"/servicedockauths"+dumpSuffix).load if (servicedockauths.nonEmpty) actions += (ServiceDockAuthsTQ.rows ++= servicedockauths) - val microservices = new TableIo[MicroserviceRow](dumpDir+"/microservices"+dumpSuffix).load - if (microservices.nonEmpty) actions += (MicroservicesTQ.rows ++= microservices) - - val microservicekeys = new TableIo[MicroserviceKeyRow](dumpDir+"/microservicekeys"+dumpSuffix).load - if (microservicekeys.nonEmpty) actions += (MicroserviceKeysTQ.rows ++= microservicekeys) - - val workloads = new TableIo[WorkloadRow](dumpDir+"/workloads"+dumpSuffix).load - if (workloads.nonEmpty) actions += (WorkloadsTQ.rows ++= workloads) - - val workloadkeys = new TableIo[WorkloadKeyRow](dumpDir+"/workloadkeys"+dumpSuffix).load - if (workloadkeys.nonEmpty) actions += (WorkloadKeysTQ.rows ++= workloadkeys) - val patterns = new TableIo[PatternRow](dumpDir+"/patterns"+dumpSuffix).load if (patterns.nonEmpty) actions += (PatternsTQ.rows ++= patterns) diff --git a/src/main/scala/com/horizon/exchangeapi/MicroservicesRoutes.scala b/src/main/scala/com/horizon/exchangeapi/MicroservicesRoutes.scala deleted file mode 100644 index 1a837a77..00000000 --- a/src/main/scala/com/horizon/exchangeapi/MicroservicesRoutes.scala +++ /dev/null @@ -1,587 +0,0 @@ -/** Services routes for all of the /microservices api methods. */ -package com.horizon.exchangeapi - -import org.json4s._ -import org.json4s.jackson.JsonMethods._ -import org.json4s.jackson.Serialization.write -import org.scalatra._ -import org.scalatra.swagger._ -import org.slf4j._ -import slick.jdbc.PostgresProfile.api._ -import com.horizon.exchangeapi.tables._ -import scala.collection.immutable._ -import scala.collection.mutable.{HashMap => MutableHashMap} -import scala.util._ -//import java.net._ - -//====== These are the input and output structures for /microservices routes. Swagger and/or json seem to require they be outside the trait. - -/** Output format for GET /orgs/{orgid}/microservices */ -case class GetMicroservicesResponse(microservices: Map[String,Microservice], lastIndex: Int) -case class GetMicroserviceAttributeResponse(attribute: String, value: String) - -/** Input format for POST /orgs/{orgid}/microservices or PUT /orgs/{orgid}/microservices/ */ -case class PostPutMicroserviceRequest(label: String, description: String, public: Boolean, specRef: String, version: String, arch: String, sharable: String, downloadUrl: Option[String], matchHardware: Option[Map[String,String]], userInput: List[Map[String,String]], workloads: List[MDockerImages]) { - protected implicit val jsonFormats: Formats = DefaultFormats - def validate() = { - // Currently we do not want to force that the specRef is a valid URL - //try { new URL(specRef) } - //catch { case _: MalformedURLException => halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "specRef is not valid URL format.")) } - - if (!Version(version).isValid) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "version '"+version+"' is not valid version format.")) - - // Check that it is signed - for (w <- workloads) { - //if (w.deployment != "" && (w.deployment_signature == "" || w.torrent == "")) { halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "this microservice definition does not appear to be signed.")) } - if (w.deployment != "" && w.deployment_signature == "") { halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "this microservice definition does not appear to be signed.")) } - } - } - - def formId(orgid: String) = MicroservicesTQ.formId(orgid, specRef, version, arch) - - def toMicroserviceRow(microservice: String, orgid: String, owner: String) = MicroserviceRow(microservice, orgid, owner, label, description, public, specRef, version, arch, sharable, downloadUrl.getOrElse(""), write(matchHardware), write(userInput), write(workloads), ApiTime.nowUTC) -} - -case class PatchMicroserviceRequest(label: Option[String], description: Option[String], public: Option[Boolean], specRef: Option[String], version: Option[String], arch: Option[String], sharable: Option[String], downloadUrl: Option[String]) { - protected implicit val jsonFormats: Formats = DefaultFormats - - /** Returns a tuple of the db action to update parts of the microservice, and the attribute name being updated. */ - def getDbUpdate(microservice: String, orgid: String): (DBIO[_],String) = { - val lastUpdated = ApiTime.nowUTC - //todo: support updating more than 1 attribute - // find the 1st attribute that was specified in the body and create a db action to update it for this microservice - label match { case Some(lab) => return ((for { d <- MicroservicesTQ.rows if d.microservice === microservice } yield (d.microservice,d.label,d.lastUpdated)).update((microservice, lab, lastUpdated)), "label"); case _ => ; } - description match { case Some(desc) => return ((for { d <- MicroservicesTQ.rows if d.microservice === microservice } yield (d.microservice,d.description,d.lastUpdated)).update((microservice, desc, lastUpdated)), "description"); case _ => ; } - public match { case Some(pub) => return ((for { d <- MicroservicesTQ.rows if d.microservice === microservice } yield (d.microservice,d.public,d.lastUpdated)).update((microservice, pub, lastUpdated)), "public"); case _ => ; } - specRef match { case Some(spec) => return ((for { d <- MicroservicesTQ.rows if d.microservice === microservice } yield (d.microservice,d.specRef,d.lastUpdated)).update((microservice, spec, lastUpdated)), "specRef"); case _ => ; } - version match { case Some(ver) => return ((for { d <- MicroservicesTQ.rows if d.microservice === microservice } yield (d.microservice,d.version,d.lastUpdated)).update((microservice, ver, lastUpdated)), "version"); case _ => ; } - arch match { case Some(ar) => return ((for { d <- MicroservicesTQ.rows if d.microservice === microservice } yield (d.microservice,d.arch,d.lastUpdated)).update((microservice, ar, lastUpdated)), "arch"); case _ => ; } - sharable match { case Some(shr) => return ((for { d <- MicroservicesTQ.rows if d.microservice === microservice } yield (d.microservice,d.sharable,d.lastUpdated)).update((microservice, shr, lastUpdated)), "sharable"); case _ => ; } - downloadUrl match { case Some(url) => return ((for { d <- MicroservicesTQ.rows if d.microservice === microservice } yield (d.microservice,d.downloadUrl,d.lastUpdated)).update((microservice, url, lastUpdated)), "downloadUrl"); case _ => ; } - return (null, null) - } -} - - -/** Input format for PUT /orgs/{orgid}/microservices/{id}/keys/ */ -case class PutMicroserviceKeyRequest(key: String) { - def toMicroserviceKey = MicroserviceKey(key, ApiTime.nowUTC) - def toMicroserviceKeyRow(microserviceId: String, keyId: String) = MicroserviceKeyRow(keyId, microserviceId, key, ApiTime.nowUTC) - def validate(keyId: String) = { - //if (keyId != formId) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "the key id should be in the form keyOrgid_key")) - } -} - - - -/** Implementation for all of the /microservices routes */ -trait MicroserviceRoutes extends ScalatraBase with FutureSupport with SwaggerSupport with AuthenticationSupport { - def db: Database // get access to the db object in ExchangeApiApp - def logger: Logger // get access to the logger object in ExchangeApiApp - protected implicit def jsonFormats: Formats - - /* ====== GET /orgs/{orgid}/microservices ================================ */ - val getMicroservices = - (apiOperation[GetMicroservicesResponse]("getMicroservices") - summary("Returns all microservices") - description("""Returns all microservice definitions in this org. Can be run by any user, node, or agbot.""") - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("id", DataType.String, Option[String]("Username of exchange user, or ID of the node or agbot. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("token", DataType.String, Option[String]("Password of exchange user, or token of the node or agbot. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("owner", DataType.String, Option[String]("Filter results to only include microservices with this owner (can include % for wildcard - the URL encoding for % is %25)"), paramType=ParamType.Query, required=false), - Parameter("public", DataType.String, Option[String]("Filter results to only include microservices with this public setting"), paramType=ParamType.Query, required=false), - Parameter("specRef", DataType.String, Option[String]("Filter results to only include microservices with this specRef (can include % for wildcard - the URL encoding for % is %25)"), paramType=ParamType.Query, required=false), - Parameter("version", DataType.String, Option[String]("Filter results to only include microservices with this version (can include % for wildcard - the URL encoding for % is %25)"), paramType=ParamType.Query, required=false), - Parameter("arch", DataType.String, Option[String]("Filter results to only include microservices with this arch (can include % for wildcard - the URL encoding for % is %25)"), paramType=ParamType.Query, required=false) - ) - responseMessages(ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - - get("/orgs/:orgid/microservices", operation(getMicroservices)) ({ - val orgid = params("orgid") - val ident = credsAndLog().authenticate().authorizeTo(TMicroservice(OrgAndId(orgid,"*").toString),Access.READ) - val resp = response - //var q = MicroservicesTQ.rows.subquery - var q = MicroservicesTQ.getAllMicroservices(orgid) - // If multiple filters are specified they are anded together by adding the next filter to the previous filter by using q.filter - params.get("owner").foreach(owner => { if (owner.contains("%")) q = q.filter(_.owner like owner) else q = q.filter(_.owner === owner) }) - params.get("public").foreach(public => { if (public.toLowerCase == "true") q = q.filter(_.public === true) else q = q.filter(_.public === false) }) - params.get("specRef").foreach(specRef => { if (specRef.contains("%")) q = q.filter(_.specRef like specRef) else q = q.filter(_.specRef === specRef) }) - params.get("version").foreach(version => { if (version.contains("%")) q = q.filter(_.version like version) else q = q.filter(_.version === version) }) - params.get("arch").foreach(arch => { if (arch.contains("%")) q = q.filter(_.arch like arch) else q = q.filter(_.arch === arch) }) - - db.run(q.result).map({ list => - logger.debug("GET /orgs/"+orgid+"/microservices result size: "+list.size) - val microservices = new MutableHashMap[String,Microservice] - if (list.nonEmpty) for (a <- list) if (ident.getOrg == a.orgid || a.public || ident.isSuperUser || ident.isMultiTenantAgbot) microservices.put(a.microservice, a.toMicroservice) - if (microservices.nonEmpty) resp.setStatus(HttpCode.OK) - else resp.setStatus(HttpCode.NOT_FOUND) - GetMicroservicesResponse(microservices.toMap, 0) - }) - }) - - /* ====== GET /orgs/{orgid}/microservices/{microservice} ================================ */ - val getOneMicroservice = - (apiOperation[GetMicroservicesResponse]("getOneMicroservice") - summary("Returns a microservice") - description("""Returns the microservice with the specified id in the exchange DB. Can be run by a user, node, or agbot.""") - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("microservice", DataType.String, Option[String]("Microservice id."), paramType=ParamType.Path), - Parameter("id", DataType.String, Option[String]("Username of exchange user, or ID of the node or agbot. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("token", DataType.String, Option[String]("Password of exchange user, or token of the node or agbot. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("attribute", DataType.String, Option[String]("Which attribute value should be returned. Only 1 attribute can be specified. If not specified, the entire microservice resource will be returned."), paramType=ParamType.Query, required=false) - ) - responseMessages(ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.BAD_INPUT,"bad input"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - - get("/orgs/:orgid/microservices/:microservice", operation(getOneMicroservice)) ({ - val orgid = params("orgid") - val bareMicro = params("microservice") // but do not have a hack/fix for the name - val microservice = OrgAndId(orgid,bareMicro).toString - credsAndLog().authenticate().authorizeTo(TMicroservice(microservice),Access.READ) - val resp = response - params.get("attribute") match { - case Some(attribute) => ; // Only returning 1 attr of the microservice - val q = MicroservicesTQ.getAttribute(microservice, attribute) // get the proper db query for this attribute - if (q == null) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "Microservice attribute name '"+attribute+"' is not an attribute of the microservice resource.")) - db.run(q.result).map({ list => - logger.trace("GET /orgs/"+orgid+"/microservices/"+bareMicro+" attribute result: "+list.toString) - if (list.nonEmpty) { - resp.setStatus(HttpCode.OK) - GetMicroserviceAttributeResponse(attribute, list.head.toString) - } else { - resp.setStatus(HttpCode.NOT_FOUND) - ApiResponse(ApiResponseType.NOT_FOUND, "not found") - } - }) - - case None => ; // Return the whole microservice resource - db.run(MicroservicesTQ.getMicroservice(microservice).result).map({ list => - logger.debug("GET /orgs/"+orgid+"/microservices/"+bareMicro+" result: "+list.toString) - val microservices = new MutableHashMap[String,Microservice] - if (list.nonEmpty) for (a <- list) microservices.put(a.microservice, a.toMicroservice) - if (microservices.nonEmpty) resp.setStatus(HttpCode.OK) - else resp.setStatus(HttpCode.NOT_FOUND) - GetMicroservicesResponse(microservices.toMap, 0) - }) - } - }) - - // =========== POST /orgs/{orgid}/microservices =============================== - val postMicroservices = - (apiOperation[ApiResponse]("postMicroservices") - summary "Adds a microservice" - description """Creates a microservice resource. A microservice provides access to node data or services that can be used by potentially multiple workloads. The microservice resource contains the metadata that Horizon needs to deploy the docker images that implement this microservice. If public is set to true, the microservice can be shared across organizations. This can only be called by a user. The **request body** structure: - -``` -// (remove all of the comments like this before using) -{ - "label": "GPS for x86_64", // this will be displayed in the node registration UI - "description": "blah blah", - "public": true, // whether or not it can be viewed by other organizations - "specRef": "https://bluehorizon.network/documentation/microservice/gps", // the unique identifier of this MS - "version": "1.0.0", - "arch": "amd64", - "sharable": "exclusive", // or: "single", "multiple" - "downloadUrl": "", // reserved for future use, can be omitted - "matchHardware": {}, // reserved for future use, can be omitted (will be hints to the node about how to tell if it has the physical sensors required by this MS - // Values the node owner will be prompted for and will be set as env vars to the container. - "userInput": [ - { - "name": "foo", - "label": "The Foo Value", - "type": "string", // or: "int", "float", "string list" - "defaultValue": "bar" - } - ], - // The docker images that will be deployed on edge nodes for this microservice - "workloads": [ - { - "deployment": "{\"services\":{\"gps\":{\"image\":\"summit.hovitos.engineering/x86/gps:2.0.3\",\"privileged\":true,\"nodes\":[\"/dev/bus/usb/001/001:/dev/bus/usb/001/001\"]}}}", - "deployment_signature": "EURzSk=", // filled in by the Horizon signing process - "torrent": "{\"url\":\"https://images.bluehorizon.network/139e5b32f271e43698565ff0a37c525609f86178.json\",\"signature\":\"L6/iZxGXloE=\"}" // filled in by the Horizon signing process - } - ] -} -```""" - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("username", DataType.String, Option[String]("Username of exchange user. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), - Parameter("password", DataType.String, Option[String]("Password of the user. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("body", DataType[PostPutMicroserviceRequest], - Option[String]("Microservice object that needs to be updated in the exchange. See details in the Implementation Notes above."), - paramType = ParamType.Body) - ) - responseMessages(ResponseMessage(HttpCode.POST_OK,"created/updated"), ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.BAD_INPUT,"bad input"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - val postMicroservices2 = (apiOperation[PostPutMicroserviceRequest]("postMicroservices2") summary("a") description("a")) // for some bizarre reason, the PostMicroserviceRequest class has to be used in apiOperation() for it to be recognized in the body Parameter above - - post("/orgs/:orgid/microservices", operation(postMicroservices)) ({ - val orgid = params("orgid") - val ident = credsAndLog().authenticate().authorizeTo(TMicroservice(OrgAndId(orgid,"").toString),Access.CREATE) - val microserviceReq = try { parse(request.body).extract[PostPutMicroserviceRequest] } - catch { case e: Exception => halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "Error parsing the input body json: "+e)) } - microserviceReq.validate() - val microservice = microserviceReq.formId(orgid) - val owner = ident match { case IUser(creds) => creds.id; case _ => "" } - val resp = response - db.run(MicroservicesTQ.getNumOwned(owner).result.flatMap({ xs => - logger.debug("POST /orgs/"+orgid+"/microservices num owned by "+owner+": "+xs) - val numOwned = xs - val maxMicroservices = ExchConfig.getInt("api.limits.maxMicroservices") - if (maxMicroservices == 0 || numOwned <= maxMicroservices) { // we are not sure if this is a create or update, but if they are already over the limit, stop them anyway - microserviceReq.toMicroserviceRow(microservice, orgid, owner).insert.asTry - } - else DBIO.failed(new Throwable("Access Denied: you are over the limit of "+maxMicroservices+ " microservices")).asTry - })).map({ xs => - logger.debug("POST /orgs/"+orgid+"/microservices result: "+xs.toString) - xs match { - case Success(_) => if (owner != "") AuthCache.microservices.putOwner(microservice, owner) // currently only users are allowed to update microservice resources, so owner should never be blank - AuthCache.microservices.putIsPublic(microservice, microserviceReq.public) - resp.setStatus(HttpCode.POST_OK) - ApiResponse(ApiResponseType.OK, "microservice '"+microservice+"' created") - case Failure(t) => if (t.getMessage.startsWith("Access Denied:")) { - resp.setStatus(HttpCode.ACCESS_DENIED) - ApiResponse(ApiResponseType.ACCESS_DENIED, "microservice '"+microservice+"' not created: "+t.getMessage) - } else if (t.getMessage.contains("duplicate key value violates unique constraint")) { - resp.setStatus(HttpCode.ALREADY_EXISTS) - ApiResponse(ApiResponseType.ALREADY_EXISTS, "microservice '" + microservice + "' already exists: " + t.getMessage) - } else { - resp.setStatus(HttpCode.INTERNAL_ERROR) - ApiResponse(ApiResponseType.INTERNAL_ERROR, "microservice '"+microservice+"' not created: "+t.toString) - } - } - }) - }) - - // =========== PUT /orgs/{orgid}/microservices/{microservice} =============================== - val putMicroservices = - (apiOperation[ApiResponse]("putMicroservices") - summary "Updates a microservice" - description """Does a full replace of an existing microservice. This can only be called by the user that originally created it.""" - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("microservice", DataType.String, Option[String]("Microservice id."), paramType=ParamType.Path), - Parameter("username", DataType.String, Option[String]("Username of exchange user. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), - Parameter("password", DataType.String, Option[String]("Password of the user. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("body", DataType[PostPutMicroserviceRequest], - Option[String]("Microservice object that needs to be updated in the exchange. See details in the Implementation Notes above."), - paramType = ParamType.Body) - ) - responseMessages(ResponseMessage(HttpCode.POST_OK,"created/updated"), ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.BAD_INPUT,"bad input"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - val putMicroservices2 = (apiOperation[PostPutMicroserviceRequest]("putMicroservices2") summary("a") description("a")) // for some bizarre reason, the PutMicroserviceRequest class has to be used in apiOperation() for it to be recognized in the body Parameter above - - put("/orgs/:orgid/microservices/:microservice", operation(putMicroservices)) ({ - val orgid = params("orgid") - val bareMicro = params("microservice") // but do not have a hack/fix for the name - val microservice = OrgAndId(orgid,bareMicro).toString - val ident = credsAndLog().authenticate().authorizeTo(TMicroservice(microservice),Access.WRITE) - val microserviceReq = try { parse(request.body).extract[PostPutMicroserviceRequest] } - catch { case e: Exception => halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "Error parsing the input body json: "+e)) } - microserviceReq.validate() - val owner = ident match { case IUser(creds) => creds.id; case _ => "" } - val resp = response - db.run(microserviceReq.toMicroserviceRow(microservice, orgid, owner).update.asTry).map({ xs => - logger.debug("PUT /orgs/"+orgid+"/microservices/"+bareMicro+" result: "+xs.toString) - xs match { - case Success(n) => try { - val numUpdated = n.toString.toInt // i think n is an AnyRef so we have to do this to get it to an int - if (numUpdated > 0) { - if (owner != "") AuthCache.microservices.putOwner(microservice, owner) // currently only users are allowed to update microservice resources, so owner should never be blank - AuthCache.microservices.putIsPublic(microservice, microserviceReq.public) - resp.setStatus(HttpCode.PUT_OK) - ApiResponse(ApiResponseType.OK, "microservice updated") - } else { - resp.setStatus(HttpCode.NOT_FOUND) - ApiResponse(ApiResponseType.NOT_FOUND, "microservice '"+microservice+"' not found") - } - } catch { case e: Exception => resp.setStatus(HttpCode.INTERNAL_ERROR); ApiResponse(ApiResponseType.INTERNAL_ERROR, "microservice '"+microservice+"' not updated: "+e) } // the specific exception is NumberFormatException - case Failure(t) => resp.setStatus(HttpCode.INTERNAL_ERROR) - ApiResponse(ApiResponseType.INTERNAL_ERROR, "microservice '"+microservice+"' not updated: "+t.toString) - } - }) - }) - - // =========== PATCH /orgs/{orgid}/microservices/{microservice} =============================== - val patchMicroservices = - (apiOperation[Map[String,String]]("patchMicroservices") - summary "Updates 1 attribute of a microservice" - description """Updates one attribute of a microservice in the exchange DB. This can only be called by the user that originally created this microservice resource.""" - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("microservice", DataType.String, Option[String]("Microservice id."), paramType=ParamType.Path), - Parameter("username", DataType.String, Option[String]("Username of owning user. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), - Parameter("password", DataType.String, Option[String]("Password of the user. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("body", DataType[PatchMicroserviceRequest], - Option[String]("Partial microservice object that contains an attribute to be updated in this microservice. See details in the Implementation Notes above."), - paramType = ParamType.Body) - ) - responseMessages(ResponseMessage(HttpCode.POST_OK,"created/updated"), ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.BAD_INPUT,"bad input"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - val patchMicroservices2 = (apiOperation[PatchMicroserviceRequest]("patchMicroservices2") summary("a") description("a")) // for some bizarre reason, the PatchMicroserviceRequest class has to be used in apiOperation() for it to be recognized in the body Parameter above - - patch("/orgs/:orgid/microservices/:microservice", operation(patchMicroservices)) ({ - val orgid = params("orgid") - val bareMicro = params("microservice") // but do not have a hack/fix for the name - val microservice = OrgAndId(orgid,bareMicro).toString - credsAndLog().authenticate().authorizeTo(TMicroservice(microservice),Access.WRITE) - val microserviceReq = try { parse(request.body).extract[PatchMicroserviceRequest] } - catch { case e: Exception => halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "Error parsing the input body json: "+e)) } // the specific exception is MappingException - logger.trace("PATCH /orgs/"+orgid+"/microservices/"+bareMicro+" input: "+microserviceReq.toString) - val resp = response - val (action, attrName) = microserviceReq.getDbUpdate(microservice, orgid) - if (action == null) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "no valid microservice attribute specified")) - db.run(action.transactionally.asTry).map({ xs => - logger.debug("PATCH /orgs/"+orgid+"/microservices/"+bareMicro+" result: "+xs.toString) - xs match { - case Success(v) => try { - val numUpdated = v.toString.toInt // v comes to us as type Any - if (numUpdated > 0) { // there were no db errors, but determine if it actually found it or not - if (attrName == "public") AuthCache.microservices.putIsPublic(microservice, microserviceReq.public.getOrElse(false)) - resp.setStatus(HttpCode.PUT_OK) - ApiResponse(ApiResponseType.OK, "attribute '"+attrName+"' of microservice '"+microservice+"' updated") - } else { - resp.setStatus(HttpCode.NOT_FOUND) - ApiResponse(ApiResponseType.NOT_FOUND, "microservice '"+microservice+"' not found") - } - } catch { case e: Exception => resp.setStatus(HttpCode.INTERNAL_ERROR); ApiResponse(ApiResponseType.INTERNAL_ERROR, "Unexpected result from update: "+e) } - case Failure(t) => resp.setStatus(HttpCode.INTERNAL_ERROR) - ApiResponse(ApiResponseType.INTERNAL_ERROR, "microservice '"+microservice+"' not updated: "+t.toString) - } - }) - }) - - // =========== DELETE /orgs/{orgid}/microservices/{microservice} =============================== - val deleteMicroservices = - (apiOperation[ApiResponse]("deleteMicroservices") - summary "Deletes a microservice" - description "Deletes a microservice from the exchange DB. Can only be run by the owning user." - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("microservice", DataType.String, Option[String]("Microservice id."), paramType=ParamType.Path), - Parameter("username", DataType.String, Option[String]("Username of owning user. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), - Parameter("password", DataType.String, Option[String]("Password of the user. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false) - ) - responseMessages(ResponseMessage(HttpCode.DELETED,"deleted"), ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - - delete("/orgs/:orgid/microservices/:microservice", operation(deleteMicroservices)) ({ - val orgid = params("orgid") - val bareMicro = params("microservice") // but do not have a hack/fix for the name - val microservice = OrgAndId(orgid,bareMicro).toString - credsAndLog().authenticate().authorizeTo(TMicroservice(microservice),Access.WRITE) - // remove does *not* throw an exception if the key does not exist - val resp = response - db.run(MicroservicesTQ.getMicroservice(microservice).delete.transactionally.asTry).map({ xs => - logger.debug("DELETE /orgs/"+orgid+"/microservices/"+bareMicro+" result: "+xs.toString) - xs match { - case Success(v) => if (v > 0) { // there were no db errors, but determine if it actually found it or not - AuthCache.microservices.removeOwner(microservice) - AuthCache.microservices.removeIsPublic(microservice) - resp.setStatus(HttpCode.DELETED) - ApiResponse(ApiResponseType.OK, "microservice deleted") - } else { - resp.setStatus(HttpCode.NOT_FOUND) - ApiResponse(ApiResponseType.NOT_FOUND, "microservice '"+microservice+"' not found") - } - case Failure(t) => resp.setStatus(HttpCode.INTERNAL_ERROR) - ApiResponse(ApiResponseType.INTERNAL_ERROR, "microservice '"+microservice+"' not deleted: "+t.toString) - } - }) - }) - - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - /* ====== GET /orgs/{orgid}/microservices/{microservice}/keys ================================ */ - val getMicroserviceKeys = - (apiOperation[List[String]]("getMicroserviceKeys") - summary "Returns all keys/certs for this microservice" - description """Returns all the signing public keys/certs for this microservice. Can be run by any credentials able to view the microservice.""" - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("microservice", DataType.String, Option[String]("Microservice id."), paramType=ParamType.Path), - Parameter("username", DataType.String, Option[String]("Username of owning user. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), - Parameter("password", DataType.String, Option[String]("Password of the user. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false) - ) - responseMessages(ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - - get("/orgs/:orgid/microservices/:microservice/keys", operation(getMicroserviceKeys)) ({ - val orgid = params("orgid") - val microservice = params("microservice") - val compositeId = OrgAndId(orgid,microservice).toString - credsAndLog().authenticate().authorizeTo(TMicroservice(compositeId),Access.READ) - val resp = response - db.run(MicroserviceKeysTQ.getKeys(compositeId).result).map({ list => - logger.debug("GET /orgs/"+orgid+"/microservices/"+microservice+"/keys result size: "+list.size) - //logger.trace("GET /orgs/"+orgid+"/microservices/"+id+"/keys result: "+list.toString) - if (list.nonEmpty) resp.setStatus(HttpCode.OK) - else resp.setStatus(HttpCode.NOT_FOUND) - list.map(_.keyId) - }) - }) - - /* ====== GET /orgs/{orgid}/microservices/{microservice}/keys/{keyid} ================================ */ - val getOneMicroserviceKey = - (apiOperation[String]("getOneMicroserviceKey") - summary "Returns a key/cert for this microservice" - description """Returns the signing public key/cert with the specified keyid for this microservice. The raw content of the key/cert is returned, not json. Can be run by any credentials able to view the microservice.""" - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("microservice", DataType.String, Option[String]("Microservice id."), paramType=ParamType.Path), - Parameter("keyid", DataType.String, Option[String]("ID of the key."), paramType = ParamType.Path), - Parameter("username", DataType.String, Option[String]("Username of owning user. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), - Parameter("password", DataType.String, Option[String]("Password of the user. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false) - ) - produces "text/plain" - responseMessages(ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - - get("/orgs/:orgid/microservices/:microservice/keys/:keyid", operation(getOneMicroserviceKey)) ({ - val orgid = params("orgid") - val microservice = params("microservice") - val compositeId = OrgAndId(orgid,microservice).toString - val keyId = params("keyid") - credsAndLog().authenticate().authorizeTo(TMicroservice(compositeId),Access.READ) - val resp = response - db.run(MicroserviceKeysTQ.getKey(compositeId, keyId).result).map({ list => - logger.debug("GET /orgs/"+orgid+"/microservices/"+microservice+"/keys/"+keyId+" result: "+list.size) - if (list.nonEmpty) { - // Return the raw key, not json - resp.setStatus(HttpCode.OK) - resp.setHeader("Content-Disposition", "attachment; filename="+keyId) - resp.setHeader("Content-Type", "text/plain") - resp.setHeader("Content-Length", list.head.key.length.toString) - list.head.key - } - else { - resp.setStatus(HttpCode.NOT_FOUND) - ApiResponse(ApiResponseType.NOT_FOUND, "key '"+keyId+"' not found") - } - }) - }) - - // =========== PUT /orgs/{orgid}/microservices/{microservice}/keys/{keyid} =============================== - val putMicroserviceKey = - (apiOperation[ApiResponse]("putMicroserviceKey") - summary "Adds/updates a key/cert for the microservice" - description """Adds a new signing public key/cert, or updates an existing key/cert, for this microservice. This can only be run by the microservice owning user. Note that the input body is just the bytes of the key/cert (not the typical json), so the 'Content-Type' header must be set to 'text/plain'.""" - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("microservice", DataType.String, Option[String]("Microservice id."), paramType=ParamType.Path), - Parameter("keyid", DataType.String, Option[String]("ID of the key."), paramType = ParamType.Path), - Parameter("username", DataType.String, Option[String]("Username of owning user. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), - Parameter("password", DataType.String, Option[String]("Password of the user. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("body", DataType[String], - Option[String]("Key object that needs to be added to, or updated in, the exchange. See details in the Implementation Notes above."), - paramType = ParamType.Body) - ) - responseMessages(ResponseMessage(HttpCode.POST_OK,"created/updated"), ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.BAD_INPUT,"bad input"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - val putMicroserviceKey2 = (apiOperation[String]("putKey2") summary("a") description("a")) // for some bizarre reason, the PutKeysRequest class has to be used in apiOperation() for it to be recognized in the body Parameter above - - put("/orgs/:orgid/microservices/:microservice/keys/:keyid", operation(putMicroserviceKey)) ({ - val orgid = params("orgid") - val microservice = params("microservice") - val compositeId = OrgAndId(orgid,microservice).toString - val keyId = params("keyid") - credsAndLog().authenticate().authorizeTo(TMicroservice(compositeId),Access.WRITE) - val keyReq = PutMicroserviceKeyRequest(request.body) - //val keyReq = try { parse(request.body).extract[PutMicroserviceKeyRequest] } - //catch { case e: Exception => halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "Error parsing the input body json: "+e)) } // the specific exception is MappingException - keyReq.validate(keyId) - val resp = response - db.run(keyReq.toMicroserviceKeyRow(compositeId, keyId).upsert.asTry).map({ xs => - logger.debug("PUT /orgs/"+orgid+"/microservices/"+microservice+"/keys/"+keyId+" result: "+xs.toString) - xs match { - case Success(_) => resp.setStatus(HttpCode.PUT_OK) - ApiResponse(ApiResponseType.OK, "key added or updated") - case Failure(t) => if (t.getMessage.startsWith("Access Denied:")) { - resp.setStatus(HttpCode.ACCESS_DENIED) - ApiResponse(ApiResponseType.ACCESS_DENIED, "key '"+keyId+"' for microservice '"+compositeId+"' not inserted or updated: "+t.getMessage) - } else { - resp.setStatus(HttpCode.BAD_INPUT) - ApiResponse(ApiResponseType.BAD_INPUT, "key '"+keyId+"' for microservice '"+compositeId+"' not inserted or updated: "+t.getMessage) - } - } - }) - }) - - // =========== DELETE /orgs/{orgid}/microservices/{microservice}/keys =============================== - val deleteMicroserviceAllKey = - (apiOperation[ApiResponse]("deleteMicroserviceAllKey") - summary "Deletes all keys of a microservice" - description "Deletes all of the current keys/certs for this microservice. This can only be run by the microservice owning user." - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("microservice", DataType.String, Option[String]("Microservice id."), paramType=ParamType.Path), - Parameter("username", DataType.String, Option[String]("Username of owning user. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), - Parameter("password", DataType.String, Option[String]("Password of the user. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false) - ) - responseMessages(ResponseMessage(HttpCode.DELETED,"deleted"), ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - - delete("/orgs/:orgid/microservices/:microservice/keys", operation(deleteMicroserviceAllKey)) ({ - val orgid = params("orgid") - val microservice = params("microservice") - val compositeId = OrgAndId(orgid,microservice).toString - credsAndLog().authenticate().authorizeTo(TMicroservice(compositeId),Access.WRITE) - val resp = response - db.run(MicroserviceKeysTQ.getKeys(compositeId).delete.asTry).map({ xs => - logger.debug("DELETE /microservices/"+microservice+"/keys result: "+xs.toString) - xs match { - case Success(v) => if (v > 0) { // there were no db errors, but determine if it actually found it or not - resp.setStatus(HttpCode.DELETED) - ApiResponse(ApiResponseType.OK, "microservice keys deleted") - } else { - resp.setStatus(HttpCode.NOT_FOUND) - ApiResponse(ApiResponseType.NOT_FOUND, "no keys for microservice '"+compositeId+"' found") - } - case Failure(t) => resp.setStatus(HttpCode.INTERNAL_ERROR) - ApiResponse(ApiResponseType.INTERNAL_ERROR, "keys for microservice '"+compositeId+"' not deleted: "+t.toString) - } - }) - }) - - // =========== DELETE /orgs/{orgid}/microservices/{microservice}/keys/{keyid} =============================== - val deleteMicroserviceKey = - (apiOperation[ApiResponse]("deleteMicroserviceKey") - summary "Deletes a key of a microservice" - description "Deletes a key/cert for this microservice. This can only be run by the microservice owning user." - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("microservice", DataType.String, Option[String]("Microservice id."), paramType=ParamType.Path), - Parameter("keyid", DataType.String, Option[String]("ID of the key."), paramType = ParamType.Path), - Parameter("username", DataType.String, Option[String]("Username of owning user. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), - Parameter("password", DataType.String, Option[String]("Password of the user. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false) - ) - responseMessages(ResponseMessage(HttpCode.DELETED,"deleted"), ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - - delete("/orgs/:orgid/microservices/:microservice/keys/:keyid", operation(deleteMicroserviceKey)) ({ - val orgid = params("orgid") - val microservice = params("microservice") - val compositeId = OrgAndId(orgid,microservice).toString - val keyId = params("keyid") - credsAndLog().authenticate().authorizeTo(TMicroservice(compositeId),Access.WRITE) - val resp = response - db.run(MicroserviceKeysTQ.getKey(compositeId,keyId).delete.asTry).map({ xs => - logger.debug("DELETE /microservices/"+microservice+"/keys/"+keyId+" result: "+xs.toString) - xs match { - case Success(v) => if (v > 0) { // there were no db errors, but determine if it actually found it or not - resp.setStatus(HttpCode.DELETED) - ApiResponse(ApiResponseType.OK, "microservice key deleted") - } else { - resp.setStatus(HttpCode.NOT_FOUND) - ApiResponse(ApiResponseType.NOT_FOUND, "key '"+keyId+"' for microservice '"+compositeId+"' not found") - } - case Failure(t) => resp.setStatus(HttpCode.INTERNAL_ERROR) - ApiResponse(ApiResponseType.INTERNAL_ERROR, "key '"+keyId+"' for microservice '"+compositeId+"' not deleted: "+t.toString) - } - }) - }) - -} \ No newline at end of file diff --git a/src/main/scala/com/horizon/exchangeapi/NodesRoutes.scala b/src/main/scala/com/horizon/exchangeapi/NodesRoutes.scala index f8c57a5a..2390795d 100644 --- a/src/main/scala/com/horizon/exchangeapi/NodesRoutes.scala +++ b/src/main/scala/com/horizon/exchangeapi/NodesRoutes.scala @@ -11,7 +11,7 @@ import org.slf4j._ import slick.jdbc.PostgresProfile.api._ import scala.collection.immutable._ -import scala.collection.mutable.{ListBuffer, HashMap => MutableHashMap, Set => MutableSet} +import scala.collection.mutable.{ListBuffer, HashMap => MutableHashMap} import scala.util._ import scala.util.control.Breaks._ @@ -22,18 +22,11 @@ case class GetNodesResponse(nodes: Map[String,Node], lastIndex: Int) case class GetNodeAttributeResponse(attribute: String, value: String) /** Input for pattern-based search for nodes to make agreements with. */ -case class PostPatternSearchRequest(workloadUrl: Option[String], serviceUrl: Option[String], nodeOrgids: Option[List[String]], secondsStale: Int, startIndex: Int, numEntries: Int) { - def validate() = { - if (workloadUrl.isDefined && serviceUrl.isDefined) { - halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "can not specify both the 'workloadUrl' and 'serviceUrl' fields.")) - } else if (workloadUrl.isEmpty && serviceUrl.isEmpty) { - halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "either the 'workloadUrl' or 'serviceUrl' field must be specified.")) - } - } +case class PostPatternSearchRequest(serviceUrl: String, nodeOrgids: Option[List[String]], secondsStale: Int, startIndex: Int, numEntries: Int) { + def validate() = { } } // Tried this to have names on the tuple returned from the db, but didn't work... -//case class PatternSearchDbResponse(id: Rep[String], msgEndPoint: Rep[String], publicKey: Rep[String], workloadUrl: Rep[Option[String]], state: Rep[Option[String]]) case class PatternSearchHashElement(msgEndPoint: String, publicKey: String, noAgreementYet: Boolean) case class PatternNodeResponse(id: String, msgEndPoint: String, publicKey: String) @@ -49,20 +42,13 @@ class NodeHealthHashElement(var lastHeartbeat: String, var agreements: Map[Strin case class PostNodeHealthResponse(nodes: Map[String,NodeHealthHashElement]) -/** Input for microservice-based (citizen scientist) search, POST /orgs/"+orgid+"/search/nodes */ -case class PostSearchNodesRequest(desiredMicroservices: Option[List[RegMicroserviceSearch]], desiredServices: Option[List[RegServiceSearch]], secondsStale: Int, propertiesToReturn: List[String], startIndex: Int, numEntries: Int) { +/** Input for service-based (citizen scientist) search, POST /orgs/"+orgid+"/search/nodes */ +case class PostSearchNodesRequest(desiredServices: List[RegServiceSearch], secondsStale: Int, propertiesToReturn: Option[List[String]], startIndex: Int, numEntries: Int) { /** Halts the request with an error msg if the user input is invalid. */ def validate() = { - for (m <- desiredMicroservices.getOrElse(List())) { - m.validate match { - case Some(s) => halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, s)) - case None => ; - } - } - for (m <- desiredServices.getOrElse(List())) { - // now we support more than 1 agreement for a MS - // if (m.numAgreements != 1) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "invalid value "+m.numAgreements+" for numAgreements in "+m.url+". Currently it must always be 1.")) - m.validate match { + for (svc <- desiredServices) { + // now we support more than 1 agreement for a service + svc.validate match { case Some(s) => halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, s)) case None => ; } @@ -76,88 +62,45 @@ case class PostSearchNodesRequest(desiredMicroservices: Option[List[RegMicroserv // Loop thru the existing nodes and services in the DB. (Should probably make this more FP style) var nodesResp: List[NodeResponse] = List() for ((id,d) <- nodes) { // the db query now filters out stale nodes - if (desiredServices.isDefined) { - // Get all services for this node that are not max'd out on agreements - var availableServices: List[RegService] = List() - for (m <- d.registeredServices) { - breakable { - // do not even bother checking this against the search criteria if this service is already at its agreement limit - val agNode = agHash.agHash.get(id) - agNode match { - case Some(agNode2) => val agNum = agNode2.get(m.url) - agNum match { - case Some(agNum2) => if (agNum2 >= m.numAgreements) break // this is really a continue - case None => ; // no agreements for this service, nothing to do - } - case None => ; // no agreements for this node, nothing to do - } - availableServices = availableServices :+ m - } - } - - // We now have several services for 1 node from the db (that are not max'd out on agreements). See if all of the desired services are satisfied. - var servicesResp: List[RegService] = List() + // Get all services for this node that are not max'd out on agreements + var availableServices: List[RegService] = List() + for (m <- d.registeredServices) { breakable { - for (desiredService <- desiredServices.get) { - var found: Boolean = false - breakable { - for (availableService <- availableServices) { - if (desiredService.matches(availableService)) { - servicesResp = servicesResp :+ availableService - found = true - break - } + // do not even bother checking this against the search criteria if this service is already at its agreement limit + val agNode = agHash.agHash.get(id) + agNode match { + case Some(agNode2) => val agNum = agNode2.get(m.url) + agNum match { + case Some(agNum2) => if (agNum2 >= m.numAgreements) break // this is really a continue + case None => ; // no agreements for this service, nothing to do } - } - if (!found) break // we did not find one of the required services, so end early + case None => ; // no agreements for this node, nothing to do } + availableServices = availableServices :+ m } + } - if (servicesResp.length == desiredServices.get.length) { - // all required services were available in this node, so add this node to the response list - nodesResp = nodesResp :+ NodeResponse(id, d.name, List(), servicesResp, d.msgEndPoint, d.publicKey) - } - } else { // still using the old microservices - // Get all microservices for this node that are not max'd out on agreements - var availableMicros: List[RegMicroservice] = List() - for (m <- d.registeredMicroservices) { + // We now have several services for 1 node from the db (that are not max'd out on agreements). See if all of the desired services are satisfied. + var servicesResp: List[RegService] = List() + breakable { + for (desiredService <- desiredServices) { + var found: Boolean = false breakable { - // do not even bother checking this against the search criteria if this micro is already at its agreement limit - val agNode = agHash.agHash.get(id) - agNode match { - case Some(agNode2) => val agNum = agNode2.get(m.url) - agNum match { - case Some(agNum2) => if (agNum2 >= m.numAgreements) break // this is really a continue - case None => ; // no agreements for this microservice, nothing to do - } - case None => ; // no agreements for this node, nothing to do - } - availableMicros = availableMicros :+ m - } - } - - // We now have several microservices for 1 node from the db (that are not max'd out on agreements). See if all of the desired micros are satisfied. - var microsResp: List[RegMicroservice] = List() - breakable { - for (desiredMicro <- desiredMicroservices.get) { - var found: Boolean = false - breakable { - for (availableMicro <- availableMicros) { - if (desiredMicro.matches(availableMicro)) { - microsResp = microsResp :+ availableMicro - found = true - break - } + for (availableService <- availableServices) { + if (desiredService.matches(availableService)) { + servicesResp = servicesResp :+ availableService + found = true + break } } - if (!found) break // we did not find one of the required micros, so end early } + if (!found) break // we did not find one of the required services, so end early } + } - if (microsResp.length == desiredMicroservices.get.length) { - // all required micros were available in this node, so add this node to the response list - nodesResp = nodesResp :+ NodeResponse(id, d.name, microsResp, List(), d.msgEndPoint, d.publicKey) - } + if (servicesResp.length == desiredServices.length) { + // all required services were available in this node, so add this node to the response list + nodesResp = nodesResp :+ NodeResponse(id, d.name, servicesResp, d.msgEndPoint, d.publicKey) } } // return the search result to the rest client @@ -165,29 +108,18 @@ case class PostSearchNodesRequest(desiredMicroservices: Option[List[RegMicroserv } } -case class NodeResponse(id: String, name: String, microservices: List[RegMicroservice], services: List[RegService], msgEndPoint: String, publicKey: String) +case class NodeResponse(id: String, name: String, services: List[RegService], msgEndPoint: String, publicKey: String) case class PostSearchNodesResponse(nodes: List[NodeResponse], lastIndex: Int) /** Input format for PUT /orgs/{orgid}/nodes/ */ -case class PutNodesRequest(token: String, name: String, pattern: String, registeredMicroservices: Option[List[RegMicroservice]], registeredServices: Option[List[RegService]], msgEndPoint: String, softwareVersions: Map[String,String], publicKey: String) { +case class PutNodesRequest(token: String, name: String, pattern: String, registeredServices: Option[List[RegService]], msgEndPoint: String, softwareVersions: Map[String,String], publicKey: String) { protected implicit val jsonFormats: Formats = DefaultFormats /** Halts the request with an error msg if the user input is invalid. */ def validate() = { - if (registeredMicroservices.isDefined && registeredServices.isDefined) { - halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "can not specify both the 'registeredMicroservices' and 'registeredServices' fields.")) - } // if (msgEndPoint == "" && publicKey == "") halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "either msgEndPoint or publicKey must be specified.")) <-- skipping this check because POST /agbots/{id}/msgs checks for the publicKey if (token == "") halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "the token specified must not be blank")) if (pattern != "" && """.*/.*""".r.findFirstIn(pattern).isEmpty) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "the 'pattern' attribute must have the orgid prepended, with a slash separating")) - for (m <- registeredMicroservices.getOrElse(List())) { - // now we support more than 1 agreement for a MS - // if (m.numAgreements != 1) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "invalid value "+m.numAgreements+" for numAgreements in "+m.url+". Currently it must always be 1.")) - m.validate match { - case Some(s) => halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, s)) - case None => ; - } - } for (m <- registeredServices.getOrElse(List())) { // now we support more than 1 agreement for a MS // if (m.numAgreements != 1) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "invalid value "+m.numAgreements+" for numAgreements in "+m.url+". Currently it must always be 1.")) @@ -201,46 +133,14 @@ case class PutNodesRequest(token: String, name: String, pattern: String, registe /** Get the db actions to insert or update all parts of the node */ def getDbUpsert(id: String, orgid: String, owner: String): DBIO[_] = { //println("getDbUpsert: registeredServices: "+registeredServices) - // Accumulate the actions in a list, starting with the action to insert/update the node itself - val actions = ListBuffer[DBIO[_]](NodeRow(id, orgid, token, name, owner, pattern, write(registeredServices), msgEndPoint, write(softwareVersions), ApiTime.nowUTC, publicKey).upsert) - val microsUrls = MutableSet[String]() // the url attribute of the micros we are creating, so we can delete everthing else for this node - val propsIds = MutableSet[String]() // the propId attribute of the props we are creating, so we can delete everthing else for this node - // Now add actions to insert/update the node's micros and props - for (m <- registeredMicroservices.getOrElse(List())) { - actions += m.toRegMicroserviceRow(id).upsert - microsUrls += m.url - for (p <- m.properties) { - actions += p.toPropRow(id,m.url).upsert - propsIds += id+"|"+m.url+"|"+p.name - } - } - // handle the case where they changed what micros or props this node has, form selects to delete all micros and props that reference this node that aren't in microsUrls, propsIds - actions += PropsTQ.rows.filter(_.propId like id+"|%").filterNot(_.propId inSet propsIds).delete // props that reference this node, but are not in the list we just created/updated - actions += RegMicroservicesTQ.rows.filter(_.nodeId === id).filterNot(_.url inSet microsUrls).delete // micros that reference this node, but are not in the list we just created/updated - - DBIO.seq(actions.toList: _*) // convert the list of actions to a DBIO seq + NodeRow(id, orgid, token, name, owner, pattern, write(registeredServices), msgEndPoint, write(softwareVersions), ApiTime.nowUTC, publicKey).upsert } /** Get the db actions to update all parts of the node. This is run, instead of getDbUpsert(), when it is a node doing it, * because we can't let a node create new nodes. */ def getDbUpdate(id: String, orgid: String, owner: String): DBIO[_] = { //println("getDbUpdate: registeredServices: "+registeredServices) - val actions = ListBuffer[DBIO[_]](NodeRow(id, orgid, token, name, owner, pattern, write(registeredServices), msgEndPoint, write(softwareVersions), ApiTime.nowUTC, publicKey).update) - val microsUrls = MutableSet[String]() // the url attribute of the micros we are updating, so we can delete everthing else for this node - val propsIds = MutableSet[String]() // the propId attribute of the props we are updating, so we can delete everthing else for this node - for (m <- registeredMicroservices.getOrElse(List())) { - actions += m.toRegMicroserviceRow(id).upsert // only the node should be update (the rest should be upsert), because that's the only one we know exists - microsUrls += m.url - for (p <- m.properties) { - actions += p.toPropRow(id,m.url).upsert - propsIds += id+"|"+m.url+"|"+p.name - } - } - // handle the case where they changed what micros or props this node has, form selects to delete all micros and props that reference this node that aren't in microsUrls, propsIds - actions += PropsTQ.rows.filter(_.propId like id+"|%").filterNot(_.propId inSet propsIds).delete // props that reference this node, but are not in the list we just updated - actions += RegMicroservicesTQ.rows.filter(_.nodeId === id).filterNot(_.url inSet microsUrls).delete // micros that reference this node, but are not in the list we just updated - - DBIO.seq(actions.toList: _*) + NodeRow(id, orgid, token, name, owner, pattern, write(registeredServices), msgEndPoint, write(softwareVersions), ApiTime.nowUTC, publicKey).update } /** Not used any more, kept for reference of how to access object store - Returns the microservice templates for the registeredMicroservices in this object @@ -311,17 +211,11 @@ case class PatchNodesRequest(token: Option[String], name: Option[String], patter } -case class PutNodeStatusRequest(connectivity: Map[String,Boolean], microservices: Option[List[OneMicroservice]], workloads: Option[List[OneWorkload]], services: Option[List[OneService]]) { +case class PutNodeStatusRequest(connectivity: Map[String,Boolean], services: List[OneService]) { protected implicit val jsonFormats: Formats = DefaultFormats - def validate() = { - if ( (microservices.isDefined || workloads.isDefined) && services.isDefined ) { - halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "you can not specify both the 'services' and either 'microservices' or 'workloads' fields.")) - } else if (microservices.isEmpty && workloads.isEmpty && services.isEmpty) { - halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "you must specify at least 1 of the 'services', 'microservices', or 'workloads' fields.")) - } - } + def validate() = { } - def toNodeStatusRow(nodeId: String) = NodeStatusRow(nodeId, write(connectivity), write(microservices), write(workloads), write(services), ApiTime.nowUTC) + def toNodeStatusRow(nodeId: String) = NodeStatusRow(nodeId, write(connectivity), write(services), ApiTime.nowUTC) } @@ -329,20 +223,17 @@ case class PutNodeStatusRequest(connectivity: Map[String,Boolean], microservices case class GetNodeAgreementsResponse(agreements: Map[String,NodeAgreement], lastIndex: Int) /** Input format for PUT /orgs/{orgid}/nodes/{id}/agreements/ */ -case class PutNodeAgreementRequest(microservices: Option[List[NAMicroservice]], workload: Option[NAWorkload], services: Option[List[NAService]], agreementService: Option[NAgrService], state: String) { +case class PutNodeAgreementRequest(services: Option[List[NAService]], agreementService: Option[NAgrService], state: String) { protected implicit val jsonFormats: Formats = DefaultFormats def validate() = { - if ( (microservices.isDefined || workload.isDefined) && (services.isDefined || agreementService.isDefined) ) { - halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "you can not specify the old fields 'microservices' and 'workload' and the new fields 'services' or 'agreementService'.")) - } else if (microservices.isEmpty && workload.isEmpty && services.isEmpty && agreementService.isEmpty) { - halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "you must specify at least 1 of 'microservices', 'workload', 'services', or 'agreementService'.")) + if (services.isEmpty && agreementService.isEmpty) { + halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "you must specify at least 1 of 'services' or 'agreementService'.")) } } - //def toNodeAgreement = NodeAgreement(microservices, workload, state, ApiTime.nowUTC) def toNodeAgreementRow(nodeId: String, agId: String) = { - if (agreementService.isDefined) NodeAgreementRow(agId, nodeId, write(microservices), "", "", "", write(services), agreementService.get.orgid, agreementService.get.pattern, agreementService.get.url, state, ApiTime.nowUTC) - else NodeAgreementRow(agId, nodeId, write(microservices), workload.get.orgid, workload.get.pattern, workload.get.url, write(services), "", "", "", state, ApiTime.nowUTC) + if (agreementService.isDefined) NodeAgreementRow(agId, nodeId, write(services), agreementService.get.orgid, agreementService.get.pattern, agreementService.get.url, state, ApiTime.nowUTC) + else NodeAgreementRow(agId, nodeId, write(services), "", "", "", state, ApiTime.nowUTC) } } @@ -398,14 +289,15 @@ trait NodesRoutes extends ScalatraBase with FutureSupport with SwaggerSupport wi // The nodes, microservices, and properties tables all combine to form the Node object, so we do joins to get them all. // Note: joinLeft is necessary here so that if no micros exist for a node, we still get the node (and likewise for the micro if no props exist). // This means m and p below are wrapped in Option because they may not always be there - var q = for { - ((d, m), p) <- NodesTQ.getAllNodes(orgid) joinLeft RegMicroservicesTQ.rows on (_.id === _.nodeId) joinLeft PropsTQ.rows on (_._2.map(_.msId) === _.msId) - } yield (d, m, p) + //var q = for { + // ((d, m), p) <- NodesTQ.getAllNodes(orgid) joinLeft RegMicroservicesTQ.rows on (_.id === _.nodeId) joinLeft PropsTQ.rows on (_._2.map(_.msId) === _.msId) + //} yield (d, m, p) + var q = NodesTQ.getAllNodes(orgid) // add filters - params.get("idfilter").foreach(id => { if (id.contains("%")) q = q.filter(_._1.id like id) else q = q.filter(_._1.id === id) }) - params.get("name").foreach(name => { if (name.contains("%")) q = q.filter(_._1.name like name) else q = q.filter(_._1.name === name) }) - params.get("owner").foreach(owner => { if (owner.contains("%")) q = q.filter(_._1.owner like owner) else q = q.filter(_._1.owner === owner) }) + params.get("idfilter").foreach(id => { if (id.contains("%")) q = q.filter(_.id like id) else q = q.filter(_.id === id) }) + params.get("name").foreach(name => { if (name.contains("%")) q = q.filter(_.name like name) else q = q.filter(_.name === name) }) + params.get("owner").foreach(owner => { if (owner.contains("%")) q = q.filter(_.owner like owner) else q = q.filter(_.owner === owner) }) db.run(q.result).map({ list => logger.debug("GET /orgs/"+orgid+"/nodes result size: "+list.size) @@ -426,7 +318,7 @@ trait NodesRoutes extends ScalatraBase with FutureSupport with SwaggerSupport wi Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), Parameter("id", DataType.String, Option[String]("ID (orgid/nodeid) of the node."), paramType=ParamType.Path), Parameter("token", DataType.String, Option[String]("Token of the node. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("attribute", DataType.String, Option[String]("Which attribute value should be returned. Only 1 attribute can be specified, and it must be 1 of the direct attributes of the node resource (not of the microservices). If not specified, the entire node resource (including microservices) will be returned."), paramType=ParamType.Query, required=false) + Parameter("attribute", DataType.String, Option[String]("Which attribute value should be returned. Only 1 attribute can be specified, and it must be 1 of the direct attributes of the node resource (not of the services). If not specified, the entire node resource (including services) will be returned."), paramType=ParamType.Query, required=false) ) responseMessages(ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.BAD_INPUT,"bad input"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) ) @@ -453,16 +345,8 @@ trait NodesRoutes extends ScalatraBase with FutureSupport with SwaggerSupport wi } }) - case None => ; // Return the whole node, including the microservices - // The nodes, microservices, and properties tables all combine to form the Node object, so we do joins to get them all. - // Note: joinLeft is necessary here so that if no micros exist for a node, we still get the node (and likewise for the micro if no props exist). - // This means m and p below are wrapped in Option because sometimes they may not always be there - val q = for { - // (((d, m), p), s) <- NodesTQ.rows joinLeft MicroservicesTQ.rows on (_.id === _.nodeId) joinLeft PropsTQ.rows on (_._2.map(_.msId) === _.msId) joinLeft SoftwareVersionsTQ.rows on (_._1._1.id === _.nodeId) - ((d, m), p) <- NodesTQ.rows joinLeft RegMicroservicesTQ.rows on (_.id === _.nodeId) joinLeft PropsTQ.rows on (_._2.map(_.msId) === _.msId) - if d.id === id - } yield (d, m, p) - + case None => ; // Return the whole node + val q = NodesTQ.getNode(id) db.run(q.result).map({ list => logger.debug("GET /orgs/"+orgid+"/nodes/"+bareId+" result: "+list.size) if (list.nonEmpty) { @@ -485,8 +369,7 @@ trait NodesRoutes extends ScalatraBase with FutureSupport with SwaggerSupport wi ``` { - "serviceUrl": "https://bluehorizon.network/services/sdr", // The service the node does not have an agreement with yet. Only specify this or workloadUrl, not both - "workloadUrl": "https://bluehorizon.network/workloads/sdr", + "serviceUrl": "https://bluehorizon.network/services/sdr", // The service the node does not have an agreement with yet "nodeOrgids": [ "org1", "org2", "..." ], // if not specified, defaults to the same org the pattern is in "secondsStale": 60, // max number of seconds since the exchange has heard from the node, 0 if you do not care "startIndex": 0, // for pagination, ignored right now @@ -517,32 +400,25 @@ trait NodesRoutes extends ScalatraBase with FutureSupport with SwaggerSupport wi searchProps.validate() val nodeOrgids = searchProps.nodeOrgids.getOrElse(List(orgid)).toSet logger.debug("POST /orgs/"+orgid+"/patterns/"+pattern+"/search criteria: "+searchProps.toString) - val isService = searchProps.serviceUrl.isDefined - val ourService = searchProps.serviceUrl.getOrElse(searchProps.workloadUrl.get) + val ourService = searchProps.serviceUrl val resp = response /* Narrow down the db query results as much as possible by joining the Nodes and NodeAgreements tables and filtering. In english, the join gets: n.id, n.msgEndPoint, n.publicKey, n.lastHeartbeat, a.serviceUrl, a.state The filter is: n.pattern==ourpattern (the filter a.state=="" is applied later in our code below) - Then we have to go thru all of the results and find nodes that do NOT have an agreement for ourworkload. + Then we have to go thru all of the results and find nodes that do NOT have an agreement for ourService. Note about Slick usage: joinLeft returns node rows even if they don't have any agreements (which means the agreement cols are Option() ) */ val oldestTime = if (searchProps.secondsStale > 0) ApiTime.pastUTC(searchProps.secondsStale) else ApiTime.beginningUTC - val q = if (isService) { + val q = for { (n, a) <- NodesTQ.rows.filter(_.orgid inSet(nodeOrgids)).filter(_.pattern === compositePat).filter(_.publicKey =!= "").filter(_.lastHeartbeat >= oldestTime) joinLeft NodeAgreementsTQ.rows on (_.id === _.nodeId) } yield (n.id, n.msgEndPoint, n.publicKey, a.map(_.agrSvcUrl), a.map(_.state)) - } else { - for { - (n, a) <- NodesTQ.rows.filter(_.orgid inSet(nodeOrgids)).filter(_.pattern === compositePat).filter(_.publicKey =!= "").filter(_.lastHeartbeat >= oldestTime) joinLeft NodeAgreementsTQ.rows on (_.id === _.nodeId) - } yield (n.id, n.msgEndPoint, n.publicKey, a.map(_.workloadUrl), a.map(_.state)) - } db.run(PatternsTQ.getServices(compositePat).result.flatMap({ list => logger.debug("POST /orgs/"+orgid+"/patterns/"+pattern+"/search getServices size: "+list.size) logger.trace("POST /orgs/"+orgid+"/patterns/"+pattern+"/search: looking for '"+ourService+"', searching getServices: "+list.toString()) - if (!isService) q.result.asTry // we do not check the workloadUrl because that is going away - else if (list.nonEmpty) { + if (list.nonEmpty) { val services = PatternsTQ.getServicesFromString(list.head) // we should have found only 1 pattern services string, now parse it to get service list var found = false breakable { for ( svc <- services) { @@ -561,7 +437,7 @@ trait NodesRoutes extends ScalatraBase with FutureSupport with SwaggerSupport wi xs match { case Success(list) => if (list.nonEmpty) { // Go thru the rows and build a hash of the nodes that do NOT have an agreement for our service - val nodeHash = new MutableHashMap[String,PatternSearchHashElement] // key is node id, value noAgreementYet which is true if so far we haven't hit an agreement for our workload for this node + val nodeHash = new MutableHashMap[String,PatternSearchHashElement] // key is node id, value noAgreementYet which is true if so far we haven't hit an agreement for our service for this node for ( (id, msgEndPoint, publicKey, serviceUrlOption, stateOption) <- list ) { //logger.trace("id: "+id+", serviceUrlOption: "+serviceUrlOption.getOrElse("")+", ourService: "+ourService+", stateOption: "+stateOption.getOrElse("")) nodeHash.get(id) match { @@ -594,7 +470,6 @@ trait NodesRoutes extends ScalatraBase with FutureSupport with SwaggerSupport wi // Go thru the rows and build a hash of the nodes, adding the agreement to its value as we encounter them val nodeHash = new MutableHashMap[String,NodeHealthHashElement] // key is node id, value has lastHeartbeat and the agreements map for ( (nodeId, lastHeartbeat, agrId, agrLastUpdated) <- list ) { - //logger.trace("id: "+id+", workloadUrlOption: "+workloadUrlOption.getOrElse("")+", searchProps.workloadUrl: "+searchProps.workloadUrl+", stateOption: "+stateOption.getOrElse("")) nodeHash.get(nodeId) match { case Some(nodeElement) => agrId match { // this node is already in the hash, add the agreement if it's there case Some(agId) => nodeElement.agreements = nodeElement.agreements + ((agId, NodeHealthAgreementElement(agrLastUpdated.getOrElse("")))) // if we are here, lastHeartbeat is already set and the agreement Map is already created @@ -678,10 +553,10 @@ trait NodesRoutes extends ScalatraBase with FutureSupport with SwaggerSupport wi ``` { - "desiredMicroservices": [ // list of data microservices you are interested in + "desiredServices": [ // list of data services you are interested in { - "url": "https://bluehorizon.network/microservices/rtlsdr", - "properties": [ // list of properties to match specific nodes/microservices + "url": "https://bluehorizon.network/services/rtlsdr", + "properties": [ // list of properties to match specific nodes/services { "name": "arch", // typical property names are: arch, version, dataVerification, memory "value": "arm", // should always be a string (even for boolean and int). Use "*" for wildcard @@ -692,9 +567,6 @@ trait NodesRoutes extends ScalatraBase with FutureSupport with SwaggerSupport wi } ], "secondsStale": 60, // max number of seconds since the exchange has heard from the node, 0 if you do not care - "propertiesToReturn": [ // ignored right now - "string" - ], "startIndex": 0, // for pagination, ignored right now "numEntries": 0 // ignored right now } @@ -720,52 +592,27 @@ trait NodesRoutes extends ScalatraBase with FutureSupport with SwaggerSupport wi searchProps.validate() logger.debug("POST /orgs/"+orgid+"/search/nodes criteria: "+searchProps.desiredServices.toString) val resp = response - if (searchProps.desiredServices.isDefined) { - // Narrow down the db query results as much as possible with db selects, then searchProps.matches will do the rest. - var q = NodesTQ.getNonPatternNodes(orgid).filter(_.publicKey =!= "") - // Also filter out nodes that are too stale (have not heartbeated recently) - if (searchProps.secondsStale > 0) q = q.filter(_.lastHeartbeat >= ApiTime.pastUTC(searchProps.secondsStale)) - - var agHash: AgreementsHash = null - db.run(NodeAgreementsTQ.getAgreementsWithState.result.flatMap({ agList => - logger.debug("POST /orgs/" + orgid + "/search/nodes aglist result size: " + agList.size) - //logger.trace("POST /orgs/" + orgid + "/search/nodes aglist result: " + agList.toString) - agHash = new AgreementsHash(agList, searchProps.desiredServices.isDefined) - q.result // queue up our node/ms/prop query next - })).map({ list => - logger.debug("POST /orgs/" + orgid + "/search/nodes result size: " + list.size) - // logger.trace("POST /orgs/"+orgid+"/search/nodes result: "+list.toString) - // logger.trace("POST /orgs/"+orgid+"/search/nodes agHash: "+agHash.agHash.toString) - if (list.nonEmpty) resp.setStatus(HttpCode.POST_OK) //todo: this check only catches if there are no nodes at all, not the case in which there are some nodes, but they do not have the right services - else resp.setStatus(HttpCode.NOT_FOUND) - val nodes = new MutableHashMap[String,Node] // the key is node id - if (list.nonEmpty) for (a <- list) nodes.put(a.id, a.toNode(false)) - searchProps.matches(nodes.toMap, agHash) - }) - } else { // old microservices - // Narrow down the db query results as much as possible with db selects, then searchProps.matches will do the rest. - var q = for { - // Note: use this commented out line for the special case of IBM agbots being able to search nodes from all orgs - //((d, m), p) <- NodesTQ.rows joinLeft RegMicroservicesTQ.rows on (_.id === _.nodeId) joinLeft PropsTQ.rows on (_._2.map(_.msId) === _.msId) - ((d, m), p) <- NodesTQ.getNonPatternNodes(orgid).filter(_.publicKey =!= "") joinLeft RegMicroservicesTQ.rows on (_.id === _.nodeId) joinLeft PropsTQ.rows on (_._2.map(_.msId) === _.msId) - } yield (d, m, p) - // Also filter out nodes that are too stale (have not heartbeated recently) - if (searchProps.secondsStale > 0) q = q.filter(_._1.lastHeartbeat >= ApiTime.pastUTC(searchProps.secondsStale)) - - var agHash: AgreementsHash = null - db.run(NodeAgreementsTQ.getAgreementsWithState.result.flatMap({ agList => - logger.debug("POST /orgs/" + orgid + "/search/nodes aglist result size: " + agList.size) - //logger.trace("POST /orgs/" + orgid + "/search/nodes aglist result: " + agList.toString) - agHash = new AgreementsHash(agList, searchProps.desiredServices.isDefined) - q.result // queue up our node/ms/prop query next - })).map({ list => - logger.debug("POST /orgs/" + orgid + "/search/nodes result size: " + list.size) - if (list.nonEmpty) resp.setStatus(HttpCode.POST_OK) //todo: this check only catches if there are no nodes at all, not the case in which there are some nodes, but they do not have the right services - else resp.setStatus(HttpCode.NOT_FOUND) - val nodes = NodesTQ.parseJoin(superUser = false, list) - searchProps.matches(nodes, agHash) - }) - } + // Narrow down the db query results as much as possible with db selects, then searchProps.matches will do the rest. + var q = NodesTQ.getNonPatternNodes(orgid).filter(_.publicKey =!= "") + // Also filter out nodes that are too stale (have not heartbeated recently) + if (searchProps.secondsStale > 0) q = q.filter(_.lastHeartbeat >= ApiTime.pastUTC(searchProps.secondsStale)) + + var agHash: AgreementsHash = null + db.run(NodeAgreementsTQ.getAgreementsWithState.result.flatMap({ agList => + logger.debug("POST /orgs/" + orgid + "/search/nodes aglist result size: " + agList.size) + //logger.trace("POST /orgs/" + orgid + "/search/nodes aglist result: " + agList.toString) + agHash = new AgreementsHash(agList) + q.result // queue up our node query next + })).map({ list => + logger.debug("POST /orgs/" + orgid + "/search/nodes result size: " + list.size) + // logger.trace("POST /orgs/"+orgid+"/search/nodes result: "+list.toString) + // logger.trace("POST /orgs/"+orgid+"/search/nodes agHash: "+agHash.agHash.toString) + if (list.nonEmpty) resp.setStatus(HttpCode.POST_OK) //todo: this check only catches if there are no nodes at all, not the case in which there are some nodes, but they do not have the right services + else resp.setStatus(HttpCode.NOT_FOUND) + val nodes = new MutableHashMap[String,Node] // the key is node id + if (list.nonEmpty) for (a <- list) nodes.put(a.id, a.toNode(false)) + searchProps.matches(nodes.toMap, agHash) + }) }) // ======== POST /org/{orgid}/search/nodehealth ======================== @@ -830,18 +677,18 @@ trait NodesRoutes extends ScalatraBase with FutureSupport with SwaggerSupport wi val putNodes = (apiOperation[Map[String,String]]("putNodes") summary "Adds/updates a node" - description """Adds a new node (RPi) to the exchange DB, or updates an existing node, and returns the microservice templates for the microservices being registered. This must be called by the user to add a node, and then can be called by that user or node to update itself. The **request body** structure: + description """Adds a new edge node to the exchange DB, or updates an existing node. This must be called by the user to add a node, and then can be called by that user or node to update itself. The **request body** structure: ``` { "token": "abc", // node token, set by user when adding this node. "name": "rpi3", // node name that you pick - "pattern": "myorg/mypattern", // (optional) points to a pattern resource that defines what workloads should be deployed to this type of node - "registeredServices": [ // list of data microservices you want to make available + "pattern": "myorg/mypattern", // (optional) points to a pattern resource that defines what services should be deployed to this type of node + "registeredServices": [ // list of data services you want to make available { "url": "https://bluehorizon.network/documentation/sdr-node-api", "numAgreements": 1, // for now always set this to 1 - "policy": "{...}" // the microservice policy file content as a json string blob + "policy": "{...}" // the service policy file content as a json string blob "properties": [ // list of properties to help agbots search for this, or requirements on the agbot { "name": "arch", // must at least include arch and version properties @@ -906,7 +753,6 @@ trait NodesRoutes extends ScalatraBase with FutureSupport with SwaggerSupport wi xs match { case Success(_) => AuthCache.nodes.putBoth(Creds(id,node.token),owner) // the token passed in to the cache should be the non-hashed one resp.setStatus(HttpCode.PUT_OK) - //microTmpls ApiResponse(ApiResponseType.OK, "node added or updated") case Failure(t) => if (t.getMessage.startsWith("Access Denied:")) { resp.setStatus(HttpCode.ACCESS_DENIED) @@ -1052,7 +898,7 @@ trait NodesRoutes extends ScalatraBase with FutureSupport with SwaggerSupport wi val getNodeStatus = (apiOperation[NodeStatus]("getNodeStatus") summary("Returns the node status") - description("""Returns the node run time status, for example workload container status. Can be run by a user or the node.""") + description("""Returns the node run time status, for example service container status. Can be run by a user or the node.""") parameters( Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), Parameter("id", DataType.String, Option[String]("ID (orgid/nodeid) of the node."), paramType=ParamType.Path), @@ -1081,8 +927,7 @@ trait NodesRoutes extends ScalatraBase with FutureSupport with SwaggerSupport wi val putNodeStatus = (apiOperation[ApiResponse]("putNodeStatus") summary "Adds/updates the node status" - description """Adds or updates the run time status of a node. This is called by the - node or owning user. The **request body** structure: + description """Adds or updates the run time status of a node. This is called by the node or owning user. The **request body** structure: ``` { @@ -1090,26 +935,10 @@ trait NodesRoutes extends ScalatraBase with FutureSupport with SwaggerSupport wi "firmware.bluehorizon.network": true, "images.bluehorizon.network": true }, - "microservices": [ - { - "specRef": "https://bluehorizon.network/microservices/gps", - "orgid": "mycompany", - "version": "2.0.4", - "arch": "amd64", - "contanerStatus": [ - { - "name": "/bluehorizon.network-microservices-gps_2.0.4_78a98f1f-2eed-467c-aea2-278fb8161595-gps", - "image": "summit.hovitos.engineering/x86/gps:2.0.4", - "created": 1505939808, - "state": "running" - } - ] - } - ], - "workloads": [ + "services": [ { "agreementId": "78d7912aafb6c11b7a776f77d958519a6dc718b9bd3da36a1442ebb18fe9da30", - "workloadUrl":"https://bluehorizon.network/workloads/location", + "serviceUrl":"https://bluehorizon.network/services/location", "orgid":"ling.com", "version":"1.2", "arch":"amd64", @@ -1266,13 +1095,13 @@ trait NodesRoutes extends ScalatraBase with FutureSupport with SwaggerSupport wi ``` { - "microservices": [ // specify this for CS-type agreements - {"orgid": "myorg", "url": "https://bluehorizon.network/microservices/rtlsdr"} // url is API spec ref + "services": [ // specify this for CS-type agreements + {"orgid": "myorg", "url": "https://bluehorizon.network/services/rtlsdr"} ], - "workload": { // specify this for pattern-type agreements + "agreementService": { // specify this for pattern-type agreements "orgid": "myorg", "pattern": "mynodetype", - "url": "https://bluehorizon.network/workloads/sdr" + "url": "https://bluehorizon.network/services/sdr" }, "state": "negotiating" // current agreement state: negotiating, signed, finalized, etc. } diff --git a/src/main/scala/com/horizon/exchangeapi/PatternsRoutes.scala b/src/main/scala/com/horizon/exchangeapi/PatternsRoutes.scala index 5fa40eaf..22a33156 100644 --- a/src/main/scala/com/horizon/exchangeapi/PatternsRoutes.scala +++ b/src/main/scala/com/horizon/exchangeapi/PatternsRoutes.scala @@ -23,49 +23,28 @@ case class GetPatternsResponse(patterns: Map[String,Pattern], lastIndex: Int) case class GetPatternAttributeResponse(attribute: String, value: String) /** Input format for POST/PUT /orgs/{orgid}/patterns/ */ -case class PostPutPatternRequest(label: String, description: Option[String], public: Option[Boolean], workloads: Option[List[PWorkloads]], services: Option[List[PServices]], agreementProtocols: Option[List[Map[String,String]]]) { +case class PostPutPatternRequest(label: String, description: Option[String], public: Option[Boolean], services: List[PServices], agreementProtocols: Option[List[Map[String,String]]]) { protected implicit val jsonFormats: Formats = DefaultFormats def validate(): Unit = { - if (services.isDefined && services.get.nonEmpty) { - if (workloads.isDefined && workloads.get.nonEmpty) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "can not specify both the 'services' and 'workloads' fields.")) - // Check that it is signed and check the version syntax - for (s <- services.get) { - for (sv <- s.serviceVersions) { - if (!Version(sv.version).isValid) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "version '" + sv.version + "' is not valid version format.")) - if (sv.deployment_overrides.getOrElse("") != "" && sv.deployment_overrides_signature.getOrElse("") == "") { - halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "this pattern definition does not appear to be signed.")) - } - } - } - } else if (workloads.isDefined && workloads.get.nonEmpty) { - if (services.isDefined && services.get.nonEmpty) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "can not specify both the 'services' and 'workloads' fields.")) - // Check that it is signed and check the version syntax - for (w <- workloads.get) { - for (wv <- w.workloadVersions) { - if (!Version(wv.version).isValid) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "version '" + wv.version + "' is not valid version format.")) - if (wv.deployment_overrides.getOrElse("") != "" && wv.deployment_overrides_signature.getOrElse("") == "") { - halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "this pattern definition does not appear to be signed.")) - } + // Check that it is signed and check the version syntax + for (s <- services) { + for (sv <- s.serviceVersions) { + if (!Version(sv.version).isValid) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "version '" + sv.version + "' is not valid version format.")) + if (sv.deployment_overrides.getOrElse("") != "" && sv.deployment_overrides_signature.getOrElse("") == "") { + halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "this pattern definition does not appear to be signed.")) } } - } else { - halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "either the 'services' or 'workloads' field must be specified.")) } } - // Build a list of db actions to verify that the referenced workloads exist - def validateServiceIds: DBIO[Vector[Int]] = { - if (services.isDefined && services.get.nonEmpty) PatternsTQ.validateServiceIds(services.get) - else if (workloads.isDefined && workloads.get.nonEmpty) PatternsTQ.validateWorkloadIds(workloads.get) - else halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "either the 'services' or 'workloads' field must be specified and not empty.")) - } + // Build a list of db actions to verify that the referenced services exist + def validateServiceIds: DBIO[Vector[Int]] = { PatternsTQ.validateServiceIds(services) } // Note: write() handles correctly the case where the optional fields are None. - //def toPatternRow(pattern: String, orgid: String, owner: String) = PatternRow(pattern, orgid, owner, label, description, public, write(workloads), write(services), write(agreementProtocols), ApiTime.nowUTC) 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 services2 = if (services.nonEmpty) { - services.get.map({ s => + services.map({ s => val nodeHealth2 = s.nodeHealth.orElse(Some(Map("missing_heartbeat_interval" -> 600, "check_agreement_status" -> 120))) PServices(s.serviceUrl, s.serviceOrgid, s.serviceArch, s.agreementLess, s.serviceVersions, s.dataVerification, nodeHealth2) }) @@ -73,11 +52,11 @@ case class PostPutPatternRequest(label: String, description: Option[String], pub services } val agreementProtocols2 = agreementProtocols.orElse(Some(List(Map("name" -> "Basic")))) - PatternRow(pattern, orgid, owner, label, description.getOrElse(label), public.getOrElse(false), write(workloads), write(services2), write(agreementProtocols2), ApiTime.nowUTC) + PatternRow(pattern, orgid, owner, label, description.getOrElse(label), public.getOrElse(false), write(services2), write(agreementProtocols2), ApiTime.nowUTC) } } -case class PatchPatternRequest(label: Option[String], description: Option[String], public: Option[Boolean], workloads: Option[List[PWorkloads]], services: Option[List[PServices]], agreementProtocols: Option[List[Map[String,String]]]) { +case class PatchPatternRequest(label: Option[String], description: Option[String], public: Option[Boolean], services: Option[List[PServices]], agreementProtocols: Option[List[Map[String,String]]]) { protected implicit val jsonFormats: Formats = DefaultFormats /** Returns a tuple of the db action to update parts of the pattern, and the attribute name being updated. */ @@ -88,7 +67,6 @@ case class PatchPatternRequest(label: Option[String], description: Option[String label match { case Some(lab) => return ((for { d <- PatternsTQ.rows if d.pattern === pattern } yield (d.pattern,d.label,d.lastUpdated)).update((pattern, lab, lastUpdated)), "label"); case _ => ; } description match { case Some(desc) => return ((for { d <- PatternsTQ.rows if d.pattern === pattern } yield (d.pattern,d.description,d.lastUpdated)).update((pattern, desc, lastUpdated)), "description"); case _ => ; } public match { case Some(pub) => return ((for { d <- PatternsTQ.rows if d.pattern === pattern } yield (d.pattern,d.public,d.lastUpdated)).update((pattern, pub, lastUpdated)), "public"); case _ => ; } - workloads match { case Some(wk) => return ((for { d <- PatternsTQ.rows if d.pattern === pattern } yield (d.pattern,d.workloads,d.lastUpdated)).update((pattern, write(wk), lastUpdated)), "workloads"); case _ => ; } services match { case Some(svc) => return ((for { d <- PatternsTQ.rows if d.pattern === pattern } yield (d.pattern,d.services,d.lastUpdated)).update((pattern, write(svc), lastUpdated)), "services"); case _ => ; } agreementProtocols match { case Some(ap) => return ((for { d <- PatternsTQ.rows if d.pattern === pattern } yield (d.pattern,d.agreementProtocols,d.lastUpdated)).update((pattern, write(ap), lastUpdated)), "agreementProtocols"); case _ => ; } return (null, null) @@ -206,7 +184,7 @@ trait PatternRoutes extends ScalatraBase with FutureSupport with SwaggerSupport val postPatterns = (apiOperation[ApiResponse]("postPatterns") summary "Adds a pattern" - description """Creates a pattern resource. A pattern resource specifies all of the services that should be deployed for a type of node. When a node registers with Horizon, it can specify a pattern name to quickly tell Horizon what should be deployed on it. Patterns are not typically intended to be shared across organizations because they also specify deployment policy. This can only be called by a user. The **request body** structure: + description """Creates a pattern resource. A pattern resource specifies all of the services that should be deployed for a type of node. When a node registers with Horizon, it can specify a pattern name to quickly tell Horizon what should be deployed on it. This can only be called by a user. The **request body** structure: ``` // (remove all of the comments like this before using) @@ -262,7 +240,6 @@ trait PatternRoutes extends ScalatraBase with FutureSupport with SwaggerSupport } } ], - "workloads": [], // same as services except with s/service/workload/ // The Horizon agreement protocol(s) to use. "Basic" means make agreements w/o a blockchain. "Citizen Scientist" means use ethereum to record the agreement. "agreementProtocols": [ { @@ -296,7 +273,7 @@ trait PatternRoutes extends ScalatraBase with FutureSupport with SwaggerSupport // Get optional agbots that should be updated with this new pattern //val agbotParams = multiParams("updateagbot") val resp = response - // validateServiceIds() checks either the services or workloads, whichever is defined + // validateServiceIds() checks that the services referenced exist db.run(patternReq.validateServiceIds.asTry.flatMap({ xs => logger.debug("POST /orgs/"+orgid+"/patterns"+barePattern+" service validation: "+xs.toString) xs match { @@ -435,7 +412,7 @@ trait PatternRoutes extends ScalatraBase with FutureSupport with SwaggerSupport val resp = response val (action, attrName) = patternReq.getDbUpdate(pattern, orgid) if (action == null) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "no valid pattern attribute specified")) - val patValidateAction = if (attrName == "workloads") PatternsTQ.validateWorkloadIds(patternReq.workloads.get) else if (attrName == "services") PatternsTQ.validateServiceIds(patternReq.services.get) else DBIO.successful(Vector()) + val patValidateAction = if (attrName == "services") PatternsTQ.validateServiceIds(patternReq.services.get) else DBIO.successful(Vector()) db.run(patValidateAction.asTry.flatMap({ xs => logger.debug("PUT /orgs/"+orgid+"/patterns"+barePattern+" service validation: "+xs.toString) xs match { diff --git a/src/main/scala/com/horizon/exchangeapi/WorkloadsRoutes.scala b/src/main/scala/com/horizon/exchangeapi/WorkloadsRoutes.scala deleted file mode 100644 index 3a6a2441..00000000 --- a/src/main/scala/com/horizon/exchangeapi/WorkloadsRoutes.scala +++ /dev/null @@ -1,675 +0,0 @@ -/** Services routes for all of the /orgs/{orgid}/workloads api methods. */ -package com.horizon.exchangeapi - -import org.json4s._ -import org.json4s.jackson.JsonMethods._ -import org.json4s.jackson.Serialization.write -import org.scalatra._ -import org.scalatra.swagger._ -import org.slf4j._ -import slick.jdbc.PostgresProfile.api._ -import com.horizon.exchangeapi.tables._ -//import org.scalatra.servlet.{FileUploadSupport, MultipartConfig, SizeConstraintExceededException} - -import scala.collection.immutable._ -import scala.collection.mutable.{ListBuffer, HashMap => MutableHashMap} -import scala.util._ -//import java.net._ -import scala.util.control.Breaks._ - -//====== These are the input and output structures for /orgs/{orgid}/workloads routes. Swagger and/or json seem to require they be outside the trait. - -/** Output format for GET /orgs/{orgid}/workloads */ -case class GetWorkloadsResponse(workloads: Map[String,Workload], lastIndex: Int) -case class GetWorkloadAttributeResponse(attribute: String, value: String) - -/** Input format for POST /microservices or PUT /orgs/{orgid}/workloads/ */ -case class PostPutWorkloadRequest(label: String, description: String, public: Boolean, workloadUrl: String, version: String, arch: String, downloadUrl: Option[String], apiSpec: List[WMicroservices], userInput: List[Map[String,String]], workloads: List[MDockerImages]) { - protected implicit val jsonFormats: Formats = DefaultFormats - def validate() = { - // Currently we do not want to force that the workloadUrl is a valid URL - //try { new URL(workloadUrl) } - //catch { case _: MalformedURLException => halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "workloadUrl is not valid URL format.")) } - - if (!Version(version).isValid) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "version '"+version+"' is not valid version format.")) - - // Check that it is signed - for (w <- workloads) { - //if (w.deployment != "" && (w.deployment_signature == "" || w.torrent == "")) { halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "this workload definition does not appear to be signed.")) } - if (w.deployment != "" && w.deployment_signature == "") { halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "this workload definition does not appear to be signed.")) } - } - } - - // Build a list of db actions to verify that the referenced workloads exist - def validateMicroserviceIds: DBIO[Vector[Int]] = { - if (apiSpec.isEmpty) return DBIO.successful(Vector()) - val actions = ListBuffer[DBIO[Int]]() - for (m <- apiSpec) { - val microId = MicroservicesTQ.formId(m.org, m.specRef, m.version, m.arch) // need to wildcard version, because it is an osgi version range - actions += MicroservicesTQ.getMicroservice(microId).length.result - } - return DBIO.sequence(actions.toVector) // convert the list of actions to a DBIO sequence because that returns query values - } - - def formId(orgid: String) = WorkloadsTQ.formId(orgid, workloadUrl, version, arch) - - def toWorkloadRow(workload: String, orgid: String, owner: String) = WorkloadRow(workload, orgid, owner, label, description, public, workloadUrl, version, arch, downloadUrl.getOrElse(""), write(apiSpec), write(userInput), write(workloads), ApiTime.nowUTC) -} - -case class PatchWorkloadRequest(label: Option[String], description: Option[String], public: Option[Boolean], workloadUrl: Option[String], version: Option[String], arch: Option[String], downloadUrl: Option[String]) { - protected implicit val jsonFormats: Formats = DefaultFormats - - /** Returns a tuple of the db action to update parts of the workload, and the attribute name being updated. */ - def getDbUpdate(workload: String, orgid: String): (DBIO[_],String) = { - val lastUpdated = ApiTime.nowUTC - //todo: support updating more than 1 attribute - // find the 1st attribute that was specified in the body and create a db action to update it for this workload - label match { case Some(lab) => return ((for { d <- WorkloadsTQ.rows if d.workload === workload } yield (d.workload,d.label,d.lastUpdated)).update((workload, lab, lastUpdated)), "label"); case _ => ; } - description match { case Some(desc) => return ((for { d <- WorkloadsTQ.rows if d.workload === workload } yield (d.workload,d.description,d.lastUpdated)).update((workload, desc, lastUpdated)), "description"); case _ => ; } - public match { case Some(pub) => return ((for { d <- WorkloadsTQ.rows if d.workload === workload } yield (d.workload,d.public,d.lastUpdated)).update((workload, pub, lastUpdated)), "public"); case _ => ; } - workloadUrl match { case Some(url) => return ((for { d <- WorkloadsTQ.rows if d.workload === workload } yield (d.workload,d.workloadUrl,d.lastUpdated)).update((workload, url, lastUpdated)), "workloadUrl"); case _ => ; } - version match { case Some(ver) => return ((for { d <- WorkloadsTQ.rows if d.workload === workload } yield (d.workload,d.version,d.lastUpdated)).update((workload, ver, lastUpdated)), "version"); case _ => ; } - arch match { case Some(ar) => return ((for { d <- WorkloadsTQ.rows if d.workload === workload } yield (d.workload,d.arch,d.lastUpdated)).update((workload, ar, lastUpdated)), "arch"); case _ => ; } - downloadUrl match { case Some(url) => return ((for { d <- WorkloadsTQ.rows if d.workload === workload } yield (d.workload,d.downloadUrl,d.lastUpdated)).update((workload, url, lastUpdated)), "downloadUrl"); case _ => ; } - return (null, null) - } -} - - -/** Input format for PUT /orgs/{orgid}/workloads/{workload}/keys/ */ -case class PutWorkloadKeyRequest(key: String) { - def toWorkloadKey = WorkloadKey(key, ApiTime.nowUTC) - def toWorkloadKeyRow(workloadId: String, keyId: String) = WorkloadKeyRow(keyId, workloadId, key, ApiTime.nowUTC) - def validate(keyId: String) = { - //if (keyId != formId) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "the key id should be in the form keyOrgid_key")) - } -} - - - -/** Implementation for all of the /orgs/{orgid}/workloads routes */ -trait WorkloadRoutes extends ScalatraBase with FutureSupport with SwaggerSupport with AuthenticationSupport /*with FileUploadSupport*/ { - def db: Database // get access to the db object in ExchangeApiApp - def logger: Logger // get access to the logger object in ExchangeApiApp - protected implicit def jsonFormats: Formats - //configureMultipartHandling(MultipartConfig(maxFileSize = Some(3*1024*1024))) - - /* ====== GET /orgs/{orgid}/workloads ================================ */ - val getWorkloads = - (apiOperation[GetWorkloadsResponse]("getWorkloads") - summary("Returns all workloads") - description("""Returns all workload definitions in the exchange DB. Can be run by any user, node, or agbot.""") - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("id", DataType.String, Option[String]("Username of exchange user, or ID of the node or agbot. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("token", DataType.String, Option[String]("Password of exchange user, or token of the node or agbot. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("owner", DataType.String, Option[String]("Filter results to only include workloads with this owner (can include % for wildcard - the URL encoding for % is %25)"), paramType=ParamType.Query, required=false), - Parameter("public", DataType.String, Option[String]("Filter results to only include workloads with this public setting"), paramType=ParamType.Query, required=false), - Parameter("workloadUrl", DataType.String, Option[String]("Filter results to only include workloads with this workloadUrl (can include % for wildcard - the URL encoding for % is %25)"), paramType=ParamType.Query, required=false), - Parameter("version", DataType.String, Option[String]("Filter results to only include workloads with this version (can include % for wildcard - the URL encoding for % is %25)"), paramType=ParamType.Query, required=false), - Parameter("arch", DataType.String, Option[String]("Filter results to only include workloads with this arch (can include % for wildcard - the URL encoding for % is %25)"), paramType=ParamType.Query, required=false), - Parameter("specRef", DataType.String, Option[String]("Filter results to only include workloads that use this microservice specRef (can include % for wildcard - the URL encoding for % is %25)"), paramType=ParamType.Query, required=false) - ) - responseMessages(ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - - get("/orgs/:orgid/workloads", operation(getWorkloads)) ({ - val orgid = params("orgid") - val ident = credsAndLog().authenticate().authorizeTo(TWorkload(OrgAndId(orgid,"*").toString),Access.READ) - val resp = response - //var q = WorkloadsTQ.rows.subquery - var q = WorkloadsTQ.getAllWorkloads(orgid) - // If multiple filters are specified they are anded together by adding the next filter to the previous filter by using q.filter - params.get("owner").foreach(owner => { if (owner.contains("%")) q = q.filter(_.owner like owner) else q = q.filter(_.owner === owner) }) - params.get("public").foreach(public => { if (public.toLowerCase == "true") q = q.filter(_.public === true) else q = q.filter(_.public === false) }) - params.get("workloadUrl").foreach(workloadUrl => { if (workloadUrl.contains("%")) q = q.filter(_.workloadUrl like workloadUrl) else q = q.filter(_.workloadUrl === workloadUrl) }) - params.get("version").foreach(version => { if (version.contains("%")) q = q.filter(_.version like version) else q = q.filter(_.version === version) }) - params.get("arch").foreach(arch => { if (arch.contains("%")) q = q.filter(_.arch like arch) else q = q.filter(_.arch === arch) }) - - // We are cheating a little on this one because the whole apiSpec structure is serialized into a json string when put in the db, so it has a string value like - // [{"specRef":"https://bluehorizon.network/documentation/microservice/rtlsdr","version":"1.0.0","arch":"amd64"}]. But we can still match on the specRef. - params.get("specRef").foreach(specRef => { - val specRef2 = "%\"specRef\":\"" + specRef + "\"%" - q = q.filter(_.apiSpec like specRef2) - }) - - db.run(q.result).map({ list => - logger.debug("GET /orgs/"+orgid+"/workloads result size: "+list.size) - val workloads = new MutableHashMap[String,Workload] - if (list.nonEmpty) for (a <- list) if (ident.getOrg == a.orgid || a.public || ident.isSuperUser || ident.isMultiTenantAgbot) workloads.put(a.workload, a.toWorkload) - if (workloads.nonEmpty) resp.setStatus(HttpCode.OK) - else resp.setStatus(HttpCode.NOT_FOUND) - GetWorkloadsResponse(workloads.toMap, 0) - }) - }) - - /* ====== GET /orgs/{orgid}/workloads/{workload} ================================ */ - val getOneWorkload = - (apiOperation[GetWorkloadsResponse]("getOneWorkload") - summary("Returns a workload") - description("""Returns the workload with the specified id in the exchange DB. Can be run by a user, node, or agbot.""") - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("workload", DataType.String, Option[String]("Workload id."), paramType=ParamType.Path), - Parameter("id", DataType.String, Option[String]("Username of exchange user, or ID of the node or agbot. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("token", DataType.String, Option[String]("Password of exchange user, or token of the node or agbot. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("attribute", DataType.String, Option[String]("Which attribute value should be returned. Only 1 attribute can be specified. If not specified, the entire workload resource will be returned."), paramType=ParamType.Query, required=false) - ) - responseMessages(ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.BAD_INPUT,"bad input"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - - get("/orgs/:orgid/workloads/:workload", operation(getOneWorkload)) ({ - val orgid = params("orgid") - val bareWorkload = params("workload") // but do not have a hack/fix for the name - val workload = OrgAndId(orgid,bareWorkload).toString - credsAndLog().authenticate().authorizeTo(TWorkload(workload),Access.READ) - val resp = response - params.get("attribute") match { - case Some(attribute) => ; // Only returning 1 attr of the workload - val q = WorkloadsTQ.getAttribute(workload, attribute) // get the proper db query for this attribute - if (q == null) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "Workload attribute name '"+attribute+"' is not an attribute of the workload resource.")) - db.run(q.result).map({ list => - logger.trace("GET /orgs/"+orgid+"/workloads/"+bareWorkload+" attribute result: "+list.toString) - if (list.nonEmpty) { - resp.setStatus(HttpCode.OK) - GetWorkloadAttributeResponse(attribute, list.head.toString) - } else { - resp.setStatus(HttpCode.NOT_FOUND) - ApiResponse(ApiResponseType.NOT_FOUND, "not found") - } - }) - - case None => ; // Return the whole workload resource - db.run(WorkloadsTQ.getWorkload(workload).result).map({ list => - logger.debug("GET /orgs/"+orgid+"/workloads/"+bareWorkload+" result: "+list.toString) - val workloads = new MutableHashMap[String,Workload] - if (list.nonEmpty) for (a <- list) workloads.put(a.workload, a.toWorkload) - if (workloads.nonEmpty) resp.setStatus(HttpCode.OK) - else resp.setStatus(HttpCode.NOT_FOUND) - GetWorkloadsResponse(workloads.toMap, 0) - }) - } - }) - - // =========== POST /orgs/{orgid}/workloads =============================== - val postWorkloads = - (apiOperation[ApiResponse]("postWorkloads") - summary "Adds a workload" - description """Creates a workload resource. A workload resource contains the metadata that Horizon needs to deploy the docker images that implement this workload. Think of a workload as an edge application. The workload can require 1 or more microservices that Horizon should also deploy when deploying this workload. If public is set to true, the workload can be shared across organizations. This can only be called by a user. The **request body** structure: - -``` -// (remove all of the comments like this before using) -{ - "label": "Location for x86_64", // this will be displayed in the node registration UI - "description": "blah blah", - "public": true, // whether or not it can be viewed by other organizations - "workloadUrl": "https://bluehorizon.network/documentation/workload/location", // the unique identifier of this workload - "version": "1.0.0", - "arch": "amd64", - "downloadUrl": "", // reserved for future use, can be omitted - // The microservices used by this workload. (The microservices must exist before creating this workload.) - "apiSpec": [ - { - "specRef": "https://bluehorizon.network/documentation/microservice/gps", - "org": "myorg", - "version": "[1.0.0,INFINITY)", // an OSGI-formatted version range - "arch": "amd64" - } - ], - // Values the node owner will be prompted for and will be set as env vars to the container. - "userInput": [ - { - "name": "foo", - "label": "The Foo Value", - "type": "string", // or: "int", "float", "list of strings" - "defaultValue": "bar" - } - ], - // The docker images that will be deployed on edge nodes for this workload - "workloads": [ - { - "deployment": "{\"services\":{\"location\":{\"image\":\"summit.hovitos.engineering/x86/location:2.0.6\",\"environment\":[\"USE_NEW_STAGING_URL=false\"]}}}", - "deployment_signature": "EURzSkDyk66qE6esYUDkLWLzM=", // filled in by the Horizon signing process - "torrent": "{\"url\":\"https://images.bluehorizon.network/139e5b32f271e43698565ff0a37c525609f86178.json\",\"signature\":\"L6/iZxGXloE=\"}" // filled in by the Horizon signing process - } - ] -} -```""" - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("username", DataType.String, Option[String]("Username of exchange user. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), - Parameter("password", DataType.String, Option[String]("Password of the user. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("body", DataType[PostPutWorkloadRequest], - Option[String]("Workload object that needs to be updated in the exchange. See details in the Implementation Notes above."), - paramType = ParamType.Body) - ) - responseMessages(ResponseMessage(HttpCode.POST_OK,"created/updated"), ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.BAD_INPUT,"bad input"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - val postWorkloads2 = (apiOperation[PostPutWorkloadRequest]("postWorkloads2") summary("a") description("a")) // for some bizarre reason, the PostWorkloadRequest class has to be used in apiOperation() for it to be recognized in the body Parameter above - - post("/orgs/:orgid/workloads", operation(postWorkloads)) ({ - val orgid = params("orgid") - val ident = credsAndLog().authenticate().authorizeTo(TWorkload(OrgAndId(orgid,"").toString),Access.CREATE) - val workloadReq = try { parse(request.body).extract[PostPutWorkloadRequest] } - catch { case e: Exception => halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "Error parsing the input body json: "+e)) } - workloadReq.validate() - val workload = workloadReq.formId(orgid) - val owner = ident match { case IUser(creds) => creds.id; case _ => "" } - val resp = response - - val msIds = workloadReq.apiSpec.map( m => MicroservicesTQ.formId(m.org, m.specRef, "%", m.arch) ) // make a list of MS wildcarded searches for the required MSes - val action = if (workloadReq.apiSpec.isEmpty) DBIO.successful(Vector()) // no MSes to look for - else { - // The inner map() and reduceLeft() OR together all of the likes to give to filter() - MicroservicesTQ.rows.filter(m => { msIds.map(m.microservice like _).reduceLeft(_ || _) }).map(m => (m.orgid, m.specRef, m.version, m.arch)).result - } - - db.run(action.asTry.flatMap({ xs => - logger.debug("POST /orgs/"+orgid+"/workloads apiSpec validation: "+xs.toString) - xs match { - case Success(rows) => var invalidIndex = -1 - // rows is a sequence of some MicroserviceRow cols which is a superset of what we need. Go thru each apiSpec in the workload request and make - // sure there is an MS that matches the version range specified. If the apiSpec list is empty, this will fall thru and succeed. - breakable { for ( (apiSpec, index) <- workloadReq.apiSpec.zipWithIndex) { - breakable { - for ( (orgid,specRef,version,arch) <- rows ) { - //logger.debug("orgid: "+orgid+", specRef: "+specRef+", version: "+version+", arch: "+arch) - if (specRef == apiSpec.specRef && orgid == apiSpec.org && arch == apiSpec.arch && (Version(version) in VersionRange(apiSpec.version)) ) break // we satisfied this apiSpec requirement so move on to the next - } - invalidIndex = index // we finished the inner for loop but did not find an MS that satisfied the requirement - } // if we find an MS that satisfies the requirment, it breaks to this line - if (invalidIndex >= 0) break // an apiSpec requirement was not satisfied, so break out and return an error - } } - if (invalidIndex < 0) WorkloadsTQ.getNumOwned(owner).result.asTry - else DBIO.failed(new Throwable("the "+Nth(invalidIndex+1)+" referenced microservice (apiSpec) does not exist in the exchange")).asTry - case Failure(t) => DBIO.failed(new Throwable(t.getMessage)).asTry - } - }).flatMap({ xs => - logger.debug("POST /orgs/"+orgid+"/workloads num owned by "+owner+": "+xs) - xs match { - case Success(num) => val numOwned = num - val maxWorkloads = ExchConfig.getInt("api.limits.maxWorkloads") - if (maxWorkloads == 0 || numOwned <= maxWorkloads) { // we are not sure if this is a create or update, but if they are already over the limit, stop them anyway - workloadReq.toWorkloadRow(workload, orgid, owner).insert.asTry - } - else DBIO.failed(new Throwable("Access Denied: you are over the limit of "+maxWorkloads+ " workloads")).asTry - case Failure(t) => DBIO.failed(new Throwable(t.getMessage)).asTry - } - })).map({ xs => - logger.debug("POST /orgs/"+orgid+"/workloads result: "+xs.toString) - xs match { - case Success(_) => if (owner != "") AuthCache.workloads.putOwner(workload, owner) // currently only users are allowed to update workload resources, so owner should never be blank - AuthCache.workloads.putIsPublic(workload, workloadReq.public) - resp.setStatus(HttpCode.POST_OK) - ApiResponse(ApiResponseType.OK, "workload '"+workload+"' created") - case Failure(t) => if (t.getMessage.startsWith("Access Denied:")) { - resp.setStatus(HttpCode.ACCESS_DENIED) - ApiResponse(ApiResponseType.ACCESS_DENIED, "workload '" + workload + "' not created: " + t.getMessage) - } else if (t.getMessage.contains("duplicate key value violates unique constraint")) { - resp.setStatus(HttpCode.ALREADY_EXISTS) - ApiResponse(ApiResponseType.ALREADY_EXISTS, "workload '" + workload + "' already exists: " + t.getMessage) - } else { - resp.setStatus(HttpCode.BAD_INPUT) - ApiResponse(ApiResponseType.BAD_INPUT, "workload '"+workload+"' not created: "+t.getMessage) - } - } - }) - }) - - // =========== PUT /orgs/{orgid}/workloads/{workload} =============================== - val putWorkloads = - (apiOperation[ApiResponse]("putWorkloads") - summary "Updates a workload" - description """Does a full replace of an existing workload. This can only be called by the user that originally created it.""" - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("workload", DataType.String, Option[String]("Workload id."), paramType=ParamType.Path), - Parameter("username", DataType.String, Option[String]("Username of exchange user. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), - Parameter("password", DataType.String, Option[String]("Password of the user. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("body", DataType[PostPutWorkloadRequest], - Option[String]("Workload object that needs to be updated in the exchange. See details in the Implementation Notes above."), - paramType = ParamType.Body) - ) - responseMessages(ResponseMessage(HttpCode.POST_OK,"created/updated"), ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.BAD_INPUT,"bad input"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - val putWorkloads2 = (apiOperation[PostPutWorkloadRequest]("putWorkloads2") summary("a") description("a")) // for some bizarre reason, the PutWorkloadRequest class has to be used in apiOperation() for it to be recognized in the body Parameter above - - put("/orgs/:orgid/workloads/:workload", operation(putWorkloads)) ({ - val orgid = params("orgid") - val bareWorkload = params("workload") // but do not have a hack/fix for the name - val workload = OrgAndId(orgid,bareWorkload).toString - val ident = credsAndLog().authenticate().authorizeTo(TWorkload(workload),Access.WRITE) - val workloadReq = try { parse(request.body).extract[PostPutWorkloadRequest] } - catch { case e: Exception => halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "Error parsing the input body json: "+e)) } - workloadReq.validate() - val owner = ident match { case IUser(creds) => creds.id; case _ => "" } - val resp = response - - val msIds = workloadReq.apiSpec.map( m => MicroservicesTQ.formId(m.org, m.specRef, "%", m.arch) ) // make a list of MS wildcarded searches for the required MSes - val action = if (workloadReq.apiSpec.isEmpty) DBIO.successful(Vector()) // no MSes to look for - else { - // The inner map() and reduceLeft() OR together all of the likes to give to filter() - MicroservicesTQ.rows.filter(m => { msIds.map(m.microservice like _).reduceLeft(_ || _) }).map(m => (m.orgid, m.specRef, m.version, m.arch)).result - } - - db.run(action.asTry.flatMap({ xs => - logger.debug("POST /orgs/"+orgid+"/workloads apiSpec validation: "+xs.toString) - xs match { - case Success(rows) => var invalidIndex = -1 - // rows is a sequence of some MicroserviceRow cols which is a superset of what we need. Go thru each apiSpec in the workload request and make - // sure there is an MS that matches the version range specified. If the apiSpec list is empty, this will fall thru and succeed. - breakable { for ( (apiSpec, index) <- workloadReq.apiSpec.zipWithIndex) { - breakable { - for ( (orgid,specRef,version,arch) <- rows ) { - //logger.debug("orgid: "+orgid+", specRef: "+specRef+", version: "+version+", arch: "+arch) - if (specRef == apiSpec.specRef && orgid == apiSpec.org && arch == apiSpec.arch && (Version(version) in VersionRange(apiSpec.version)) ) break // we satisfied this apiSpec requirement so move on to the next - } - invalidIndex = index // we finished the inner for loop but did not find an MS that satisfied the requirement - } // if we find an MS that satisfies the requirment, it breaks to this line - if (invalidIndex >= 0) break // an apiSpec requirement was not satisfied, so break out and return an error - } } - if (invalidIndex < 0) workloadReq.toWorkloadRow(workload, orgid, owner).update.asTry - else DBIO.failed(new Throwable("the "+Nth(invalidIndex+1)+" referenced microservice (apiSpec) does not exist in the exchange")).asTry - case Failure(t) => DBIO.failed(new Throwable(t.getMessage)).asTry - } - })).map({ xs => - logger.debug("PUT /orgs/"+orgid+"/workloads/"+bareWorkload+" result: "+xs.toString) - xs match { - case Success(n) => try { - val numUpdated = n.toString.toInt // i think n is an AnyRef so we have to do this to get it to an int - if (numUpdated > 0) { - if (owner != "") AuthCache.workloads.putOwner(workload, owner) // currently only users are allowed to update workload resources, so owner should never be blank - AuthCache.workloads.putIsPublic(workload, workloadReq.public) - resp.setStatus(HttpCode.PUT_OK) - ApiResponse(ApiResponseType.OK, "workload updated") - } else { - resp.setStatus(HttpCode.NOT_FOUND) - ApiResponse(ApiResponseType.NOT_FOUND, "workload '"+workload+"' not found") - } - } catch { case e: Exception => resp.setStatus(HttpCode.INTERNAL_ERROR); ApiResponse(ApiResponseType.INTERNAL_ERROR, "workload '"+workload+"' not updated: "+e) } // the specific exception is NumberFormatException - case Failure(t) => resp.setStatus(HttpCode.BAD_INPUT) - ApiResponse(ApiResponseType.BAD_INPUT, "workload '"+workload+"' not updated: "+t.getMessage) - } - }) - }) - - // =========== PATCH /orgs/{orgid}/workloads/{workload} =============================== - val patchWorkloads = - (apiOperation[Map[String,String]]("patchWorkloads") - summary "Updates 1 attribute of a workload" - description """Updates one attribute of a workload in the exchange DB. This can only be called by the user that originally created this workload resource.""" - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("workload", DataType.String, Option[String]("Workload id."), paramType=ParamType.Path), - Parameter("username", DataType.String, Option[String]("Username of owning user. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), - Parameter("password", DataType.String, Option[String]("Password of the user. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("body", DataType[PatchWorkloadRequest], - Option[String]("Partial workload object that contains an attribute to be updated in this workload. See details in the Implementation Notes above."), - paramType = ParamType.Body) - ) - responseMessages(ResponseMessage(HttpCode.POST_OK,"created/updated"), ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.BAD_INPUT,"bad input"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - val patchWorkloads2 = (apiOperation[PatchWorkloadRequest]("patchWorkloads2") summary("a") description("a")) // for some bizarre reason, the PatchWorkloadRequest class has to be used in apiOperation() for it to be recognized in the body Parameter above - - patch("/orgs/:orgid/workloads/:workload", operation(patchWorkloads)) ({ - val orgid = params("orgid") - val bareWorkload = params("workload") // but do not have a hack/fix for the name - val workload = OrgAndId(orgid,bareWorkload).toString - credsAndLog().authenticate().authorizeTo(TWorkload(workload),Access.WRITE) - val workloadReq = try { parse(request.body).extract[PatchWorkloadRequest] } - catch { case e: Exception => halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "Error parsing the input body json: "+e)) } // the specific exception is MappingException - logger.trace("PATCH /orgs/"+orgid+"/workloads/"+bareWorkload+" input: "+workloadReq.toString) - val resp = response - val (action, attrName) = workloadReq.getDbUpdate(workload, orgid) - if (action == null) halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "no valid workload attribute specified")) - db.run(action.transactionally.asTry).map({ xs => - logger.debug("PATCH /orgs/"+orgid+"/workloads/"+bareWorkload+" result: "+xs.toString) - xs match { - case Success(v) => try { - val numUpdated = v.toString.toInt // v comes to us as type Any - if (numUpdated > 0) { // there were no db errors, but determine if it actually found it or not - if (attrName == "public") AuthCache.workloads.putIsPublic(workload, workloadReq.public.getOrElse(false)) - resp.setStatus(HttpCode.PUT_OK) - ApiResponse(ApiResponseType.OK, "attribute '"+attrName+"' of workload '"+workload+"' updated") - } else { - resp.setStatus(HttpCode.NOT_FOUND) - ApiResponse(ApiResponseType.NOT_FOUND, "workload '"+workload+"' not found") - } - } catch { case e: Exception => resp.setStatus(HttpCode.INTERNAL_ERROR); ApiResponse(ApiResponseType.INTERNAL_ERROR, "Unexpected result from update: "+e) } - case Failure(t) => resp.setStatus(HttpCode.INTERNAL_ERROR) - ApiResponse(ApiResponseType.INTERNAL_ERROR, "workload '"+workload+"' not updated: "+t.toString) - } - }) - }) - - // =========== DELETE /orgs/{orgid}/workloads/{workload} =============================== - val deleteWorkloads = - (apiOperation[ApiResponse]("deleteWorkloads") - summary "Deletes a workload" - description "Deletes a workload from the exchange DB. Can only be run by the owning user." - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("workload", DataType.String, Option[String]("Workload id."), paramType=ParamType.Path), - Parameter("username", DataType.String, Option[String]("Username of owning user. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), - Parameter("password", DataType.String, Option[String]("Password of the user. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false) - ) - responseMessages(ResponseMessage(HttpCode.DELETED,"deleted"), ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - - delete("/orgs/:orgid/workloads/:workload", operation(deleteWorkloads)) ({ - val orgid = params("orgid") - val bareWorkload = params("workload") // but do not have a hack/fix for the name - val workload = OrgAndId(orgid,bareWorkload).toString - credsAndLog().authenticate().authorizeTo(TWorkload(workload),Access.WRITE) - // remove does *not* throw an exception if the key does not exist - val resp = response - db.run(WorkloadsTQ.getWorkload(workload).delete.transactionally.asTry).map({ xs => - logger.debug("DELETE /orgs/"+orgid+"/workloads/"+bareWorkload+" result: "+xs.toString) - xs match { - case Success(v) => if (v > 0) { // there were no db errors, but determine if it actually found it or not - AuthCache.workloads.removeOwner(workload) - AuthCache.workloads.removeIsPublic(workload) - resp.setStatus(HttpCode.DELETED) - ApiResponse(ApiResponseType.OK, "workload deleted") - } else { - resp.setStatus(HttpCode.NOT_FOUND) - ApiResponse(ApiResponseType.NOT_FOUND, "workload '"+workload+"' not found") - } - case Failure(t) => resp.setStatus(HttpCode.INTERNAL_ERROR) - ApiResponse(ApiResponseType.INTERNAL_ERROR, "workload '"+workload+"' not deleted: "+t.toString) - } - }) - }) - - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - /* ====== GET /orgs/{orgid}/workloads/{workload}/keys ================================ */ - val getWorkloadKeys = - (apiOperation[List[String]]("getWorkloadKeys") - summary "Returns all keys/certs for this workload" - description """Returns all the signing public keys/certs for this workload. Can be run by any credentials able to view the workload.""" - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("workload", DataType.String, Option[String]("Workload id."), paramType=ParamType.Path), - Parameter("username", DataType.String, Option[String]("Username of owning user. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), - Parameter("password", DataType.String, Option[String]("Password of the user. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false) - ) - responseMessages(ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - - get("/orgs/:orgid/workloads/:workload/keys", operation(getWorkloadKeys)) ({ - val orgid = params("orgid") - val workload = params("workload") - val compositeId = OrgAndId(orgid,workload).toString - credsAndLog().authenticate().authorizeTo(TWorkload(compositeId),Access.READ) - val resp = response - db.run(WorkloadKeysTQ.getKeys(compositeId).result).map({ list => - logger.debug("GET /orgs/"+orgid+"/workloads/"+workload+"/keys result size: "+list.size) - //logger.trace("GET /orgs/"+orgid+"/workloads/"+id+"/keys result: "+list.toString) - if (list.nonEmpty) resp.setStatus(HttpCode.OK) - else resp.setStatus(HttpCode.NOT_FOUND) - list.map(_.keyId) - }) - }) - - /* ====== GET /orgs/{orgid}/workloads/{workload}/keys/{keyid} ================================ */ - val getOneWorkloadKey = - (apiOperation[String]("getOneWorkloadKey") - summary "Returns a key/cert for this workload" - description """Returns the signing public key/cert with the specified keyid for this workload. The raw content of the key/cert is returned, not json. Can be run by any credentials able to view the workload.""" - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("workload", DataType.String, Option[String]("Workload id."), paramType=ParamType.Path), - Parameter("keyid", DataType.String, Option[String]("ID of the key."), paramType = ParamType.Path), - Parameter("username", DataType.String, Option[String]("Username of owning user. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), - Parameter("password", DataType.String, Option[String]("Password of the user. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false) - ) - produces "text/plain" - responseMessages(ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - - get("/orgs/:orgid/workloads/:workload/keys/:keyid", operation(getOneWorkloadKey)) ({ - val orgid = params("orgid") - val workload = params("workload") - val compositeId = OrgAndId(orgid,workload).toString - val keyId = params("keyid") - credsAndLog().authenticate().authorizeTo(TWorkload(compositeId),Access.READ) - val resp = response - db.run(WorkloadKeysTQ.getKey(compositeId, keyId).result).map({ list => - logger.debug("GET /orgs/"+orgid+"/workloads/"+workload+"/keys/"+keyId+" result: "+list.size) - if (list.nonEmpty) { - // Return the raw key, not json - resp.setStatus(HttpCode.OK) - resp.setHeader("Content-Disposition", "attachment; filename="+keyId) - resp.setHeader("Content-Type", "text/plain") - resp.setHeader("Content-Length", list.head.key.length.toString) - list.head.key - } - else { - resp.setStatus(HttpCode.NOT_FOUND) - ApiResponse(ApiResponseType.NOT_FOUND, "key '"+keyId+"' not found") - } - }) - }) - - // =========== PUT /orgs/{orgid}/workloads/{workload}/keys/{keyid} =============================== - val putWorkloadKey = - (apiOperation[ApiResponse]("putWorkloadKey") - summary "Adds/updates a key/cert for the workload" - description """Adds a new signing public key/cert, or updates an existing key/cert, for this workload. This can only be run by the workload owning user. Note that the input body is just the bytes of the key/cert (not the typical json), so the 'Content-Type' header must be set to 'text/plain'.""" - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("workload", DataType.String, Option[String]("Workload id."), paramType=ParamType.Path), - Parameter("keyid", DataType.String, Option[String]("ID of the key."), paramType = ParamType.Path), - Parameter("username", DataType.String, Option[String]("Username of owning user. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), - Parameter("password", DataType.String, Option[String]("Password of the user. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false), - Parameter("body", DataType[String], - Option[String]("Key object that needs to be added to, or updated in, the exchange. See details in the Implementation Notes above."), - paramType = ParamType.Body) - ) - responseMessages(ResponseMessage(HttpCode.POST_OK,"created/updated"), ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.BAD_INPUT,"bad input"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - val putWorkloadKey2 = (apiOperation[String]("putKey2") summary("a") description("a")) // for some bizarre reason, the PutKeysRequest class has to be used in apiOperation() for it to be recognized in the body Parameter above - - put("/orgs/:orgid/workloads/:workload/keys/:keyid", operation(putWorkloadKey)) ({ - val orgid = params("orgid") - val workload = params("workload") - val compositeId = OrgAndId(orgid,workload).toString - val keyId = params("keyid") - credsAndLog().authenticate().authorizeTo(TWorkload(compositeId),Access.WRITE) - val keyReq = PutWorkloadKeyRequest(request.body) - //val keyReq = try { parse(request.body).extract[PutWorkloadKeyRequest] } - //catch { case e: Exception => halt(HttpCode.BAD_INPUT, ApiResponse(ApiResponseType.BAD_INPUT, "Error parsing the input body json: "+e)) } // the specific exception is MappingException - keyReq.validate(keyId) - val resp = response - db.run(keyReq.toWorkloadKeyRow(compositeId, keyId).upsert.asTry).map({ xs => - logger.debug("PUT /orgs/"+orgid+"/workloads/"+workload+"/keys/"+keyId+" result: "+xs.toString) - xs match { - case Success(_) => resp.setStatus(HttpCode.PUT_OK) - ApiResponse(ApiResponseType.OK, "key added or updated") - case Failure(t) => if (t.getMessage.startsWith("Access Denied:")) { - resp.setStatus(HttpCode.ACCESS_DENIED) - ApiResponse(ApiResponseType.ACCESS_DENIED, "key '"+keyId+"' for workload '"+compositeId+"' not inserted or updated: "+t.getMessage) - } else { - resp.setStatus(HttpCode.BAD_INPUT) - ApiResponse(ApiResponseType.BAD_INPUT, "key '"+keyId+"' for workload '"+compositeId+"' not inserted or updated: "+t.getMessage) - } - } - }) - }) - - // =========== DELETE /orgs/{orgid}/workloads/{workload}/keys =============================== - val deleteWorkloadAllKey = - (apiOperation[ApiResponse]("deleteWorkloadAllKey") - summary "Deletes all keys of a workload" - description "Deletes all of the current keys/certs for this workload. This can only be run by the workload owning user." - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("workload", DataType.String, Option[String]("Workload id."), paramType=ParamType.Path), - Parameter("username", DataType.String, Option[String]("Username of owning user. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), - Parameter("password", DataType.String, Option[String]("Password of the user. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false) - ) - responseMessages(ResponseMessage(HttpCode.DELETED,"deleted"), ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - - delete("/orgs/:orgid/workloads/:workload/keys", operation(deleteWorkloadAllKey)) ({ - val orgid = params("orgid") - val workload = params("workload") - val compositeId = OrgAndId(orgid,workload).toString - credsAndLog().authenticate().authorizeTo(TWorkload(compositeId),Access.WRITE) - val resp = response - db.run(WorkloadKeysTQ.getKeys(compositeId).delete.asTry).map({ xs => - logger.debug("DELETE /workloads/"+workload+"/keys result: "+xs.toString) - xs match { - case Success(v) => if (v > 0) { // there were no db errors, but determine if it actually found it or not - resp.setStatus(HttpCode.DELETED) - ApiResponse(ApiResponseType.OK, "workload keys deleted") - } else { - resp.setStatus(HttpCode.NOT_FOUND) - ApiResponse(ApiResponseType.NOT_FOUND, "no keys for workload '"+compositeId+"' found") - } - case Failure(t) => resp.setStatus(HttpCode.INTERNAL_ERROR) - ApiResponse(ApiResponseType.INTERNAL_ERROR, "keys for workload '"+compositeId+"' not deleted: "+t.toString) - } - }) - }) - - // =========== DELETE /orgs/{orgid}/workloads/{workload}/keys/{keyid} =============================== - val deleteWorkloadKey = - (apiOperation[ApiResponse]("deleteWorkloadKey") - summary "Deletes a key of a workload" - description "Deletes a key/cert for this workload. This can only be run by the workload owning user." - parameters( - Parameter("orgid", DataType.String, Option[String]("Organization id."), paramType=ParamType.Path), - Parameter("workload", DataType.String, Option[String]("Workload id."), paramType=ParamType.Path), - Parameter("keyid", DataType.String, Option[String]("ID of the key."), paramType = ParamType.Path), - Parameter("username", DataType.String, Option[String]("Username of owning user. This parameter can also be passed in the HTTP Header."), paramType = ParamType.Query, required=false), - Parameter("password", DataType.String, Option[String]("Password of the user. This parameter can also be passed in the HTTP Header."), paramType=ParamType.Query, required=false) - ) - responseMessages(ResponseMessage(HttpCode.DELETED,"deleted"), ResponseMessage(HttpCode.BADCREDS,"invalid credentials"), ResponseMessage(HttpCode.ACCESS_DENIED,"access denied"), ResponseMessage(HttpCode.NOT_FOUND,"not found")) - ) - - delete("/orgs/:orgid/workloads/:workload/keys/:keyid", operation(deleteWorkloadKey)) ({ - val orgid = params("orgid") - val workload = params("workload") - val compositeId = OrgAndId(orgid,workload).toString - val keyId = params("keyid") - credsAndLog().authenticate().authorizeTo(TWorkload(compositeId),Access.WRITE) - val resp = response - db.run(WorkloadKeysTQ.getKey(compositeId,keyId).delete.asTry).map({ xs => - logger.debug("DELETE /workloads/"+workload+"/keys/"+keyId+" result: "+xs.toString) - xs match { - case Success(v) => if (v > 0) { // there were no db errors, but determine if it actually found it or not - resp.setStatus(HttpCode.DELETED) - ApiResponse(ApiResponseType.OK, "workload key deleted") - } else { - resp.setStatus(HttpCode.NOT_FOUND) - ApiResponse(ApiResponseType.NOT_FOUND, "key '"+keyId+"' for workload '"+compositeId+"' not found") - } - case Failure(t) => resp.setStatus(HttpCode.INTERNAL_ERROR) - ApiResponse(ApiResponseType.INTERNAL_ERROR, "key '"+keyId+"' for workload '"+compositeId+"' not deleted: "+t.toString) - } - }) - }) - -} \ No newline at end of file diff --git a/src/main/scala/com/horizon/exchangeapi/tables/Agbots.scala b/src/main/scala/com/horizon/exchangeapi/tables/Agbots.scala index 4b141afd..0213515a 100644 --- a/src/main/scala/com/horizon/exchangeapi/tables/Agbots.scala +++ b/src/main/scala/com/horizon/exchangeapi/tables/Agbots.scala @@ -115,25 +115,22 @@ case class AgbotPattern(patternOrgid: String, pattern: String, nodeOrgid: String case class AAWorkload(orgid: String, pattern: String, url: String) case class AAService(orgid: String, pattern: String, url: String) -case class AgbotAgreementRow(agrId: String, agbotId: String, workloadOrgid: String, workloadPattern: String, workloadUrl: String, serviceOrgid: String, servicePattern: String, serviceUrl: String, state: String, lastUpdated: String, dataLastReceived: String) { - def toAgbotAgreement = AgbotAgreement(AAWorkload(workloadOrgid, workloadPattern, workloadUrl), AAService(serviceOrgid, servicePattern, serviceUrl), state, lastUpdated, dataLastReceived) +case class AgbotAgreementRow(agrId: String, agbotId: String, serviceOrgid: String, servicePattern: String, serviceUrl: String, state: String, lastUpdated: String, dataLastReceived: String) { + def toAgbotAgreement = AgbotAgreement(AAService(serviceOrgid, servicePattern, serviceUrl), state, lastUpdated, dataLastReceived) def upsert: DBIO[_] = AgbotAgreementsTQ.rows.insertOrUpdate(this) } class AgbotAgreements(tag: Tag) extends Table[AgbotAgreementRow](tag, "agbotagreements") { - def agrId = column[String]("agrid", O.PrimaryKey) // ethereum agreeement ids are unique + def agrId = column[String]("agrid", O.PrimaryKey) // agreeement ids are unique def agbotId = column[String]("agbotid") - def workloadOrgid = column[String]("workloadorgid") - def workloadPattern = column[String]("workloadpattern") - def workloadUrl = column[String]("workloadurl") def serviceOrgid = column[String]("serviceorgid") def servicePattern = column[String]("servicepattern") def serviceUrl = column[String]("serviceurl") def state = column[String]("state") def lastUpdated = column[String]("lastUpdated") def dataLastReceived = column[String]("dataLastReceived") - def * = (agrId, agbotId, workloadOrgid, workloadPattern, workloadUrl, serviceOrgid, servicePattern, serviceUrl, state, lastUpdated, dataLastReceived) <> (AgbotAgreementRow.tupled, AgbotAgreementRow.unapply) + def * = (agrId, agbotId, serviceOrgid, servicePattern, serviceUrl, state, lastUpdated, dataLastReceived) <> (AgbotAgreementRow.tupled, AgbotAgreementRow.unapply) def agbot = foreignKey("agbot_fk", agbotId, AgbotsTQ.rows)(_.id, onUpdate=ForeignKeyAction.Cascade, onDelete=ForeignKeyAction.Cascade) } @@ -146,7 +143,7 @@ object AgbotAgreementsTQ { def getAgreementsWithState = rows.filter(_.state =!= "") } -case class AgbotAgreement(workload: AAWorkload, service: AAService, state: String, lastUpdated: String, dataLastReceived: String) +case class AgbotAgreement(service: AAService, state: String, lastUpdated: String, dataLastReceived: String) /** The agbotmsgs table holds the msgs sent to agbots by nodes */ diff --git a/src/main/scala/com/horizon/exchangeapi/tables/Blockchains.scala b/src/main/scala/com/horizon/exchangeapi/tables/Blockchains.scala deleted file mode 100644 index e90b8cd4..00000000 --- a/src/main/scala/com/horizon/exchangeapi/tables/Blockchains.scala +++ /dev/null @@ -1,141 +0,0 @@ -package com.horizon.exchangeapi.tables - -import slick.jdbc.PostgresProfile.api._ - - -/** Contains the object representations of the DB tables related to bctypes. */ - -case class BctypeRow(bctype: String, orgid: String, description: String, definedBy: String, details: String, lastUpdated: String) { - // protected implicit val jsonFormats: Formats = DefaultFormats - - def toBctype: Bctype = { - // val ci = if (containerInfo != "") read[Map[String,String]](containerInfo) else Map[String,String]() - new Bctype(description, definedBy, details, lastUpdated) - } - - def upsert: DBIO[_] = BctypesTQ.rows.insertOrUpdate(this) -} - -/** Mapping of the bctypes db table to a scala class */ -class Bctypes(tag: Tag) extends Table[BctypeRow](tag, "bctypes") { - def bctype = column[String]("bctype", O.PrimaryKey) // the content of this is orgid/workload - def orgid = column[String]("orgid") - def description = column[String]("description") - def definedBy = column[String]("definedby") - def details = column[String]("details") - def lastUpdated = column[String]("lastupdated") - // this describes what you get back when you return rows from a query - def * = (bctype, orgid, description, definedBy, details, lastUpdated) <> (BctypeRow.tupled, BctypeRow.unapply) - def user = foreignKey("user_fk", definedBy, UsersTQ.rows)(_.username, onUpdate=ForeignKeyAction.Cascade, onDelete=ForeignKeyAction.Cascade) - def orgidKey = foreignKey("orgid_fk", orgid, OrgsTQ.rows)(_.orgid, onUpdate=ForeignKeyAction.Cascade, onDelete=ForeignKeyAction.Cascade) -} - -// Instance to access the bctypes table -object BctypesTQ { - val rows = TableQuery[Bctypes] - - def getAllBctypes(orgid: String) = rows.filter(_.orgid === orgid) - def getBctype(bctype: String) = rows.filter(_.bctype === bctype) - def getDescription(bctype: String) = rows.filter(_.bctype === bctype).map(_.description) - def getOwner(bctype: String) = rows.filter(_.bctype === bctype).map(_.definedBy) - def getNumOwned(owner: String) = rows.filter(_.definedBy === owner).length - def getDetails(bctype: String) = rows.filter(_.bctype === bctype).map(_.details) - def getLastUpdated(bctype: String) = rows.filter(_.bctype === bctype).map(_.lastUpdated) - - /** Returns a query for the specified bctype attribute value. Returns null if an invalid attribute name is given. */ - def getAttribute(bctype: String, attrName: String): Query[_,_,Seq] = { - val filter = rows.filter(_.bctype === bctype) - // According to 1 post by a slick developer, there is not yet a way to do this properly dynamically - return attrName match { - case "description" => filter.map(_.description) - case "definedBy" => filter.map(_.definedBy) - case "details" => filter.map(_.details) - case "lastUpdated" => filter.map(_.lastUpdated) - case _ => null - } - } - - /** Returns the actions to delete the bctype and the blockchains that reference it */ - def getDeleteActions(bctype: String): DBIO[_] = getBctype(bctype).delete // with the foreign keys set up correctly and onDelete=cascade, the db will automatically delete these associated blockchain rows -} - -// This is the bctype table minus the key - used as the data structure to return to the REST clients -class Bctype(var description: String, var definedBy: String, var details: String, var lastUpdated: String) { - def copy = new Bctype(description, definedBy, details, lastUpdated) -} - -/** One instance of a blockchain. From a rest api perspective, this is a sub-resource of bctype. */ -case class BlockchainRow(name: String, bctype: String, orgid: String, description: String, definedBy: String, public: Boolean, details: String, lastUpdated: String) { - def toBlockchain: Blockchain = { - Blockchain(description, definedBy, public, details, lastUpdated) - } - - def upsert: DBIO[_] = BlockchainsTQ.rows.insertOrUpdate(this) //TODO: this might work now, the fix might be in 3.2 or 3.2.1 which came out in 7/2017. This currently does not work due to this bug: https://github.com/slick/slick/issues/966 - def update: DBIO[_] = BlockchainsTQ.getBlockchain(bctype,name).update(this) - def insert: DBIO[_] = (BlockchainsTQ.rows += this) -} - -class Blockchains(tag: Tag) extends Table[BlockchainRow](tag, "blockchains") { - // def name = column[String]("name", O.PrimaryKey) // name is not necessarily unique across all BC types, so need the type as a 2nd key - def name = column[String]("name") // name is not necessarily unique across all BC types, so need the type as a 2nd key - def bctype = column[String]("bctype") // another key - see below - def orgid = column[String]("orgid") - def description = column[String]("description") - def definedBy = column[String]("definedby") - def public = column[Boolean]("public") - def details = column[String]("details") - def lastUpdated = column[String]("lastupdated") - // this describes what you get back when you return rows from a query - def * = (name, bctype, orgid, description, definedBy, public, details, lastUpdated) <> (BlockchainRow.tupled, BlockchainRow.unapply) - def primKey = primaryKey("pk_bc", (name, bctype)) - // def idx = index("idx_bc", (name, bctype), unique = true) - def bctypekey = foreignKey("bctype_fk", bctype, BctypesTQ.rows)(_.bctype, onUpdate=ForeignKeyAction.Cascade, onDelete=ForeignKeyAction.Cascade) - def user = foreignKey("user_fk", definedBy, UsersTQ.rows)(_.username, onUpdate=ForeignKeyAction.Cascade, onDelete=ForeignKeyAction.Cascade) - def orgidKey = foreignKey("orgid_fk", orgid, OrgsTQ.rows)(_.orgid, onUpdate=ForeignKeyAction.Cascade, onDelete=ForeignKeyAction.Cascade) -} - -object BlockchainsTQ { - val rows = TableQuery[Blockchains] - - def getAllBlockchains(orgid: String) = rows.filter(_.orgid === orgid) - def getBlockchains(bctype: String) = rows.filter(_.bctype === bctype) - def getBlockchain(bctype: String, name: String) = rows.filter( r => {r.bctype === bctype && r.name === name} ) - def getOwner(bctype: String, name: String) = rows.filter( r => {r.bctype === bctype && r.name === name} ).map(_.definedBy) - - /** The id is the name and bctype concatenated together */ - def getOwner2(id: String) = { - val (bctype, name) = id.split("""\|""", 2) match { - case Array(s) => (s, "") - case Array(s1, s2) => (s1, s2) - case _ => ("", "") - } - rows.filter( r => {r.bctype === bctype && r.name === name} ).map(_.definedBy) - } - - /** The id is the name and bctype concatenated together */ - def getPublic2(id: String) = { - val (bctype, name) = id.split("""\|""", 2) match { - case Array(s) => (s, "") - case Array(s1, s2) => (s1, s2) - case _ => ("", "") - } - rows.filter( r => {r.bctype === bctype && r.name === name} ).map(_.public) - } - - def getNumOwned(owner: String) = rows.filter(_.definedBy === owner).length - - /** Returns a query for the specified blockchain attribute value. Returns null if an invalid attribute name is given. */ - def getAttribute(bctype: String, name: String, attrName: String): Query[_,_,Seq] = { - val filter = rows.filter( r => {r.bctype === bctype && r.name === name} ) - // According to 1 post by a slick developer, there is not yet a way to do this properly dynamically - return attrName match { - case "description" => filter.map(_.description) - case "definedBy" => filter.map(_.definedBy) - case "details" => filter.map(_.details) - case "lastUpdated" => filter.map(_.lastUpdated) - case _ => null - } - } -} - -case class Blockchain(description: String, definedBy: String, public: Boolean, details: String, lastUpdated: String) diff --git a/src/main/scala/com/horizon/exchangeapi/tables/Microservices.scala b/src/main/scala/com/horizon/exchangeapi/tables/Microservices.scala deleted file mode 100644 index 2674a6d2..00000000 --- a/src/main/scala/com/horizon/exchangeapi/tables/Microservices.scala +++ /dev/null @@ -1,139 +0,0 @@ -package com.horizon.exchangeapi.tables - -import com.horizon.exchangeapi.OrgAndId -import org.json4s._ -import org.json4s.jackson.Serialization.read -import slick.jdbc.PostgresProfile.api._ - - -/** Contains the object representations of the DB tables related to microservices. */ -case class MDockerImages(deployment: String, deployment_signature: String, torrent: String) - -case class MicroserviceRow(microservice: String, orgid: String, owner: String, label: String, description: String, public: Boolean, specRef: String, version: String, arch: String, sharable: String, downloadUrl: String, matchHardware: String, userInput: String, workloads: String, lastUpdated: String) { - protected implicit val jsonFormats: Formats = DefaultFormats - - def toMicroservice: Microservice = { - val mh = if (matchHardware != "") read[Map[String,String]](matchHardware) else Map[String,String]() - val input = if (userInput != "") read[List[Map[String,String]]](userInput) else List[Map[String,String]]() - val wrk = if (workloads != "") read[List[MDockerImages]](workloads) else List[MDockerImages]() - new Microservice(owner, label, description, public, specRef, version, arch, sharable, downloadUrl, mh, input, wrk, lastUpdated) - } - - // update returns a DB action to update this row - def update: DBIO[_] = (for { m <- MicroservicesTQ.rows if m.microservice === microservice } yield m).update(this) - - // insert returns a DB action to insert this row - def insert: DBIO[_] = MicroservicesTQ.rows += this -} - -//case class MicroIdentifiers(orgid: Rep[String], specRef: Rep[String], version: Rep[String], arch: Rep[String]) - -/** Mapping of the microservices db table to a scala class */ -class Microservices(tag: Tag) extends Table[MicroserviceRow](tag, "microservices") { - def microservice = column[String]("microservice", O.PrimaryKey) // the content of this is orgid/microservice - def orgid = column[String]("orgid") - def owner = column[String]("owner") - def label = column[String]("label") - def description = column[String]("description") - def public = column[Boolean]("public") - def specRef = column[String]("specref") - def version = column[String]("version") - def arch = column[String]("arch") - def sharable = column[String]("sharable") - def downloadUrl = column[String]("downloadurl") - def matchHardware = column[String]("matchhardware") - def userInput = column[String]("userinput") - def workloads = column[String]("workloads") - def lastUpdated = column[String]("lastupdated") - // this describes what you get back when you return rows from a query - def * = (microservice, orgid, owner, label, description, public, specRef, version, arch, sharable, downloadUrl, matchHardware, userInput, workloads, lastUpdated) <> (MicroserviceRow.tupled, MicroserviceRow.unapply) - def user = foreignKey("user_fk", owner, UsersTQ.rows)(_.username, onUpdate=ForeignKeyAction.Cascade, onDelete=ForeignKeyAction.Cascade) - def orgidKey = foreignKey("orgid_fk", orgid, OrgsTQ.rows)(_.orgid, onUpdate=ForeignKeyAction.Cascade, onDelete=ForeignKeyAction.Cascade) -} - -// Instance to access the microservices table -object MicroservicesTQ { - val rows = TableQuery[Microservices] - - def formId(orgid: String, specRef: String, version: String, arch: String): String = { - // Remove the https:// from the beginning of specRef and replace troublesome chars with a dash. It has already been checked as a valid URL in validate(). - val specRef2 = """^[A-Za-z0-9+.-]*?://""".r replaceFirstIn (specRef, "") - val specRef3 = """[$!*,;/?@&~=%]""".r replaceAllIn (specRef2, "-") // I think possible chars in valid urls are: $_.+!*,;/?:@&~=%- - return OrgAndId(orgid, specRef3 + "_" + version + "_" + arch).toString - } - - def getAllMicroservices(orgid: String) = rows.filter(_.orgid === orgid) - def getMicroservice(microservice: String) = if (microservice.contains("%")) rows.filter(_.microservice like microservice) else rows.filter(_.microservice === microservice) - def getOwner(microservice: String) = rows.filter(_.microservice === microservice).map(_.owner) - def getNumOwned(owner: String) = rows.filter(_.owner === owner).length - def getLabel(microservice: String) = rows.filter(_.microservice === microservice).map(_.label) - def getDescription(microservice: String) = rows.filter(_.microservice === microservice).map(_.description) - def getPublic(microservice: String) = rows.filter(_.microservice === microservice).map(_.public) - def getSpecRef(microservice: String) = rows.filter(_.microservice === microservice).map(_.specRef) - def getVersion(microservice: String) = rows.filter(_.microservice === microservice).map(_.version) - def getArch(microservice: String) = rows.filter(_.microservice === microservice).map(_.arch) - def getSharable(microservice: String) = rows.filter(_.microservice === microservice).map(_.sharable) - def getDownloadUrl(microservice: String) = rows.filter(_.microservice === microservice).map(_.downloadUrl) - def getMatchHardware(microservice: String) = rows.filter(_.microservice === microservice).map(_.matchHardware) - def getUserInput(microservice: String) = rows.filter(_.microservice === microservice).map(_.userInput) - def getWorkloads(microservice: String) = rows.filter(_.microservice === microservice).map(_.workloads) - def getLastUpdated(microservice: String) = rows.filter(_.microservice === microservice).map(_.lastUpdated) - - /** Returns a query for the specified microservice attribute value. Returns null if an invalid attribute name is given. */ - def getAttribute(microservice: String, attrName: String): Query[_,_,Seq] = { - val filter = rows.filter(_.microservice === microservice) - // According to 1 post by a slick developer, there is not yet a way to do this properly dynamically - return attrName match { - case "owner" => filter.map(_.owner) - case "label" => filter.map(_.label) - case "description" => filter.map(_.description) - case "public" => filter.map(_.public) - case "specRef" => filter.map(_.specRef) - case "version" => filter.map(_.version) - case "arch" => filter.map(_.arch) - case "sharable" => filter.map(_.sharable) - case "downloadUrl" => filter.map(_.downloadUrl) - case "matchHardware" => filter.map(_.matchHardware) - case "userInput" => filter.map(_.userInput) - case "workloads" => filter.map(_.workloads) - case "lastUpdated" => filter.map(_.lastUpdated) - case _ => null - } - } - - /** Returns the actions to delete the microservice and the blockchains that reference it */ - def getDeleteActions(microservice: String): DBIO[_] = getMicroservice(microservice).delete // with the foreign keys set up correctly and onDelete=cascade, the db will automatically delete these associated blockchain rows -} - -// This is the microservice table minus the key - used as the data structure to return to the REST clients -class Microservice(var owner: String, var label: String, var description: String, var public: Boolean, var specRef: String, var version: String, var arch: String, var sharable: String, var downloadUrl: String, var matchHardware: Map[String,String], var userInput: List[Map[String,String]], var workloads: List[MDockerImages], var lastUpdated: String) { - // If we end up needing this, we might have to do deep copies of the variables that are actually structures - //def copy = new Microservice(owner, label, description, specRef, version, arch, sharable, downloadUrl, matchHardware, userInput, workloads, lastUpdated) -} - - -// Key is a sub-resource of microservice -case class MicroserviceKeyRow(keyId: String, microserviceId: String, key: String, lastUpdated: String) { - def toMicroserviceKey = MicroserviceKey(key, lastUpdated) - - def upsert: DBIO[_] = MicroserviceKeysTQ.rows.insertOrUpdate(this) -} - -class MicroserviceKeys(tag: Tag) extends Table[MicroserviceKeyRow](tag, "microservicekeys") { - def keyId = column[String]("keyid") // key - the key name - def microserviceId = column[String]("microserviceid") // additional key - the composite orgid/microserviceid - def key = column[String]("key") // the actual key content - def lastUpdated = column[String]("lastupdated") - def * = (keyId, microserviceId, key, lastUpdated) <> (MicroserviceKeyRow.tupled, MicroserviceKeyRow.unapply) - def primKey = primaryKey("pk_msk", (keyId, microserviceId)) - def microservice = foreignKey("microservice_fk", microserviceId, MicroservicesTQ.rows)(_.microservice, onUpdate=ForeignKeyAction.Cascade, onDelete=ForeignKeyAction.Cascade) -} - -object MicroserviceKeysTQ { - val rows = TableQuery[MicroserviceKeys] - - def getKeys(microserviceId: String) = rows.filter(_.microserviceId === microserviceId) - def getKey(microserviceId: String, keyId: String) = rows.filter( r => {r.microserviceId === microserviceId && r.keyId === keyId} ) -} - -case class MicroserviceKey(key: String, lastUpdated: String) diff --git a/src/main/scala/com/horizon/exchangeapi/tables/Nodes.scala b/src/main/scala/com/horizon/exchangeapi/tables/Nodes.scala index cf3179a2..a8292563 100644 --- a/src/main/scala/com/horizon/exchangeapi/tables/Nodes.scala +++ b/src/main/scala/com/horizon/exchangeapi/tables/Nodes.scala @@ -4,7 +4,7 @@ import com.horizon.exchangeapi._ import org.json4s._ import org.json4s.jackson.Serialization.read import slick.jdbc.PostgresProfile.api._ -import scala.collection.mutable.{ListBuffer, HashMap => MutableHashMap} //renaming this so i do not have to qualify every use of a immutable collection +import scala.collection.mutable.{HashMap => MutableHashMap} //renaming this so i do not have to qualify every use of a immutable collection /** We define this trait because services in the DB and in the search criteria need the same methods, but have slightly different constructor args */ trait RegServiceTrait { @@ -15,14 +15,14 @@ trait RegServiceTrait { def validate: Option[String] = { for (p <- properties) { p.validate match { - case Some(msg) => return Option[String](url+": "+msg) // prepend the url so they know which microservice was bad + case Some(msg) => return Option[String](url+": "+msg) // prepend the url so they know which service was bad case None => ; // continue checking } } return None // this means it is valid } - /** Returns true if this micro (the search) matches that micro (an entry in the db) + /** Returns true if this service (the search) matches that service (an entry in the db) * Rules for comparison: * - if both parties do not have the same property names, it is as if wildcard was specified */ @@ -39,7 +39,7 @@ trait RegServiceTrait { } } -/** 1 microservice in the search criteria */ +/** 1 service in the search criteria */ case class RegServiceSearch(url: String, properties: List[Prop]) extends RegServiceTrait /** Contains the object representations of the DB tables related to nodes. */ @@ -52,7 +52,7 @@ case class NodeRow(id: String, orgid: String, token: String, name: String, owner val tok = if (superUser) token else StrConstants.hiddenPw val swv = if (softwareVersions != "") read[Map[String,String]](softwareVersions) else Map[String,String]() val rsvc = if (regServices != "") read[List[RegService]](regServices) else List[RegService]() - new Node(tok, name, owner, pattern, List[RegMicroservice](), rsvc, msgEndPoint, swv, lastHeartbeat, publicKey) + new Node(tok, name, owner, pattern, rsvc, msgEndPoint, swv, lastHeartbeat, publicKey) } def putInHashMap(superUser: Boolean, nodes: MutableHashMap[String,Node]): Unit = { @@ -79,7 +79,7 @@ case class NodeRow(id: String, orgid: String, token: String, name: String, owner /** Mapping of the nodes db table to a scala class */ class Nodes(tag: Tag) extends Table[NodeRow](tag, "nodes") { - def id = column[String]("id", O.PrimaryKey) + def id = column[String]("id", O.PrimaryKey) // in the form org/nodeid def orgid = column[String]("orgid") def token = column[String]("token") def name = column[String]("name") @@ -132,54 +132,35 @@ object NodesTQ { } } - /** Separate the join of the nodes, microservices, properties, and swversions tables into their respective scala classes (collapsing duplicates) and return a hash containing it all. - * Note: this can also be used when querying node rows that have services (not microservices), because the services are faithfully preserved in the Node object. */ - def parseJoin(superUser: Boolean, list: Seq[(NodeRow, Option[RegMicroserviceRow], Option[PropRow])] ): Map[String,Node] = { + /** Separate the join of the nodes and properties tables into their respective scala classes (collapsing duplicates) and return a hash containing it all. + * Note: this can also be used when querying node rows that have services, because the services are faithfully preserved in the Node object. */ + def parseJoin(superUser: Boolean, list: Seq[NodeRow] ): Map[String,Node] = { // Separate the partially duplicate join rows into maps that only keep unique values val nodes = new MutableHashMap[String,Node] // the key is node id - val micros = new MutableHashMap[String,MutableHashMap[String,RegMicroservice]] // 1st key is node id, 2nd key is micro id - val props = new MutableHashMap[String,MutableHashMap[String,Prop]] // 1st key is micro id, 2nd key is prop id - for ((d, mOption, pOption) <- list) { + for (d <- list) { d.putInHashMap(superUser, nodes) - if (mOption.isDefined) mOption.get.putInHashMap(micros) - if (pOption.isDefined) pOption.get.putInHashMap(props) } - // Now fill in the nodes map, turning the maps we created above for micros, props into lists - for ((nodeId, d) <- nodes) { - if (micros.get(nodeId).isDefined) { - var microList = ListBuffer[RegMicroservice]() - for ((msId, m) <- micros(nodeId)) { - val propList = if (props.get(msId).isDefined) props(msId).values.toList else List[Prop]() - microList += RegMicroservice(m.url, m.numAgreements, m.policy, propList) - } - d.registeredMicroservices = microList.toList // replace the empty micro list we put in there initially - } - } nodes.toMap } } // 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 pattern: String, var registeredMicroservices: List[RegMicroservice], var registeredServices: List[RegService], var msgEndPoint: String, var softwareVersions: Map[String,String], var lastHeartbeat: String, var publicKey: String) { - def copy = new Node(token, name, owner, pattern, registeredMicroservices, registeredServices, msgEndPoint, softwareVersions, lastHeartbeat, publicKey) +class Node(var token: String, var name: String, var owner: String, var pattern: String, var registeredServices: List[RegService], var msgEndPoint: String, var softwareVersions: Map[String,String], var lastHeartbeat: String, var publicKey: String) { + def copy = new Node(token, name, owner, pattern, registeredServices, msgEndPoint, softwareVersions, lastHeartbeat, publicKey) } case class ContainerStatus(name: String, image: String, created: Int, state: String) -case class OneMicroservice(specRef: String, orgid: String, version: String, arch: String, containerStatus: List[ContainerStatus]) -case class OneWorkload(agreementId: String, workloadUrl: String, orgid: String, version: String, arch: String, containerStatus: List[ContainerStatus]) case class OneService(agreementId: String, serviceUrl: String, orgid: String, version: String, arch: String, containerStatus: List[ContainerStatus]) -case class NodeStatusRow(nodeId: String, connectivity: String, microservices: String, workloads: String, services: String, lastUpdated: String) { +case class NodeStatusRow(nodeId: String, connectivity: String, services: String, lastUpdated: String) { protected implicit val jsonFormats: Formats = DefaultFormats def toNodeStatus: NodeStatus = { val con = if (connectivity != "") read[Map[String,Boolean]](connectivity) else Map[String,Boolean]() - val ms = if (microservices != "") read[List[OneMicroservice]](microservices) else List[OneMicroservice]() - val wrk = if (workloads != "") read[List[OneWorkload]](workloads) else List[OneWorkload]() val svc = if (services != "") read[List[OneService]](services) else List[OneService]() - return NodeStatus(con, ms, wrk, svc, lastUpdated) + return NodeStatus(con, svc, lastUpdated) } def upsert: DBIO[_] = NodeStatusTQ.rows.insertOrUpdate(this) @@ -188,11 +169,9 @@ case class NodeStatusRow(nodeId: String, connectivity: String, microservices: St class NodeStatuses(tag: Tag) extends Table[NodeStatusRow](tag, "nodestatus") { def nodeId = column[String]("nodeid", O.PrimaryKey) def connectivity = column[String]("connectivity") - def microservices = column[String]("microservice") - def workloads = column[String]("workloads") def services = column[String]("services") def lastUpdated = column[String]("lastUpdated") - def * = (nodeId, connectivity, microservices, workloads, services, lastUpdated) <> (NodeStatusRow.tupled, NodeStatusRow.unapply) + def * = (nodeId, connectivity, services, lastUpdated) <> (NodeStatusRow.tupled, NodeStatusRow.unapply) def node = foreignKey("node_fk", nodeId, NodesTQ.rows)(_.id, onUpdate = ForeignKeyAction.Cascade, onDelete = ForeignKeyAction.Cascade) } @@ -201,44 +180,36 @@ object NodeStatusTQ { def getNodeStatus(nodeId: String) = rows.filter(_.nodeId === nodeId) } -case class NodeStatus(connectivity: Map[String,Boolean], microservices: List[OneMicroservice], workloads: List[OneWorkload], services: List[OneService], lastUpdated: String) +case class NodeStatus(connectivity: Map[String,Boolean], services: List[OneService], lastUpdated: String) -case class NAMicroservice(orgid: String, url: String) case class NAService(orgid: String, url: String) -case class NAWorkload(orgid: String, pattern: String, url: String) case class NAgrService(orgid: String, pattern: String, url: String) -case class NodeAgreementRow(agId: String, nodeId: String, microservices: String, workloadOrgid: String, workloadPattern: String, workloadUrl: String, services: String, agrSvcOrgid: String, agrSvcPattern: String, agrSvcUrl: String, state: String, lastUpdated: String) { +case class NodeAgreementRow(agId: String, nodeId: String, services: String, agrSvcOrgid: String, agrSvcPattern: String, agrSvcUrl: String, state: String, lastUpdated: String) { protected implicit val jsonFormats: Formats = DefaultFormats // Translates the MS string into a data structure - def getMicroservices: List[NAMicroservice] = if (microservices != "") read[List[NAMicroservice]](microservices) else List[NAMicroservice]() def getServices: List[NAService] = if (services != "") read[List[NAService]](services) else List[NAService]() - def getWorkload = NAWorkload(workloadOrgid, workloadPattern, workloadUrl) def getNAgrService = NAgrService(agrSvcOrgid, agrSvcPattern, agrSvcUrl) def toNodeAgreement = { - NodeAgreement(getMicroservices, getWorkload, getServices, getNAgrService, state, lastUpdated) + NodeAgreement(getServices, getNAgrService, state, lastUpdated) } def upsert: DBIO[_] = NodeAgreementsTQ.rows.insertOrUpdate(this) } class NodeAgreements(tag: Tag) extends Table[NodeAgreementRow](tag, "nodeagreements") { - def agId = column[String]("agid", O.PrimaryKey) // ethereum agreeement ids are unique + def agId = column[String]("agid", O.PrimaryKey) // agreement ids are unique def nodeId = column[String]("nodeid") - def microservices = column[String]("microservice") - def workloadOrgid = column[String]("workloadorgid") - def workloadPattern = column[String]("workloadpattern") - def workloadUrl = column[String]("workloadurl") def services = column[String]("services") def agrSvcOrgid = column[String]("agrsvcorgid") def agrSvcPattern = column[String]("agrsvcpattern") def agrSvcUrl = column[String]("agrsvcurl") def state = column[String]("state") def lastUpdated = column[String]("lastUpdated") - def * = (agId, nodeId, microservices, workloadOrgid, workloadPattern, workloadUrl, services, agrSvcOrgid, agrSvcPattern, agrSvcUrl, state, lastUpdated) <> (NodeAgreementRow.tupled, NodeAgreementRow.unapply) + def * = (agId, nodeId, services, agrSvcOrgid, agrSvcPattern, agrSvcUrl, state, lastUpdated) <> (NodeAgreementRow.tupled, NodeAgreementRow.unapply) def node = foreignKey("node_fk", nodeId, NodesTQ.rows)(_.id, onUpdate=ForeignKeyAction.Cascade, onDelete=ForeignKeyAction.Cascade) } @@ -251,50 +222,30 @@ object NodeAgreementsTQ { def getAgreementsWithState = rows.filter(_.state =!= "") } -case class NodeAgreement(microservices: List[NAMicroservice], workload: NAWorkload, services: List[NAService], agrService: NAgrService, state: String, lastUpdated: String) +case class NodeAgreement(services: List[NAService], agrService: NAgrService, state: String, lastUpdated: String) -/** Builds a hash of the current number of agreements for each node and microservice in the org, so we can check them quickly */ -class AgreementsHash(dbNodesAgreements: Seq[NodeAgreementRow], useServices: Boolean = true) { +/** Builds a hash of the current number of agreements for each node and service in the org, so we can check them quickly */ +class AgreementsHash(dbNodesAgreements: Seq[NodeAgreementRow]) { protected implicit val jsonFormats: Formats = DefaultFormats - // The 1st level key of this hash is the node id, the 2nd level key is the microservice url, the leaf value is current number of agreements + // The 1st level key of this hash is the node id, the 2nd level key is the service url, the leaf value is current number of agreements var agHash = new MutableHashMap[String,MutableHashMap[String,Int]]() - if (useServices) { - for (a <- dbNodesAgreements) { - val micros = a.getServices - agHash.get(a.nodeId) match { - case Some(nodeHash) => for (ms <- micros) { - val numAgs = nodeHash.get(ms.url) // node hash is there so find or create the service hashes within it - numAgs match { - case Some(numAgs2) => nodeHash.put(ms.url, numAgs2 + 1) - case None => nodeHash.put(ms.url, 1) - } + for (a <- dbNodesAgreements) { + val svcs = a.getServices + agHash.get(a.nodeId) match { + case Some(nodeHash) => for (ms <- svcs) { + val numAgs = nodeHash.get(ms.url) // node hash is there so find or create the service hashes within it + numAgs match { + case Some(numAgs2) => nodeHash.put(ms.url, numAgs2 + 1) + case None => nodeHash.put(ms.url, 1) } - case None => val nodeHash = new MutableHashMap[String, Int]() // this node is not in the hash yet, so create it and add the service hashes - for (ms <- micros) { - nodeHash.put(ms.url, 1) - } - agHash += ((a.nodeId, nodeHash)) } - } - } else { - for (a <- dbNodesAgreements) { - val micros = a.getMicroservices - agHash.get(a.nodeId) match { - case Some(nodeHash) => for (ms <- micros) { - val numAgs = nodeHash.get(ms.url) // node hash is there so find or create the microservice hashes within it - numAgs match { - case Some(numAgs2) => nodeHash.put(ms.url, numAgs2 + 1) - case None => nodeHash.put(ms.url, 1) - } + case None => val nodeHash = new MutableHashMap[String, Int]() // this node is not in the hash yet, so create it and add the service hashes + for (ms <- svcs) { + nodeHash.put(ms.url, 1) } - case None => val nodeHash = new MutableHashMap[String, Int]() // this node is not in the hash yet, so create it and add the microservice hashes - for (ms <- micros) { - nodeHash.put(ms.url, 1) - } - agHash += ((a.nodeId, nodeHash)) - } + agHash += ((a.nodeId, nodeHash)) } } } @@ -332,150 +283,9 @@ object NodeMsgsTQ { case class NodeMsg(msgId: Int, agbotId: String, agbotPubKey: String, message: String, timeSent: String, timeExpires: String) -/* -case class SoftwareVersionRow(swId: Int, nodeId: String, name: String, version: String) { - def toSoftwareVersion = version - - def putInHashMap(swVersions: MutableHashMap[String,MutableHashMap[String,String]]): Unit = { - swVersions.get(nodeId) match { - case Some(node) => ; // do not need to add the entry, because it is already there - case None => swVersions.put(nodeId, new MutableHashMap[String,String]) - } - val sMap = swVersions.get(nodeId).get - sMap.get(name) match { - case Some(sw) => ; // do not need to add the entry, because it is already there - case None => sMap.put(name, toSoftwareVersion) - } - } -} - -class SoftwareVersions(tag: Tag) extends Table[SoftwareVersionRow](tag, "swversions") { - def swId = column[Int]("swid", O.PrimaryKey, O.AutoInc) - def nodeId = column[String]("nodeid") - def name = column[String]("name") - def version = column[String]("version") - def * = (swId, nodeId, name, version) <> (SoftwareVersionRow.tupled, SoftwareVersionRow.unapply) - def node = foreignKey("node_fk", nodeId, NodesTQ.rows)(_.id) -} - -object SoftwareVersionsTQ { - val rows = TableQuery[SoftwareVersions] -} -*/ - -case class RegMicroserviceRow(msId: String, nodeId: String, url: String, numAgreements: Int, policy: String) { - def toRegMicroservice = RegMicroservice(url, numAgreements, policy, List[Prop]()) - - def putInHashMap(micros: MutableHashMap[String,MutableHashMap[String,RegMicroservice]]): Unit = { - micros.get(nodeId) match { - case Some(_) => ; // do not need to add the entry, because it is already there - case None => micros.put(nodeId, new MutableHashMap[String,RegMicroservice]) - } - val mMap = micros(nodeId) - mMap.get(msId) match { - case Some(_) => ; // do not need to add the entry, because it is already there - case None => mMap.put(msId, toRegMicroservice) - } - } - - def upsert: DBIO[_] = RegMicroservicesTQ.rows.insertOrUpdate(this) - def update: DBIO[_] = RegMicroservicesTQ.rows.update(this) -} - -class RegMicroservices(tag: Tag) extends Table[RegMicroserviceRow](tag, "nodemicros") { // <- not changing table name to regmicroservices because i do not want to cause a db schema change - def msId = column[String]("msid", O.PrimaryKey) // we form this key as | - def nodeId = column[String]("nodeid") - def url = column[String]("url") - def numAgreements = column[Int]("numagreements") - def policy = column[String]("policy") - def * = (msId, nodeId, url, numAgreements, policy) <> (RegMicroserviceRow.tupled, RegMicroserviceRow.unapply) - def node = foreignKey("node_fk", nodeId, NodesTQ.rows)(_.id, onUpdate=ForeignKeyAction.Cascade, onDelete=ForeignKeyAction.Cascade) -} - -// object testmicros extends TableQuery(new TestMicros(_)) {} -object RegMicroservicesTQ { - val rows = TableQuery[RegMicroservices] -} - -/** We define this trait because microservices in the DB and in the search criteria need the same methods, but have slightly different constructor args */ -trait RegMicroserviceTrait { - def url: String - def properties: List[Prop] - - /** Returns an error msg if the user input is invalid. */ - def validate: Option[String] = { - for (p <- properties) { - p.validate match { - case Some(msg) => return Option[String](url+": "+msg) // prepend the url so they know which microservice was bad - case None => ; // continue checking - } - } - return None // this means it is valid - } - - /** Returns true if this micro (the search) matches that micro (an entry in the db) - * Rules for comparison: - * - if both parties do not have the same property names, it is as if wildcard was specified - */ - def matches(that: RegMicroservice): Boolean = { - if (url != that.url) return false - // go thru each of our props, finding and comparing the corresponding prop in that - for (thatP <- that.properties) { - properties.find(p => thatP.name == p.name) match { - case None => ; // if the node does not specify this property, that is equivalent to it specifying wildcard - case Some(p) => if (!p.matches(thatP)) return false - } - } - return true - } -} - -/** 1 microservice in the search criteria */ -case class RegMicroserviceSearch(url: String, properties: List[Prop]) extends RegMicroserviceTrait - -/** 1 microservice within a node in the DB */ -case class RegMicroservice(url: String, numAgreements: Int, policy: String, properties: List[Prop]) extends RegMicroserviceTrait { - def toRegMicroserviceRow(nodeId: String) = RegMicroserviceRow(nodeId+"|"+url, nodeId, url, numAgreements, policy) -} - -case class PropRow(propId: String, msId: String, name: String, value: String, propType: String, op: String) { - def toProp = Prop(name, value, propType, op) - - def putInHashMap(props: MutableHashMap[String,MutableHashMap[String,Prop]]) { - props.get(msId) match { - case Some(_) => ; // do not need to add the entry, because it is already there - case None => props.put(msId, new MutableHashMap[String,Prop]) - } - val pMap = props(msId) - pMap.get(propId) match { - case Some(_) => ; // do not need to add the entry, because it is already there - case None => pMap.put(propId, toProp) - } - } - - def upsert: DBIO[_] = PropsTQ.rows.insertOrUpdate(this) - def update: DBIO[_] = PropsTQ.rows.update(this) -} - -class Props(tag: Tag) extends Table[PropRow](tag, "properties") { - def propId = column[String]("propid", O.PrimaryKey) // we form this key as | - def msId = column[String]("msid") - def name = column[String]("name") - def value = column[String]("value") - def propType = column[String]("proptype") - def op = column[String]("op") - def * = (propId, msId, name, value, propType, op) <> (PropRow.tupled, PropRow.unapply) - // def node = foreignKey("node_fk", nodeId, NodesTQ.rows)(_.id) - def ms = foreignKey("ms_fk", msId, RegMicroservicesTQ.rows)(_.msId, onUpdate=ForeignKeyAction.Cascade, onDelete=ForeignKeyAction.Cascade) -} - -object PropsTQ { - val rows = TableQuery[Props] -} - /** 1 generic property that is used in the node search criteria */ case class Prop(name: String, value: String, propType: String, op: String) { - def toPropRow(nodeId: String, msUrl: String) = PropRow(nodeId+"|"+msUrl+"|"+name, nodeId+"|"+msUrl, name, value, propType, op) + //def toPropRow(nodeId: String, msUrl: String) = PropRow(nodeId+"|"+msUrl+"|"+name, nodeId+"|"+msUrl, name, value, propType, op) /** Returns an error msg if the user input is invalid. */ def validate: Option[String] = { diff --git a/src/main/scala/com/horizon/exchangeapi/tables/Patterns.scala b/src/main/scala/com/horizon/exchangeapi/tables/Patterns.scala index 44eed553..756b8f46 100644 --- a/src/main/scala/com/horizon/exchangeapi/tables/Patterns.scala +++ b/src/main/scala/com/horizon/exchangeapi/tables/Patterns.scala @@ -8,29 +8,17 @@ import scala.collection.mutable.ListBuffer /** Contains the object representations of the DB tables related to patterns. */ -case class PWorkloads(workloadUrl: String, workloadOrgid: String, workloadArch: String, workloadVersions: List[PServiceVersions], dataVerification: Option[Map[String,Any]], nodeHealth: Option[Map[String,Int]]) case class PServices(serviceUrl: String, serviceOrgid: String, serviceArch: String, agreementLess: Option[Boolean], serviceVersions: List[PServiceVersions], dataVerification: Option[Map[String,Any]], nodeHealth: Option[Map[String,Int]]) case class PServiceVersions(version: String, deployment_overrides: Option[String], deployment_overrides_signature: Option[String], priority: Option[Map[String,Int]], upgradePolicy: Option[Map[String,String]]) case class PDataVerification(enabled: Boolean, URL: String, user: String, password: String, interval: Int, check_rate: Int, metering: Map[String,Any]) -case class PatternRow(pattern: String, orgid: String, owner: String, label: String, description: String, public: Boolean, workloads: String, services: String, agreementProtocols: String, lastUpdated: String) { +case class PatternRow(pattern: String, orgid: String, owner: String, label: String, description: String, public: Boolean, services: String, agreementProtocols: String, lastUpdated: String) { protected implicit val jsonFormats: Formats = DefaultFormats def toPattern: Pattern = { - val wrk = if (workloads == "") List[PWorkloads]() else read[List[PWorkloads]](workloads) val svc = if (services == "") List[PServices]() else read[List[PServices]](services) - /* Do not need this anymore because putting Option[] around the nodeHealth type makes the json reading and writing tolerant of it not being there - { - try { read[List[PWorkloads]](workloads) } - catch { case _: MappingException => val oldWrk = read[List[POldWorkloads]](workloads) // this pattern in the DB does not have the new nodeHealth field, so convert it - val newList = new ListBuffer[PWorkloads] - for (w <- oldWrk) { newList += PWorkloads(w.workloadUrl, w.workloadOrgid, w.workloadArch, w.workloadVersions, w.dataVerification, Some(Map())) } - newList.toList - } - } - */ val agproto = if (agreementProtocols != "") read[List[Map[String,String]]](agreementProtocols) else List[Map[String,String]]() - new Pattern(owner, label, description, public, wrk, svc, agproto, lastUpdated) + new Pattern(owner, label, description, public, svc, agproto, lastUpdated) } // update returns a DB action to update this row @@ -48,12 +36,11 @@ class Patterns(tag: Tag) extends Table[PatternRow](tag, "patterns") { def label = column[String]("label") def description = column[String]("description") def public = column[Boolean]("public") - def workloads = column[String]("workloads") def services = column[String]("services") def agreementProtocols = column[String]("agreementProtocols") def lastUpdated = column[String]("lastupdated") // this describes what you get back when you return rows from a query - def * = (pattern, orgid, owner, label, description, public, workloads, services, agreementProtocols, lastUpdated) <> (PatternRow.tupled, PatternRow.unapply) + def * = (pattern, orgid, owner, label, description, public, services, agreementProtocols, lastUpdated) <> (PatternRow.tupled, PatternRow.unapply) def user = foreignKey("user_fk", owner, UsersTQ.rows)(_.username, onUpdate=ForeignKeyAction.Cascade, onDelete=ForeignKeyAction.Cascade) def orgidKey = foreignKey("orgid_fk", orgid, OrgsTQ.rows)(_.orgid, onUpdate=ForeignKeyAction.Cascade, onDelete=ForeignKeyAction.Cascade) } @@ -77,21 +64,6 @@ object PatternsTQ { return DBIO.sequence(actions.toVector) // convert the list of actions to a DBIO sequence } - // Build a list of db actions to verify that the referenced workloads exist - def validateWorkloadIds(workloads: List[PWorkloads]): DBIO[Vector[Int]] = { - // Currently, anax does not support a pattern with no workloads, so do not support that here - val actions = ListBuffer[DBIO[Int]]() - for (w <- workloads) { - for (wv <- w.workloadVersions) { - val workId = WorkloadsTQ.formId(w.workloadOrgid, w.workloadUrl, wv.version, w.workloadArch) - //println("workId: "+workId) - actions += WorkloadsTQ.getWorkload(workId).length.result - } - } - //return DBIO.seq(actions: _*) // convert the list of actions to a DBIO seq - return DBIO.sequence(actions.toVector) // convert the list of actions to a DBIO sequence - } - def getAllPatterns(orgid: String) = rows.filter(_.orgid === orgid) def getPattern(pattern: String) = if (pattern.contains("%")) rows.filter(_.pattern like pattern) else rows.filter(_.pattern === pattern) def getOwner(pattern: String) = rows.filter(_.pattern === pattern).map(_.owner) @@ -99,7 +71,6 @@ object PatternsTQ { def getLabel(pattern: String) = rows.filter(_.pattern === pattern).map(_.label) def getDescription(pattern: String) = rows.filter(_.pattern === pattern).map(_.description) def getPublic(pattern: String) = rows.filter(_.pattern === pattern).map(_.public) - def getWorkloads(pattern: String) = rows.filter(_.pattern === pattern).map(_.workloads) def getServices(pattern: String) = rows.filter(_.pattern === pattern).map(_.services) def getServicesFromString(services: String) = if (services == "") List[PServices]() else read[List[PServices]](services) def getAgreementProtocols(pattern: String) = rows.filter(_.pattern === pattern).map(_.agreementProtocols) @@ -114,7 +85,6 @@ object PatternsTQ { case "label" => filter.map(_.label) case "description" => filter.map(_.description) case "public" => filter.map(_.public) - case "workloads" => filter.map(_.workloads) case "services" => filter.map(_.services) case "agreementProtocols" => filter.map(_.agreementProtocols) case "lastUpdated" => filter.map(_.lastUpdated) @@ -127,8 +97,8 @@ object PatternsTQ { } // This is the pattern table minus the key - used as the data structure to return to the REST clients -class Pattern(var owner: String, var label: String, var description: String, var public: Boolean, var workloads: List[PWorkloads], var services: List[PServices], var agreementProtocols: List[Map[String,String]], var lastUpdated: String) { - def copy = new Pattern(owner, label, description, public, workloads, services, agreementProtocols, lastUpdated) +class Pattern(var owner: String, var label: String, var description: String, var public: Boolean, var services: List[PServices], var agreementProtocols: List[Map[String,String]], var lastUpdated: String) { + def copy = new Pattern(owner, label, description, public, services, agreementProtocols, lastUpdated) } diff --git a/src/main/scala/com/horizon/exchangeapi/tables/Schema.scala b/src/main/scala/com/horizon/exchangeapi/tables/Schema.scala index 05a069c6..8e9e7fc2 100644 --- a/src/main/scala/com/horizon/exchangeapi/tables/Schema.scala +++ b/src/main/scala/com/horizon/exchangeapi/tables/Schema.scala @@ -46,7 +46,7 @@ object SchemaTQ { case 1 => DBIO.seq(NodeStatusTQ.rows.schema.create) // v1.37.0 case 2 => DBIO.seq(sqlu"alter table agbots drop column patterns", AgbotPatternsTQ.rows.schema.create) // v1.38.0 case 3 => DBIO.seq(ServicesTQ.rows.schema.create) // v1.45.0 - case 4 => DBIO.seq(WorkloadKeysTQ.rows.schema.create, MicroserviceKeysTQ.rows.schema.create, ServiceKeysTQ.rows.schema.create, PatternKeysTQ.rows.schema.create) // v1.46.0 + case 4 => DBIO.seq(/*WorkloadKeysTQ.rows.schema.create, MicroserviceKeysTQ.rows.schema.create, ServiceKeysTQ.rows.schema.create,*/ PatternKeysTQ.rows.schema.create) // v1.46.0 case 5 => DBIO.seq(sqlu"alter table patterns add column services character varying not null default ''") // v1.47.0 case 6 => DBIO.seq(sqlu"alter table nodes add column regservices character varying not null default ''") // v1.47.0 case 7 => DBIO.seq( // v1.47.0 @@ -70,11 +70,27 @@ object SchemaTQ { case 11 => DBIO.seq( // v1.56.0 sqlu"alter table servicedockauths add column username character varying not null default ''" ) + case 12 => DBIO.seq( // v1.62.0 + sqlu"alter table patterns drop column workloads", + sqlu"alter table agbotagreements drop column workloadorgid", + sqlu"alter table agbotagreements drop column workloadpattern", + sqlu"alter table agbotagreements drop column workloadurl", + sqlu"alter table nodestatus drop column microservice", + sqlu"alter table nodestatus drop column workloads", + sqlu"alter table nodeagreements drop column microservice", + sqlu"alter table nodeagreements drop column workloadorgid", + sqlu"alter table nodeagreements drop column workloadpattern", + sqlu"alter table nodeagreements drop column workloadurl", + sqlu"drop table if exists properties", sqlu"drop table if exists nodemicros", + sqlu"drop table if exists microservicekeys", sqlu"drop table if exists microservices", + sqlu"drop table if exists workloadkeys", sqlu"drop table if exists workloads", + sqlu"drop table if exists blockchains", sqlu"drop table if exists bctypes" + ) case other => logger.error("getUpgradeSchemaStep was given invalid step "+other); DBIO.seq() // should never get here } } - val latestSchemaVersion = 11 // NOTE: THIS MUST BE CHANGED WHEN YOU ADD TO getUpgradeSchemaStep() - val latestSchemaDescription = "Added username to table servicedockauths" + val latestSchemaVersion = 12 // NOTE: THIS MUST BE CHANGED WHEN YOU ADD TO getUpgradeSchemaStep() + val latestSchemaDescription = "Removed microservice, workload, and blockchain tables and columns" def isLatestSchemaVersion(fromSchemaVersion: Int) = fromSchemaVersion >= latestSchemaVersion diff --git a/src/main/scala/com/horizon/exchangeapi/tables/Workloads.scala b/src/main/scala/com/horizon/exchangeapi/tables/Workloads.scala deleted file mode 100644 index a50c5e51..00000000 --- a/src/main/scala/com/horizon/exchangeapi/tables/Workloads.scala +++ /dev/null @@ -1,135 +0,0 @@ -package com.horizon.exchangeapi.tables - -//import java.sql.Blob - -import com.horizon.exchangeapi.OrgAndId -import org.json4s._ -import org.json4s.jackson.Serialization.read -import slick.jdbc.PostgresProfile.api._ - - -/** Contains the object representations of the DB tables related to workloads. */ -case class WMicroservices(specRef: String, org: String, version: String, arch: String) - -case class WorkloadRow(workload: String, orgid: String, owner: String, label: String, description: String, public: Boolean, workloadUrl: String, version: String, arch: String, downloadUrl: String, apiSpec: String, userInput: String, workloads: String, lastUpdated: String) { - protected implicit val jsonFormats: Formats = DefaultFormats - - def toWorkload: Workload = { - val spec = if (apiSpec != "") read[List[WMicroservices]](apiSpec) else List[WMicroservices]() - val input = if (userInput != "") read[List[Map[String,String]]](userInput) else List[Map[String,String]]() - val wrk = if (workloads != "") read[List[MDockerImages]](workloads) else List[MDockerImages]() - new Workload(owner, label, description, public, workloadUrl, version, arch, downloadUrl, spec, input, wrk, lastUpdated) - } - - // update returns a DB action to update this row - def update: DBIO[_] = (for { m <- WorkloadsTQ.rows if m.workload === workload } yield m).update(this) - - // insert returns a DB action to insert this row - def insert: DBIO[_] = WorkloadsTQ.rows += this -} - -/** Mapping of the workloads db table to a scala class */ -class Workloads(tag: Tag) extends Table[WorkloadRow](tag, "workloads") { - def workload = column[String]("workload", O.PrimaryKey) // the content of this is orgid/workload - def orgid = column[String]("orgid") - def owner = column[String]("owner") - def label = column[String]("label") - def description = column[String]("description") - def public = column[Boolean]("public") - def workloadUrl = column[String]("workloadurl") - def version = column[String]("version") - def arch = column[String]("arch") - def downloadUrl = column[String]("downloadurl") - def apiSpec = column[String]("apispec") - def userInput = column[String]("userinput") - def workloads = column[String]("workloads") - def lastUpdated = column[String]("lastupdated") - // this describes what you get back when you return rows from a query - def * = (workload, orgid, owner, label, description, public, workloadUrl, version, arch, downloadUrl, apiSpec, userInput, workloads, lastUpdated) <> (WorkloadRow.tupled, WorkloadRow.unapply) - def user = foreignKey("user_fk", owner, UsersTQ.rows)(_.username, onUpdate=ForeignKeyAction.Cascade, onDelete=ForeignKeyAction.Cascade) - def orgidKey = foreignKey("orgid_fk", orgid, OrgsTQ.rows)(_.orgid, onUpdate=ForeignKeyAction.Cascade, onDelete=ForeignKeyAction.Cascade) -} - -// Instance to access the workloads table -object WorkloadsTQ { - val rows = TableQuery[Workloads] - - def formId(orgid: String, url: String, version: String, arch: String): String = { - // Remove the https:// from the beginning of workloadUrl and replace troublesome chars with a dash. It has already been checked as a valid URL in validate(). - val workloadUrl2 = """^[A-Za-z0-9+.-]*?://""".r replaceFirstIn (url, "") - val workloadUrl3 = """[$!*,;/?@&~=%]""".r replaceAllIn (workloadUrl2, "-") // I think possible chars in valid urls are: $_.+!*,;/?:@&~=%- - return OrgAndId(orgid, workloadUrl3 + "_" + version + "_" + arch).toString - } - - def getAllWorkloads(orgid: String) = rows.filter(_.orgid === orgid) - def getWorkload(workload: String) = rows.filter(_.workload === workload) - def getOwner(workload: String) = rows.filter(_.workload === workload).map(_.owner) - def getNumOwned(owner: String) = rows.filter(_.owner === owner).length - def getLabel(workload: String) = rows.filter(_.workload === workload).map(_.label) - def getDescription(workload: String) = rows.filter(_.workload === workload).map(_.description) - def getPublic(workload: String) = rows.filter(_.workload === workload).map(_.public) - def getWorkloadUrl(workload: String) = rows.filter(_.workload === workload).map(_.workloadUrl) - def getVersion(workload: String) = rows.filter(_.workload === workload).map(_.version) - def getArch(workload: String) = rows.filter(_.workload === workload).map(_.arch) - def getDownloadUrl(workload: String) = rows.filter(_.workload === workload).map(_.downloadUrl) - def getApiSpec(workload: String) = rows.filter(_.workload === workload).map(_.apiSpec) - def getUserInput(workload: String) = rows.filter(_.workload === workload).map(_.userInput) - def getWorkloads(workload: String) = rows.filter(_.workload === workload).map(_.workloads) - def getLastUpdated(workload: String) = rows.filter(_.workload === workload).map(_.lastUpdated) - - /** Returns a query for the specified workload attribute value. Returns null if an invalid attribute name is given. */ - def getAttribute(workload: String, attrName: String): Query[_,_,Seq] = { - val filter = rows.filter(_.workload === workload) - // According to 1 post by a slick developer, there is not yet a way to do this properly dynamically - return attrName match { - case "owner" => filter.map(_.owner) - case "label" => filter.map(_.label) - case "description" => filter.map(_.description) - case "public" => filter.map(_.public) - case "workloadUrl" => filter.map(_.workloadUrl) - case "version" => filter.map(_.version) - case "arch" => filter.map(_.arch) - case "downloadUrl" => filter.map(_.downloadUrl) - case "apiSpec" => filter.map(_.apiSpec) - case "userInput" => filter.map(_.userInput) - case "workloads" => filter.map(_.workloads) - case "lastUpdated" => filter.map(_.lastUpdated) - case _ => null - } - } - - /** Returns the actions to delete the workload and the blockchains that reference it */ - def getDeleteActions(workload: String): DBIO[_] = getWorkload(workload).delete // with the foreign keys set up correctly and onDelete=cascade, the db will automatically delete these associated blockchain rows -} - -// This is the workload table minus the key - used as the data structure to return to the REST clients -class Workload(var owner: String, var label: String, var description: String, var public: Boolean, var workloadUrl: String, var version: String, var arch: String, var downloadUrl: String, var apiSpec: List[WMicroservices], var userInput: List[Map[String,String]], var workloads: List[MDockerImages], var lastUpdated: String) { - def copy = new Workload(owner, label, description, public, workloadUrl, version, arch, downloadUrl, apiSpec, userInput, workloads, lastUpdated) -} - - -// Key is a sub-resource of workload -case class WorkloadKeyRow(keyId: String, workloadId: String, key: String, lastUpdated: String) { - def toWorkloadKey = WorkloadKey(key, lastUpdated) - - def upsert: DBIO[_] = WorkloadKeysTQ.rows.insertOrUpdate(this) -} - -class WorkloadKeys(tag: Tag) extends Table[WorkloadKeyRow](tag, "workloadkeys") { - def keyId = column[String]("keyid") // key - the key name - def workloadId = column[String]("workloadid") // additional key - the composite orgid/workloadid - def key = column[String]("key") // the actual key content - def lastUpdated = column[String]("lastupdated") - def * = (keyId, workloadId, key, lastUpdated) <> (WorkloadKeyRow.tupled, WorkloadKeyRow.unapply) - def primKey = primaryKey("pk_wkk", (keyId, workloadId)) - def workload = foreignKey("workload_fk", workloadId, WorkloadsTQ.rows)(_.workload, onUpdate=ForeignKeyAction.Cascade, onDelete=ForeignKeyAction.Cascade) -} - -object WorkloadKeysTQ { - val rows = TableQuery[WorkloadKeys] - - def getKeys(workloadId: String) = rows.filter(_.workloadId === workloadId) - def getKey(workloadId: String, keyId: String) = rows.filter( r => {r.workloadId === workloadId && r.keyId === keyId} ) -} - -case class WorkloadKey(key: String, lastUpdated: String) diff --git a/src/test/bash/patch/bctypes/bt1-desc.sh b/src/test/bash/patch/bctypes/bt1-desc.sh deleted file mode 100755 index 408525c3..00000000 --- a/src/test/bash/patch/bctypes/bt1-desc.sh +++ /dev/null @@ -1,6 +0,0 @@ -# Update node 1 as node -source `dirname $0`/../../functions.sh PATCH $* - -curl $copts -X PATCH -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "description": "A more thorough description..." -}' $EXCHANGE_URL_ROOT/v1/bctypes/bt1 | $parse diff --git a/src/test/bash/patch/blockchains/bc1-desc.sh b/src/test/bash/patch/blockchains/bc1-desc.sh deleted file mode 100755 index f8b40e24..00000000 --- a/src/test/bash/patch/blockchains/bc1-desc.sh +++ /dev/null @@ -1,6 +0,0 @@ -# Update node 1 as node -source `dirname $0`/../../functions.sh PATCH $* - -curl $copts -X PATCH -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "description": "A more thorough blockchain description..." -}' $EXCHANGE_URL_ROOT/v1/bctypes/bt1/blockchains/bc1 | $parse diff --git a/src/test/bash/patch/microservices/patch.sh b/src/test/bash/patch/microservices/patch.sh deleted file mode 100755 index d542bc01..00000000 --- a/src/test/bash/patch/microservices/patch.sh +++ /dev/null @@ -1,6 +0,0 @@ -# Updates a microservice -source `dirname $0`/../../functions.sh PATCH $* - -curl $copts -X PATCH -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_ORG/$EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "public": false -}' $EXCHANGE_URL_ROOT/v1/orgs/$EXCHANGE_ORG/microservices/bluehorizon.network-microservices-network_1.0.0_amd64 | $parse diff --git a/src/test/bash/patch/workloads/patch.sh b/src/test/bash/patch/workloads/patch.sh deleted file mode 100755 index 43876d87..00000000 --- a/src/test/bash/patch/workloads/patch.sh +++ /dev/null @@ -1,6 +0,0 @@ -# Updates a microservice -source `dirname $0`/../../functions.sh PATCH $* - -curl $copts -X PATCH -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_ORG/$EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "downloadUrl": "this is now patched" -}' $EXCHANGE_URL_ROOT/v1/orgs/$EXCHANGE_ORG/workloads/bluehorizon.network-workloads-netspeed_1.0.0_amd64 | $parse diff --git a/src/test/bash/post/microservices/create.sh b/src/test/bash/post/microservices/create.sh deleted file mode 100755 index 5f56a72b..00000000 --- a/src/test/bash/post/microservices/create.sh +++ /dev/null @@ -1,32 +0,0 @@ -# Adds a microservice -source `dirname $0`/../../functions.sh POST $* - -curl $copts -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_ORG/$EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "label": "GPS x86_64", - "description": "blah blah", - "public": true, - "specRef": "https://bluehorizon.network/documentation/microservice/gps", - "version": "1.0.0", - "arch": "amd64", - "sharable": "singleton", - "downloadUrl": "", - "matchHardware": { - "usbnodeIds": "1546:01a7", - "devFiles": "/dev/ttyUSB*" - }, - "userInput": [ - { - "name": "foo", - "label": "The Foo Value", - "type": "string", - "defaultValue": "bar" - } - ], - "workloads": [ - { - "deployment": "{\"services\":{\"gps\":{\"image\":\"summit.hovitos.engineering/x86/gps:2.0.3\",\"privileged\":true,\"nodes\":[\"/dev/bus/usb/001/001:/dev/bus/usb/001/001\"]}}}", - "deployment_signature": "EURzSkDyk66qE6esYUDkLWLzM=", - "torrent": "{\"url\":\"https://images.bluehorizon.network/28f57c.torrent\",\"images\":[{\"file\":\"d98bf.tar.gz\",\"signature\":\"kckH14DUj3bX=\"}]}" - } - ] -}' $EXCHANGE_URL_ROOT/v1/orgs/$EXCHANGE_ORG/microservices | $parse diff --git a/src/test/bash/post/microservices/sdr-create.sh b/src/test/bash/post/microservices/sdr-create.sh deleted file mode 100755 index 9f42b72a..00000000 --- a/src/test/bash/post/microservices/sdr-create.sh +++ /dev/null @@ -1,29 +0,0 @@ -# Adds a microservice -source `dirname $0`/../../functions.sh POST $* - -curl $copts -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_ORG/$EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "label": "SDR for arm", - "description": "blah blah", - "public": true, - "specRef": "https://bluehorizon.network/documentation/microservice/rtlsdr", - "version": "1.0.0", - "arch": "arm", - "sharable": "none", - "downloadUrl": "", - "matchHardware": {}, - "userInput": [ - { - "name": "foo", - "label": "The Foo Value", - "type": "string", - "defaultValue": "bar" - } - ], - "workloads": [ - { - "deployment": "{\"services\":{\"rtlsdr\":{\"image\":\"summit.hovitos.engineering/armhf/rtlsdr:volcano\",\"privileged\":true,\"nodes\":[\"/dev/bus/usb/001/001:/dev/bus/usb/001/001\"]}}}", - "deployment_signature": "EURzSkDyk66qE6esYUDkLWLzM=", - "torrent": "{\"url\":\"https://images.bluehorizon.network/28f57c.torrent\",\"images\":[{\"file\":\"d98bf.tar.gz\",\"signature\":\"kckH14DUj3bX=\"}]}" - } - ] -}' $EXCHANGE_URL_ROOT/v1/orgs/$EXCHANGE_ORG/microservices | $parse diff --git a/src/test/bash/post/patterns/p1.sh b/src/test/bash/post/patterns/p1.sh index 47b0cf70..2fda8763 100755 --- a/src/test/bash/post/patterns/p1.sh +++ b/src/test/bash/post/patterns/p1.sh @@ -1,14 +1,14 @@ -# Adds a workload +# Adds a pattern source `dirname $0`/../../functions.sh POST $* curl $copts -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_ORG/$EXCHANGE_USER:$EXCHANGE_PW" -d '{ "label": "My Pattern", "description": "blah blah", "public": true, - "workloads": [ + "services": [ { - "workloadUrl": "https://bluehorizon.network/workloads/netspeed", - "workloadOrgid": "'$EXCHANGE_ORG'", - "workloadArch": "amd64", - "workloadVersions": [ + "serviceUrl": "https://bluehorizon.network/services/netspeed", + "serviceOrgid": "'$EXCHANGE_ORG'", + "serviceArch": "amd64", + "serviceVersions": [ { "version": "1.0.0", "deployment_overrides": "{\"services\":{\"location\":{\"environment\":[\"USE_NEW_STAGING_URL=false\"]}}}", diff --git a/src/test/bash/post/search/devices/multiple-wildcard.sh b/src/test/bash/post/search/devices/multiple-wildcard.sh index 8a7f4075..19fbc676 100755 --- a/src/test/bash/post/search/devices/multiple-wildcard.sh +++ b/src/test/bash/post/search/devices/multiple-wildcard.sh @@ -2,7 +2,7 @@ source `dirname $0`/../../../functions.sh POST $* curl $copts -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "desiredMicroservices": [ + "desiredServices": [ { "url": "https://bluehorizon.network/documentation/netspeed-node-api", "properties": [ diff --git a/src/test/bash/post/search/devices/netspeed-dave.sh b/src/test/bash/post/search/devices/netspeed-dave.sh index 8eff1819..0662f5f4 100755 --- a/src/test/bash/post/search/devices/netspeed-dave.sh +++ b/src/test/bash/post/search/devices/netspeed-dave.sh @@ -2,7 +2,7 @@ source `dirname $0`/../../../functions.sh POST $* curl $copts -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic a1:abcdef" -d '{ - "desiredMicroservices": [ + "desiredServices": [ { "url": "https://bluehorizon.network/documentation/netspeed-node-api", "properties": [ diff --git a/src/test/bash/post/search/devices/netspeed-wildcard-agbot.sh b/src/test/bash/post/search/devices/netspeed-wildcard-agbot.sh index 23aed275..928988d3 100755 --- a/src/test/bash/post/search/devices/netspeed-wildcard-agbot.sh +++ b/src/test/bash/post/search/devices/netspeed-wildcard-agbot.sh @@ -2,7 +2,7 @@ source `dirname $0`/../../../functions.sh POST $* curl $copts -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic a1:abcdef" -d '{ - "desiredMicroservices": [ + "desiredServices": [ { "url": "https://bluehorizon.network/documentation/netspeed-node-api", "properties": [ diff --git a/src/test/bash/post/search/devices/netspeed-wildcard.sh b/src/test/bash/post/search/devices/netspeed-wildcard.sh index ab11a3c0..6b665950 100755 --- a/src/test/bash/post/search/devices/netspeed-wildcard.sh +++ b/src/test/bash/post/search/devices/netspeed-wildcard.sh @@ -2,7 +2,7 @@ source `dirname $0`/../../../functions.sh POST $* curl $copts -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "desiredMicroservices": [ +# "desiredServices": [ { "url": "https://bluehorizon.network/documentation/netspeed-node-api", "properties": [ diff --git a/src/test/bash/post/search/devices/sdr-arch-x86.sh b/src/test/bash/post/search/devices/sdr-arch-x86.sh index 5527b16c..863bd087 100755 --- a/src/test/bash/post/search/devices/sdr-arch-x86.sh +++ b/src/test/bash/post/search/devices/sdr-arch-x86.sh @@ -2,7 +2,7 @@ source `dirname $0`/../../../functions.sh POST $* curl $copts -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "desiredMicroservices": [ + "desiredServices": [ { "url": "https://bluehorizon.network/documentation/sdr-node-api", "properties": [ diff --git a/src/test/bash/post/search/devices/sdr-badpw.sh b/src/test/bash/post/search/devices/sdr-badpw.sh index 14ea7bd1..855700b8 100755 --- a/src/test/bash/post/search/devices/sdr-badpw.sh +++ b/src/test/bash/post/search/devices/sdr-badpw.sh @@ -2,7 +2,7 @@ source `dirname $0`/../../../functions.sh POST $* curl $copts -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_USER:badpw" -d '{ - "desiredMicroservices": [ + "desiredServices": [ { "url": "https://bluehorizon.network/documentation/sdr-node-api", "properties": [ diff --git a/src/test/bash/post/search/devices/sdr-datav-false.sh b/src/test/bash/post/search/devices/sdr-datav-false.sh index cc766a14..54d0e4fb 100755 --- a/src/test/bash/post/search/devices/sdr-datav-false.sh +++ b/src/test/bash/post/search/devices/sdr-datav-false.sh @@ -2,7 +2,7 @@ source `dirname $0`/../../../functions.sh POST $* curl $copts -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "desiredMicroservices": [ + "desiredServices": [ { "url": "https://bluehorizon.network/documentation/sdr-node-api", "properties": [ diff --git a/src/test/bash/post/search/devices/sdr-good-agbot.sh b/src/test/bash/post/search/devices/sdr-good-agbot.sh index a5519e59..a7537ba5 100755 --- a/src/test/bash/post/search/devices/sdr-good-agbot.sh +++ b/src/test/bash/post/search/devices/sdr-good-agbot.sh @@ -2,7 +2,7 @@ source `dirname $0`/../../../functions.sh POST $* curl $copts -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic a1:abcdef" -d '{ - "desiredMicroservices": [ + "desiredServices": [ { "url": "https://bluehorizon.network/documentation/sdr-node-api", "properties": [ diff --git a/src/test/bash/post/search/devices/sdr-good.sh b/src/test/bash/post/search/devices/sdr-good.sh index c7c794f9..9d7a7307 100755 --- a/src/test/bash/post/search/devices/sdr-good.sh +++ b/src/test/bash/post/search/devices/sdr-good.sh @@ -2,7 +2,7 @@ source `dirname $0`/../../../functions.sh POST $* curl $copts -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "desiredMicroservices": [ + "desiredServices": [ { "url": "https://bluehorizon.network/documentation/sdr-node-api", "properties": [ diff --git a/src/test/bash/post/search/devices/sdr-memory-400.sh b/src/test/bash/post/search/devices/sdr-memory-400.sh index c9c39996..48e1ead5 100755 --- a/src/test/bash/post/search/devices/sdr-memory-400.sh +++ b/src/test/bash/post/search/devices/sdr-memory-400.sh @@ -2,7 +2,7 @@ source `dirname $0`/../../../functions.sh POST $* curl $copts -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "desiredMicroservices": [ + "desiredServices": [ { "url": "https://bluehorizon.network/documentation/sdr-node-api", "properties": [ diff --git a/src/test/bash/post/search/devices/sdr-noversion.sh b/src/test/bash/post/search/devices/sdr-noversion.sh index ce3ac2fb..acf6b005 100755 --- a/src/test/bash/post/search/devices/sdr-noversion.sh +++ b/src/test/bash/post/search/devices/sdr-noversion.sh @@ -2,7 +2,7 @@ source `dirname $0`/../../../functions.sh POST $* curl $copts -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "desiredMicroservices": [ + "desiredServices": [ { "url": "https://bluehorizon.network/documentation/sdr-node-api", "properties": [ diff --git a/src/test/bash/post/search/devices/sdr-version-2.sh b/src/test/bash/post/search/devices/sdr-version-2.sh index 742bf0ea..d3af3dc4 100755 --- a/src/test/bash/post/search/devices/sdr-version-2.sh +++ b/src/test/bash/post/search/devices/sdr-version-2.sh @@ -2,7 +2,7 @@ source `dirname $0`/../../../functions.sh POST $* curl $copts -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "desiredMicroservices": [ + "desiredServices": [ { "url": "https://bluehorizon.network/documentation/sdr-node-api", "properties": [ diff --git a/src/test/bash/post/search/devices/sdr-wildcard-prod.sh b/src/test/bash/post/search/devices/sdr-wildcard-prod.sh index 42ed57d3..68a8458d 100755 --- a/src/test/bash/post/search/devices/sdr-wildcard-prod.sh +++ b/src/test/bash/post/search/devices/sdr-wildcard-prod.sh @@ -2,7 +2,7 @@ source `dirname $0`/../../../functions.sh POST $* curl $copts -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "desiredMicroservices": [ + "desiredServices": [ { "url": "https://bluehorizon.network/documentation/sdr-node-api", "properties": [ diff --git a/src/test/bash/post/search/devices/sdr-wildcard-stale.sh b/src/test/bash/post/search/devices/sdr-wildcard-stale.sh index 2a356544..6dc15a29 100755 --- a/src/test/bash/post/search/devices/sdr-wildcard-stale.sh +++ b/src/test/bash/post/search/devices/sdr-wildcard-stale.sh @@ -2,7 +2,7 @@ source `dirname $0`/../../../functions.sh POST $* curl $copts -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "desiredMicroservices": [ + "desiredServices": [ { "url": "https://bluehorizon.network/documentation/sdr-node-api", "properties": [ diff --git a/src/test/bash/post/search/devices/sdr-wildcard.sh b/src/test/bash/post/search/devices/sdr-wildcard.sh index b0532508..0c385d24 100755 --- a/src/test/bash/post/search/devices/sdr-wildcard.sh +++ b/src/test/bash/post/search/devices/sdr-wildcard.sh @@ -2,7 +2,7 @@ source `dirname $0`/../../../functions.sh POST $* curl $copts -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "desiredMicroservices": [ + "desiredServices": [ { "url": "https://bluehorizon.network/documentation/sdr-node-api", "properties": [ diff --git a/src/test/bash/post/workloads/create-no-ms.sh b/src/test/bash/post/workloads/create-no-ms.sh deleted file mode 100755 index 2018970a..00000000 --- a/src/test/bash/post/workloads/create-no-ms.sh +++ /dev/null @@ -1,15 +0,0 @@ -# Adds a workload -source `dirname $0`/../../functions.sh POST '' $* - -curl $copts -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_ORG/$EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "label": "Location for x86_64", - "description": "blah blah", - "public": true, - "workloadUrl": "https://bluehorizon.network/workloads/netspeed3", - "version": "1.0.0", - "arch": "amd64", - "downloadUrl": "", - "apiSpec": [], - "userInput": [], - "workloads": [] -}' $EXCHANGE_URL_ROOT/v1/orgs/$EXCHANGE_ORG/workloads | $parse diff --git a/src/test/bash/post/workloads/create.sh b/src/test/bash/post/workloads/create.sh deleted file mode 100755 index 30e41d38..00000000 --- a/src/test/bash/post/workloads/create.sh +++ /dev/null @@ -1,28 +0,0 @@ -# Adds a workload -source `dirname $0`/../../functions.sh POST '' $* - -curl $copts -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_ORG/$EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "label": "Location for x86_64", - "description": "blah blah", - "public": true, - "workloadUrl": "https://bluehorizon.network/workloads/netspeed2", - "version": "1.0.0", - "arch": "amd64", - "downloadUrl": "", - "apiSpec": [ - { - "specRef": "https://bluehorizon.network/microservices/network", - "org": "IBM", - "version": "2.0.0", - "arch": "amd64" - }, - { - "specRef": "https://bluehorizon.network/microservices/rtlsdr", - "org": "IBM", - "version": "1.0.0", - "arch": "amd64" - } - ], - "userInput": [], - "workloads": [] -}' $EXCHANGE_URL_ROOT/v1/orgs/$EXCHANGE_ORG/workloads | $parse diff --git a/src/test/bash/post/workloads/sdr-create.sh b/src/test/bash/post/workloads/sdr-create.sh deleted file mode 100755 index 77f792eb..00000000 --- a/src/test/bash/post/workloads/sdr-create.sh +++ /dev/null @@ -1,35 +0,0 @@ -# Adds a workload -source `dirname $0`/../../functions.sh POST '' $* - -curl $copts -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_ORG/$EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "label": "SDR for arm", - "description": "blah blah", - "public": true, - "workloadUrl": "https://bluehorizon.network//workloads/apollo", - "version": "1.0.0", - "arch": "arm", - "downloadUrl": "", - "apiSpec": [ - { - "specRef": "https://bluehorizon.network//microservices/rtlsdr", - "org": "IBM", - "version": "1.0.0", - "arch": "amd64" - } - ], - "userInput": [ - { - "name": "foo", - "label": "The Foo Value", - "type": "string", - "defaultValue": "bar" - } - ], - "workloads": [ - { - "deployment": "{\"services\":{\"apollo\":{\"image\":\"summit.hovitos.engineering/armhf/apollo:2.1\",\"environment\":[\"USE_NEW_STAGING_URL=false\"]}}}", - "deployment_signature": "EURzSkDyk66qE6esYUDkLWLzM=", - "torrent": "{\"url\":\"https://images.bluehorizon.network/28f57c.torrent\",\"images\":[{\"file\":\"d98bf.tar.gz\",\"signature\":\"kckH14DUj3bX=\"}]}" - } - ] -}' $EXCHANGE_URL_ROOT/v1/orgs/$EXCHANGE_ORG/workloads | $parse diff --git a/src/test/bash/primedb.sh b/src/test/bash/primedb.sh index 2cf1ddba..3fa62cac 100755 --- a/src/test/bash/primedb.sh +++ b/src/test/bash/primedb.sh @@ -79,42 +79,6 @@ svcDockAuthRegistry='registry.ng.bluemix.net' svcDockAuthUsername='iamapikey' svcDockAuthToken='abcdefghijk' -microid="bluehorizon.network-microservices-network_1.0.0_amd64" -microurl="https://bluehorizon.network/microservices/network" -microarch="amd64" -microversion="1.0.0" - -msKeyId="mykey.pem" -msKey='-----BEGIN CERTIFICATE----- -MIII+jCCBOKgAwIBAgIUEfeMrmSFxCUKATcNPcowfs/lU9owDQYJKoZIhvcNAQEL -BQAwJjEMMAoGA1UEChMDaWJtMRYwFAYDVQQDDA1icEB1cy5pYm0uY29tMB4XDTE4 -MDEwMjAxNDkyMFoXDTIyMDEwMjEzNDgzMFowJjEMMAoGA1UEChMDaWJtMRYwFAYD -VQQDDA1icEB1cy5pYm0uY29tMIIEIjANBgkqhkiG9w0BAQEFAAOCBA8AMIIECgKC ------END CERTIFICATE----- -' - -microid2="bluehorizon.network-microservices-rtlsdr_2.0.0_amd64" -microurl2="https://bluehorizon.network/microservices/rtlsdr" -microarch2="amd64" -microversion2="2.0.0" - -workid="bluehorizon.network-workloads-netspeed_1.0.0_amd64" -workurl="https://bluehorizon.network/workloads/netspeed" -workarch="amd64" -workversion="1.0.0" - -wkKeyId="mykey2.pem" -wkKey='-----BEGIN CERTIFICATE----- -MIII+jCCBOKgAwIBAgIUEfeMrmSFxCUKATcNPcowfs/lU9owDQYJKoZIhvcNAQEL -BQAwJjEMMAoGA1UEChMDaWJtMRYwFAYDVQQDDA1icEB1cy5pYm0uY29tMB4XDTE4 -MDEwMjAxNDkyMFoXDTIyMDEwMjEzNDgzMFowJjEMMAoGA1UEChMDaWJtMRYwFAYD -VQQDDA1icEB1cy5pYm0uY29tMIIEIjANBgkqhkiG9w0BAQEFAAOCBA8AMIIECgKC ------END CERTIFICATE----- -' - -workid2="bluehorizon.network-workloads-weather_1.0.0_amd64" -workurl2="https://bluehorizon.network/workloads/weather" - patid="p1" patid2="p2" @@ -127,10 +91,6 @@ VQQDDA1icEB1cy5pYm0uY29tMIIEIjANBgkqhkiG9w0BAQEFAAOCBA8AMIIECgKC -----END CERTIFICATE----- ' -bctypeid="bct1" - -blockchainid="bc1" - #curlBasicArgs="-s -w %{http_code} --output /dev/null $accept" curlBasicArgs="-sS -w %{http_code} $accept" # set -x @@ -304,94 +264,6 @@ else echo "orgs/$orgid2/services/$svcid exists" fi -rc=$(curlfind $userauth "orgs/$orgid/microservices/$microid") -checkrc "$rc" 200 404 -if [[ $rc != 200 ]]; then - curlcreate "POST" $userauth "orgs/$orgid/microservices" '{"label": "Network x86_64", "description": "blah blah", "public": true, "specRef": "'$microurl'", - "version": "'$microversion'", "arch": "'$microarch'", "sharable": "single", "downloadUrl": "", - "matchHardware": {}, - "userInput": [], - "workloads": [] }' -else - echo "orgs/$orgid/microservices/$microid exists" -fi - -rc=$(curlfind $userauth "orgs/$orgid/microservices/$microid/keys/$msKeyId") -checkrc "$rc" 200 404 -if [[ $rc != 200 ]]; then - curlcreate "PUT" $userauth "orgs/$orgid/microservices/$microid/keys/$msKeyId" "$msKey" "$contenttext" -else - echo "orgs/$orgid/microservices/$microid/keys/$msKeyId exists" -fi - -rc=$(curlfind $userauth "orgs/$orgid/microservices/$microid2") -checkrc "$rc" 200 404 -if [[ $rc != 200 ]]; then - curlcreate "POST" $userauth "orgs/$orgid/microservices" '{"label": "Network x86_64", "description": "blah blah", "public": true, "specRef": "'$microurl2'", - "version": "'$microversion2'", "arch": "'$microarch2'", "sharable": "single", "downloadUrl": "", - "matchHardware": {}, - "userInput": [], - "workloads": [] }' -else - echo "orgs/$orgid/microservices/$microid2 exists" -fi - -rc=$(curlfind $userauthorg2 "orgs/$orgid2/microservices/$microid") -checkrc "$rc" 200 404 -if [[ $rc != 200 ]]; then - curlcreate "POST" $userauthorg2 "orgs/$orgid2/microservices" '{"label": "Network x86_64", "description": "blah blah", "public": false, "specRef": "'$microurl'", - "version": "'$microversion'", "arch": "'$microarch'", "sharable": "single", "downloadUrl": "", - "matchHardware": {}, - "userInput": [], - "workloads": [] }' -else - echo "orgs/$orgid2/microservices/$microid exists" -fi - -rc=$(curlfind $userauth "orgs/$orgid/workloads/$workid") -checkrc "$rc" 200 404 -if [[ $rc != 200 ]]; then - curlcreate "POST" $userauth "orgs/$orgid/workloads" '{"label": "Netspeed x86_64", "description": "blah blah", "public": true, "workloadUrl": "'$workurl'", - "version": "'$workversion'", "arch": "'$workarch'", "downloadUrl": "", - "apiSpec": [{ "specRef": "'$microurl'", "org": "'$orgid'", "version": "'$microversion'", "arch": "'$microarch'" }], - "userInput": [], - "workloads": [] }' -else - echo "orgs/$orgid/workloads/$workid exists" -fi - -rc=$(curlfind $userauth "orgs/$orgid/workloads/$workid/keys/$wkKeyId") -checkrc "$rc" 200 404 -if [[ $rc != 200 ]]; then - curlcreate "PUT" $userauth "orgs/$orgid/workloads/$workid/keys/$wkKeyId" "$wkKey" "$contenttext" -else - echo "orgs/$orgid/workloads/$workid/keys/$wkKeyId exists" -fi - -rc=$(curlfind $userauth "orgs/$orgid/workloads/$workid2") -checkrc "$rc" 200 404 -if [[ $rc != 200 ]]; then - curlcreate "POST" $userauth "orgs/$orgid/workloads" '{"label": "Weather x86_64", "description": "blah blah", "public": true, "workloadUrl": "'$workurl2'", - "version": "1.0.0", "arch": "amd64", "downloadUrl": "", - "apiSpec": [{ "specRef": "'$microurl'", "org": "'$orgid'", "version": "'$microversion'", "arch": "'$microarch'" }], - "userInput": [], - "workloads": [] }' -else - echo "orgs/$orgid/workloads/$workid2 exists" -fi - -rc=$(curlfind $userauthorg2 "orgs/$orgid2/workloads/$workid") -checkrc "$rc" 200 404 -if [[ $rc != 200 ]]; then - curlcreate "POST" $userauthorg2 "orgs/$orgid2/workloads" '{"label": "Netspeed x86_64", "description": "blah blah", "public": false, "workloadUrl": "'$workurl'", - "version": "'$workversion'", "arch": "'$workarch'", "downloadUrl": "", - "apiSpec": [{ "specRef": "'$microurl'", "org": "'$orgid2'", "version": "'$microversion'", "arch": "'$microarch'" }], - "userInput": [], - "workloads": [] }' -else - echo "orgs/$orgid2/workloads/$workid exists" -fi - rc=$(curlfind $userauth "orgs/$orgid/patterns/$patid") checkrc "$rc" 200 404 if [[ $rc != 200 ]]; then @@ -502,7 +374,7 @@ fi rc=$(curlfind $userauthorg2 "orgs/$orgid2/nodes/$nodeid") checkrc "$rc" 200 404 if [[ $rc != 200 ]]; then - curlcreate "PUT" $userauthorg2 "orgs/$orgid2/nodes/$nodeid" '{"token": "'$nodetoken'", "name": "rpi1", "pattern": "'$orgid'/'$patid'", "registeredMicroservices": [], "msgEndPoint": "", "softwareVersions": {}, "publicKey": "ABC" }' + curlcreate "PUT" $userauthorg2 "orgs/$orgid2/nodes/$nodeid" '{"token": "'$nodetoken'", "name": "rpi1", "pattern": "'$orgid'/'$patid'", "registeredServices": [], "msgEndPoint": "", "softwareVersions": {}, "publicKey": "ABC" }' else echo "orgs/$orgid2/nodes/$nodeid exists" fi @@ -510,7 +382,7 @@ fi rc=$(curlfind $userauth "orgs/$orgid/nodes/$nodeid/status") checkrc "$rc" 200 404 if [[ $rc != 200 ]]; then - curlcreate "PUT" $nodeauth "orgs/$orgid/nodes/$nodeid/status" '{ "connectivity": {"firmware.bluehorizon.network": true}, "microservices": [], "workloads": [] }' + curlcreate "PUT" $nodeauth "orgs/$orgid/nodes/$nodeid/status" '{ "connectivity": {"firmware.bluehorizon.network": true}, "services": [] }' else echo "orgs/$orgid/nodes/$nodeid/status exists" fi @@ -583,7 +455,7 @@ fi rc=$(curlfind $userauth "orgs/$orgid/agbots/$agbotid/agreements/$agreementid1") checkrc "$rc" 200 404 if [[ $rc != 200 ]]; then - curlcreate "PUT" $agbotauth "orgs/$orgid/agbots/$agbotid/agreements/$agreementid1" '{"workload": {"orgid": "'$orgid'", "pattern": "'$patid'", "url": "'$workurl'"}, "state": "negotiating"}' + curlcreate "PUT" $agbotauth "orgs/$orgid/agbots/$agbotid/agreements/$agreementid1" '{"service": {"orgid": "'$orgid'", "pattern": "'$patid'", "url": "'$svcurl'"}, "state": "negotiating"}' else echo "orgs/$orgid/agbots/$agbotid/agreements/$agreementid1 exists" fi @@ -592,20 +464,4 @@ fi curlputpost "POST" $agbotauth "orgs/$orgid/nodes/$nodeid/msgs" '{"message": "hey there", "ttl": 300}' curlputpost "POST" $nodeauth "orgs/$orgid/agbots/$agbotid/msgs" '{"message": "hey there", "ttl": 300}' -rc=$(curlfind $userauth "orgs/$orgid/bctypes/$bctypeid") -checkrc "$rc" 200 404 -if [[ $rc != 200 ]]; then - curlcreate "PUT" $userauth "orgs/$orgid/bctypes/$bctypeid" '{"description": "abc", "details": "escaped json"}' -else - echo "orgs/$orgid/bctypes/$bctypeid exists" -fi - -rc=$(curlfind $userauth "orgs/$orgid/bctypes/$bctypeid/blockchains/$blockchainid") -checkrc "$rc" 200 404 -if [[ $rc != 200 ]]; then - curlcreate "PUT" $userauth "orgs/$orgid/bctypes/$bctypeid/blockchains/$blockchainid" '{"description": "abc", "public": true, "details": "escaped json"}' -else - echo "orgs/$orgid/bctypes/$bctypeid/blockchains/$blockchainid exists" -fi - echo "All resources added successfully" diff --git a/src/test/bash/put/bctypes/bt1-badinput.sh b/src/test/bash/put/bctypes/bt1-badinput.sh deleted file mode 100755 index 3888a245..00000000 --- a/src/test/bash/put/bctypes/bt1-badinput.sh +++ /dev/null @@ -1,6 +0,0 @@ -# Adds bc type to exchange -source `dirname $0`/../../functions.sh PUT $* - -curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "description": "abc" -}' $EXCHANGE_URL_ROOT/v1/bctypes/bt1 | $parse diff --git a/src/test/bash/put/bctypes/bt1.sh b/src/test/bash/put/bctypes/bt1.sh deleted file mode 100755 index 9b76d034..00000000 --- a/src/test/bash/put/bctypes/bt1.sh +++ /dev/null @@ -1,7 +0,0 @@ -# Adds bc type to exchange -source `dirname $0`/../../functions.sh PUT $* - -curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "description": "abc", - "details": "{\"foo\":\"bar\"}" -}' $EXCHANGE_URL_ROOT/v1/bctypes/bt1 | $parse diff --git a/src/test/bash/put/blockchains/bc1-badinput.sh b/src/test/bash/put/blockchains/bc1-badinput.sh deleted file mode 100755 index cfe0169c..00000000 --- a/src/test/bash/put/blockchains/bc1-badinput.sh +++ /dev/null @@ -1,7 +0,0 @@ -# Adds blockchain to exchange -source `dirname $0`/../../functions.sh PUT $* - -curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "descriptionx": "bc1 desc bad input", - "details": "json escaped string4" -}' $EXCHANGE_URL_ROOT/v1/bctypes/bt1/blockchains/bc1 | $parse diff --git a/src/test/bash/put/blockchains/bc1-root.sh b/src/test/bash/put/blockchains/bc1-root.sh deleted file mode 100755 index ef717039..00000000 --- a/src/test/bash/put/blockchains/bc1-root.sh +++ /dev/null @@ -1,7 +0,0 @@ -# Adds blockchain to exchange -source `dirname $0`/../../functions.sh PUT $* - -curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic root:$EXCHANGE_ROOTPW" -d '{ - "description": "bc1 owned by root", - "details": "json escaped string3" -}' $EXCHANGE_URL_ROOT/v1/bctypes/bt1/blockchains/bc1 | $parse diff --git a/src/test/bash/put/blockchains/bc1-update.sh b/src/test/bash/put/blockchains/bc1-update.sh deleted file mode 100755 index a728d4c4..00000000 --- a/src/test/bash/put/blockchains/bc1-update.sh +++ /dev/null @@ -1,7 +0,0 @@ -# Adds blockchain to exchange -source `dirname $0`/../../functions.sh PUT $* - -curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_ORG/$EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "description": "bc1 desc updated", - "details": "json escaped string2" -}' $EXCHANGE_URL_ROOT/v1/orgs/$EXCHANGE_ORG/bctypes/bt1/blockchains/bc1 | $parse diff --git a/src/test/bash/put/blockchains/bc1-user2.sh b/src/test/bash/put/blockchains/bc1-user2.sh deleted file mode 100755 index 59152915..00000000 --- a/src/test/bash/put/blockchains/bc1-user2.sh +++ /dev/null @@ -1,8 +0,0 @@ -# Adds blockchain to exchange -source `dirname $0`/../../functions.sh PUT $* - -curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_ORG/${EXCHANGE_USER}2:$EXCHANGE_PW" -d '{ - "description": "bc1 desc2", - "public": true, - "details": "json escaped string" -}' $EXCHANGE_URL_ROOT/v1/orgs/$EXCHANGE_ORG/bctypes/bct1/blockchains/bc1 | $parse diff --git a/src/test/bash/put/blockchains/bc1.sh b/src/test/bash/put/blockchains/bc1.sh deleted file mode 100755 index 9d0c2616..00000000 --- a/src/test/bash/put/blockchains/bc1.sh +++ /dev/null @@ -1,8 +0,0 @@ -# Adds blockchain to exchange -source `dirname $0`/../../functions.sh PUT $* - -curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_ORG/$EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "description": "bc1 desc", - "public": true, - "details": "json escaped string" -}' $EXCHANGE_URL_ROOT/v1/orgs/$EXCHANGE_ORG/bctypes/bct1/blockchains/bc1 | $parse diff --git a/src/test/bash/put/microservices/keys/key.sh b/src/test/bash/put/microservices/keys/key.sh deleted file mode 100755 index f7f4b8bf..00000000 --- a/src/test/bash/put/microservices/keys/key.sh +++ /dev/null @@ -1,10 +0,0 @@ -# Adds a service -source `dirname $0`/../../../functions.sh '' '' $* - -curl $copts -X PUT -H 'Content-Type: text/plain' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_ORG/$EXCHANGE_USER:$EXCHANGE_PW" -d '-----BEGIN CERTIFICATE----- -MIII+jCCBOKgAwIBAgIUEfeMrmSFxCUKATcNPcowfs/lU9owDQYJKoZIhvcNAQEL -BQAwJjEMMAoGA1UEChMDaWJtMRYwFAYDVQQDDA1icEB1cy5pYm0uY29tMB4XDTE4 -MDEwMjAxNDkyMFoXDTIyMDEwMjEzNDgzMFowJjEMMAoGA1UEChMDaWJtMRYwFAYD -VQQDDA1icEB1cy5pYm0uY29tMIIEIjANBgkqhkiG9w0BAQEFAAOCBA8AMIIECgKC ------END CERTIFICATE----- -' $EXCHANGE_URL_ROOT/v1/orgs/$EXCHANGE_ORG/microservices/bluehorizon.network-microservices-rtlsdr_2.0.0_amd64/keys/mykey2.pem | $parse diff --git a/src/test/bash/put/microservices/update-2.sh b/src/test/bash/put/microservices/update-2.sh deleted file mode 100755 index 7a699923..00000000 --- a/src/test/bash/put/microservices/update-2.sh +++ /dev/null @@ -1,31 +0,0 @@ -# Updates a microservice as the wrong user -source `dirname $0`/../../functions.sh PUT $* - -curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic 2:$EXCHANGE_PW" -d '{ - "label": "GPS x86_64", - "description": "blah blah", - "specRef": "https://bluehorizon.network/documentation/microservice/gps", - "version": "1.0.0", - "arch": "amd64", - "sharable": "singleton", - "downloadUrl": "this should not work", - "matchHardware": { - "usbnodeIds": "1546:01a7", - "devFiles": "/dev/ttyUSB*" - }, - "userInput": [ - { - "name": "foo", - "label": "The Foo Value", - "type": "string", - "defaultValue": "bar" - } - ], - "workloads": [ - { - "deployment": "{\"services\":{\"gps\":{\"image\":\"summit.hovitos.engineering/x86/gps:2.0.3\",\"privileged\":true,\"nodes\":[\"/dev/bus/usb/001/001:/dev/bus/usb/001/001\"]}}}", - "deployment_signature": "EURzSkDyk66qE6esYUDkLWLzM=", - "torrent": "{\"url\":\"https://images.bluehorizon.network/28f57c.torrent\",\"images\":[{\"file\":\"d98bf.tar.gz\",\"signature\":\"kckH14DUj3bX=\"}]}" - } - ] -}' $EXCHANGE_URL_ROOT/v1/microservices/bluehorizon.network-documentation-microservice-gps_1.0.0_amd64 | $parse diff --git a/src/test/bash/put/microservices/update.sh b/src/test/bash/put/microservices/update.sh deleted file mode 100755 index 5e1d308c..00000000 --- a/src/test/bash/put/microservices/update.sh +++ /dev/null @@ -1,31 +0,0 @@ -# Updates a microservice -source `dirname $0`/../../functions.sh PUT $* - -curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "label": "GPS x86_64", - "description": "blah blah", - "specRef": "https://bluehorizon.network/documentation/microservice/gps", - "version": "1.0.0", - "arch": "amd64", - "sharable": "singleton", - "downloadUrl": "this is not used yet", - "matchHardware": { - "usbnodeIds": "1546:01a7", - "devFiles": "/dev/ttyUSB*" - }, - "userInput": [ - { - "name": "foo", - "label": "The Foo Value", - "type": "string", - "defaultValue": "bar" - } - ], - "workloads": [ - { - "deployment": "{\"services\":{\"gps\":{\"image\":\"summit.hovitos.engineering/x86/gps:2.0.3\",\"privileged\":true,\"nodes\":[\"/dev/bus/usb/001/001:/dev/bus/usb/001/001\"]}}}", - "deployment_signature": "EURzSkDyk66qE6esYUDkLWLzM=", - "torrent": "{\"url\":\"https://images.bluehorizon.network/28f57c.torrent\",\"images\":[{\"file\":\"d98bf.tar.gz\",\"signature\":\"kckH14DUj3bX=\"}]}" - } - ] -}' $EXCHANGE_URL_ROOT/v1/microservices/bluehorizon.network-documentation-microservice-gps_1.0.0_amd64 | $parse diff --git a/src/test/bash/put/nodes/1-user-changes.sh b/src/test/bash/put/nodes/1-user-changes.sh index 1ffe62c2..bb4e949b 100755 --- a/src/test/bash/put/nodes/1-user-changes.sh +++ b/src/test/bash/put/nodes/1-user-changes.sh @@ -4,7 +4,7 @@ source `dirname $0`/../../functions.sh PUT $* curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_ORG/$EXCHANGE_USER:$EXCHANGE_PW" -d '{ "token": "abc123", "name": "rpi1-modified-micros", - "registeredMicroservices": [ + "desiredServices": [ { "url": "https://bluehorizon.network/documentation/sdr-node-api", "numAgreements": 1, diff --git a/src/test/bash/put/nodes/1-user-compatible.sh b/src/test/bash/put/nodes/1-user-compatible.sh index c92e5186..6c782daf 100755 --- a/src/test/bash/put/nodes/1-user-compatible.sh +++ b/src/test/bash/put/nodes/1-user-compatible.sh @@ -4,7 +4,7 @@ source `dirname $0`/../../functions.sh PUT $* curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_ORG/$EXCHANGE_USER:$EXCHANGE_PW" -d '{ "token": "abc123", "name": "rpi1", - "registeredMicroservices": [ + "desiredServices": [ { "url": "https://bluehorizon.network/documentation/sdr-node-api", "numAgreements": 1, diff --git a/src/test/bash/put/nodes/2-memory-400-node.sh b/src/test/bash/put/nodes/2-memory-400-node.sh index d92a5728..42f22a0b 100755 --- a/src/test/bash/put/nodes/2-memory-400-node.sh +++ b/src/test/bash/put/nodes/2-memory-400-node.sh @@ -4,7 +4,7 @@ source `dirname $0`/../../functions.sh PUT $* curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic 2:abcdef" -d '{ "token": "abcdef", "name": "rpi2-node", - "registeredMicroservices": [ + "desiredServices": [ { "url": "https://bluehorizon.network/documentation/sdr-node-api", "numAgreements": 1, diff --git a/src/test/bash/put/nodes/2-memory-400.sh b/src/test/bash/put/nodes/2-memory-400.sh index 19026c72..3fe22edc 100755 --- a/src/test/bash/put/nodes/2-memory-400.sh +++ b/src/test/bash/put/nodes/2-memory-400.sh @@ -4,7 +4,7 @@ source `dirname $0`/../../functions.sh PUT $* curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_USER:$EXCHANGE_PW" -d '{ "token": "abcdef", "name": "rpi2", - "registeredMicroservices": [ + "desiredServices": [ { "url": "https://bluehorizon.network/documentation/sdr-node-api", "numAgreements": 1, diff --git a/src/test/bash/put/nodes/3-version-2-root.sh b/src/test/bash/put/nodes/3-version-2-root.sh index 5953a2f2..97525480 100755 --- a/src/test/bash/put/nodes/3-version-2-root.sh +++ b/src/test/bash/put/nodes/3-version-2-root.sh @@ -4,7 +4,7 @@ source `dirname $0`/../../functions.sh PUT $* curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic root:$EXCHANGE_ROOTPW" -d '{ "token": "def", "name": "rpi3-from-root", - "registeredMicroservices": [ + "desiredServices": [ { "url": "https://bluehorizon.network/documentation/sdr-node-api", "numAgreements": 1, diff --git a/src/test/bash/put/nodes/3-version-2-user.sh b/src/test/bash/put/nodes/3-version-2-user.sh index a9e48071..989a3cd9 100755 --- a/src/test/bash/put/nodes/3-version-2-user.sh +++ b/src/test/bash/put/nodes/3-version-2-user.sh @@ -4,7 +4,7 @@ source `dirname $0`/../../functions.sh PUT $* curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_USER:$EXCHANGE_PW" -d '{ "token": "def", "name": "rpi3", - "registeredMicroservices": [ + "desiredServices": [ { "url": "https://bluehorizon.network/documentation/sdr-node-api", "numAgreements": 1, diff --git a/src/test/bash/put/nodes/4-badbool.sh b/src/test/bash/put/nodes/4-badbool.sh index 4da52060..7d07f8c0 100755 --- a/src/test/bash/put/nodes/4-badbool.sh +++ b/src/test/bash/put/nodes/4-badbool.sh @@ -4,7 +4,7 @@ source `dirname $0`/../../functions.sh PUT $* curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_USER:$EXCHANGE_PW" -d '{ "token": "a", "name": "rpi4", - "registeredMicroservices": [ + "desiredServices": [ { "url": "https://bluehorizon.network/documentation/sdr-node-api", "numAgreements": 1, diff --git a/src/test/bash/put/nodes/4-badint.sh b/src/test/bash/put/nodes/4-badint.sh index bc72501a..0c8d54b6 100755 --- a/src/test/bash/put/nodes/4-badint.sh +++ b/src/test/bash/put/nodes/4-badint.sh @@ -4,7 +4,7 @@ source `dirname $0`/../../functions.sh PUT $* curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_USER:$EXCHANGE_PW" -d '{ "token": "a", "name": "rpi4", - "registeredMicroservices": [ + "desiredServices": [ { "url": "https://bluehorizon.network/documentation/sdr-node-api", "numAgreements": 1, diff --git a/src/test/bash/put/nodes/4-badop.sh b/src/test/bash/put/nodes/4-badop.sh index f5e44d13..a488a1cc 100755 --- a/src/test/bash/put/nodes/4-badop.sh +++ b/src/test/bash/put/nodes/4-badop.sh @@ -4,7 +4,7 @@ source `dirname $0`/../../functions.sh PUT $* curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_USER:$EXCHANGE_PW" -d '{ "token": "a", "name": "rpi4", - "registeredMicroservices": [ + "desiredServices": [ { "url": "https://bluehorizon.network/documentation/sdr-node-api", "numAgreements": 1, diff --git a/src/test/bash/put/nodes/999-badinput.sh b/src/test/bash/put/nodes/999-badinput.sh index dc02af0e..e443b279 100755 --- a/src/test/bash/put/nodes/999-badinput.sh +++ b/src/test/bash/put/nodes/999-badinput.sh @@ -4,7 +4,7 @@ source `dirname $0`/../../functions.sh PUT $* curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_USER:$EXCHANGE_PW" -d '{ "token": "abc123", "foobar": "rpi999", - "registeredMicroservices": [ + "desiredServices": [ { "url": "https://bluehorizon.network/documentation/sdr-node-api", "numAgreements": 1, diff --git a/src/test/bash/put/nodes/status/1-node.sh b/src/test/bash/put/nodes/status/1-node.sh index fe5c2b8f..3c14e9c7 100755 --- a/src/test/bash/put/nodes/status/1-node.sh +++ b/src/test/bash/put/nodes/status/1-node.sh @@ -6,26 +6,10 @@ curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/j "firmware.bluehorizon.network": true, "images.bluehorizon.network": true }, - "microservices": [ - { - "specRef": "https://bluehorizon.network/microservices/gps", - "orgid": "mycompany", - "version": "2.0.4", - "arch": "amd64", - "contanerStatus": [ - { - "name": "/bluehorizon.network-microservices-gps_2.0.4_78a98f1f-2eed-467c-aea2-278fb8161595-gps", - "image": "summit.hovitos.engineering/x86/gps:2.0.4", - "created": 1505939808, - "state": "running" - } - ] - } - ], - "workloads": [ + "services": [ { "agreementId": "78d7912aafb6c11b7a776f77d958519a6dc718b9bd3da36a1442ebb18fe9da30", - "workloadUrl":"https://bluehorizon.network/workloads/location", + "serviceUrl":"https://bluehorizon.network/workloads/location", "orgid":"ling.com", "version":"1.2", "arch":"amd64", diff --git a/src/test/bash/put/nodes/weird-user.sh b/src/test/bash/put/nodes/weird-user.sh index 056ce639..2e168cf1 100755 --- a/src/test/bash/put/nodes/weird-user.sh +++ b/src/test/bash/put/nodes/weird-user.sh @@ -5,7 +5,7 @@ curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/j "token": "abc123", "name": "rpi1", "pattern": "'$EXCHANGE_ORG'/mypat", - "registeredMicroservices": [], + "desiredServices": [], "msgEndPoint": "whisper-id", "softwareVersions": {}, "publicKey": "ABC" diff --git a/src/test/bash/put/workloads/keys/key.sh b/src/test/bash/put/workloads/keys/key.sh deleted file mode 100755 index 3d5ac099..00000000 --- a/src/test/bash/put/workloads/keys/key.sh +++ /dev/null @@ -1,10 +0,0 @@ -# Adds a service -source `dirname $0`/../../../functions.sh '' '' $* - -curl $copts -X PUT -H 'Content-Type: text/plain' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_ORG/$EXCHANGE_USER:$EXCHANGE_PW" -d '-----BEGIN CERTIFICATE----- -MIII+jCCBOKgAwIBAgIUEfeMrmSFxCUKATcNPcowfs/lU9owDQYJKoZIhvcNAQEL -BQAwJjEMMAoGA1UEChMDaWJtMRYwFAYDVQQDDA1icEB1cy5pYm0uY29tMB4XDTE4 -MDEwMjAxNDkyMFoXDTIyMDEwMjEzNDgzMFowJjEMMAoGA1UEChMDaWJtMRYwFAYD -VQQDDA1icEB1cy5pYm0uY29tMIIEIjANBgkqhkiG9w0BAQEFAAOCBA8AMIIECgKC ------END CERTIFICATE----- -' $EXCHANGE_URL_ROOT/v1/orgs/$EXCHANGE_ORG/workloads/bluehorizon.network-workloads-netspeed_1.0.0_amd64/keys/mykey.pem | $parse diff --git a/src/test/bash/put/workloads/update-2.sh b/src/test/bash/put/workloads/update-2.sh deleted file mode 100755 index 67042c16..00000000 --- a/src/test/bash/put/workloads/update-2.sh +++ /dev/null @@ -1,33 +0,0 @@ -# Adds a workload -source `dirname $0`/../../functions.sh PUT $* - -curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic 2:$EXCHANGE_PW" -d '{ - "label": "Location for x86_64", - "description": "blah blah", - "workloadUrl": "https://bluehorizon.network/documentation/workload/location", - "version": "1.0.0", - "arch": "amd64", - "downloadUrl": "this should not work", - "apiSpec": [ - { - "specRef": "https://bluehorizon.network/documentation/microservice/gps", - "version": "1.0.0", - "arch": "amd64" - } - ], - "userInput": [ - { - "name": "foo", - "label": "The Foo Value", - "type": "string", - "defaultValue": "bar" - } - ], - "workloads": [ - { - "deployment": "{\"services\":{\"location\":{\"image\":\"summit.hovitos.engineering/x86/location:2.0.6\",\"environment\":[\"USE_NEW_STAGING_URL=false\"]}}}", - "deployment_signature": "EURzSkDyk66qE6esYUDkLWLzM=", - "torrent": "{\"url\":\"https://images.bluehorizon.network/28f57c.torrent\",\"images\":[{\"file\":\"d98bf.tar.gz\",\"signature\":\"kckH14DUj3bX=\"}]}" - } - ] -}' $EXCHANGE_URL_ROOT/v1/workloads/bluehorizon.network-documentation-workload-location_1.0.0_amd64 | $parse diff --git a/src/test/bash/put/workloads/update.sh b/src/test/bash/put/workloads/update.sh deleted file mode 100755 index fa72d036..00000000 --- a/src/test/bash/put/workloads/update.sh +++ /dev/null @@ -1,33 +0,0 @@ -# Adds a workload -source `dirname $0`/../../functions.sh PUT $* - -curl $copts -X PUT -H 'Content-Type: application/json' -H 'Accept: application/json' -H "Authorization:Basic $EXCHANGE_USER:$EXCHANGE_PW" -d '{ - "label": "Location for x86_64", - "description": "blah blah", - "workloadUrl": "https://bluehorizon.network/documentation/workload/location", - "version": "1.0.0", - "arch": "amd64", - "downloadUrl": "this is not used yet", - "apiSpec": [ - { - "specRef": "https://bluehorizon.network/documentation/microservice/gps", - "version": "1.0.0", - "arch": "amd64" - } - ], - "userInput": [ - { - "name": "foo", - "label": "The Foo Value", - "type": "string", - "defaultValue": "bar" - } - ], - "workloads": [ - { - "deployment": "{\"services\":{\"location\":{\"image\":\"summit.hovitos.engineering/x86/location:2.0.6\",\"environment\":[\"USE_NEW_STAGING_URL=false\"]}}}", - "deployment_signature": "EURzSkDyk66qE6esYUDkLWLzM=", - "torrent": "{\"url\":\"https://images.bluehorizon.network/28f57c.torrent\",\"images\":[{\"file\":\"d98bf.tar.gz\",\"signature\":\"kckH14DUj3bX=\"}]}" - } - ] -}' $EXCHANGE_URL_ROOT/v1/workloads/bluehorizon.network-documentation-workload-location_1.0.0_amd64 | $parse diff --git a/src/test/bash/scale/test.sh b/src/test/bash/scale/test.sh index 5b96583c..fcfa519b 100755 --- a/src/test/bash/scale/test.sh +++ b/src/test/bash/scale/test.sh @@ -205,26 +205,6 @@ bigstart=`date +%s` # Get rid of anything left over from a previous run curldelete $EX_NUM_USERS $rootauth "users/$userbase" "NOT_FOUND_OK" -# testings when adding new rest methods... -#curlcreate "POST" 1 "" "users/$userbase" '{"password": "pw", "email": "foo@gmail.com"}' -#curlcreate "PUT" 1 $userauth "node/$nodebase" '{"token": "'$nodetoken'", "name": "pi", "registeredMicroservices": [{"url": "https://bluehorizon.network/documentation/sdr-node-api", "numAgreements": 1, "policy": "{blob}", "properties": [{"name": "arch", "value": "arm", "propType": "string", "op": "in"},{"name": "version", "value": "1.0", "propType": "version", "op": "in"}]}], "msgEndPoint": "whisper-id", "softwareVersions": {"horizon": "3.2.1"}, "publicKey": "ABC"}' -#curlcreate "PUT" 1 $userauth "agbots/$agbotbase" '{"token": "'$agbottoken'", "name": "agbot", "msgEndPoint": "whisper-id", "publicKey": "ABC"}' -#curlputpost "POST" 2 $agbotauth "node/$nodeid/msgs" '{"message": "hey there", "ttl": 300}' -#curlget 1 $nodeauth "node/$nodeid/msgs" -#deletemsgs $nodeauth "node/$nodeid/msgs" -#curlputpost "POST" 2 $nodeauth "agbots/$agbotid/msgs" '{"message": "hey there", "ttl": 300}' -#curlget 1 $agbotauth "agbots/$agbotid/msgs" -#deletemsgs $agbotauth "agbots/$agbotid/msgs" -#curlcreate "PUT" 1 $userauth "bctypes/$bctypebase" '{"description": "abc", "details": "escaped json"}' -#curlputpost "PUT" 1 $userauth "bctypes/$bctypeid" '{"description": "abc", "details": "escaped json"}' -#curlget 1 $nodeauth bctypes -#curlget 1 $nodeauth bctypes/$bctypeid -#curlcreate "PUT" 1 $userauth "bctypes/$bctypeid/blockchains/$blockchainbase" '{"description": "abc", "details": "escaped json"}' -#curlputpost "PUT" 1 $userauth "bctypes/$bctypeid/blockchains/$blockchainid" '{"description": "abc", "details": "escaped json"}' -#curlget 1 $nodeauth "bctypes/$bctypeid/blockchains" -#curlget 1 $nodeauth "bctypes/$bctypeid/blockchains/$blockchainid" -#exit - # Users ================================================= # Put (create) users u* @@ -245,10 +225,10 @@ curlputpost "POST" $EX_PERF_REPEAT $userauth users/$user/confirm # node ================================================= # Put (create) node d* -curlcreate "PUT" $EX_NUM_node $userauth "node/$nodebase" '{"token": "'$nodetoken'", "name": "pi", "registeredMicroservices": [{"url": "https://bluehorizon.network/documentation/sdr-node-api", "numAgreements": 1, "policy": "{blob}", "properties": [{"name": "arch", "value": "arm", "propType": "string", "op": "in"},{"name": "version", "value": "1.0", "propType": "version", "op": "in"}]}], "msgEndPoint": "whisper-id", "softwareVersions": {"horizon": "3.2.1"}, "publicKey": "ABC"}' +curlcreate "PUT" $EX_NUM_node $userauth "node/$nodebase" '{"token": "'$nodetoken'", "name": "pi", "registeredServices": [{"url": "https://bluehorizon.network/documentation/sdr-node-api", "numAgreements": 1, "policy": "{blob}", "properties": [{"name": "arch", "value": "arm", "propType": "string", "op": "in"},{"name": "version", "value": "1.0", "propType": "version", "op": "in"}]}], "msgEndPoint": "whisper-id", "softwareVersions": {"horizon": "3.2.1"}, "publicKey": "ABC"}' # Put (update) node/d1 -curlputpost "PUT" $EX_PERF_REPEAT $nodeauth "node/$nodeid" '{"token": "'$nodetoken'", "name": "pi", "registeredMicroservices": [{"url": "https://bluehorizon.network/documentation/sdr-node-api", "numAgreements": 1, "policy": "{blob}", "properties": [{"name": "arch", "value": "arm", "propType": "string", "op": "in"},{"name": "version", "value": "1.0", "propType": "version", "op": "in"}]}], "msgEndPoint": "whisper-id", "softwareVersions": {"horizon": "3.2.1"}, "publicKey": "ABC"}' +curlputpost "PUT" $EX_PERF_REPEAT $nodeauth "node/$nodeid" '{"token": "'$nodetoken'", "name": "pi", "registeredServices": [{"url": "https://bluehorizon.network/documentation/sdr-node-api", "numAgreements": 1, "policy": "{blob}", "properties": [{"name": "arch", "value": "arm", "propType": "string", "op": "in"},{"name": "version", "value": "1.0", "propType": "version", "op": "in"}]}], "msgEndPoint": "whisper-id", "softwareVersions": {"horizon": "3.2.1"}, "publicKey": "ABC"}' # Get all node curlget $EX_PERF_REPEAT $userauth node @@ -277,15 +257,15 @@ curlget $EX_PERF_REPEAT $agbotauth agbots/$agbotid curlputpost "POST" $EX_PERF_REPEAT $agbotauth agbots/$agbotid/heartbeat # Post search/node -curlputpost "POST" $EX_PERF_REPEAT $agbotauth search/node '{"desiredMicroservices": [{"url": "https://bluehorizon.network/documentation/sdr-node-api", "properties": [{"name": "arch", "value": "arm", "propType": "wildcard", "op": "in"}, {"name": "version", "value": "*", "propType": "version", "op": "in"}]}], "secondsStale": 0, "propertiesToReturn": ["string"], "startIndex": 0, "numEntries": 0}' +curlputpost "POST" $EX_PERF_REPEAT $agbotauth search/node '{"registeredServices": [{"url": "https://bluehorizon.network/documentation/sdr-node-api", "properties": [{"name": "arch", "value": "arm", "propType": "wildcard", "op": "in"}, {"name": "version", "value": "*", "propType": "version", "op": "in"}]}], "secondsStale": 0, "propertiesToReturn": ["string"], "startIndex": 0, "numEntries": 0}' # Agreements ================================================= # Put (create) node d1 agreements agr* -curlcreate "PUT" $EX_NUM_AGREEMENTS $nodeauth "node/$nodeid/agreements/$agreementbase" '{"microservice": "sdr", "state": "negotiating"}' +curlcreate "PUT" $EX_NUM_AGREEMENTS $nodeauth "node/$nodeid/agreements/$agreementbase" '{"service": "sdr", "state": "negotiating"}' # Put (update) node d1 agreement agr1 -curlputpost "PUT" $EX_PERF_REPEAT $nodeauth "node/$nodeid/agreements/$agreementid" '{"microservice": "sdr", "state": "negotiating"}' +curlputpost "PUT" $EX_PERF_REPEAT $nodeauth "node/$nodeid/agreements/$agreementid" '{"service": "sdr", "state": "negotiating"}' # Get all node d1 agreements curlget $EX_PERF_REPEAT $nodeauth "node/$nodeid/agreements" @@ -294,10 +274,10 @@ curlget $EX_PERF_REPEAT $nodeauth "node/$nodeid/agreements" curlget $EX_PERF_REPEAT $nodeauth "node/$nodeid/agreements/$agreementid" # Put (create) agbot a1 agreements agr* -curlcreate "PUT" $EX_NUM_AGREEMENTS $agbotauth "agbots/$agbotid/agreements/$agreementbase" '{"workload": "sdr-arm.json", "state": "negotiating"}' +curlcreate "PUT" $EX_NUM_AGREEMENTS $agbotauth "agbots/$agbotid/agreements/$agreementbase" '{"service": "sdr-arm.json", "state": "negotiating"}' # Put (update) agbot a1 agreement agr1 -curlputpost "PUT" $EX_PERF_REPEAT $agbotauth "agbots/$agbotid/agreements/$agreementid" '{"workload": "sdr-arm.json", "state": "negotiating"}' +curlputpost "PUT" $EX_PERF_REPEAT $agbotauth "agbots/$agbotid/agreements/$agreementid" '{"service": "sdr-arm.json", "state": "negotiating"}' # Get all agbot a1 agreements curlget $EX_PERF_REPEAT $agbotauth "agbots/$agbotid/agreements" diff --git a/src/test/scala/exchangeapi/AgbotsSuite.scala b/src/test/scala/exchangeapi/AgbotsSuite.scala index 3e220510..27fca344 100644 --- a/src/test/scala/exchangeapi/AgbotsSuite.scala +++ b/src/test/scala/exchangeapi/AgbotsSuite.scala @@ -69,7 +69,6 @@ class AgbotsSuite extends FunSuite { val svcurl = "https://bluehorizon.network/services/netspeed" val svcarch = "amd64" val svcversion = "1.0.0" - val micro = "mymicro" val nodeId = "mynode" val nodeToken = nodeId+"tok" val NODEAUTH = ("Authorization","Basic "+authpref+nodeId+":"+nodeToken) @@ -303,8 +302,8 @@ class AgbotsSuite extends FunSuite { // Note: when we delete the org, this service will get deleted test("POST /orgs/"+orgid+"/patterns/"+pattern+" - add "+pattern+" and check that agbot can read it") { - val input = PostPutPatternRequest(pattern, None, None, None, - Some(List( PServices(svcurl, orgid, svcarch, None, List(PServiceVersions(svcversion, None, None, None, None)), None, None ))), + val input = PostPutPatternRequest(pattern, None, None, + List( PServices(svcurl, orgid, svcarch, None, List(PServiceVersions(svcversion, None, None, None, None)), None, None )), None ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString @@ -352,7 +351,7 @@ class AgbotsSuite extends FunSuite { if (ibmAgbotId != "") { // Also create a node to make sure they can msg each other - val input = PutNodesRequest(nodeToken, "rpi" + nodeId + "-norm", orgid + "/" + pattern, None, None, "", Map(), "NODEABC") + val input = PutNodesRequest(nodeToken, "rpi" + nodeId + "-norm", orgid + "/" + pattern, None, "", Map(), "NODEABC") var response2 = Http(URL + "/nodes/" + nodeId).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: " + response2.code) assert(response2.code === HttpCode.PUT_OK) @@ -371,8 +370,8 @@ class AgbotsSuite extends FunSuite { } test("POST /orgs/"+orgid2+"/patterns/"+pattern2+" - add "+pattern2) { - val input = PostPutPatternRequest(pattern2, None, None, None, - Some(List( PServices(svcurl, orgid, svcarch, None, List(PServiceVersions(svcversion, None, None, None, None)), None, None ))), + val input = PostPutPatternRequest(pattern2, None, None, + List( PServices(svcurl, orgid, svcarch, None, List(PServiceVersions(svcversion, None, None, None, None)), None, None )), None ) val response = Http(URL2+"/patterns/"+pattern2).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH2).asString @@ -381,20 +380,6 @@ class AgbotsSuite extends FunSuite { } // Note: when we delete the org, this pattern will get deleted - test("POST /orgs/"+orgid+"/microservices - add "+micro+" and check that agbot can read it") { - val input = PostPutMicroserviceRequest(micro+" arm", "desc", public = false, "https://msurl", "1.0.0", "arm", "single", None, None, List(Map()), List()) - val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.POST_OK) - - val response2: HttpResponse[String] = Http(URL+"/microservices").headers(ACCEPT).headers(AGBOTAUTH).asString - info("code: "+response2.code) - assert(response2.code === HttpCode.OK) - val respObj = parse(response2.body).extract[GetMicroservicesResponse] - assert(respObj.microservices.size === 1) - } - // Note: when we delete the org, this pattern will get deleted - // Test the pattern sub-resources test("POST /orgs/"+orgid+"/agbots/"+agbotId+"/patterns - as user") { @@ -537,7 +522,7 @@ class AgbotsSuite extends FunSuite { /** Add an agreement for agbot 9930 - as the agbot */ test("PUT /orgs/"+orgid+"/agbots/"+agbotId+"/agreements/"+agreementId+" - as agbot") { - val input = PutAgbotAgreementRequest(None, Some(AAService(orgid, pattern, "sdr")), "signed") + val input = PutAgbotAgreementRequest(AAService(orgid, pattern, "sdr"), "signed") val response = Http(URL+"/agbots/"+agbotId+"/agreements/"+agreementId).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.PUT_OK) @@ -545,7 +530,7 @@ class AgbotsSuite extends FunSuite { /** Update an agreement for agbot 9930 - as the agbot */ test("PUT /orgs/"+orgid+"/agbots/"+agbotId+"/agreements/"+agreementId+" - update as agbot") { - val input = PutAgbotAgreementRequest(None, Some(AAService(orgid, pattern, "sdr")), "finalized") + val input = PutAgbotAgreementRequest(AAService(orgid, pattern, "sdr"), "finalized") val response = Http(URL+"/agbots/"+agbotId+"/agreements/"+agreementId).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.PUT_OK) @@ -553,7 +538,7 @@ class AgbotsSuite extends FunSuite { /** Update the agreement for agbot 9930 - as user */ test("PUT /orgs/"+orgid+"/agbots/"+agbotId+"/agreements/"+agreementId+" - as user") { - val input = PutAgbotAgreementRequest(None, Some(AAService(orgid, pattern, "sdr")), "negotiating") + val input = PutAgbotAgreementRequest(AAService(orgid, pattern, "sdr"), "negotiating") val response = Http(URL+"/agbots/"+agbotId+"/agreements/"+agreementId).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.PUT_OK) @@ -561,7 +546,7 @@ class AgbotsSuite extends FunSuite { /** Add a 2nd agreement for agbot 9930 - as the agbot */ test("PUT /orgs/"+orgid+"/agbots/"+agbotId+"/agreements/9951 - 2nd agreement as agbot") { - val input = PutAgbotAgreementRequest(None, Some(AAService(orgid, pattern, "netspeed")), "signed") + val input = PutAgbotAgreementRequest(AAService(orgid, pattern, "netspeed"), "signed") val response = Http(URL+"/agbots/"+agbotId+"/agreements/9951").postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.PUT_OK) @@ -581,7 +566,7 @@ class AgbotsSuite extends FunSuite { assert(response.code === HttpCode.PUT_OK) // Now try adding another agreement - expect it to be rejected - val input = PutAgbotAgreementRequest(None, Some(AAService(orgid, pattern, "netspeed")), "signed") + val input = PutAgbotAgreementRequest(AAService(orgid, pattern, "netspeed"), "signed") response = Http(URL+"/agbots/"+agbotId+"/agreements/9952").postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.ACCESS_DENIED) diff --git a/src/test/scala/exchangeapi/BlockchainsSuite.scala b/src/test/scala/exchangeapi/BlockchainsSuite.scala deleted file mode 100644 index e8199596..00000000 --- a/src/test/scala/exchangeapi/BlockchainsSuite.scala +++ /dev/null @@ -1,638 +0,0 @@ -package exchangeapi - -import org.scalatest.FunSuite - -import org.junit.runner.RunWith -import org.scalatest.junit.JUnitRunner -import scalaj.http._ -import org.json4s._ -//import org.json4s.JsonDSL._ -import org.json4s.jackson.JsonMethods._ -import org.json4s.native.Serialization.write -import com.horizon.exchangeapi._ -import com.horizon.exchangeapi.tables._ -import scala.collection.immutable._ -import java.time._ -//import java.util.Properties - -/** - * Tests for the /bctypes routes. To run - * the test suite, you can either: - * - run the "test" command in the SBT console - * - right-click the file in eclipse and chose "Run As" - "JUnit Test" - * - * clear and detailed tutorial of FunSuite: http://doc.scalatest.org/1.9.1/index.html#org.scalatest.FunSuite - */ -@RunWith(classOf[JUnitRunner]) -class BlockchainsSuite extends FunSuite { - - val localUrlRoot = "http://localhost:8080" - val urlRoot = sys.env.getOrElse("EXCHANGE_URL_ROOT", localUrlRoot) - val runningLocally = (urlRoot == localUrlRoot) - val ACCEPT = ("Accept","application/json") - val CONTENT = ("Content-Type","application/json") - val orgid = "BlockchainsSuiteTests" - val authpref=orgid+"/" - val URL = urlRoot+"/v1/orgs/"+orgid - val NOORGURL = urlRoot+"/v1" - val user = "9995" - val orguser = authpref+user - val pw = user+"pw" - val USERAUTH = ("Authorization","Basic "+orguser+":"+pw) - val user2 = "9996" - val orguser2 = authpref+user2 - val pw2 = user2+"pw" - val USER2AUTH = ("Authorization","Basic "+orguser2+":"+pw2) - val rootuser = Role.superUser - val rootpw = sys.env.getOrElse("EXCHANGE_ROOTPW", "") // need to put this root pw in config.json - val ROOTAUTH = ("Authorization","Basic "+rootuser+":"+rootpw) - val nodeId = "9910" // the 1st node created, that i will use to run some rest methods - val orgnodeId = authpref+nodeId - val nodeToken = nodeId+"tok" - val NODEAUTH = ("Authorization","Basic "+orgnodeId+":"+nodeToken) - val agbotId = "9945" - val orgagbotId = authpref+agbotId - val agbotToken = agbotId+"tok" - val AGBOTAUTH = ("Authorization","Basic "+orgagbotId+":"+agbotToken) - val bctype = "9920" - val orgbctype = authpref+bctype - val bctype2 = "9921" - val orgbctype2 = authpref+bctype2 - val bctype3 = "9922" - val bctype4 = "9923" - val bcname = "9925" - val bcname2 = "9926" - val bcname3 = "9927" - //var numExistingBctypes = 0 // this will be set later - - implicit val formats = DefaultFormats // Brings in default date formats etc. - - /** Delete all the test users */ - def deleteAllUsers() = { - for (i <- List(user,user2)) { - val response = Http(URL+"/users/"+i).method("delete").headers(ACCEPT).headers(ROOTAUTH).asString - info("DELETE "+i+", code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.DELETED || response.code === HttpCode.NOT_FOUND) - } - } - - /** Create an org to use for this test */ - test("POST /orgs/"+orgid+" - create org") { - // Try deleting it 1st, in case it is left over from previous test - var response = Http(URL).method("delete").headers(ACCEPT).headers(ROOTAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.DELETED || response.code === HttpCode.NOT_FOUND) - - val input = PostPutOrgRequest("My Org", "desc") - response = Http(URL).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(ROOTAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.POST_OK) - } - - /** Delete all the test users, in case they exist from a previous run. Do not need to delete the bctypes and - * blockchains, because they are deleted when the user is deleted. */ - test("Begin - DELETE all test users") { - deleteAllUsers() - } - - /** Add users, node, bctype for future tests */ - test("Add users, node, bctype for future tests") { - var userInput = PostPutUsersRequest(pw, admin = false, user+"@hotmail.com") - var userResponse = Http(URL+"/users/"+user).postData(write(userInput)).method("post").headers(CONTENT).headers(ACCEPT).headers(ROOTAUTH).asString - info("code: "+userResponse.code+", userResponse.body: "+userResponse.body) - assert(userResponse.code === HttpCode.POST_OK) - - userInput = PostPutUsersRequest(pw2, admin = false, user2+"@hotmail.com") - userResponse = Http(URL+"/users/"+user2).postData(write(userInput)).method("post").headers(CONTENT).headers(ACCEPT).headers(ROOTAUTH).asString - info("code: "+userResponse.code+", userResponse.body: "+userResponse.body) - assert(userResponse.code === HttpCode.POST_OK) - - val devInput = PutNodesRequest(nodeToken, "bc dev test", "", None, Some(List(RegService("foo",1,"{}",List( - Prop("arch","arm","string","in"), - Prop("version","2.0.0","version","in"), - Prop("blockchainProtocols","agProto","list","in"))))), "whisper-id", Map(), "NODEABC") - val devResponse = Http(URL+"/nodes/"+nodeId).postData(write(devInput)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+devResponse.code) - assert(devResponse.code === HttpCode.PUT_OK) - - val agbotInput = PutAgbotsRequest(agbotToken, "agbot"+agbotId+"-norm", /*List[APattern](),*/ "whisper-id", "ABC") - val agbotResponse = Http(URL+"/agbots/"+agbotId).postData(write(agbotInput)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+agbotResponse.code+", agbotResponse.body: "+agbotResponse.body) - assert(agbotResponse.code === HttpCode.PUT_OK) - } - - /* - test("GET /orgs/"+orgid+"/bctypes - get initial number of bctypes in the db") { - val response: HttpResponse[String] = Http(URL+"/bctypes").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val getBctypeResp = parse(response.body).extract[GetBctypesResponse] - numExistingBctypes = getBctypeResp.bctypes.size - info("initially "+numExistingBctypes+" bctypes") - } - */ - - test("PUT /orgs/"+orgid+"/bctypes/"+bctype+" - add as user") { - val input = PutBctypeRequest(bctype+" desc", "json escaped string") - val response = Http(URL+"/bctypes/"+bctype).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.PUT_OK) - } - - test("PUT /orgs/"+orgid+"/bctypes/"+bctype+" - update as same user") { - val input = PutBctypeRequest(bctype+" new desc", "json escaped string") - val response = Http(URL+"/bctypes/"+bctype).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.PUT_OK) - } - - test("PUT /orgs/"+orgid+"/bctypes/"+bctype+" - update as 2nd user - should fail") { - val input = PutBctypeRequest(bctype+" yet another desc", "json escaped string") - val response = Http(URL+"/bctypes/"+bctype).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.ACCESS_DENIED) - } - - test("PUT /orgs/"+orgid+"/bctypes/"+bctype2+" - invalid bctype body") { - val badJsonInput = """{ - "description": "foo" - }""" - val response = Http(URL+"/bctypes/"+bctype2).postData(badJsonInput).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.BAD_INPUT) // for now this is what is returned when the json-to-scala conversion fails - } - - test("PUT /orgs/"+orgid+"/bctypes/"+bctype2+" - as node - should fail") { - val input = PutBctypeRequest(bctype2+" desc", "json escaped string") - val response = Http(URL+"/bctypes/"+bctype2).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(NODEAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.ACCESS_DENIED) - } - - test("PUT /orgs/"+orgid+"/bctypes/"+bctype2+" - as agbot - should fail") { - val input = PutBctypeRequest(bctype2+" desc", "json escaped string") - val response = Http(URL+"/bctypes/"+bctype2).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.ACCESS_DENIED) - } - - test("PUT /orgs/"+orgid+"/bctypes/"+bctype2+" - add bctype2 as 2nd user") { - val input = PutBctypeRequest(bctype2+" desc", "json escaped string") - val response = Http(URL+"/bctypes/"+bctype2).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.PUT_OK) - } - - test("PUT /orgs/"+orgid+"/bctypes/"+bctype3+" - with low maxBlockchains - should fail") { - 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 origMaxBlockchains = ExchConfig.getInt("api.limits.maxBlockchains") - - // Change the maxBlockchains config value in the svr - var configInput = AdminConfigRequest("api.limits.maxBlockchains", "1") // user only owns 1 currently - var 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) - - // Add 1 bctype - should succeed - var input = PutBctypeRequest(bctype3+" desc", "json escaped string") - response = Http(URL+"/bctypes/"+bctype3).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.PUT_OK) - - // Now try adding another bctype - expect it to be rejected - input = PutBctypeRequest(bctype4+" desc", "json escaped string") - response = Http(URL+"/bctypes/"+bctype4).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.ACCESS_DENIED) - val respObj = parse(response.body).extract[ApiResponse] - assert(respObj.msg.contains("Access Denied")) - - // Delete the one that succeeded - response = Http(URL+"/bctypes/"+bctype3).method("delete").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.DELETED) - - // Restore the maxBlockchains config value in the svr - configInput = AdminConfigRequest("api.limits.maxBlockchains", origMaxBlockchains.toString) - 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) - } - } - - test("GET /orgs/"+orgid+"/bctypes") { - val response: HttpResponse[String] = Http(URL+"/bctypes").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val getBctypeResp = parse(response.body).extract[GetBctypesResponse] - assert(getBctypeResp.bctypes.size === 2) - - assert(getBctypeResp.bctypes.contains(orgbctype)) - var bt = getBctypeResp.bctypes(orgbctype) // the 2nd get turns the Some(val) into val - assert(bt.description === bctype+" new desc") - assert(bt.definedBy === orguser) - - assert(getBctypeResp.bctypes.contains(orgbctype2)) - bt = getBctypeResp.bctypes(orgbctype2) // the 2nd get turns the Some(val) into val - assert(bt.description === bctype2+" desc") - assert(bt.definedBy === orguser2) - } - - test("GET /orgs/"+orgid+"/bctypes - filter owner and description") { - val response: HttpResponse[String] = Http(URL+"/bctypes").headers(ACCEPT).headers(USERAUTH).param("owner",orguser2).param("description",bctype2+"%").asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val getBctypeResp = parse(response.body).extract[GetBctypesResponse] - assert(getBctypeResp.bctypes.size === 1) - assert(getBctypeResp.bctypes.contains(orgbctype2)) - } - - test("GET /orgs/"+orgid+"/bctypes - as node") { - val response: HttpResponse[String] = Http(URL+"/bctypes").headers(ACCEPT).headers(NODEAUTH).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val getBctypeResp = parse(response.body).extract[GetBctypesResponse] - assert(getBctypeResp.bctypes.size === 2) - } - - test("GET /orgs/"+orgid+"/bctypes - as agbot") { - val response: HttpResponse[String] = Http(URL+"/bctypes").headers(ACCEPT).headers(AGBOTAUTH).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val getBctypeResp = parse(response.body).extract[GetBctypesResponse] - assert(getBctypeResp.bctypes.size === 2) - } - - test("GET /orgs/"+orgid+"/bctypes/"+bctype+" - as user") { - val response: HttpResponse[String] = Http(URL+"/bctypes/"+bctype).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val getBctypeResp = parse(response.body).extract[GetBctypesResponse] - assert(getBctypeResp.bctypes.size === 1) - - assert(getBctypeResp.bctypes.contains(orgbctype)) - val bt = getBctypeResp.bctypes(orgbctype) // the 2nd get turns the Some(val) into val - assert(bt.description === bctype+" new desc") - - // Verify the lastHeartbeat from the POST heartbeat above is within a few seconds of now. Format is: 2016-09-29T13:04:56.850Z[UTC] - val now: Long = System.currentTimeMillis / 1000 // seconds since 1/1/1970 - val lastUp = ZonedDateTime.parse(bt.lastUpdated).toEpochSecond - assert(now - lastUp <= 5) // should not now be more than 3 seconds from the time the heartbeat was done above - } - - test("GET /orgs/"+orgid+"/bctypes/"+bctype+" - as node") { // will do agbot soon - val response: HttpResponse[String] = Http(URL+"/bctypes/"+bctype).headers(ACCEPT).headers(NODEAUTH).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val getBctypeResp = parse(response.body).extract[GetBctypesResponse] - assert(getBctypeResp.bctypes.size === 1) - } - - test("PATCH /orgs/"+orgid+"/bctypes/"+bctype+" - as user") { - val newDesc = "\""+bctype+" patched desc\"" - val jsonInput = """{ - "description": """+newDesc+""" - }""" - val response = Http(URL+"/bctypes/"+bctype).postData(jsonInput).method("patch").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.PUT_OK) - } - - test("PATCH /orgs/"+orgid+"/bctypes/"+bctype+" - as user2 - should fail") { - val newDesc = "\""+bctype+" patched2 desc\"" - val jsonInput = """{ - "description": """+newDesc+""" - }""" - val response = Http(URL+"/bctypes/"+bctype).postData(jsonInput).method("patch").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.ACCESS_DENIED) - } - - test("GET /orgs/"+orgid+"/bctypes/"+bctype+" - as agbot, check patch by getting that 1 attr") { - val response: HttpResponse[String] = Http(URL+"/bctypes/"+bctype).headers(ACCEPT).headers(AGBOTAUTH).param("attribute","description").asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val getBctypeResp = parse(response.body).extract[GetBctypeAttributeResponse] - assert(getBctypeResp.attribute === "description") - assert(getBctypeResp.value === bctype+" patched desc") - } - - test("GET /orgs/"+orgid+"/bctypes/"+bctype+"notthere - as user - should fail") { - val response: HttpResponse[String] = Http(URL+"/bctypes/"+bctype+"notthere").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.NOT_FOUND) - val getBctypeResp = parse(response.body).extract[GetBctypesResponse] - assert(getBctypeResp.bctypes.size === 0) - } - - //======== Blockchain tests ============== - - test("GET /orgs/"+orgid+"/bctypes/"+bctype+"/blockchains - none there yet") { - val response: HttpResponse[String] = Http(URL+"/bctypes/"+bctype+"/blockchains").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.NOT_FOUND) - val getBcResp = parse(response.body).extract[GetBlockchainsResponse] - assert(getBcResp.blockchains.size === 0) - } - - test("GET /orgs/"+orgid+"/bctypes/"+bctype+"/blockchains/"+bcname+" - not there yet") { - val response: HttpResponse[String] = Http(URL+"/bctypes/"+bctype+"/blockchains/"+bcname).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.NOT_FOUND) - val getBcResp = parse(response.body).extract[GetBlockchainsResponse] - assert(getBcResp.blockchains.size === 0) - } - - test("PUT /orgs/"+orgid+"/bctypes/"+bctype+"/blockchains/"+bcname+" - add as user") { - val input = PutBlockchainRequest(bctype+"-"+bcname+" desc", public = true, "json escaped string") - val response = Http(URL+"/bctypes/"+bctype+"/blockchains/"+bcname).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.PUT_OK) - } - - test("PUT /orgs/"+orgid+"/bctypes/"+bctype+"/blockchains/"+bcname+" - update as same user") { - val input = PutBlockchainRequest(bctype+"-"+bcname+" new desc", public = true, "json escaped string") - val response = Http(URL+"/bctypes/"+bctype+"/blockchains/"+bcname).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.PUT_OK) - } - - test("PUT /orgs/"+orgid+"/bctypes/"+bctype+"/blockchains/"+bcname+" - update as 2nd user - should fail") { - val input = PutBlockchainRequest(bctype+"-"+bcname+" yet another desc", public = true, "json escaped string") - val response = Http(URL+"/bctypes/"+bctype+"/blockchains/"+bcname).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.ACCESS_DENIED) - } - - test("PUT /orgs/"+orgid+"/bctypes/"+bctype+"/blockchains/"+bcname2+" - invalid blockchain body") { - val badJsonInput = """{ - "descriptionx": "foo", - "details": "json escaped string" - }""" - val response = Http(URL+"/bctypes/"+bctype+"/blockchains/"+bcname2).postData(badJsonInput).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.BAD_INPUT) - } - - test("PUT /orgs/"+orgid+"/bctypes/"+bctype+"/blockchains/"+bcname2+" - as node - should fail") { - val input = PutBlockchainRequest(bctype+"-"+bcname2+" desc", public = true, "json escaped string") - val response = Http(URL+"/bctypes/"+bctype+"/blockchains/"+bcname2).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(NODEAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.ACCESS_DENIED) - } - - test("PUT /orgs/"+orgid+"/bctypes/"+bctype+"/blockchains/"+bcname2+" - as agbot - should fail") { - val input = PutBlockchainRequest(bctype+"-"+bcname2+" desc", public = true, "json escaped string") - val response = Http(URL+"/bctypes/"+bctype+"/blockchains/"+bcname2).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.ACCESS_DENIED) - } - - test("PUT /orgs/"+orgid+"/bctypes/"+bctype+"/blockchains/"+bcname2+" - add bcname2 as user2") { - val input = PutBlockchainRequest(bctype+"-"+bcname2+" desc", public = true, "json escaped string") - val response = Http(URL+"/bctypes/"+bctype+"/blockchains/"+bcname2).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.PUT_OK) - } - - test("PUT /orgs/"+orgid+"/bctypes/"+bctype2+"/blockchains/"+bcname2+" - as user2 - and duplicate bcname should be ok") { - val input = PutBlockchainRequest(bctype2+"-"+bcname2+" desc", public = true, "json escaped string") - val response = Http(URL+"/bctypes/"+bctype2+"/blockchains/"+bcname2).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.PUT_OK) - } - - test("PUT /orgs/"+orgid+"/bctypes/"+bctype+"/blockchains/"+bcname3+" - with low maxBlockchains - should fail") { - 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 earlier - val origMaxBlockchains = ExchConfig.getInt("api.limits.maxBlockchains") - - // Change the maxBlockchains config value in the svr - var configInput = AdminConfigRequest("api.limits.maxBlockchains", "1") // user owns 2 currently - var 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) - - // Now try adding another blockchain - expect it to be rejected - val input = PutBlockchainRequest(bctype+"-"+bcname3+" desc", public = true, "json escaped string") - response = Http(URL+"/bctypes/"+bctype+"/blockchains/"+bcname3).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.ACCESS_DENIED) - val respObj = parse(response.body).extract[ApiResponse] - assert(respObj.msg.contains("Access Denied")) - - // Restore the maxBlockchains config value in the svr - configInput = AdminConfigRequest("api.limits.maxBlockchains", origMaxBlockchains.toString) - 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) - } - } - - test("GET /orgs/"+orgid+"/bctypes/"+bctype+"/blockchains") { - val response: HttpResponse[String] = Http(URL+"/bctypes/"+bctype+"/blockchains").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.OK) - val getBcResp = parse(response.body).extract[GetBlockchainsResponse] - assert(getBcResp.blockchains.size === 2) - - assert(getBcResp.blockchains.contains(bcname)) - var bc = getBcResp.blockchains(bcname) // the 2nd get turns the Some(val) into val - assert(bc.description === bctype+"-"+bcname+" new desc") - assert(bc.definedBy === orguser) - - assert(getBcResp.blockchains.contains(bcname2)) - bc = getBcResp.blockchains(bcname2) // the 2nd get turns the Some(val) into val - assert(bc.description === bctype+"-"+bcname2+" desc") - assert(bc.definedBy === orguser2) - } - - test("GET /orgs/"+orgid+"/bctypes/"+bctype+"/blockchains - filter owner and description") { - val response: HttpResponse[String] = Http(URL+"/bctypes/"+bctype+"/blockchains").headers(ACCEPT).headers(USERAUTH).param("owner",orguser2).param("description",bctype2+"%").asString - info("code: "+response.code) - assert(response.code === HttpCode.OK) - val getBcResp = parse(response.body).extract[GetBlockchainsResponse] - assert(getBcResp.blockchains.size === 2) - assert(getBcResp.blockchains.contains(bcname2)) - } - - test("GET /orgs/"+orgid+"/bctypes/"+bctype+"/blockchains - as node") { - val response: HttpResponse[String] = Http(URL+"/bctypes/"+bctype+"/blockchains").headers(ACCEPT).headers(NODEAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.OK) - val getBcResp = parse(response.body).extract[GetBlockchainsResponse] - assert(getBcResp.blockchains.size === 2) - } - - // GET /orgs/"+orgid+"/bctypes/{bctype}/blockchains - as agbot is done below - - test("GET /orgs/"+orgid+"/bctypes/"+bctype+"/blockchains/"+bcname+" - as user") { - val response: HttpResponse[String] = Http(URL+"/bctypes/"+bctype+"/blockchains/"+bcname).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.OK) - val getBcResp = parse(response.body).extract[GetBlockchainsResponse] - assert(getBcResp.blockchains.size === 1) - - assert(getBcResp.blockchains.contains(bcname)) - val bc = getBcResp.blockchains(bcname) // the 2nd get turns the Some(val) into val - assert(bc.description === bctype+"-"+bcname+" new desc") - - // Verify the lastHeartbeat from the POST heartbeat above is within a few seconds of now. Format is: 2016-09-29T13:04:56.850Z[UTC] - val now: Long = System.currentTimeMillis / 1000 // seconds since 1/1/1970 - val lastUp = ZonedDateTime.parse(bc.lastUpdated).toEpochSecond - assert(now - lastUp <= 5) // should not now be more than 3 seconds from the time the heartbeat was done above - } - - test("GET /orgs/"+orgid+"/bctypes/"+bctype+"/blockchains/"+bcname+" - as node") { - val response: HttpResponse[String] = Http(URL+"/bctypes/"+bctype+"/blockchains/"+bcname).headers(ACCEPT).headers(NODEAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.OK) - val getBcResp = parse(response.body).extract[GetBlockchainsResponse] - assert(getBcResp.blockchains.size === 1) - } - - test("PATCH /orgs/"+orgid+"/bctypes/"+bctype+"/blockchains/"+bcname+" - as user") { - val newDesc = "\""+bctype+"-"+bcname+" patched desc\"" - val jsonInput = """{ - "description": """+newDesc+""" - }""" - val response = Http(URL+"/bctypes/"+bctype+"/blockchains/"+bcname).postData(jsonInput).method("patch").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.PUT_OK) - } - - test("PATCH /orgs/"+orgid+"/bctypes/"+bctype+"/blockchains/"+bcname+" - as user2 - should fail") { - val newDesc = "\""+bctype+"-"+bcname+" patched2 desc\"" - val jsonInput = """{ - "description": """+newDesc+""" - }""" - val response = Http(URL+"/bctypes/"+bctype+"/blockchains/"+bcname).postData(jsonInput).method("patch").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.ACCESS_DENIED) - } - - test("GET /orgs/"+orgid+"/bctypes/"+bctype+"/blockchains/"+bcname+" - as agbot, check patch by getting that 1 attr") { - val response: HttpResponse[String] = Http(URL+"/bctypes/"+bctype+"/blockchains/"+bcname).headers(ACCEPT).headers(AGBOTAUTH).param("attribute","description").asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val getBcResp = parse(response.body).extract[GetBlockchainAttributeResponse] - assert(getBcResp.attribute === "description") - assert(getBcResp.value === bctype+"-"+bcname+" patched desc") - } - - test("GET /orgs/"+orgid+"/bctypes/"+bctype+"/blockchains/"+bcname+"notthere - as user - should fail") { - val response: HttpResponse[String] = Http(URL+"/bctypes/"+bctype+"/blockchains/"+bcname+"notthere").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.NOT_FOUND) - val getBcResp = parse(response.body).extract[GetBlockchainsResponse] - assert(getBcResp.blockchains.size === 0) - } - - test("DELETE /orgs/"+orgid+"/bctypes/"+bctype+"/blockchains/"+bcname+" - as node - should fail") { - val response = Http(URL+"/bctypes/"+bctype+"/blockchains/"+bcname).method("delete").headers(ACCEPT).headers(NODEAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.ACCESS_DENIED) - } - - test("DELETE /orgs/"+orgid+"/bctypes/"+bctype+"/blockchains/"+bcname+" - as user2 - should fail") { - val response = Http(URL+"/bctypes/"+bctype+"/blockchains/"+bcname).method("delete").headers(ACCEPT).headers(USER2AUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.ACCESS_DENIED) - } - - test("GET /orgs/"+orgid+"/bctypes/"+bctype+"/blockchains/"+bcname+" - as agbot - verify still there") { - val response: HttpResponse[String] = Http(URL+"/bctypes/"+bctype+"/blockchains/"+bcname).headers(ACCEPT).headers(AGBOTAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.OK) - val getBcResp = parse(response.body).extract[GetBlockchainsResponse] - assert(getBcResp.blockchains.size === 1) - } - - test("DELETE /orgs/"+orgid+"/bctypes/"+bctype+"/blockchains/"+bcname+" - as user") { - val response = Http(URL+"/bctypes/"+bctype+"/blockchains/"+bcname).method("delete").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.DELETED) - } - - test("GET /orgs/"+orgid+"/bctypes/"+bctype+"/blockchains/"+bcname+" - as node - verify gone") { - val response: HttpResponse[String] = Http(URL+"/bctypes/"+bctype+"/blockchains/"+bcname).headers(ACCEPT).headers(NODEAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.NOT_FOUND) - val getBcResp = parse(response.body).extract[GetBlockchainsResponse] - assert(getBcResp.blockchains.size === 0) - } - - test("DELETE /orgs/"+orgid+"/bctypes/"+bctype+" - which should also delete bcname2") { - val response = Http(URL+"/bctypes/"+bctype).method("delete").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.DELETED) - } - - test("GET /orgs/"+orgid+"/bctypes/"+bctype+"/blockchains/"+bcname2+" - as user - verify gone") { - val response: HttpResponse[String] = Http(URL+"/bctypes/"+bctype+"/blockchains/"+bcname2).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.NOT_FOUND) - val getBcResp = parse(response.body).extract[GetBlockchainsResponse] - assert(getBcResp.blockchains.size === 0) - } - - test("GET /orgs/"+orgid+"/bctypes/"+bctype+" - as user - verify gone") { - val response: HttpResponse[String] = Http(URL+"/bctypes/"+bctype).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.NOT_FOUND) - val getBctypeResp = parse(response.body).extract[GetBctypesResponse] - assert(getBctypeResp.bctypes.size === 0) - } - - test("DELETE /orgs/"+orgid+"/users/"+user2+" - which should also delete bctype2 and bcname2") { - val response = Http(URL+"/users/"+user2).method("delete").headers(ACCEPT).headers(ROOTAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.DELETED) - } - - test("GET /orgs/"+orgid+"/bctypes/"+bctype2+"/blockchains/"+bcname2+" - as user - verify gone") { - val response: HttpResponse[String] = Http(URL+"/bctypes/"+bctype2+"/blockchains/"+bcname2).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.NOT_FOUND) - val getBcResp = parse(response.body).extract[GetBlockchainsResponse] - assert(getBcResp.blockchains.size === 0) - } - - test("GET /orgs/"+orgid+"/bctypes/"+bctype2+" - as user - verify gone") { - val response: HttpResponse[String] = Http(URL+"/bctypes/"+bctype2).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.NOT_FOUND) - val getBctypeResp = parse(response.body).extract[GetBctypesResponse] - assert(getBctypeResp.bctypes.size === 0) - } - - /** Clean up, delete all the test bctypes */ - test("Cleanup - DELETE all test bctypes and blockchains") { - deleteAllUsers() - } - - /** Delete the org we used for this test */ - test("POST /orgs/"+orgid+" - delete org") { - // Try deleting it 1st, in case it is left over from previous test - val response = Http(URL).method("delete").headers(ACCEPT).headers(ROOTAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.DELETED) - } - -} \ No newline at end of file diff --git a/src/test/scala/exchangeapi/FrontEndSuite.scala b/src/test/scala/exchangeapi/FrontEndSuite.scala index af69ab80..5a66b64b 100644 --- a/src/test/scala/exchangeapi/FrontEndSuite.scala +++ b/src/test/scala/exchangeapi/FrontEndSuite.scala @@ -52,8 +52,6 @@ class FrontEndSuite extends FunSuite { val agProto = "ExchangeAutomatedTest" val msBase = "ms1" val msUrl = "http://" + msBase - val microservice = msBase + "_1.0.0_arm" - val orgmicroservice = authpref+microservice val svcid = "bluehorizon.network-services-sdr_1.0.0_amd64" val svcurl = "https://bluehorizon.network/services/sdr" val svcarch = "amd64" @@ -127,25 +125,6 @@ class FrontEndSuite extends FunSuite { assert(response.code === HttpCode.POST_OK) } - test("POST /orgs/"+orgid+"/microservices - create "+microservice) { - val input = PostPutMicroserviceRequest(msBase+" arm", "desc", public = false, msUrl, "1.0.0", "arm", "single", None, None, List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a",""))) - val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(TYPEUSER).headers(IDUSER).headers(ORGHEAD).headers(ISSUERHEAD).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.POST_OK) - val respObj = parse(response.body).extract[ApiResponse] - assert(respObj.msg.contains("microservice '"+orgmicroservice+"' created")) - } - - test("GET /orgs/"+orgid+"/microservices") { - val response: HttpResponse[String] = Http(URL+"/microservices").headers(ACCEPT).headers(TYPEAPIKEY).headers(IDAPIKEY).headers(ORGHEAD).headers(ISSUERHEAD).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val respObj = parse(response.body).extract[GetMicroservicesResponse] - assert(respObj.microservices.size === 1) - assert(respObj.microservices.contains(orgmicroservice)) - } - test("POST /orgs/"+orgid+"/services - create "+svcid) { val input = PostPutServiceRequest("test-service", None, public = false, svcurl, svcversion, svcarch, "multiple", None, None, None, "", "", None) val response = Http(URL+"/services").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(TYPEUSER).headers(IDUSER).headers(ORGHEAD).headers(ISSUERHEAD).asString @@ -165,8 +144,8 @@ class FrontEndSuite extends FunSuite { } test("POST /orgs/"+orgid+"/patterns/"+pattern+" - create "+pattern) { - val input = PostPutPatternRequest(ptBase, None, None, None, - Some(List( PServices(svcurl, orgid, svcarch, None, List(PServiceVersions(svcversion, None, None, None, None)), Some(Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]())), Some(Map("check_agreement_status" -> 120)) ))), + val input = PostPutPatternRequest(ptBase, None, None, + List( PServices(svcurl, orgid, svcarch, None, List(PServiceVersions(svcversion, None, None, None, None)), Some(Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]())), Some(Map("check_agreement_status" -> 120)) )), None ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(TYPEUSER).headers(IDUSER).headers(ORGHEAD).headers(ISSUERHEAD).asString @@ -186,7 +165,7 @@ class FrontEndSuite extends FunSuite { } test("PUT /orgs/"+orgid+"/nodes/"+nodeId+" - create node") { - val input = PutNodesRequest(nodeToken, nodeId+"-normal", orgpattern, None, + val input = PutNodesRequest(nodeToken, nodeId+"-normal", orgpattern, Some(List( RegService(SDRSPEC,1,"{json policy for "+nodeId+" pws}",List( Prop("arch","arm","string","in"), @@ -201,7 +180,7 @@ class FrontEndSuite extends FunSuite { } test("PUT /orgs/"+orgid+"/nodes/"+nodeId+" - update node") { - val input = PutNodesRequest(nodeToken, nodeId+"-update", orgpattern, None, + val input = PutNodesRequest(nodeToken, nodeId+"-update", orgpattern, Some(List( RegService(SDRSPEC,1,"{json policy for "+nodeId+" pws}",List( Prop("arch","arm","string","in"), @@ -257,7 +236,7 @@ class FrontEndSuite extends FunSuite { } test("POST /orgs/"+orgid+"/patterns/"+pattern+"/search - for "+SDRSPEC) { - val input = PostPatternSearchRequest(None, Some(SDRSPEC), None, 86400, 0, 0) + val input = PostPatternSearchRequest(SDRSPEC, None, 86400, 0, 0) val response = Http(URL+"/patterns/"+pattern+"/search").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(TYPEUSER).headers(IDUSER).headers(ORGHEAD).headers(ISSUERHEAD).asString info("code: "+response.code+", response.body: "+response.body) info("code: "+response.code) @@ -268,20 +247,20 @@ class FrontEndSuite extends FunSuite { assert(nodes.count(d => d.id==orgnodeId) === 1) } - test("PATCH /orgs/"+orgid+"/nodes/"+nodeId+" - remove pattern from node so we can search for microservices") { + test("PATCH /orgs/"+orgid+"/nodes/"+nodeId+" - remove pattern from node so we can search for services") { val jsonInput = """{ "pattern": "" }""" val response = Http(URL+"/nodes/"+nodeId).postData(jsonInput).method("patch").headers(CONTENT).headers(ACCEPT).headers(TYPEUSER).headers(IDUSER).headers(ORGHEAD).headers(ISSUERHEAD).asString assert(response.code === HttpCode.PUT_OK) } test("POST /orgs/"+orgid+"/search/nodes/ - for "+SDRSPEC) { - val input = PostSearchNodesRequest(None, Some(List(RegServiceSearch(SDRSPEC,List( + val input = PostSearchNodesRequest(List(RegServiceSearch(SDRSPEC,List( Prop("arch","arm","string","in"), Prop("memory","2","int",">="), Prop("version","*","version","in"), Prop("agreementProtocols",agProto,"list","in"), - Prop("dataVerification","","wildcard","="))))), - 86400, List[String](""), 0, 0) + Prop("dataVerification","","wildcard","=")))), + 86400, None, 0, 0) val response = Http(URL+"/search/nodes").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(TYPEUSER).headers(IDUSER).headers(ORGHEAD).headers(ISSUERHEAD).asString info("code: "+response.code) assert(response.code === HttpCode.POST_OK) @@ -292,14 +271,14 @@ class FrontEndSuite extends FunSuite { } test("PUT /orgs/"+orgid+"/nodes/"+nodeId+"/agreements/"+agreementId+" - create node agreement") { - val input = PutNodeAgreementRequest(None, None, Some(List(NAService(orgid,SDRSPEC))), Some(NAgrService("","","")), "signed") + val input = PutNodeAgreementRequest(Some(List(NAService(orgid,SDRSPEC))), Some(NAgrService("","","")), "signed") val response = Http(URL+"/nodes/"+nodeId+"/agreements/"+agreementId).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(TYPENODE).headers(IDNODE).headers(ORGHEAD).headers(ISSUERHEAD).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.PUT_OK) } test("PUT /orgs/"+orgid+"/nodes/"+nodeId+"/agreements/"+agreementId+" - update node agreement") { - val input = PutNodeAgreementRequest(None, None, Some(List(NAService(orgid,SDRSPEC))), Some(NAgrService("","","")), "finalized") + val input = PutNodeAgreementRequest(Some(List(NAService(orgid,SDRSPEC))), Some(NAgrService("","","")), "finalized") val response = Http(URL+"/nodes/"+nodeId+"/agreements/"+agreementId).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(TYPENODE).headers(IDNODE).headers(ORGHEAD).headers(ISSUERHEAD).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.PUT_OK) diff --git a/src/test/scala/exchangeapi/MicroservicesSuite.scala b/src/test/scala/exchangeapi/MicroservicesSuite.scala deleted file mode 100644 index d543c27c..00000000 --- a/src/test/scala/exchangeapi/MicroservicesSuite.scala +++ /dev/null @@ -1,473 +0,0 @@ -package exchangeapi - -import java.time._ - -import com.horizon.exchangeapi._ -import com.horizon.exchangeapi.tables._ -import org.json4s._ -import org.json4s.jackson.JsonMethods._ -import org.json4s.native.Serialization.write -import org.junit.runner.RunWith -import org.scalatest.FunSuite -import org.scalatest.junit.JUnitRunner - -import scala.collection.immutable._ -import scalaj.http._ - -/** - * Tests for the /microservices routes. To run - * the test suite, you can either: - * - run the "test" command in the SBT console - * - right-click the file in eclipse and chose "Run As" - "JUnit Test" - * - * clear and detailed tutorial of FunSuite: http://doc.scalatest.org/1.9.1/index.html#org.scalatest.FunSuite - */ -@RunWith(classOf[JUnitRunner]) -class MicroservicesSuite extends FunSuite { - - val localUrlRoot = "http://localhost:8080" - val urlRoot = sys.env.getOrElse("EXCHANGE_URL_ROOT", localUrlRoot) - val runningLocally = (urlRoot == localUrlRoot) - val ACCEPT = ("Accept","application/json") - val ACCEPTTEXT = ("Accept","text/plain") - val CONTENT = ("Content-Type","application/json") - val CONTENTTEXT = ("Content-Type","text/plain") - val orgid = "MicroservicesSuiteTests" - val authpref=orgid+"/" - val URL = urlRoot+"/v1/orgs/"+orgid - //val NOORGURL = urlRoot+"/v1" - val user = "9997" - val orguser = authpref+user - val pw = user+"pw" - val USERAUTH = ("Authorization","Basic "+orguser+":"+pw) - val user2 = "9998" - val orguser2 = authpref+user2 - val pw2 = user2+"pw" - val USER2AUTH = ("Authorization","Basic "+orguser2+":"+pw2) - val rootuser = Role.superUser - val rootpw = sys.env.getOrElse("EXCHANGE_ROOTPW", "") // need to put this root pw in config.json - val ROOTAUTH = ("Authorization","Basic "+rootuser+":"+rootpw) - val nodeId = "9911" // the 1st node created, that i will use to run some rest methods - val nodeToken = nodeId+"tok" - val NODEAUTH = ("Authorization","Basic "+authpref+nodeId+":"+nodeToken) - val agbotId = "9946" - val agbotToken = agbotId+"tok" - val AGBOTAUTH = ("Authorization","Basic "+authpref+agbotId+":"+agbotToken) - val msBase = "ms9920" - val msUrl = "http://" + msBase - val microservice = msBase + "_1.0.0_arm" - val orgmicroservice = authpref+microservice - val msBase2 = "ms9921" - val msUrl2 = "http://" + msBase2 - val microservice2 = msBase2 + "_1.0.0_arm" - val orgmicroservice2 = authpref+microservice2 - val msBase3 = "ms9922" - val msUrl3 = "http://" + msBase3 - val microservice3 = msBase3 + "_1.0.0_arm" - val keyId = "mykey.pem" - val key = "abcdefghijk" - val keyId2 = "mykey2.pem" - val key2 = "lnmopqrstuvwxyz" - //val orgmicroservice3 = authpref+microservice3 - //var numExistingMicroservices = 0 // this will be set later - - implicit val formats = DefaultFormats // Brings in default date formats etc. - - /** Delete all the test users */ - def deleteAllUsers() = { - for (i <- List(user,user2)) { - val response = Http(URL+"/users/"+i).method("delete").headers(ACCEPT).headers(ROOTAUTH).asString - info("DELETE "+i+", code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.DELETED || response.code === HttpCode.NOT_FOUND) - } - } - - /** Create an org to use for this test */ - test("POST /orgs/"+orgid+" - create org") { - // Try deleting it 1st, in case it is left over from previous test - var response = Http(URL).method("delete").headers(ACCEPT).headers(ROOTAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.DELETED || response.code === HttpCode.NOT_FOUND) - - val input = PostPutOrgRequest("My Org", "desc") - response = Http(URL).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(ROOTAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.POST_OK) - } - - /** Delete all the test users, in case they exist from a previous run. Do not need to delete the microservices, because they are deleted when the user is deleted. */ - test("Begin - DELETE all test users") { - if (rootpw == "") fail("The exchange root password must be set in EXCHANGE_ROOTPW and must also be put in config.json.") - deleteAllUsers() - } - - /** Add users, node, microservice for future tests */ - test("Add users, node, agbot for future tests") { - var userInput = PostPutUsersRequest(pw, admin = false, user+"@hotmail.com") - var userResponse = Http(URL+"/users/"+user).postData(write(userInput)).method("post").headers(CONTENT).headers(ACCEPT).headers(ROOTAUTH).asString - info("code: "+userResponse.code+", userResponse.body: "+userResponse.body) - assert(userResponse.code === HttpCode.POST_OK) - - userInput = PostPutUsersRequest(pw2, admin = false, user2+"@hotmail.com") - userResponse = Http(URL+"/users/"+user2).postData(write(userInput)).method("post").headers(CONTENT).headers(ACCEPT).headers(ROOTAUTH).asString - info("code: "+userResponse.code+", userResponse.body: "+userResponse.body) - assert(userResponse.code === HttpCode.POST_OK) - - val devInput = PutNodesRequest(nodeToken, "bc dev test", "", None, Some(List(RegService("foo",1,"{}",List( - Prop("arch","arm","string","in"), - Prop("version","2.0.0","version","in"), - Prop("blockchainProtocols","agProto","list","in"))))), "whisper-id", Map(), "NODEABC") - val devResponse = Http(URL+"/nodes/"+nodeId).postData(write(devInput)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+devResponse.code) - assert(devResponse.code === HttpCode.PUT_OK) - - val agbotInput = PutAgbotsRequest(agbotToken, "agbot"+agbotId+"-norm", /*List[APattern](),*/ "whisper-id", "ABC") - val agbotResponse = Http(URL+"/agbots/"+agbotId).postData(write(agbotInput)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+agbotResponse.code+", agbotResponse.body: "+agbotResponse.body) - assert(agbotResponse.code === HttpCode.PUT_OK) - } - - test("PUT /orgs/"+orgid+"/microservices/"+microservice+" - update MS that is not there yet - should fail") { - val input = PostPutMicroserviceRequest(msBase+" arm", "desc", public = false, msUrl, "1.0.0", "arm", "single", Some("updated"), Some(Map("usbNodeIds" -> "1546:01a7")), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a",""))) - val response = Http(URL+"/microservices/"+microservice).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.NOT_FOUND) - } - - test("POST /orgs/"+orgid+"/microservices - add "+microservice+" that is not signed - should fail") { - val input = PostPutMicroserviceRequest(msBase+" arm", "desc", public = false, msUrl, "1.0.0", "arm", "single", None, Some(Map("usbNodeIds" -> "1546:01a7")), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","","a"))) - val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.BAD_INPUT) - } - - test("POST /orgs/"+orgid+"/microservices - add "+microservice+" as user, and omit matchHardware") { - val input = PostPutMicroserviceRequest(msBase+" arm", "desc", public = false, msUrl, "1.0.0", "arm", "single", None, None, List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) - val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.POST_OK) - val respObj = parse(response.body).extract[ApiResponse] - assert(respObj.msg.contains("microservice '"+orgmicroservice+"' created")) - } - - test("POST /orgs/"+orgid+"/microservices - add "+microservice+" again - should fail") { - val input = PostPutMicroserviceRequest(msBase+" arm", "desc", public = false, msUrl, "1.0.0", "arm", "single", None, Some(Map("usbNodeIds" -> "1546:01a7")), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) - val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.ALREADY_EXISTS) - } - - test("PUT /orgs/"+orgid+"/microservices/"+microservice+" - update as same user") { - val input = PostPutMicroserviceRequest(msBase+" arm", "desc", public = false, msUrl, "1.0.0", "arm", "single", Some("updated"), Some(Map("usbNodeIds" -> "1546:01a7")), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) - val response = Http(URL+"/microservices/"+microservice).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.PUT_OK) - } - - test("PUT /orgs/"+orgid+"/microservices/"+microservice+" - update as 2nd user - should fail") { - val input = PostPutMicroserviceRequest(msBase+" arm", "desc", public = false, msUrl, "1.0.0", "arm", "single", Some("should not work"), Some(Map("usbNodeIds" -> "1546:01a7")), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) - val response = Http(URL+"/microservices/"+microservice).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.ACCESS_DENIED) - } - - test("PUT /orgs/"+orgid+"/microservices/"+microservice+" - update as agbot - should fail") { - val input = PostPutMicroserviceRequest(msBase+" arm", "desc", public = false, msUrl, "1.0.0", "arm", "single", None, Some(Map("usbNodeIds" -> "1546:01a7")), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) - val response = Http(URL+"/microservices/"+microservice).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.ACCESS_DENIED) - } - - test("PUT /orgs/"+orgid+"/microservices/"+microservice2+" - invalid microservice body") { - val badJsonInput = """{ - "labelxx": "GPS x86_64" - }""" - val response = Http(URL+"/microservices/"+microservice2).postData(badJsonInput).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.BAD_INPUT) - } - - test("POST /orgs/"+orgid+"/microservices - add "+microservice2+" as node - should fail") { - val input = PostPutMicroserviceRequest(msBase2+" arm", "desc", public = false, msUrl2, "1.0.0", "arm", "single", None, Some(Map("usbNodeIds" -> "1546:01a7")), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) - val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(NODEAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.ACCESS_DENIED) - } - - test("POST /orgs/"+orgid+"/microservices - add "+microservice2+" as 2nd user") { - val input = PostPutMicroserviceRequest(msBase2+" arm", "desc", public = true, msUrl2, "1.0.0", "arm", "single", None, Some(Map("usbNodeIds" -> "1546:01a7")), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) - val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.POST_OK) - } - -/*todo: when all test suites are run at the same time, there are sometimes timing problems with them all setting config values... -test("POST /orgs/"+orgid+"/microservices - with low maxMicroservices - should fail") { - 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 origMaxMicroservices = ExchConfig.getInt("api.limits.maxMicroservices") - - // Change the maxMicroservices config value in the svr - var configInput = AdminConfigRequest("api.limits.maxMicroservices", "0") // user only owns 1 currently - var response = Http(URL+"/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) - - // Now try adding another microservice - expect it to be rejected - val input = PostPutMicroserviceRequest(msBase3+" arm", "desc", msUrl3, "1.0.0", "arm", "single", None, Some(Map("usbNodeIds" -> "1546:01a7")), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) - response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.ACCESS_DENIED) - val respObj = parse(response.body).extract[ApiResponse] - assert(respObj.msg.contains("Access Denied")) - - // Restore the maxMicroservices config value in the svr - configInput = AdminConfigRequest("api.limits.maxMicroservices", origMaxMicroservices.toString) - response = Http(URL+"/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) - } -} -*/ - -test("GET /orgs/"+orgid+"/microservices") { - val response: HttpResponse[String] = Http(URL+"/microservices").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val respObj = parse(response.body).extract[GetMicroservicesResponse] - assert(respObj.microservices.size === 2) - - assert(respObj.microservices.contains(orgmicroservice)) - var ms = respObj.microservices(orgmicroservice) // the 2nd get turns the Some(val) into val - assert(ms.label === msBase+" arm") - assert(ms.owner === orguser) - - assert(respObj.microservices.contains(orgmicroservice2)) - ms = respObj.microservices(orgmicroservice2) // the 2nd get turns the Some(val) into val - assert(ms.label === msBase2+" arm") - assert(ms.owner === orguser2) -} - - test("GET /orgs/"+orgid+"/microservices - filter owner and specRef") { - val response: HttpResponse[String] = Http(URL+"/microservices").headers(ACCEPT).headers(USERAUTH).param("owner",orguser2).param("specRef",msUrl2+"%").asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val respObj = parse(response.body).extract[GetMicroservicesResponse] - assert(respObj.microservices.size === 1) - assert(respObj.microservices.contains(orgmicroservice2)) - } - - test("GET /orgs/"+orgid+"/microservices - filter by public setting") { - // Find the public==true microservices - var response: HttpResponse[String] = Http(URL+"/microservices").headers(ACCEPT).headers(USERAUTH).param("public","true").asString - info("code: "+response.code) - assert(response.code === HttpCode.OK) - var respObj = parse(response.body).extract[GetMicroservicesResponse] - assert(respObj.microservices.size === 1) - assert(respObj.microservices.contains(orgmicroservice2)) - - // Find the public==false microservices - response = Http(URL+"/microservices").headers(ACCEPT).headers(USERAUTH).param("public","false").asString - info("code: "+response.code) - assert(response.code === HttpCode.OK) - respObj = parse(response.body).extract[GetMicroservicesResponse] - assert(respObj.microservices.size === 1) - assert(respObj.microservices.contains(orgmicroservice)) - } - -test("GET /orgs/"+orgid+"/microservices - as node") { - val response: HttpResponse[String] = Http(URL+"/microservices").headers(ACCEPT).headers(NODEAUTH).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val respObj = parse(response.body).extract[GetMicroservicesResponse] - assert(respObj.microservices.size === 2) -} - -test("GET /orgs/"+orgid+"/microservices - as agbot") { - val response: HttpResponse[String] = Http(URL+"/microservices").headers(ACCEPT).headers(AGBOTAUTH).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val respObj = parse(response.body).extract[GetMicroservicesResponse] - assert(respObj.microservices.size === 2) -} - -test("GET /orgs/"+orgid+"/microservices/"+microservice+" - as user") { - val response: HttpResponse[String] = Http(URL+"/microservices/"+microservice).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val respObj = parse(response.body).extract[GetMicroservicesResponse] - assert(respObj.microservices.size === 1) - - assert(respObj.microservices.contains(orgmicroservice)) - val ms = respObj.microservices(orgmicroservice) // the 2nd get turns the Some(val) into val - assert(ms.label === msBase+" arm") - - // Verify the lastUpdated from the PUT above is within a few seconds of now. Format is: 2016-09-29T13:04:56.850Z[UTC] - val now: Long = System.currentTimeMillis / 1000 // seconds since 1/1/1970 - val lastUp = ZonedDateTime.parse(ms.lastUpdated).toEpochSecond - assert(now - lastUp <= 5) // should not be more than 3 seconds from the time the put was done above -} - -test("PATCH /orgs/"+orgid+"/microservices/"+microservice+" - as user") { - val jsonInput = """{ - "downloadUrl": "this is now patched" - }""" - val response = Http(URL+"/microservices/"+microservice).postData(jsonInput).method("patch").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.PUT_OK) -} - -test("PATCH /orgs/"+orgid+"/microservices/"+microservice+" - as user2 - should fail") { - val jsonInput = """{ - "downloadUrl": "this is now patched" - }""" - val response = Http(URL+"/microservices/"+microservice).postData(jsonInput).method("patch").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.ACCESS_DENIED) -} - -test("GET /orgs/"+orgid+"/microservices/"+microservice+" - as agbot, check patch by getting that 1 attr") { - val response: HttpResponse[String] = Http(URL+"/microservices/"+microservice).headers(ACCEPT).headers(AGBOTAUTH).param("attribute","downloadUrl").asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val respObj = parse(response.body).extract[GetMicroserviceAttributeResponse] - assert(respObj.attribute === "downloadUrl") - assert(respObj.value === "this is now patched") -} - -test("GET /orgs/"+orgid+"/microservices/"+microservice+"notthere - as user - should fail") { - val response: HttpResponse[String] = Http(URL+"/microservices/"+microservice+"notthere").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.NOT_FOUND) - val getMicroserviceResp = parse(response.body).extract[GetMicroservicesResponse] - assert(getMicroserviceResp.microservices.size === 0) -} - - - // Key tests ============================================== - test("GET /orgs/"+orgid+"/microservices/"+microservice+"/keys - no keys have been created yet - should fail") { - val response: HttpResponse[String] = Http(URL+"/microservices/"+microservice+"/keys").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.NOT_FOUND) - val resp = parse(response.body).extract[List[String]] - assert(resp.size === 0) - } - - test("PUT /orgs/"+orgid+"/microservices/"+microservice+"/keys/"+keyId+" - add "+keyId+" as user") { - //val input = PutMicroserviceKeyRequest(key) - val response = Http(URL+"/microservices/"+microservice+"/keys/"+keyId).postData(key).method("put").headers(CONTENTTEXT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.POST_OK) - } - - test("PUT /orgs/"+orgid+"/microservices/"+microservice+"/keys/"+keyId2+" - add "+keyId2+" as user") { - //val input = PutMicroserviceKeyRequest(key2) - val response = Http(URL+"/microservices/"+microservice+"/keys/"+keyId2).postData(key2).method("put").headers(CONTENTTEXT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.POST_OK) - } - - test("GET /orgs/"+orgid+"/microservices/"+microservice+"/keys - should be 2 now") { - val response: HttpResponse[String] = Http(URL+"/microservices/"+microservice+"/keys").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.OK) - val resp = parse(response.body).extract[List[String]] - assert(resp.size === 2) - assert(resp.contains(keyId) && resp.contains(keyId2)) - } - - test("GET /orgs/"+orgid+"/microservices/"+microservice+"/keys/"+keyId+" - get 1 of the keys and check content") { - val response: HttpResponse[String] = Http(URL+"/microservices/"+microservice+"/keys/"+keyId).headers(ACCEPTTEXT).headers(USERAUTH).asString - //val response: HttpResponse[Array[Byte]] = Http(URL+"/microservices/"+microservice+"/keys/"+keyId).headers(ACCEPTTEXT).headers(USERAUTH).asBytes - //val bodyStr = (response.body.map(_.toChar)).mkString - //info("code: "+response.code+", response.body: "+bodyStr) - info("code: "+response.code) - assert(response.code === HttpCode.OK) - assert(response.body === key) - } - - test("DELETE /orgs/"+orgid+"/microservices/"+microservice+"/keys/"+keyId) { - val response: HttpResponse[String] = Http(URL+"/microservices/"+microservice+"/keys/"+keyId).method("delete").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.DELETED) - } - - test("DELETE /orgs/"+orgid+"/microservices/"+microservice+"/keys/"+keyId+" try deleting it again - should fail") { - val response: HttpResponse[String] = Http(URL+"/microservices/"+microservice+"/keys/"+keyId).method("delete").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.NOT_FOUND) - } - - test("GET /orgs/"+orgid+"/microservices/"+microservice+"/keys/"+keyId+" - verify it is gone") { - val response: HttpResponse[String] = Http(URL+"/microservices/"+microservice+"/keys/"+keyId).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.NOT_FOUND) - } - - test("DELETE /orgs/"+orgid+"/microservices/"+microservice+"/keys - delete all keys") { - val response: HttpResponse[String] = Http(URL+"/microservices/"+microservice+"/keys").method("delete").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.DELETED) - } - - test("GET /orgs/"+orgid+"/microservices/"+microservice+"/keys - all keys should be gone now") { - val response: HttpResponse[String] = Http(URL+"/microservices/"+microservice+"/keys").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.NOT_FOUND) - val resp = parse(response.body).extract[List[String]] - assert(resp.size === 0) - } - - - test("DELETE /orgs/"+orgid+"/microservices/"+microservice) { - val response = Http(URL+"/microservices/"+microservice).method("delete").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.DELETED) -} - -test("GET /orgs/"+orgid+"/microservices/"+microservice+" - as user - verify gone") { - val response: HttpResponse[String] = Http(URL+"/microservices/"+microservice).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.NOT_FOUND) - val getMicroserviceResp = parse(response.body).extract[GetMicroservicesResponse] - assert(getMicroserviceResp.microservices.size === 0) -} - -test("DELETE /orgs/"+orgid+"/users/"+user2+" - which should also delete microservice2") { - val response = Http(URL+"/users/"+user2).method("delete").headers(ACCEPT).headers(ROOTAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.DELETED) -} - -test("GET /orgs/"+orgid+"/microservices/"+microservice2+" - as user - verify gone") { - val response: HttpResponse[String] = Http(URL+"/microservices/"+microservice2).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.NOT_FOUND) - val getMicroserviceResp = parse(response.body).extract[GetMicroservicesResponse] - assert(getMicroserviceResp.microservices.size === 0) -} - -/** Clean up, delete all the test microservices */ -test("Cleanup - DELETE all test microservices") { - deleteAllUsers() -} - - /** Delete the org we used for this test */ - test("POST /orgs/"+orgid+" - delete org") { - // Try deleting it 1st, in case it is left over from previous test - val response = Http(URL).method("delete").headers(ACCEPT).headers(ROOTAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.DELETED) - } - -} \ No newline at end of file diff --git a/src/test/scala/exchangeapi/NodesSuite.scala b/src/test/scala/exchangeapi/NodesSuite.scala index b8e08e47..8b9ff331 100644 --- a/src/test/scala/exchangeapi/NodesSuite.scala +++ b/src/test/scala/exchangeapi/NodesSuite.scala @@ -33,8 +33,6 @@ class NodesSuite extends FunSuite { val CONTENT = ("Content-Type","application/json") val SDRSPEC = "https://bluehorizon.network/services/sdr" val NETSPEEDSPEC = "https://bluehorizon.network/services/netspeed/" // test the trailing / for this one - val OLDSDRSPEC = "https://bluehorizon.network/workloads/sdr" - val OLDNETSPEEDSPEC = "https://bluehorizon.network/workloads/netspeed" val PWSSPEC = "https://bluehorizon.network/services/pws" val NOTTHERESPEC = "https://bluehorizon.network/services/notthere" val orgid = "NodesSuiteTests" @@ -69,16 +67,8 @@ class NodesSuite extends FunSuite { val orgnodeId4 = authpref+nodeId4 val nodeId5 = "n5" // not ever successfully created val orgnodeId5 = authpref+nodeId5 - val oldNodeId = "oldn1" // with registered microservices, instead of services - val orgOldNodeId = authpref+oldNodeId val patid = "p1" val compositePatid = orgid+"/"+patid - val patid2 = "p2" // used by the old style node - val compositePatid2 = orgid+"/"+patid2 - val workid = "bluehorizon.network-workloads-sdr_1.0.0_arm" - val workurl = OLDSDRSPEC - val workarch = "arm" - val workversion = "1.0.0" val svcid = "bluehorizon.network-services-sdr_1.0.0_amd64" val svcurl = SDRSPEC val svcarch = "amd64" @@ -159,7 +149,7 @@ class NodesSuite extends FunSuite { } } - //~~~~~ Create org, user, workload, pattern ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + //~~~~~ Create org, user, service, pattern ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Delete all the test orgs (and everything under them), in case they exist from a previous run. test("Begin - DELETE all test orgs") { @@ -197,7 +187,7 @@ class NodesSuite extends FunSuite { test("PUT /orgs/"+orgid+"/nodes/"+nodeId+" - before pattern exists - should fail") { val input = PutNodesRequest(nodeToken, "rpi"+nodeId+"-norm", compositePatid, - None, None, + None, "whisper-id", Map("horizon"->"3.2.3"), "NODEABC") val response = Http(URL+"/nodes/"+nodeId).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code) @@ -217,15 +207,15 @@ class NodesSuite extends FunSuite { info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.POST_OK) } - // Note: when we delete the org, this workload will get deleted + // Note: when we delete the org, this service will get deleted test("POST /orgs/"+orgid+"/patterns/"+patid+" - so nodes can reference it") { - val input = PostPutPatternRequest(patid, None, None, None, - Some(List( + val input = PostPutPatternRequest(patid, None, None, + List( // Reference both services in the pattern so we can search on both later on PServices(svcurl, orgid, svcarch, None, List(PServiceVersions(svcversion, None, None, None, None)), None, None ), PServices(svcurl2, orgid, svcarch2, Some(true), List(PServiceVersions(svcversion2, None, None, None, None)), None, None ) - )), + ), None ) val response = Http(URL+"/patterns/"+patid).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString @@ -238,7 +228,7 @@ class NodesSuite extends FunSuite { ExchConfig.load() test("PUT /orgs/"+orgid+"/nodes/"+nodeId+" - add normal node as user") { - val input = PutNodesRequest(nodeToken, "rpi"+nodeId+"-norm", compositePatid, None, + val input = PutNodesRequest(nodeToken, "rpi"+nodeId+"-norm", compositePatid, Some(List( RegService(PWSSPEC,1,"{json policy for "+nodeId+" pws}",List( Prop("arch","arm","string","in"), @@ -257,14 +247,14 @@ class NodesSuite extends FunSuite { } test("PUT /orgs/"+orgid2+"/nodes/"+nodeId+" - add node in 2nd org") { - val input = PutNodesRequest(nodeToken, "rpi"+nodeId+"-norm", compositePatid, None, None, "whisper-id", Map("horizon"->"3.2.3"), "NODEABCORG2") + val input = PutNodesRequest(nodeToken, "rpi"+nodeId+"-norm", compositePatid, None, "whisper-id", Map("horizon"->"3.2.3"), "NODEABCORG2") val response = Http(URL2+"/nodes/"+nodeId).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH2).asString info("code: "+response.code) assert(response.code === HttpCode.PUT_OK) } test("PUT /orgs/"+orgid+"/nodes/"+nodeId+" - normal - update as user") { - val input = PutNodesRequest(nodeToken, "rpi"+nodeId+"-normal-user", compositePatid, None, + val input = PutNodesRequest(nodeToken, "rpi"+nodeId+"-normal-user", compositePatid, Some(List( RegService(PWSSPEC,1,"{json policy for "+nodeId+" pws}",List( Prop("arch","arm","string","in"), @@ -283,7 +273,7 @@ class NodesSuite extends FunSuite { } test("PUT /orgs/"+orgid+"/nodes/"+nodeId+" - normal update - as node") { - val input = PutNodesRequest(nodeToken, "rpi"+nodeId+"-normal", compositePatid, None, + val input = PutNodesRequest(nodeToken, "rpi"+nodeId+"-normal", compositePatid, Some(List( RegService(SDRSPEC,1,"{json policy for "+nodeId+" sdr}",List( Prop("arch","arm","string","in"), @@ -303,7 +293,7 @@ class NodesSuite extends FunSuite { } test("PUT /orgs/"+orgid+"/nodes/"+nodeId2+" - node with higher memory 400, and version 2.0.0") { - val input = PutNodesRequest(nodeToken, "rpi"+nodeId2+"-mem-400-vers-2", compositePatid, None, Some(List(RegService(SDRSPEC,1,"{json policy for "+nodeId2+" sdr}",List( + val input = PutNodesRequest(nodeToken, "rpi"+nodeId2+"-mem-400-vers-2", compositePatid, Some(List(RegService(SDRSPEC,1,"{json policy for "+nodeId2+" sdr}",List( Prop("arch","arm","string","in"), Prop("memory","400","int",">="), Prop("version","2.0.0","version","in"), @@ -315,7 +305,7 @@ class NodesSuite extends FunSuite { } test("PUT /orgs/"+orgid+"/nodes/"+nodeId3+" - netspeed-amd64, but no publicKey at 1st") { - val input = PutNodesRequest(nodeToken, "rpi"+nodeId3+"-netspeed-amd64", compositePatid, None, Some(List(RegService(NETSPEEDSPEC,1,"{json policy for "+nodeId3+" netspeed}",List( + val input = PutNodesRequest(nodeToken, "rpi"+nodeId3+"-netspeed-amd64", compositePatid, Some(List(RegService(NETSPEEDSPEC,1,"{json policy for "+nodeId3+" netspeed}",List( Prop("arch","amd64","string","in"), Prop("memory","300","int",">="), Prop("version","1.0.0","version","in"), @@ -327,7 +317,7 @@ class NodesSuite extends FunSuite { } test("PUT /orgs/"+orgid+"/nodes/"+nodeId4+" - bad integer property") { - val input = PutNodesRequest(nodeToken, "rpi"+nodeId4+"-bad-int", compositePatid, None, Some(List(RegService(SDRSPEC,1,"{json policy for "+nodeId4+" sdr}",List( + val input = PutNodesRequest(nodeToken, "rpi"+nodeId4+"-bad-int", compositePatid, Some(List(RegService(SDRSPEC,1,"{json policy for "+nodeId4+" sdr}",List( Prop("arch","arm","string","in"), Prop("memory","400MB","int",">="), Prop("version","2.0.0","version","in"), @@ -367,7 +357,7 @@ class NodesSuite extends FunSuite { } test("PUT /orgs/"+orgid+"/nodes/"+nodeId4+" - bad svc url, but this is currently allowed") { - val input = PutNodesRequest(nodeToken, "rpi"+nodeId4+"-bad-url", compositePatid, None, Some(List(RegService(NOTTHERESPEC,1,"{json policy for "+nodeId4+" sdr}",List( + val input = PutNodesRequest(nodeToken, "rpi"+nodeId4+"-bad-url", compositePatid, Some(List(RegService(NOTTHERESPEC,1,"{json policy for "+nodeId4+" sdr}",List( Prop("arch","arm","string","in"), Prop("memory","400","int",">="), Prop("version","2.0.0","version","in"), @@ -600,7 +590,7 @@ class NodesSuite extends FunSuite { //~~~~~ Pattern search and nodehealth ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ test("POST /orgs/"+orgid+"/patterns/"+patid+"/search - for "+SDRSPEC+" - as agbot - should not find "+nodeId3+" because no publicKey") { - val input = PostPatternSearchRequest(None, Some(SDRSPEC), Some(List(orgid,orgid2)), 86400, 0, 0) + val input = PostPatternSearchRequest(SDRSPEC, Some(List(orgid,orgid2)), 86400, 0, 0) val response = Http(URL+"/patterns/"+patid+"/search").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString info("code: "+response.code+", response.body: "+response.body) info("code: "+response.code) @@ -614,7 +604,7 @@ class NodesSuite extends FunSuite { } test("POST /orgs/"+orgid+"/patterns/"+patid+"/search - for "+PWSSPEC+" which is not in the pattern, so should fail") { - val input = PostPatternSearchRequest(None, Some(PWSSPEC), None, 86400, 0, 0) + val input = PostPatternSearchRequest(PWSSPEC, None, 86400, 0, 0) val response = Http(URL+"/patterns/"+patid+"/search").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString info("code: "+response.code+", response.body: "+response.body) //info("code: "+response.code) @@ -657,13 +647,13 @@ class NodesSuite extends FunSuite { test("POST /orgs/"+orgid+"/search/nodes - all arm nodes") { patchNodePattern("") // remove pattern from nodes so we can search for services - val input = PostSearchNodesRequest(None, Some(List(RegServiceSearch(SDRSPEC,List( + val input = PostSearchNodesRequest(List(RegServiceSearch(SDRSPEC,List( Prop("arch","arm","string","in"), Prop("memory","2","int",">="), Prop("version","*","version","in"), Prop("agreementProtocols",agProto,"list","in"), - Prop("dataVerification","","wildcard","="))))), - 86400, List[String](""), 0, 0) + Prop("dataVerification","","wildcard","=")))), + 86400, None, 0, 0) val response = Http(URL+"/search/nodes").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code) assert(response.code === HttpCode.POST_OK) @@ -683,13 +673,13 @@ class NodesSuite extends FunSuite { } test("POST /orgs/"+orgid+"/search/nodes - netspeed arch amd64 - as agbot") { - val input = PostSearchNodesRequest(None, Some(List(RegServiceSearch(NETSPEEDSPEC,List( + val input = PostSearchNodesRequest(List(RegServiceSearch(NETSPEEDSPEC,List( Prop("arch","amd64","string","in"), Prop("memory","*","int",">="), Prop("version","[1.0.0,2.0.0]","version","in"), Prop("agreementProtocols",agProto,"list","in"), - Prop("dataVerification","","wildcard","="))))), - 86400, List[String](""), 0, 0) + Prop("dataVerification","","wildcard","=")))), + 86400, None, 0, 0) val response = Http(URL+"/search/nodes").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString info("code: "+response.code) assert(response.code === HttpCode.POST_OK) @@ -700,13 +690,13 @@ class NodesSuite extends FunSuite { } test("POST /orgs/"+orgid+"/search/nodes - netspeed arch * - as agbot") { - val input = PostSearchNodesRequest(None, Some(List(RegServiceSearch(NETSPEEDSPEC,List( + val input = PostSearchNodesRequest(List(RegServiceSearch(NETSPEEDSPEC,List( Prop("arch","*","string","in"), Prop("memory","*","int",">="), Prop("version","[1.0.0,2.0.0]","version","in"), Prop("agreementProtocols",agProto,"list","in"), - Prop("dataVerification","","wildcard","="))))), - 86400, List[String](""), 0, 0) + Prop("dataVerification","","wildcard","=")))), + 86400, None, 0, 0) val response = Http(URL+"/search/nodes").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString info("code: "+response.code) assert(response.code === HttpCode.POST_OK) @@ -718,7 +708,7 @@ class NodesSuite extends FunSuite { } test("POST /orgs/"+orgid+"/search/nodes - netspeed and sdr - as agbot") { - val input = PostSearchNodesRequest(None, Some(List( + val input = PostSearchNodesRequest(List( RegServiceSearch(NETSPEEDSPEC,List( Prop("arch","*","string","in"), Prop("memory","*","int",">="), @@ -730,8 +720,8 @@ class NodesSuite extends FunSuite { Prop("memory","2","int",">="), Prop("version","*","version","in"), Prop("agreementProtocols",agProto,"list","in"), - Prop("dataVerification","","wildcard","="))) - )), 86400, List[String](""), 0, 0) + Prop("dataVerification","","wildcard","=")) + )), 86400, None, 0, 0) val response = Http(URL+"/search/nodes").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString info("code: "+response.code) assert(response.code === HttpCode.POST_OK) @@ -746,13 +736,13 @@ class NodesSuite extends FunSuite { } test("POST /orgs/"+orgid+"/search/nodes - arch list, mem 400, version 2.0.0") { - val input = PostSearchNodesRequest(None, Some(List(RegServiceSearch(SDRSPEC,List( + val input = PostSearchNodesRequest(List(RegServiceSearch(SDRSPEC,List( Prop("arch","arm,amd64","list","in"), Prop("memory","400","int",">="), Prop("version","2.0.0,3.0.0","version","in"), Prop("agreementProtocols",agProto,"list","in"), - Prop("dataVerification","true","boolean","="))))), - 86400, List[String](""), 0, 0) + Prop("dataVerification","true","boolean","=")))), + 86400, None, 0, 0) val response = Http(URL+"/search/nodes").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code) assert(response.code === HttpCode.POST_OK) @@ -763,13 +753,13 @@ class NodesSuite extends FunSuite { } test("POST /orgs/"+orgid+"/search/nodes - data verification false - should find no matches") { - val input = PostSearchNodesRequest(None, Some(List(RegServiceSearch(SDRSPEC,List( + val input = PostSearchNodesRequest(List(RegServiceSearch(SDRSPEC,List( Prop("arch","","wildcard","in"), Prop("memory","","wildcard",">="), Prop("version","0","version","in"), // in osgi version format 0 means lower bound is 0 and upper bound infinity Prop("agreementProtocols",agProto,"list","in"), - Prop("dataVerification","false","boolean","="))))), - 86400, List[String](""), 0, 0) + Prop("dataVerification","false","boolean","=")))), + 86400, None, 0, 0) val response = Http(URL+"/search/nodes").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.POST_OK) @@ -778,12 +768,12 @@ class NodesSuite extends FunSuite { } test("POST /orgs/"+orgid+"/search/nodes - invalid propType") { - val input = PostSearchNodesRequest(None, Some(List(RegServiceSearch(SDRSPEC,List( + val input = PostSearchNodesRequest(List(RegServiceSearch(SDRSPEC,List( Prop("arch","","stringx","in"), Prop("memory","","int",">="), Prop("version","","version","in"), - Prop("dataVerification","","boolean","="))))), - 86400, List[String](""), 0, 0) + Prop("dataVerification","","boolean","=")))), + 86400, None, 0, 0) val response = Http(URL+"/search/nodes").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.BAD_INPUT) @@ -792,12 +782,12 @@ class NodesSuite extends FunSuite { } test("POST /orgs/"+orgid+"/search/nodes - invalid op") { - val input = PostSearchNodesRequest(None, Some(List(RegServiceSearch(SDRSPEC,List( + val input = PostSearchNodesRequest(List(RegServiceSearch(SDRSPEC,List( Prop("arch","","string","inx"), Prop("memory","","int",">="), Prop("version","","version","in"), - Prop("dataVerification","","boolean","="))))), - 86400, List[String](""), 0, 0) + Prop("dataVerification","","boolean","=")))), + 86400, None, 0, 0) val response = Http(URL+"/search/nodes").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.BAD_INPUT) @@ -806,12 +796,12 @@ class NodesSuite extends FunSuite { } test("POST /orgs/"+orgid+"/search/nodes - invalid version") { - val input = PostSearchNodesRequest(None, Some(List(RegServiceSearch(SDRSPEC,List( + val input = PostSearchNodesRequest(List(RegServiceSearch(SDRSPEC,List( Prop("arch","*","string","in"), Prop("memory","*","int",">="), Prop("version","1.2.3.4","version","in"), - Prop("dataVerification","*","boolean","="))))), - 86400, List[String](""), 0, 0) + Prop("dataVerification","*","boolean","=")))), + 86400, None, 0, 0) val response = Http(URL+"/search/nodes").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.BAD_INPUT) @@ -820,12 +810,12 @@ class NodesSuite extends FunSuite { } test("POST /orgs/"+orgid+"/search/nodes - invalid boolean/op combo") { - val input = PostSearchNodesRequest(None, Some(List(RegServiceSearch(SDRSPEC,List( + val input = PostSearchNodesRequest(List(RegServiceSearch(SDRSPEC,List( Prop("arch","","string","in"), Prop("memory","","int",">="), Prop("version","","version","in"), - Prop("dataVerification","","boolean","in"))))), - 86400, List[String](""), 0, 0) + Prop("dataVerification","","boolean","in")))), + 86400, None, 0, 0) val response = Http(URL+"/search/nodes").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.BAD_INPUT) @@ -834,12 +824,12 @@ class NodesSuite extends FunSuite { } test("POST /orgs/"+orgid+"/search/nodes - invalid string/op combo") { - val input = PostSearchNodesRequest(None, Some(List(RegServiceSearch(SDRSPEC,List( + val input = PostSearchNodesRequest(List(RegServiceSearch(SDRSPEC,List( Prop("arch","","string","="), Prop("memory","","int",">="), Prop("version","","version","in"), - Prop("dataVerification","","boolean","="))))), - 86400, List[String](""), 0, 0) + Prop("dataVerification","","boolean","=")))), + 86400, None, 0, 0) val response = Http(URL+"/search/nodes").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.BAD_INPUT) @@ -848,12 +838,12 @@ class NodesSuite extends FunSuite { } test("POST /orgs/"+orgid+"/search/nodes - invalid int/op combo") { - val input = PostSearchNodesRequest(None, Some(List(RegServiceSearch(SDRSPEC,List( + val input = PostSearchNodesRequest(List(RegServiceSearch(SDRSPEC,List( Prop("arch","","string","in"), Prop("memory","","int","in"), Prop("version","","version","in"), - Prop("dataVerification","","boolean","="))))), - 86400, List[String](""), 0, 0) + Prop("dataVerification","","boolean","=")))), + 86400, None, 0, 0) val response = Http(URL+"/search/nodes").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.BAD_INPUT) @@ -862,12 +852,12 @@ class NodesSuite extends FunSuite { } test("POST /orgs/"+orgid+"/search/nodes - invalid version/op combo") { - val input = PostSearchNodesRequest(None, Some(List(RegServiceSearch(SDRSPEC,List( + val input = PostSearchNodesRequest(List(RegServiceSearch(SDRSPEC,List( Prop("arch","","string","in"), Prop("memory","","int",">="), Prop("version","","version",">="), - Prop("dataVerification","","boolean","="))))), - 86400, List[String](""), 0, 0) + Prop("dataVerification","","boolean","=")))), + 86400, None, 0, 0) val response = Http(URL+"/search/nodes").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.BAD_INPUT) @@ -902,7 +892,7 @@ class NodesSuite extends FunSuite { //~~~~~ Node status ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ test("PUT /orgs/"+orgid+"/nodes/"+nodeId+"/status - as node") { - val input = PutNodeStatusRequest(Map[String,Boolean]("images.bluehorizon.network" -> true), None, None, Some(List[OneService]())) + val input = PutNodeStatusRequest(Map[String,Boolean]("images.bluehorizon.network" -> true), List[OneService]()) val response = Http(URL+"/nodes/"+nodeId+"/status").postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(NODEAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.PUT_OK) @@ -931,21 +921,21 @@ class NodesSuite extends FunSuite { //~~~~~ Node agreements, and more searches and nodehealth ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ test("PUT /orgs/"+orgid+"/nodes/"+nodeId+"/agreements/"+agreementId+" - create agreement, as node") { - val input = PutNodeAgreementRequest(None, None, Some(List(NAService(orgid,SDRSPEC))), Some(NAgrService(orgid,patid,SDRSPEC)), "signed") + val input = PutNodeAgreementRequest(Some(List(NAService(orgid,SDRSPEC))), Some(NAgrService(orgid,patid,SDRSPEC)), "signed") val response = Http(URL+"/nodes/"+nodeId+"/agreements/"+agreementId).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(NODEAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.PUT_OK) } test("PUT /orgs/"+orgid+"/nodes/"+nodeId+"/agreements/"+agreementId+" - update agreement as node") { - val input = PutNodeAgreementRequest(None, None, Some(List(NAService(orgid,SDRSPEC))), Some(NAgrService(orgid,patid,SDRSPEC)), "finalized") + val input = PutNodeAgreementRequest(Some(List(NAService(orgid,SDRSPEC))), Some(NAgrService(orgid,patid,SDRSPEC)), "finalized") val response = Http(URL+"/nodes/"+nodeId+"/agreements/"+agreementId).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(NODEAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.PUT_OK) } test("PUT /orgs/"+orgid+"/nodes/"+nodeId+"/agreements/"+agreementId+" - update agreement as user") { - val input = PutNodeAgreementRequest(None, None, Some(List(NAService(orgid,SDRSPEC))), Some(NAgrService(orgid,patid,SDRSPEC)), "negotiating") + val input = PutNodeAgreementRequest(Some(List(NAService(orgid,SDRSPEC))), Some(NAgrService(orgid,patid,SDRSPEC)), "negotiating") val response = Http(URL+"/nodes/"+nodeId+"/agreements/"+agreementId).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.PUT_OK) @@ -953,7 +943,7 @@ class NodesSuite extends FunSuite { test("POST /orgs/"+orgid+"/patterns/"+patid+"/search - for "+SDRSPEC+" - with "+nodeId+" in agreement") { patchNodePattern(compositePatid) // put pattern back in nodes so we can search for pattern nodes - val input = PostPatternSearchRequest(None, Some(SDRSPEC), Some(List(orgid,orgid2)), 86400, 0, 0) + val input = PostPatternSearchRequest(SDRSPEC, Some(List(orgid,orgid2)), 86400, 0, 0) val response = Http(URL+"/patterns/"+patid+"/search").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString info("code: "+response.code+", response.body: "+response.body) //info("code: "+response.code) @@ -965,7 +955,7 @@ class NodesSuite extends FunSuite { } test("PUT /orgs/"+orgid2+"/nodes/"+nodeId+"/agreements/"+agreementId2+" - create agreement for node in 2nd org") { - val input = PutNodeAgreementRequest(None, None, Some(List(NAService(orgid,SDRSPEC))), Some(NAgrService(orgid,patid,SDRSPEC)), "signed") + val input = PutNodeAgreementRequest(Some(List(NAService(orgid,SDRSPEC))), Some(NAgrService(orgid,patid,SDRSPEC)), "signed") val response = Http(URL2+"/nodes/"+nodeId+"/agreements/"+agreementId2).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH2).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.PUT_OK) @@ -973,7 +963,7 @@ class NodesSuite extends FunSuite { test("POST /orgs/"+orgid+"/patterns/"+patid+"/search - for "+SDRSPEC+" - with "+org2nodeId+" in agreement") { //patchNodePattern(compositePatid) // put pattern back in nodes so we can search for pattern nodes - val input = PostPatternSearchRequest(None, Some(SDRSPEC), Some(List(orgid,orgid2)), 86400, 0, 0) + val input = PostPatternSearchRequest(SDRSPEC, Some(List(orgid,orgid2)), 86400, 0, 0) val response = Http(URL+"/patterns/"+patid+"/search").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString info("code: "+response.code+", response.body: "+response.body) //info("code: "+response.code) @@ -1016,7 +1006,7 @@ class NodesSuite extends FunSuite { test("POST /orgs/"+orgid+"/search/nodes - netspeed and sdr - now no nodes, since 1 agreement made") { patchNodePattern("") // remove pattern from nodes so we can search for services - val input = PostSearchNodesRequest(None, Some(List( + val input = PostSearchNodesRequest(List( RegServiceSearch(NETSPEEDSPEC,List( Prop("arch","*","string","in"), Prop("memory","*","int",">="), @@ -1028,8 +1018,8 @@ class NodesSuite extends FunSuite { Prop("memory","2","int",">="), Prop("version","*","version","in"), Prop("agreementProtocols",agProto,"list","in"), - Prop("dataVerification","","wildcard","="))) - )), 86400, List[String](""), 0, 0) + Prop("dataVerification","","wildcard","=")) + )), 86400, None, 0, 0) val response = Http(URL+"/search/nodes").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString info("code: "+response.code) assert(response.code === HttpCode.POST_OK) @@ -1053,7 +1043,7 @@ class NodesSuite extends FunSuite { } test("PUT /orgs/"+orgid+"/nodes/"+nodeId+"/agreements/9951 - and 2nd agreement as node") { - val input = PutNodeAgreementRequest(None, None, Some(List(NAService(orgid,"pws"))), Some(NAgrService(orgid,patid,"pws")), "signed") + val input = PutNodeAgreementRequest(Some(List(NAService(orgid,"pws"))), Some(NAgrService(orgid,patid,"pws")), "signed") val response = Http(URL+"/nodes/"+nodeId+"/agreements/9951").postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(NODEAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.PUT_OK) @@ -1100,7 +1090,7 @@ class NodesSuite extends FunSuite { test("POST /orgs/"+orgid+"/patterns/"+patid+"/search - for "+SDRSPEC+" - with "+nodeId+" in agreement, should get same result as before") { patchNodePattern(compositePatid) // put pattern back in nodes so we can search for pattern nodes - val input = PostPatternSearchRequest(None, Some(SDRSPEC), None, 86400, 0, 0) + val input = PostPatternSearchRequest(SDRSPEC, None, 86400, 0, 0) val response = Http(URL+"/patterns/"+patid+"/search").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString //info("code: "+response.code+", response.body: "+response.body) info("code: "+response.code) @@ -1113,13 +1103,13 @@ class NodesSuite extends FunSuite { test("POST /orgs/"+orgid+"/search/nodes - all arm nodes, should get 1 less result, because "+nodeId+" in agreement") { patchNodePattern("") // remove pattern from nodes so we can search for services - val input = PostSearchNodesRequest(None, Some(List(RegServiceSearch(SDRSPEC,List( + val input = PostSearchNodesRequest(List(RegServiceSearch(SDRSPEC,List( Prop("arch","arm","string","in"), Prop("memory","*","int",">="), Prop("version","","wildcard","in"), Prop("agreementProtocols",agProto,"list","in"), - Prop("dataVerification","","wildcard","="))))), - 86400, List[String](""), 0, 0) + Prop("dataVerification","","wildcard","=")))), + 86400, None, 0, 0) val response = Http(URL+"/search/nodes").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code) assert(response.code === HttpCode.POST_OK) @@ -1130,13 +1120,13 @@ class NodesSuite extends FunSuite { } test("POST /orgs/"+orgid+"/search/nodes - netspeed arch arm, "+nodeId+" sdr in agreement, but netspeed not, so still should find it - as agbot") { - val input = PostSearchNodesRequest(None, Some(List(RegServiceSearch(NETSPEEDSPEC,List( + val input = PostSearchNodesRequest(List(RegServiceSearch(NETSPEEDSPEC,List( Prop("arch","arm","string","in"), Prop("memory","*","int",">="), Prop("version","[1.0.0,2.0.0]","version","in"), Prop("agreementProtocols",agProto,"list","in"), - Prop("dataVerification","","wildcard","="))))), - 86400, List[String](""), 0, 0) + Prop("dataVerification","","wildcard","=")))), + 86400, None, 0, 0) val response = Http(URL+"/search/nodes").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString info("code: "+response.code) assert(response.code === HttpCode.POST_OK) @@ -1153,7 +1143,7 @@ class NodesSuite extends FunSuite { } test("PUT /orgs/"+orgid+"/nodes/"+nodeId+"/agreements/"+agreementId+" - netspeed") { - val input = PutNodeAgreementRequest(None, None, Some(List(NAService(orgid,NETSPEEDSPEC))), Some(NAgrService(orgid,patid,NETSPEEDSPEC)), "signed") + val input = PutNodeAgreementRequest(Some(List(NAService(orgid,NETSPEEDSPEC))), Some(NAgrService(orgid,patid,NETSPEEDSPEC)), "signed") val response = Http(URL+"/nodes/"+nodeId+"/agreements/"+agreementId).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(NODEAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.PUT_OK) @@ -1161,7 +1151,7 @@ class NodesSuite extends FunSuite { test("POST /orgs/"+orgid+"/patterns/"+patid+"/search - for "+NETSPEEDSPEC+" - with "+nodeId+" in agreement") { patchNodePattern(compositePatid) // put pattern back in nodes so we can search for pattern nodes - val input = PostPatternSearchRequest(None, Some(NETSPEEDSPEC), None, 86400, 0, 0) + val input = PostPatternSearchRequest(NETSPEEDSPEC, None, 86400, 0, 0) val response = Http(URL+"/patterns/"+patid+"/search").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString //info("code: "+response.code+", response.body: "+response.body) info("code: "+response.code) @@ -1173,7 +1163,7 @@ class NodesSuite extends FunSuite { } test("POST /orgs/"+orgid+"/patterns/"+patid+"/search - for "+SDRSPEC+" - should find all nodes again") { - val input = PostPatternSearchRequest(None, Some(SDRSPEC), None, 86400, 0, 0) + val input = PostPatternSearchRequest(SDRSPEC, None, 86400, 0, 0) val response = Http(URL+"/patterns/"+patid+"/search").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString //info("code: "+response.code+", response.body: "+response.body) info("code: "+response.code) @@ -1188,13 +1178,13 @@ class NodesSuite extends FunSuite { test("POST /orgs/"+orgid+"/search/nodes - netspeed arch arm, "+nodeId+" netspeed in agreement, so shouldn't find it - as agbot") { patchNodePattern("") // remove pattern from nodes so we can search for services - val input = PostSearchNodesRequest(None, Some(List(RegServiceSearch(NETSPEEDSPEC,List( + val input = PostSearchNodesRequest(List(RegServiceSearch(NETSPEEDSPEC,List( Prop("arch","arm","string","in"), Prop("memory","*","int",">="), Prop("version","[1.0.0,2.0.0]","version","in"), Prop("agreementProtocols",agProto,"list","in"), - Prop("dataVerification","","wildcard","="))))), - 86400, List[String](""), 0, 0) + Prop("dataVerification","","wildcard","=")))), + 86400, None, 0, 0) val response = Http(URL+"/search/nodes").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString info("code: "+response.code) assert(response.code === HttpCode.POST_OK) @@ -1204,13 +1194,13 @@ class NodesSuite extends FunSuite { } test("POST /orgs/"+orgid+"/search/nodes - sdr arch arm, "+nodeId+" netspeed in agreement, but should still find the sdr - as agbot") { - val input = PostSearchNodesRequest(None, Some(List(RegServiceSearch(SDRSPEC,List( + val input = PostSearchNodesRequest(List(RegServiceSearch(SDRSPEC,List( Prop("arch","arm","string","in"), Prop("memory","*","int",">="), Prop("version","[1.0.0,2.0.0]","version","in"), Prop("agreementProtocols",agProto,"list","in"), - Prop("dataVerification","","wildcard","="))))), - 86400, List[String](""), 0, 0) + Prop("dataVerification","","wildcard","=")))), + 86400, None, 0, 0) val response = Http(URL+"/search/nodes").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString // info("code: "+response.code+", response.body: "+response.body) info("code: "+response.code) @@ -1228,7 +1218,7 @@ class NodesSuite extends FunSuite { test("POST /orgs/"+orgid+"/patterns/"+patid+"/search - for "+SDRSPEC+" - all nodes stale") { patchNodePattern(compositePatid) // put pattern back in nodes so we can search for pattern nodes Thread.sleep(1100) // delay 1.1 seconds so all nodes will be stale - val input = PostPatternSearchRequest(None, Some(SDRSPEC), None, 1, 0, 0) + val input = PostPatternSearchRequest(SDRSPEC, None, 1, 0, 0) val response = Http(URL+"/patterns/"+patid+"/search").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString //info("code: "+response.code+", response.body: "+response.body) info("code: "+response.code) @@ -1273,7 +1263,7 @@ class NodesSuite extends FunSuite { } test("POST /orgs/"+orgid+"/patterns/"+patid+"/search - for "+SDRSPEC+" - 1 node not stale") { - val input = PostPatternSearchRequest(None, Some(SDRSPEC), None, 1, 0, 0) + val input = PostPatternSearchRequest(SDRSPEC, None, 1, 0, 0) val response = Http(URL+"/patterns/"+patid+"/search").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString //info("code: "+response.code+", response.body: "+response.body) info("code: "+response.code) @@ -1287,13 +1277,13 @@ class NodesSuite extends FunSuite { test("POST /orgs/"+orgid+"/search/nodes - all arm nodes, but all stale") { patchNodePattern("") // remove pattern from nodes so we can search for services Thread.sleep(1100) // delay 1.1 seconds so all nodes will be stale - val input = PostSearchNodesRequest(None, Some(List(RegServiceSearch(SDRSPEC,List( + val input = PostSearchNodesRequest(List(RegServiceSearch(SDRSPEC,List( Prop("arch","arm","string","in"), Prop("memory","2","int",">="), Prop("version","*","version","in"), Prop("agreementProtocols",agProto,"list","in"), - Prop("dataVerification","","wildcard","="))))), - 1, List[String](""), 0, 0) + Prop("dataVerification","","wildcard","=")))), + 1, None, 0, 0) val response = Http(URL+"/search/nodes").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code) assert(response.code === HttpCode.NOT_FOUND) @@ -1313,13 +1303,13 @@ class NodesSuite extends FunSuite { test("POST /orgs/"+orgid+"/search/nodes - all arm nodes, 1 not stale") { val secondsNotStale = 1 info("secondsNotStale: "+secondsNotStale) - val input = PostSearchNodesRequest(None, Some(List(RegServiceSearch(SDRSPEC,List( + val input = PostSearchNodesRequest(List(RegServiceSearch(SDRSPEC,List( Prop("arch","arm","string","in"), Prop("memory","2","int",">="), Prop("version","*","version","in"), Prop("agreementProtocols",agProto,"list","in"), - Prop("dataVerification","","wildcard","="))))), - secondsNotStale, List[String](""), 0, 0) + Prop("dataVerification","","wildcard","=")))), + secondsNotStale, None, 0, 0) val response = Http(URL+"/search/nodes").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code) //info("code: "+response.code+", response.body: "+response.body) @@ -1354,7 +1344,7 @@ class NodesSuite extends FunSuite { assert(response.code === HttpCode.PUT_OK) // Now try adding another agreement - expect it to be rejected - val input = PutNodeAgreementRequest(None, None, Some(List(NAService(orgid,"netspeed"))), Some(NAgrService(orgid,patid,"netspeed")), "signed") + val input = PutNodeAgreementRequest(Some(List(NAService(orgid,"netspeed"))), Some(NAgrService(orgid,patid,"netspeed")), "signed") response = Http(URL+"/nodes/"+nodeId+"/agreements/9952").postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(NODEAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.ACCESS_DENIED) @@ -1396,7 +1386,7 @@ class NodesSuite extends FunSuite { assert(response.code === HttpCode.PUT_OK) // Now try adding another node - expect it to be rejected - val input = PutNodesRequest(nodeToken, "rpi"+nodeId5+"-netspeed", compositePatid, None, Some(List(RegService(NETSPEEDSPEC,1,"{json policy for "+nodeId5+" netspeed}",List( + val input = PutNodesRequest(nodeToken, "rpi"+nodeId5+"-netspeed", compositePatid, Some(List(RegService(NETSPEEDSPEC,1,"{json policy for "+nodeId5+" netspeed}",List( Prop("arch","arm","string","in"), Prop("version","1.0.0","version","in"), Prop("agreementProtocols",agProto,"list","in"))))), "whisper-id", Map(), "NODE4ABC") @@ -1690,116 +1680,6 @@ class NodesSuite extends FunSuite { } } - //~~~~~ Old style node ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - test("POST /orgs/"+orgid+"/workloads - add "+workid+" so pattern can reference it") { - val input = PostPutWorkloadRequest("test-workload", "desc", public = false, workurl, workversion, workarch, None, List(), List(Map()), List()) - val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.POST_OK) - } - // Note: when we delete the org, this workload will get deleted - - test("POST /orgs/"+orgid+"/patterns/"+patid2+" - so nodes can reference it") { - val input = PostPutPatternRequest(patid2, None, None, - Some(List( PWorkloads(workurl, orgid, workarch, List(PServiceVersions(workversion, None, None, None, None)), None, None ))), - None, None - ) - val response = Http(URL+"/patterns/"+patid2).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.POST_OK) - } - - test("PUT /orgs/"+orgid+"/nodes/"+oldNodeId+" - add old style node") { - val input = PutNodesRequest(nodeToken, "rpi"+oldNodeId+"-old", compositePatid2, - Some(List( - RegMicroservice(OLDSDRSPEC,1,"{json policy for "+oldNodeId+" sdr}",List( - Prop("arch","arm","string","in"), - Prop("version","1.0.0","version","in"), - Prop("agreementProtocols",agProto,"list","in"), - Prop("dataVerification","true","boolean","="))), - RegMicroservice(OLDNETSPEEDSPEC,1,"{json policy for "+oldNodeId+" netspeed}",List( - Prop("arch","arm","string","in"), - Prop("cpus","2","int",">="), - Prop("version","1.0.0","version","in"))) - )), None, - "whisper-id", Map("horizon"->"3.2.3"), "OLDNODEABC") - val response = Http(URL+"/nodes/"+oldNodeId).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.PUT_OK) - } - - test("GET /orgs/"+orgid+"/nodes/"+oldNodeId+" - get old style node") { - val response: HttpResponse[String] = Http(URL+"/nodes/"+oldNodeId).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val getDevResp = parse(response.body).extract[GetNodesResponse] - assert(getDevResp.nodes.size === 1) - - assert(getDevResp.nodes.contains(orgOldNodeId)) - val dev = getDevResp.nodes(orgOldNodeId) - assert(dev.name === "rpi"+oldNodeId+"-old") - - assert(dev.registeredMicroservices.length === 2) - val svc: RegMicroservice = dev.registeredMicroservices.find(m => m.url==OLDSDRSPEC).orNull - assert(svc !== null) - assert(svc.url === OLDSDRSPEC) - assert(svc.policy === "{json policy for "+oldNodeId+" sdr}") - var archProp = svc.properties.find(p => p.name=="arch").orNull - assert((archProp !== null) && (archProp.name === "arch")) - assert(archProp.value === "arm") - - assert(dev.registeredMicroservices.find(m => m.url==OLDNETSPEEDSPEC) !== None) - } - - test("POST /orgs/"+orgid+"/patterns/"+patid2+"/search - for "+OLDSDRSPEC+" in old style node") { - val input = PostPatternSearchRequest(Some(OLDSDRSPEC), None, None, 86400, 0, 0) - val response = Http(URL+"/patterns/"+patid2+"/search").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString - //info("code: "+response.code+", response.body: "+response.body) - info("code: "+response.code) - assert(response.code === HttpCode.POST_OK) - val postSearchDevResp = parse(response.body).extract[PostPatternSearchResponse] - val nodes = postSearchDevResp.nodes - assert(nodes.length === 1) - assert(nodes.count(d => d.id==orgOldNodeId) === 1) - val dev = nodes.find(d => d.id == orgOldNodeId).get // the 2nd get turns the Some(val) into val - assert(dev.publicKey === "OLDNODEABC") - } - - test("POST /orgs/"+orgid+"/search/nodes - all arm old style nodes") { - // First patch node to remove pattern - val jsonInput = """{ "pattern": "" }""" - var response = Http(URL + "/nodes/" + oldNodeId).postData(jsonInput).method("patch").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("PATCH "+oldNodeId+", code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.PUT_OK) - - val input = PostSearchNodesRequest(Some(List(RegMicroserviceSearch(OLDSDRSPEC,List( - Prop("arch","arm","string","in"), - Prop("memory","2","int",">="), - Prop("version","*","version","in"), - Prop("agreementProtocols",agProto,"list","in"), - Prop("dataVerification","","wildcard","="))))), None, - 86400, List[String](""), 0, 0) - response = Http(URL+"/search/nodes").postData(write(input)).headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - //info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.POST_OK) - val postSearchDevResp = parse(response.body).extract[PostSearchNodesResponse] - val nodes = postSearchDevResp.nodes - assert(nodes.length === 1) - assert(nodes.count(d => d.id==orgOldNodeId) === 1) - val dev = nodes.find(d => d.id == orgOldNodeId).get // the 2nd get turns the Some(val) into val - assert(dev.name === "rpi"+oldNodeId+"-old") - assert(dev.microservices.length === 1) - val svc = dev.microservices.head - assert(svc.url === OLDSDRSPEC) - assert(svc.policy === "{json policy for "+oldNodeId+" sdr}") - var archProp = svc.properties.find(p => p.name=="arch").orNull - assert((archProp !== null) && (archProp.name === "arch")) - assert(archProp.value === "arm") - } - //~~~~~ Break down ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ test("Cleanup - DELETE everything and confirm they are gone") { diff --git a/src/test/scala/exchangeapi/PatternsSuite.scala b/src/test/scala/exchangeapi/PatternsSuite.scala index 1a11d9f3..440271b2 100644 --- a/src/test/scala/exchangeapi/PatternsSuite.scala +++ b/src/test/scala/exchangeapi/PatternsSuite.scala @@ -52,10 +52,6 @@ class PatternsSuite extends FunSuite { val agbotId = "9948" val agbotToken = agbotId+"tok" val AGBOTAUTH = ("Authorization","Basic "+authpref+agbotId+":"+agbotToken) - //val workid = "bluehorizon.network-workloads-netspeed_1.0.0_amd64" - val workurl = "https://bluehorizon.network/workloads/netspeed" - val workarch = "amd64" - val workversion = "1.0.0" val svcurl = "https://bluehorizon.network/services/netspeed" val svcarch = "amd64" val svcversion = "1.0.0" @@ -114,7 +110,7 @@ class PatternsSuite extends FunSuite { info("code: " + userResponse.code + ", userResponse.body: " + userResponse.body) assert(userResponse.code === HttpCode.POST_OK) - val devInput = PutNodesRequest(nodeToken, "bc dev test", "", None, Some(List(RegService("foo", 1, "{}", List( + val devInput = PutNodesRequest(nodeToken, "bc dev test", "", Some(List(RegService("foo", 1, "{}", List( Prop("arch", "arm", "string", "in"), Prop("version", "2.0.0", "version", "in"), Prop("blockchainProtocols", "agProto", "list", "in"))))), "whisper-id", Map(), "NODEABC") @@ -129,8 +125,8 @@ class PatternsSuite extends FunSuite { } test("POST /orgs/"+orgid+"/patterns/"+pattern+" - add "+pattern+" before service exists - should fail") { - val input = PostPutPatternRequest(ptBase, None, None, None, - Some(List( PServices(svcurl, orgid, svcarch, None, List(PServiceVersions(svcversion, None, None, None, None)), None, None ))), + val input = PostPutPatternRequest(ptBase, None, None, + List( PServices(svcurl, orgid, svcarch, None, List(PServiceVersions(svcversion, None, None, None, None)), None, None )), None ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString @@ -138,13 +134,6 @@ class PatternsSuite extends FunSuite { assert(response.code === HttpCode.BAD_INPUT) } - test("Add workload for future tests") { - val workInput = PostPutWorkloadRequest("test-workload", "desc", public = false, workurl, workversion, workarch, None, List(), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) - val workResponse = Http(URL+"/workloads").postData(write(workInput)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+workResponse.code+", response.body: "+workResponse.body) - assert(workResponse.code === HttpCode.POST_OK) - } - test("Add service for future tests") { val svcInput = PostPutServiceRequest("test-service", None, public = false, svcurl, svcversion, svcarch, "multiple", None, None, Some(List(Map("name" -> "foo"))), "{\"services\":{}}","a",None) val svcResponse = Http(URL+"/services").postData(write(svcInput)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString @@ -153,8 +142,8 @@ class PatternsSuite extends FunSuite { } test("PUT /orgs/"+orgid+"/patterns/"+pattern+" - update pattern that is not there yet - should fail") { - val input = PostPutPatternRequest("Bad Pattern", None, None, None, - Some(List( PServices(svcurl, orgid, svcarch, None, List(PServiceVersions(svcversion, None, None, None, None)), None, None ))), + val input = PostPutPatternRequest("Bad Pattern", None, None, + List( PServices(svcurl, orgid, svcarch, None, List(PServiceVersions(svcversion, None, None, None, None)), None, None )), None ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString @@ -163,8 +152,8 @@ class PatternsSuite extends FunSuite { } test("POST /orgs/"+orgid+"/patterns/"+pattern+" - add "+pattern+" that is not signed - should fail") { - val input = PostPutPatternRequest(ptBase, None, None, None, - Some(List( PServices(svcurl, orgid, svcarch, None, List(PServiceVersions(svcversion, Some("{\"services\":{}}"), None, None, None)), None, None ))), + val input = PostPutPatternRequest(ptBase, None, None, + List( PServices(svcurl, orgid, svcarch, None, List(PServiceVersions(svcversion, Some("{\"services\":{}}"), None, None, None)), None, None )), None ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString @@ -173,8 +162,8 @@ class PatternsSuite extends FunSuite { } test("POST /orgs/"+orgid+"/patterns/"+pattern+" - add "+pattern+" as user") { - val input = PostPutPatternRequest(ptBase, Some("desc"), Some(true), None, - Some(List( PServices(svcurl, orgid, svcarch, Some(true), List(PServiceVersions(svcversion, Some("{\"services\":{}}"), Some("a"), Some(Map("priority_value" -> 50)), Some(Map("lifecycle" -> "immediate")))), Some(Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]())), Some(Map("check_agreement_status" -> 120)) ))), + val input = PostPutPatternRequest(ptBase, Some("desc"), Some(true), + List( PServices(svcurl, orgid, svcarch, Some(true), List(PServiceVersions(svcversion, Some("{\"services\":{}}"), Some("a"), Some(Map("priority_value" -> 50)), Some(Map("lifecycle" -> "immediate")))), Some(Map("enabled"->false, "URL"->"", "user"->"", "password"->"", "interval"->0, "check_rate"->0, "metering"->Map[String,Any]())), Some(Map("check_agreement_status" -> 120)) )), Some(List(Map("name" -> "Basic"))) ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString @@ -185,8 +174,8 @@ class PatternsSuite extends FunSuite { } test("POST /orgs/"+orgid+"/patterns/"+pattern+" - add "+pattern+" again - should fail") { - val input = PostPutPatternRequest("Bad Pattern", None, None, None, - Some(List( PServices(svcurl, orgid, svcarch, None, List(PServiceVersions(svcversion, None, None, None, None)), None, None ))), + val input = PostPutPatternRequest("Bad Pattern", None, None, + List( PServices(svcurl, orgid, svcarch, None, List(PServiceVersions(svcversion, None, None, None, None)), None, None )), None ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString @@ -195,8 +184,8 @@ class PatternsSuite extends FunSuite { } test("PUT /orgs/"+orgid+"/patterns/"+pattern+" - update as same user, w/o dataVerification or nodeHealth fields") { - val input = PostPutPatternRequest(ptBase+" amd64", None, None, Some(List()), // <- specify empty workloads field to make sure it is allowed - Some(List( PServices(svcurl, orgid, svcarch, Some(true), List(PServiceVersions(svcversion, None, None, None, None)), None, None ))), + val input = PostPutPatternRequest(ptBase+" amd64", None, None, + List( PServices(svcurl, orgid, svcarch, Some(true), List(PServiceVersions(svcversion, None, None, None, None)), None, None )), None ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString @@ -205,8 +194,8 @@ class PatternsSuite extends FunSuite { } test("PUT /orgs/"+orgid+"/patterns/"+pattern+" - update as 2nd user - should fail") { - val input = PostPutPatternRequest("Bad Pattern", Some("desc"), None, None, - Some(List( PServices(svcurl, orgid, svcarch, None, List(PServiceVersions(svcversion, None, None, None, None)), None, None ))), + val input = PostPutPatternRequest("Bad Pattern", Some("desc"), None, + List( PServices(svcurl, orgid, svcarch, None, List(PServiceVersions(svcversion, None, None, None, None)), None, None )), None ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString @@ -215,8 +204,8 @@ class PatternsSuite extends FunSuite { } test("PUT /orgs/"+orgid+"/patterns/"+pattern+" - update as agbot - should fail") { - val input = PostPutPatternRequest("Bad Pattern", None, None, None, - Some(List( PServices(svcurl, orgid, svcarch, None, List(PServiceVersions(svcversion, None, None, None, None)), None, None ))), + val input = PostPutPatternRequest("Bad Pattern", None, None, + List( PServices(svcurl, orgid, svcarch, None, List(PServiceVersions(svcversion, None, None, None, None)), None, None )), None ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString @@ -235,8 +224,8 @@ class PatternsSuite extends FunSuite { test("POST /orgs/"+orgid+"/patterns/"+pattern2+" - add "+pattern2+" as node - should fail") { val input = PostPutPatternRequest("Bad Pattern2", None, None, - Some(List( PWorkloads(workurl, orgid, workarch, List(PServiceVersions(workversion, None, None, None, None)), None, None ))), - None, None + List( PServices(svcurl, orgid, svcarch, Some(true), List(PServiceVersions(svcversion, None, None, None, None)), None, None )), + None ) val response = Http(URL+"/patterns/"+pattern2).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(NODEAUTH).asString info("code: "+response.code+", response.body: "+response.body) @@ -245,8 +234,8 @@ class PatternsSuite extends FunSuite { test("POST /orgs/"+orgid+"/patterns/"+pattern2+" - add "+pattern2+" as 2nd user") { val input = PostPutPatternRequest(ptBase2+" amd64", None, Some(true), - Some(List( PWorkloads(workurl, orgid, workarch, List(PServiceVersions(workversion, None, None, None, None)), None, None ))), - Some(List()), None + List( PServices(svcurl, orgid, svcarch, Some(true), List(PServiceVersions(svcversion, None, None, None, None)), None, None )), + None ) val response = Http(URL+"/patterns/"+pattern2).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString info("code: "+response.code+", response.body: "+response.body) diff --git a/src/test/scala/exchangeapi/ServicesSuite.scala b/src/test/scala/exchangeapi/ServicesSuite.scala index 4f772ae3..3f779139 100644 --- a/src/test/scala/exchangeapi/ServicesSuite.scala +++ b/src/test/scala/exchangeapi/ServicesSuite.scala @@ -126,7 +126,7 @@ class ServicesSuite extends FunSuite { info("code: "+userResponse.code+", userResponse.body: "+userResponse.body) assert(userResponse.code === HttpCode.POST_OK) - val devInput = PutNodesRequest(nodeToken, "bc dev test", "", None, None, "", Map(), "") + val devInput = PutNodesRequest(nodeToken, "bc dev test", "", None, "", Map(), "") val devResponse = Http(URL+"/nodes/"+nodeId).postData(write(devInput)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+devResponse.code) assert(devResponse.code === HttpCode.PUT_OK) @@ -145,7 +145,7 @@ class ServicesSuite extends FunSuite { } test("POST /orgs/"+orgid+"/services - add service so services can reference it") { - val input = PostPutServiceRequest("testMicro", Some("desc"), public = false, reqsvcurl, reqsvcversion, reqsvcarch, "single", None, None, Some(List(Map("name" -> "foo"))), "{\"services\":{}}","a",None) + val input = PostPutServiceRequest("testSvc", Some("desc"), public = false, reqsvcurl, reqsvcversion, reqsvcarch, "single", None, None, Some(List(Map("name" -> "foo"))), "{\"services\":{}}","a",None) val response = Http(URL+"/services").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.POST_OK) @@ -225,7 +225,7 @@ class ServicesSuite extends FunSuite { } test("POST /orgs/"+orgid+"/services - add 2nd service so services can reference both") { - val input = PostPutServiceRequest("testMicro", None, public = false, reqsvcurl2, reqsvcversion2, reqsvcarch2, "single", None, None, None, "", "", None) + val input = PostPutServiceRequest("testSvc", None, public = false, reqsvcurl2, reqsvcversion2, reqsvcarch2, "single", None, None, None, "", "", None) val response = Http(URL+"/services").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.POST_OK) @@ -303,7 +303,7 @@ class ServicesSuite extends FunSuite { assert(response.code === HttpCode.PUT_OK) // Now try adding another service - expect it to be rejected - val input = PostPutServiceRequest(wkBase3+" arm", "desc", wkUrl3, "1.0.0", "arm", "", List(WServices(microurl,orgid,microversion,microarch)), Some(List(Map("name" -> "foo"))), List(MDockerImages("{\"services\":{}}","a","a"))) + val input = PostPutServiceRequest(wkBase3+" arm", "desc", wkUrl3, "1.0.0", "arm", "", List(WServices(svcurl,orgid,svcversion,svcarch)), Some(List(Map("name" -> "foo"))), List(MDockerImages("{\"services\":{}}","a","a"))) response = Http(URL+"/services").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.ACCESS_DENIED) diff --git a/src/test/scala/exchangeapi/UsersSuite.scala b/src/test/scala/exchangeapi/UsersSuite.scala index 9fa37208..9ba645b4 100644 --- a/src/test/scala/exchangeapi/UsersSuite.scala +++ b/src/test/scala/exchangeapi/UsersSuite.scala @@ -1,7 +1,7 @@ package exchangeapi //import com.horizon.exchangeapi.tables.APattern -import com.horizon.exchangeapi.tables.PWorkloads +import com.horizon.exchangeapi.tables.PServices import org.scalatest.FunSuite import org.junit.runner.RunWith import org.scalatest.junit.JUnitRunner @@ -58,9 +58,6 @@ class UsersSuite extends FunSuite { val ROOTAUTH = ("Authorization","Basic "+rootuser+":"+rootpw) val CONNTIMEOUT = HttpOptions.connTimeout(20000) val READTIMEOUT = HttpOptions.readTimeout(20000) - val msBase = "ms" - val msUrl = "http://" + msBase - val microservice = msBase + "_1.0.0_arm" val svcBase = "svc" val svcurl = "http://" + svcBase val svcarch = "arm" @@ -491,53 +488,6 @@ class UsersSuite extends FunSuite { } - test("POST /orgs/"+orgid+"/microservices - add "+microservice+" as not public in 1st org") { - val input = PostPutMicroserviceRequest(msBase+" arm", "desc", public = false, msUrl, "1.0.0", "arm", "single", None, None, List(), List()) - val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.POST_OK) - } - - test("GET /orgs/"+orgid+"/microservices - as org2 user - should find no public microservices") { - val response: HttpResponse[String] = Http(URL + "/microservices").headers(ACCEPT).headers(ORG2USERAUTH).asString - info("code: " + response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.NOT_FOUND) - } - - test("GET /orgs/"+orgid+"/microservices/"+microservice+" - as org2 user - should fail") { - val response: HttpResponse[String] = Http(URL + "/microservices/" + microservice).headers(ACCEPT).headers(ORG2USERAUTH).asString - info("code: " + response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.ACCESS_DENIED) - } - - test("PATCH /orgs/"+orgid+"/microservices/"+microservice+" - to make it public") { - val jsonInput = """{ "public": true }""" - val response = Http(URL+"/microservices/"+microservice).postData(jsonInput).method("patch").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.PUT_OK) - } - - test("GET /orgs/"+orgid+"/microservices - as org2 user - this time it should find 1") { - val response: HttpResponse[String] = Http(URL + "/microservices").headers(ACCEPT).headers(ORG2USERAUTH).asString - info("code: " + response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val respObj = parse(response.body).extract[GetMicroservicesResponse] - assert(respObj.microservices.size === 1) - } - - test("GET /orgs/"+orgid+"/microservices/"+microservice+" - as org2 user - this time it should work") { - val response: HttpResponse[String] = Http(URL + "/microservices/" + microservice).headers(ACCEPT).headers(ORG2USERAUTH).asString - info("code: " + response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val respObj = parse(response.body).extract[GetMicroservicesResponse] - assert(respObj.microservices.size === 1) - } - - test("POST /orgs/"+orgid+"/services - add "+service+" as not public in 1st org") { val input = PostPutServiceRequest(svcBase+" arm", None, public = false, svcurl, svcversion, svcarch, "multiple", None, None, None, "", "", None) val response = Http(URL+"/services").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString @@ -586,7 +536,7 @@ class UsersSuite extends FunSuite { test("POST /orgs/"+orgid+"/patterns/"+pattern+" - add "+pattern+" as not public in 1st org") { - val input = PostPutPatternRequest("Pattern", None, None, Some(List(PWorkloads("a", "a", "a", List(), None, None))), None, None ) + val input = PostPutPatternRequest("Pattern", None, None, List(PServices("a", "a", "a", None, List(), None, None)), None ) val response = Http(URL+"/patterns/"+pattern).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString info("code: "+response.code+", response.body: "+response.body) assert(response.code === HttpCode.POST_OK) diff --git a/src/test/scala/exchangeapi/WorkloadsSuite.scala b/src/test/scala/exchangeapi/WorkloadsSuite.scala deleted file mode 100644 index 0119a7f8..00000000 --- a/src/test/scala/exchangeapi/WorkloadsSuite.scala +++ /dev/null @@ -1,533 +0,0 @@ -package exchangeapi - -import java.time._ - -import com.horizon.exchangeapi._ -import com.horizon.exchangeapi.tables._ -import org.json4s._ -import org.json4s.jackson.JsonMethods._ -import org.json4s.native.Serialization.write -import org.junit.runner.RunWith -import org.scalatest.FunSuite -import org.scalatest.junit.JUnitRunner - -import scala.collection.immutable._ -import scalaj.http._ - -/** - * Tests for the /workloads routes. To run - * the test suite, you can either: - * - run the "test" command in the SBT console - * - right-click the file in eclipse and chose "Run As" - "JUnit Test" - * - * clear and detailed tutorial of FunSuite: http://doc.scalatest.org/1.9.1/index.html#org.scalatest.FunSuite - */ -@RunWith(classOf[JUnitRunner]) -class WorkloadsSuite extends FunSuite { - - val localUrlRoot = "http://localhost:8080" - val urlRoot = sys.env.getOrElse("EXCHANGE_URL_ROOT", localUrlRoot) - val runningLocally = (urlRoot == localUrlRoot) - val ACCEPT = ("Accept","application/json") - val ACCEPTTEXT = ("Accept","text/plain") - val CONTENT = ("Content-Type","application/json") - val CONTENTTEXT = ("Content-Type","text/plain") - val orgid = "WorkloadsSuiteTests" - val authpref=orgid+"/" - val URL = urlRoot+"/v1/orgs/"+orgid - val user = "9999" - val orguser = authpref+user - val pw = user+"pw" - val USERAUTH = ("Authorization","Basic "+orguser+":"+pw) - val user2 = "10000" - val orguser2 = authpref+user2 - val pw2 = user2+"pw" - val USER2AUTH = ("Authorization","Basic "+orguser2+":"+pw2) - val rootuser = Role.superUser - val rootpw = sys.env.getOrElse("EXCHANGE_ROOTPW", "") // need to put this root pw in config.json - val ROOTAUTH = ("Authorization","Basic "+rootuser+":"+rootpw) - val nodeId = "9912" // the 1st node created, that i will use to run some rest methods - val nodeToken = nodeId+"tok" - val NODEAUTH = ("Authorization","Basic "+authpref+nodeId+":"+nodeToken) - val agbotId = "9947" - val agbotToken = agbotId+"tok" - val AGBOTAUTH = ("Authorization","Basic "+authpref+agbotId+":"+agbotToken) - val wkBase = "wk9920" - val wkUrl = "http://" + wkBase - val workload = wkBase + "_1.0.0_arm" - val orgworkload = authpref+workload - val wkBase2 = "wk9921" - val wkUrl2 = "http://" + wkBase2 - val workload2 = wkBase2 + "_1.0.0_arm" - val orgworkload2 = authpref+workload2 - val wkBase3 = "wk9922" - val wkUrl3 = "http://" + wkBase3 - val workload3 = wkBase3 + "_1.0.0_arm" - val microurl = "https://bluehorizon.network/microservices/network" - val microarch = "amd64" - val microversion = "1.0.0" - val microurl2 = "https://bluehorizon.network/microservices/rtlsdr" - val microarch2 = "amd64" - val microversion2 = "2.0.0" - val keyId = "mykey.pem" - val key = "abcdefghijk" - val keyId2 = "mykey2.pem" - val key2 = "lnmopqrstuvwxyz" - - implicit val formats = DefaultFormats // Brings in default date formats etc. - - /** Delete all the test users */ - def deleteAllUsers() = { - for (i <- List(user,user2)) { - val response = Http(URL+"/users/"+i).method("delete").headers(ACCEPT).headers(ROOTAUTH).asString - info("DELETE "+i+", code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.DELETED || response.code === HttpCode.NOT_FOUND) - } - } - - /** Create an org to use for this test */ - test("POST /orgs/"+orgid+" - create org") { - // Try deleting it 1st, in case it is left over from previous test - var response = Http(URL).method("delete").headers(ACCEPT).headers(ROOTAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.DELETED || response.code === HttpCode.NOT_FOUND) - - val input = PostPutOrgRequest("My Org", "desc") - response = Http(URL).postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(ROOTAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.POST_OK) - } - - /** Delete all the test users, in case they exist from a previous run. Do not need to delete the workloads, because they are deleted when the user is deleted. */ - test("Begin - DELETE all test users") { - if (rootpw == "") fail("The exchange root password must be set in EXCHANGE_ROOTPW and must also be put in config.json.") - deleteAllUsers() - } - - /** Add users, node, workload for future tests */ - test("Add users, node, workload for future tests") { - var userInput = PostPutUsersRequest(pw, admin = false, user+"@hotmail.com") - var userResponse = Http(URL+"/users/"+user).postData(write(userInput)).method("post").headers(CONTENT).headers(ACCEPT).headers(ROOTAUTH).asString - info("code: "+userResponse.code+", userResponse.body: "+userResponse.body) - assert(userResponse.code === HttpCode.POST_OK) - - userInput = PostPutUsersRequest(pw2, admin = false, user2+"@hotmail.com") - userResponse = Http(URL+"/users/"+user2).postData(write(userInput)).method("post").headers(CONTENT).headers(ACCEPT).headers(ROOTAUTH).asString - info("code: "+userResponse.code+", userResponse.body: "+userResponse.body) - assert(userResponse.code === HttpCode.POST_OK) - - val devInput = PutNodesRequest(nodeToken, "bc dev test", "", None, Some(List(RegService("foo",1,"{}",List( - Prop("arch","arm","string","in"), - Prop("version","2.0.0","version","in"), - Prop("blockchainProtocols","agProto","list","in"))))), "whisper-id", Map(), "NODEABC") - val devResponse = Http(URL+"/nodes/"+nodeId).postData(write(devInput)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+devResponse.code) - assert(devResponse.code === HttpCode.PUT_OK) - - val agbotInput = PutAgbotsRequest(agbotToken, "agbot"+agbotId+"-norm", /*List[APattern](),*/ "whisper-id", "ABC") - val agbotResponse = Http(URL+"/agbots/"+agbotId).postData(write(agbotInput)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+agbotResponse.code+", agbotResponse.body: "+agbotResponse.body) - assert(agbotResponse.code === HttpCode.PUT_OK) - } - - test("POST /orgs/"+orgid+"/workloads - add "+workload+" before the referenced microservice exists - should fail") { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", public = false, wkUrl, "1.0.0", "arm", None, List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) - val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.BAD_INPUT) - } - - test("POST /orgs/"+orgid+"/microservices - add microservice so workloads can reference it") { - val input = PostPutMicroserviceRequest("testMicro", "desc", public = false, microurl, microversion, microarch, "single", None, None, List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) - val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.POST_OK) - } - - test("PUT /orgs/"+orgid+"/workloads/"+workload+" - update WK that is not there yet - should fail") { - // PostPutWorkloadRequest(label: String, description: String, workloadUrl: String, version: String, arch: String, downloadUrl: String, apiSpec: List[Map[String,String]], userInput: List[Map[String,String]], workloads: List[Map[String,String]]) { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", public = false, wkUrl, "1.0.0", "arm", Some("updated"), List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) - val response = Http(URL+"/workloads/"+workload).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.NOT_FOUND) - } - - test("POST /orgs/"+orgid+"/workloads - add "+workload+" that is not signed - should fail") { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", public = false, wkUrl, "1.0.0", "arm", None, List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","",""))) - val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.BAD_INPUT) - } - - test("POST /orgs/"+orgid+"/workloads - add "+workload+" that needs 2 MSes - should fail") { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", public = false, wkUrl, "1.0.0", "arm", None, List(WMicroservices(microurl,orgid,microversion,microarch),WMicroservices(microurl2,orgid,microversion2,microarch2)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a",""))) - val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.BAD_INPUT) - } - - test("POST /orgs/"+orgid+"/workloads - add "+workload+" as user") { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", public = false, wkUrl, "1.0.0", "arm", None, List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) - val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.POST_OK) - val respObj = parse(response.body).extract[ApiResponse] - assert(respObj.msg.contains("workload '"+orgworkload+"' created")) - } - - test("POST /orgs/"+orgid+"/workloads - add "+workload+" again - should fail") { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", public = false, wkUrl, "1.0.0", "arm", None, List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) - val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.ALREADY_EXISTS) - } - - test("PUT /orgs/"+orgid+"/workloads/"+workload+" - update to need 2 MSes - should fail") { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", public = false, wkUrl, "1.0.0", "arm", Some("updated"), List(WMicroservices(microurl,orgid,microversion,microarch),WMicroservices(microurl2,orgid,microversion2,microarch2)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) - val response = Http(URL+"/workloads/"+workload).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.BAD_INPUT) - } - - test("PUT /orgs/"+orgid+"/workloads/"+workload+" - update needing only the existing MS") { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", public = false, wkUrl, "1.0.0", "arm", Some("updated"), List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) - val response = Http(URL+"/workloads/"+workload).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.PUT_OK) - } - - test("POST /orgs/"+orgid+"/microservices - add 2nd microservice so workloads can reference both") { - val input = PostPutMicroserviceRequest("testMicro", "desc", public = false, microurl2, microversion2, microarch2, "single", None, None, List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) - val response = Http(URL+"/microservices").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.POST_OK) - } - - test("PUT /orgs/"+orgid+"/workloads/"+workload+" - update to need 2 MSes - this time should succeed") { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", public = false, wkUrl, "1.0.0", "arm", Some("updated"), List(WMicroservices(microurl,orgid,microversion,microarch),WMicroservices(microurl2,orgid,microversion2,microarch2)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) - val response = Http(URL+"/workloads/"+workload).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.PUT_OK) - } - - test("PUT /orgs/"+orgid+"/workloads/"+workload+" - update to need 2 MSes, but 1 version is not in range - should fail") { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", public = false, wkUrl, "1.0.0", "arm", Some("updated"), List(WMicroservices(microurl,orgid,"2.0.0",microarch),WMicroservices(microurl2,orgid,microversion2,microarch2)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) - val response = Http(URL+"/workloads/"+workload).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.BAD_INPUT) - } - - test("PUT /orgs/"+orgid+"/workloads/"+workload+" - update as 2nd user - should fail") { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", public = false, wkUrl, "1.0.0", "arm", Some("should not work"), List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) - val response = Http(URL+"/workloads/"+workload).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.ACCESS_DENIED) - } - - test("PUT /orgs/"+orgid+"/workloads/"+workload+" - update as agbot - should fail") { - val input = PostPutWorkloadRequest(wkBase+" arm", "desc", public = false, wkUrl, "1.0.0", "arm", None, List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) - val response = Http(URL+"/workloads/"+workload).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(AGBOTAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.ACCESS_DENIED) - } - - test("PUT /orgs/"+orgid+"/workloads/"+workload2+" - invalid workload body") { - val badJsonInput = """{ - "labelxx": "GPS x86_64" - }""" - val response = Http(URL+"/workloads/"+workload2).postData(badJsonInput).method("put").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.BAD_INPUT) - } - - test("POST /orgs/"+orgid+"/workloads - add "+workload2+" as node - should fail") { - val input = PostPutWorkloadRequest(wkBase2+" arm", "desc", public = false, wkUrl2, "1.0.0", "arm", None, List(), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) - val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(NODEAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.ACCESS_DENIED) - } - - test("POST /orgs/"+orgid+"/workloads - add "+workload2+" as 2nd user, with no referenced MSes") { - val input = PostPutWorkloadRequest(wkBase2+" arm", "desc", public = true, wkUrl2, "1.0.0", "arm", None, List(), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) - val response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.POST_OK) - } - - test("PUT /orgs/"+orgid+"/workloads/"+workload2+" - add "+workload2+" as 2nd user, with a referenced MS so future GETs work") { - val input = PostPutWorkloadRequest(wkBase2+" arm", "desc", public = true, wkUrl2, "1.0.0", "arm", None, List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) - val response = Http(URL+"/workloads/"+workload2).postData(write(input)).method("put").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.PUT_OK) - } - - /*todo: when all test suites are run at the same time, there are sometimes timing problems them all setting config values... - test("POST /orgs/"+orgid+"/workloads - with low maxWorkloads - should fail") { - 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 origMaxWorkloads = ExchConfig.getInt("api.limits.maxWorkloads") - - // Change the maxWorkloads config value in the svr - var configInput = AdminConfigRequest("api.limits.maxWorkloads", "0") // user only owns 1 currently - var response = Http(URL+"/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) - - // Now try adding another workload - expect it to be rejected - val input = PostPutWorkloadRequest(wkBase3+" arm", "desc", wkUrl3, "1.0.0", "arm", "", List(WMicroservices(microurl,orgid,microversion,microarch)), List(Map("name" -> "foo")), List(MDockerImages("{\"services\":{}}","a","a"))) - response = Http(URL+"/workloads").postData(write(input)).method("post").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.ACCESS_DENIED) - val respObj = parse(response.body).extract[ApiResponse] - assert(respObj.msg.contains("Access Denied")) - - // Restore the maxWorkloads config value in the svr - configInput = AdminConfigRequest("api.limits.maxWorkloads", origMaxWorkloads.toString) - response = Http(URL+"/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) - } - } - */ - - test("GET /orgs/"+orgid+"/workloads") { - val response: HttpResponse[String] = Http(URL+"/workloads").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - //info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val respObj = parse(response.body).extract[GetWorkloadsResponse] - assert(respObj.workloads.size === 2) - - assert(respObj.workloads.contains(orgworkload)) - var wk = respObj.workloads(orgworkload) // the 2nd get turns the Some(val) into val - assert(wk.label === wkBase+" arm") - assert(wk.owner === orguser) - - assert(respObj.workloads.contains(orgworkload2)) - wk = respObj.workloads(orgworkload2) // the 2nd get turns the Some(val) into val - assert(wk.label === wkBase2+" arm") - assert(wk.owner === orguser2) - } - - test("GET /orgs/"+orgid+"/workloads - filter owner and workloadUrl") { - val response: HttpResponse[String] = Http(URL+"/workloads").headers(ACCEPT).headers(USERAUTH).param("owner",orguser2).param("specRef",microurl).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val respObj = parse(response.body).extract[GetWorkloadsResponse] - assert(respObj.workloads.size === 1) - assert(respObj.workloads.contains(orgworkload2)) - } - - test("GET /orgs/"+orgid+"/workloads - filter by public setting") { - // Find the public==true workloads - var response: HttpResponse[String] = Http(URL+"/workloads").headers(ACCEPT).headers(USERAUTH).param("public","true").asString - info("code: "+response.code) - assert(response.code === HttpCode.OK) - var respObj = parse(response.body).extract[GetWorkloadsResponse] - assert(respObj.workloads.size === 1) - assert(respObj.workloads.contains(orgworkload2)) - - // Find the public==false workloads - response = Http(URL+"/workloads").headers(ACCEPT).headers(USERAUTH).param("public","false").asString - info("code: "+response.code) - assert(response.code === HttpCode.OK) - respObj = parse(response.body).extract[GetWorkloadsResponse] - assert(respObj.workloads.size === 1) - assert(respObj.workloads.contains(orgworkload)) - } - - test("GET /orgs/"+orgid+"/workloads - as node") { - val response: HttpResponse[String] = Http(URL+"/workloads").headers(ACCEPT).headers(NODEAUTH).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val respObj = parse(response.body).extract[GetWorkloadsResponse] - assert(respObj.workloads.size === 2) - } - - test("GET /orgs/"+orgid+"/workloads - as agbot") { - val response: HttpResponse[String] = Http(URL+"/workloads").headers(ACCEPT).headers(AGBOTAUTH).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val respObj = parse(response.body).extract[GetWorkloadsResponse] - assert(respObj.workloads.size === 2) - } - - test("GET /orgs/"+orgid+"/workloads/"+workload+" - as user") { - val response: HttpResponse[String] = Http(URL+"/workloads/"+workload).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val respObj = parse(response.body).extract[GetWorkloadsResponse] - assert(respObj.workloads.size === 1) - - assert(respObj.workloads.contains(orgworkload)) - val wk = respObj.workloads(orgworkload) // the 2nd get turns the Some(val) into val - assert(wk.label === wkBase+" arm") - - // Verify the lastUpdated from the PUT above is within a few seconds of now. Format is: 2016-09-29T13:04:56.850Z[UTC] - val now: Long = System.currentTimeMillis / 1000 // seconds since 1/1/1970 - val lastUp = ZonedDateTime.parse(wk.lastUpdated).toEpochSecond - assert(now - lastUp <= 5) // should not be more than 3 seconds from the time the put was done above - } - - test("PATCH /orgs/"+orgid+"/workloads/"+workload+" - as user") { - val jsonInput = """{ - "downloadUrl": "this is now patched" - }""" - val response = Http(URL+"/workloads/"+workload).postData(jsonInput).method("patch").headers(CONTENT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.PUT_OK) - } - - test("PATCH /orgs/"+orgid+"/workloads/"+workload+" - as user2 - should fail") { - val jsonInput = """{ - "downloadUrl": "this is now patched" - }""" - val response = Http(URL+"/workloads/"+workload).postData(jsonInput).method("patch").headers(CONTENT).headers(ACCEPT).headers(USER2AUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.ACCESS_DENIED) - } - - test("GET /orgs/"+orgid+"/workloads/"+workload+" - as agbot, check patch by getting that 1 attr") { - val response: HttpResponse[String] = Http(URL+"/workloads/"+workload).headers(ACCEPT).headers(AGBOTAUTH).param("attribute","downloadUrl").asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.OK) - val respObj = parse(response.body).extract[GetWorkloadAttributeResponse] - assert(respObj.attribute === "downloadUrl") - assert(respObj.value === "this is now patched") - } - - test("GET /orgs/"+orgid+"/workloads/"+workload+"notthere - as user - should fail") { - val response: HttpResponse[String] = Http(URL+"/workloads/"+workload+"notthere").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.NOT_FOUND) - val getWorkloadResp = parse(response.body).extract[GetWorkloadsResponse] - assert(getWorkloadResp.workloads.size === 0) - } - - - // Key tests ============================================== - test("GET /orgs/"+orgid+"/workloads/"+workload+"/keys - no keys have been created yet - should fail") { - val response: HttpResponse[String] = Http(URL+"/workloads/"+workload+"/keys").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.NOT_FOUND) - val resp = parse(response.body).extract[List[String]] - assert(resp.size === 0) - } - - test("PUT /orgs/"+orgid+"/workloads/"+workload+"/keys/"+keyId+" - add "+keyId+" as user") { - //val input = PutWorkloadKeyRequest(key) - val response = Http(URL+"/workloads/"+workload+"/keys/"+keyId).postData(key).method("put").headers(CONTENTTEXT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.POST_OK) - } - - test("PUT /orgs/"+orgid+"/workloads/"+workload+"/keys/"+keyId2+" - add "+keyId2+" as user") { - //val input = PutWorkloadKeyRequest(key2) - val response = Http(URL+"/workloads/"+workload+"/keys/"+keyId2).postData(key2).method("put").headers(CONTENTTEXT).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.POST_OK) - } - - test("GET /orgs/"+orgid+"/workloads/"+workload+"/keys - should be 2 now") { - val response: HttpResponse[String] = Http(URL+"/workloads/"+workload+"/keys").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.OK) - val resp = parse(response.body).extract[List[String]] - assert(resp.size === 2) - assert(resp.contains(keyId) && resp.contains(keyId2)) - } - - test("GET /orgs/"+orgid+"/workloads/"+workload+"/keys/"+keyId+" - get 1 of the keys and check content") { - val response: HttpResponse[String] = Http(URL+"/workloads/"+workload+"/keys/"+keyId).headers(ACCEPTTEXT).headers(USERAUTH).asString - //val response: HttpResponse[Array[Byte]] = Http(URL+"/workloads/"+workload+"/keys/"+keyId).headers(ACCEPTTEXT).headers(USERAUTH).asBytes - //val bodyStr = (response.body.map(_.toChar)).mkString - //info("code: "+response.code+", response.body: "+bodyStr) - info("code: "+response.code) - assert(response.code === HttpCode.OK) - assert(response.body === key) - } - - test("DELETE /orgs/"+orgid+"/workloads/"+workload+"/keys/"+keyId) { - val response: HttpResponse[String] = Http(URL+"/workloads/"+workload+"/keys/"+keyId).method("delete").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.DELETED) - } - - test("DELETE /orgs/"+orgid+"/workloads/"+workload+"/keys/"+keyId+" try deleting it again - should fail") { - val response: HttpResponse[String] = Http(URL+"/workloads/"+workload+"/keys/"+keyId).method("delete").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.NOT_FOUND) - } - - test("GET /orgs/"+orgid+"/workloads/"+workload+"/keys/"+keyId+" - verify it is gone") { - val response: HttpResponse[String] = Http(URL+"/workloads/"+workload+"/keys/"+keyId).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.NOT_FOUND) - } - - test("DELETE /orgs/"+orgid+"/workloads/"+workload+"/keys - delete all keys") { - val response: HttpResponse[String] = Http(URL+"/workloads/"+workload+"/keys").method("delete").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.DELETED) - } - - test("GET /orgs/"+orgid+"/workloads/"+workload+"/keys - all keys should be gone now") { - val response: HttpResponse[String] = Http(URL+"/workloads/"+workload+"/keys").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - assert(response.code === HttpCode.NOT_FOUND) - val resp = parse(response.body).extract[List[String]] - assert(resp.size === 0) - } - - - test("DELETE /orgs/"+orgid+"/workloads/"+workload) { - val response = Http(URL+"/workloads/"+workload).method("delete").headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.DELETED) - } - - test("GET /orgs/"+orgid+"/workloads/"+workload+" - as user - verify gone") { - val response: HttpResponse[String] = Http(URL+"/workloads/"+workload).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.NOT_FOUND) - val getWorkloadResp = parse(response.body).extract[GetWorkloadsResponse] - assert(getWorkloadResp.workloads.size === 0) - } - - test("DELETE /orgs/"+orgid+"/users/"+user2+" - which should also delete workload2") { - val response = Http(URL+"/users/"+user2).method("delete").headers(ACCEPT).headers(ROOTAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.DELETED) - } - - test("GET /orgs/"+orgid+"/workloads/"+workload2+" - as user - verify gone") { - val response: HttpResponse[String] = Http(URL+"/workloads/"+workload2).headers(ACCEPT).headers(USERAUTH).asString - info("code: "+response.code) - // info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.NOT_FOUND) - val getWorkloadResp = parse(response.body).extract[GetWorkloadsResponse] - assert(getWorkloadResp.workloads.size === 0) - } - - /** Clean up, delete all the test workloads */ - test("Cleanup - DELETE all test workloads") { - deleteAllUsers() - } - - /** Delete the org we used for this test */ - test("POST /orgs/"+orgid+" - delete org") { - // Try deleting it 1st, in case it is left over from previous test - val response = Http(URL).method("delete").headers(ACCEPT).headers(ROOTAUTH).asString - info("code: "+response.code+", response.body: "+response.body) - assert(response.code === HttpCode.DELETED) - } - -} \ No newline at end of file