diff --git a/.gitignore b/.gitignore index dad7bec10..8cd3d476e 100644 --- a/.gitignore +++ b/.gitignore @@ -16,8 +16,13 @@ *.jar *.war -# Eclipse IDE +# Sonar +.sonar_lock +# Created by spring-boot-configuration-processor +.factorypath + +# Eclipse IDE *.pydevproject .project .metadata diff --git a/3rd-dependencies/README.md b/3rd-dependencies/README.md new file mode 100644 index 000000000..357ca54c2 --- /dev/null +++ b/3rd-dependencies/README.md @@ -0,0 +1,3 @@ +# 3rd Party Dependencies + +Contains listing of all maven (transient) licenses of the project. Is primary used to identify all changes between eclipse releases. diff --git a/3rd-dependencies/compile.txt b/3rd-dependencies/compile.txt index c8c59904e..6969c7735 100644 --- a/3rd-dependencies/compile.txt +++ b/3rd-dependencies/compile.txt @@ -1,74 +1,95 @@ aopalliance:aopalliance:jar:1.0:compile - com.fasterxml.jackson.core:jackson-annotations:jar:2.5.0:compile - com.fasterxml.jackson.core:jackson-core:jar:2.5.0:compile - com.fasterxml.jackson.core:jackson-databind:jar:2.5.0:compile - com.fasterxml:classmate:jar:1.3.0:compile - com.google.guava:guava:jar:18.0:compile - com.h2database:h2:jar:1.4.186:compile + ch.qos.logback:logback-classic:jar:1.1.3:compile + ch.qos.logback:logback-core:jar:1.1.3:compile + com.fasterxml.jackson.core:jackson-annotations:jar:2.5.5:compile + com.fasterxml.jackson.core:jackson-core:jar:2.5.5:compile + com.fasterxml.jackson.core:jackson-databind:jar:2.5.5:compile + com.fasterxml:classmate:jar:1.1.0:compile + com.fasterxml:classmate:jar:1.2.0:compile + com.google.collections:google-collections:jar:1.0-rc2:compile + com.google.guava:guava:jar:19.0:compile + com.h2database:h2:jar:1.4.190:compile + com.jayway.jsonpath:json-path:jar:0.9.1:compile + com.netflix.feign:feign-core:jar:8.12.1:compile + com.netflix.feign:feign-core:jar:8.14.2:compile + com.netflix.feign:feign-jackson:jar:8.14.1:compile + com.netflix.feign:feign-ribbon:jar:8.1.1:compile + com.netflix.feign:feign-slf4j:jar:8.1.1:compile + com.netflix.ribbon:ribbon-core:jar:2.0.0:compile + com.netflix.ribbon:ribbon-httpclient:jar:2.0.0:compile + com.netflix.ribbon:ribbon-loadbalancer:jar:2.0.0:compile + com.netflix.ribbon:ribbon:jar:2.0.0:compile com.rabbitmq:amqp-client:jar:3.5.5:compile com.vaadin.external.atmosphere:atmosphere-runtime:jar:2.2.7.vaadin1:compile com.vaadin.external.flute:flute:jar:1.3.0.gg2:compile com.vaadin.external.google:guava:jar:16.0.1.vaadin1:compile com.vaadin.external.slf4j:vaadin-slf4j-jdk14:jar:1.6.1:compile com.vaadin.external.streamhtmlparser:streamhtmlparser-jsilver:jar:0.0.10.vaadin1:compile - com.vaadin:vaadin-push:jar:7.5.7:compile - com.vaadin:vaadin-sass-compiler:jar:0.9.12:compile - com.vaadin:vaadin-server:jar:7.5.7:compile - com.vaadin:vaadin-shared:jar:7.5.7:compile + com.vaadin:vaadin-client-compiled:jar:7.6.3:compile + com.vaadin:vaadin-push:jar:7.6.3:compile + com.vaadin:vaadin-sass-compiler:jar:0.9.13:compile + com.vaadin:vaadin-server:jar:7.6.3:compile + com.vaadin:vaadin-shared:jar:7.6.3:compile + com.vaadin:vaadin-spring-boot-starter:jar:1.0.0:compile com.vaadin:vaadin-spring-boot:jar:1.0.0:compile com.vaadin:vaadin-spring:jar:1.0.0:compile - com.vaadin:vaadin-themes:jar:7.5.7:compile + com.vaadin:vaadin-themes:jar:7.6.3:compile + com.yahoo.platform.yui:yuicompressor:jar:2.4.8:compile commons-io:commons-io:jar:2.4:compile cz.jirutka.rsql:rsql-parser:jar:2.0.0:compile + io.reactivex:rxjava:jar:1.0.11:compile io.springfox:springfox-core:jar:2.0.3:compile - io.springfox:springfox-schema:jar:2.0.3:compile - io.springfox:springfox-spi:jar:2.0.3:compile - io.springfox:springfox-spring-web:jar:2.0.3:compile - io.springfox:springfox-swagger-common:jar:2.0.3:compile - io.springfox:springfox-swagger2:jar:2.0.3:compile - io.swagger:swagger-annotations:jar:1.5.0:compile - io.swagger:swagger-models:jar:1.5.0:compile javax.servlet:javax.servlet-api:jar:3.1.0:compile javax.transaction:javax.transaction-api:jar:1.2:compile javax.validation:validation-api:jar:1.1.0.Final:compile joda-time:joda-time:jar:2.5:compile net._01001111:jlorem:jar:1.1:compile + net.minidev:json-smart:jar:1.2:compile org.apache.commons:commons-lang3:jar:3.4:compile org.apache.commons:commons-pool2:jar:2.2:compile org.apache.logging.log4j:log4j-api:jar:2.1:compile org.apache.logging.log4j:log4j-core:jar:2.1:compile org.apache.logging.log4j:log4j-slf4j-impl:jar:2.1:compile - org.apache.tomcat:tomcat-jdbc:jar:8.0.28:compile - org.apache.tomcat:tomcat-juli:jar:8.0.28:compile + org.apache.tomcat:tomcat-jdbc:jar:8.0.30:compile + org.apache.tomcat:tomcat-juli:jar:8.0.30:compile org.aspectj:aspectjrt:jar:1.8.5:compile org.aspectj:aspectjweaver:jar:1.8.5:compile - org.eclipse.persistence:javax.persistence:jar:2.1.0:compile - org.eclipse.persistence:org.eclipse.persistence.antlr:jar:2.6.0:compile - org.eclipse.persistence:org.eclipse.persistence.asm:jar:2.6.0:compile - org.eclipse.persistence:org.eclipse.persistence.core:jar:2.6.0:compile - org.eclipse.persistence:org.eclipse.persistence.jpa.jpql:jar:2.6.0:compile - org.eclipse.persistence:org.eclipse.persistence.jpa:jar:2.6.0:compile + org.eclipse.persistence:javax.persistence:jar:2.1.1:compile + org.eclipse.persistence:org.eclipse.persistence.antlr:jar:2.6.2:compile + org.eclipse.persistence:org.eclipse.persistence.asm:jar:2.6.2:compile + org.eclipse.persistence:org.eclipse.persistence.core:jar:2.6.2:compile + org.eclipse.persistence:org.eclipse.persistence.jpa.jpql:jar:2.6.2:compile + org.eclipse.persistence:org.eclipse.persistence.jpa:jar:2.6.2:compile org.flywaydb:flyway-core:jar:3.1:compile org.glassfish:javax.json:jar:1.0.4:compile - org.hibernate:hibernate-validator:jar:5.2.2.Final:compile + org.hibernate:hibernate-validator:jar:5.2.4.Final:compile org.jboss.logging:jboss-logging:jar:3.2.1.Final:compile - org.jsoup:jsoup:jar:1.8.1:compile - org.mapstruct:mapstruct:jar:1.0.0.Beta4:compile - org.mongodb:mongo-java-driver:jar:3.0.2:compile + org.json:json:jar:20141113:compile + org.jsoup:jsoup:jar:1.8.3:compile + org.mongodb:mongo-java-driver:jar:3.2.2:compile org.objenesis:objenesis:jar:2.1:compile - org.slf4j:jcl-over-slf4j:jar:1.7.12:compile - org.slf4j:jul-to-slf4j:jar:1.7.12:compile - org.slf4j:slf4j-api:jar:1.7.7:compile + org.slf4j:jcl-over-slf4j:jar:1.7.13:compile + org.slf4j:jul-to-slf4j:jar:1.7.13:compile + org.slf4j:log4j-over-slf4j:jar:1.7.13:compile + org.slf4j:slf4j-api:jar:1.7.13:compile org.springframework.amqp:spring-amqp:jar:1.4.6.RELEASE:compile org.springframework.amqp:spring-rabbit:jar:1.4.6.RELEASE:compile - org.springframework.boot:spring-boot-autoconfigure:jar:1.2.7.RELEASE:compile - org.springframework.boot:spring-boot-starter-aop:jar:1.2.7.RELEASE:compile - org.springframework.boot:spring-boot-starter-data-jpa:jar:1.2.7.RELEASE:compile - org.springframework.boot:spring-boot-starter-jdbc:jar:1.2.7.RELEASE:compile - org.springframework.boot:spring-boot-starter-log4j2:jar:1.2.7.RELEASE:compile - org.springframework.boot:spring-boot-starter-web:jar:1.2.7.RELEASE:compile - org.springframework.boot:spring-boot-starter:jar:1.2.7.RELEASE:compile - org.springframework.boot:spring-boot:jar:1.2.7.RELEASE:compile + org.springframework.boot:spring-boot-autoconfigure:jar:1.2.8.RELEASE:compile + org.springframework.boot:spring-boot-configuration-processor:jar:1.2.8.RELEASE:compile + org.springframework.boot:spring-boot-starter-aop:jar:1.2.8.RELEASE:compile + org.springframework.boot:spring-boot-starter-data-jpa:jar:1.2.8.RELEASE:compile + org.springframework.boot:spring-boot-starter-jdbc:jar:1.2.8.RELEASE:compile + org.springframework.boot:spring-boot-starter-log4j2:jar:1.2.8.RELEASE:compile + org.springframework.boot:spring-boot-starter-logging:jar:1.2.8.RELEASE:compile + org.springframework.boot:spring-boot-starter-web:jar:1.2.8.RELEASE:compile + org.springframework.boot:spring-boot-starter:jar:1.2.8.RELEASE:compile + org.springframework.boot:spring-boot:jar:1.2.8.RELEASE:compile + org.springframework.cloud:spring-cloud-commons:jar:1.0.5.RELEASE:compile + org.springframework.cloud:spring-cloud-context:jar:1.0.5.RELEASE:compile + org.springframework.cloud:spring-cloud-netflix-core:jar:1.0.7.RELEASE:compile + org.springframework.cloud:spring-cloud-starter-feign:jar:1.0.6.RELEASE:compile + org.springframework.cloud:spring-cloud-starter-ribbon:jar:1.0.6.RELEASE:compile + org.springframework.cloud:spring-cloud-starter:jar:1.0.5.RELEASE:compile org.springframework.data:spring-data-commons:jar:1.10.1.RELEASE:compile org.springframework.data:spring-data-jpa:jar:1.8.1.RELEASE:compile org.springframework.data:spring-data-mongodb:jar:1.7.1.RELEASE:compile @@ -77,29 +98,33 @@ org.springframework.plugin:spring-plugin-core:jar:1.1.0.RELEASE:compile org.springframework.plugin:spring-plugin-metadata:jar:1.2.0.RELEASE:compile org.springframework.retry:spring-retry:jar:1.1.2.RELEASE:compile - org.springframework.security:spring-security-aspects:jar:3.2.8.RELEASE:compile - org.springframework.security:spring-security-config:jar:3.2.8.RELEASE:compile - org.springframework.security:spring-security-core:jar:3.2.8.RELEASE:compile - org.springframework.security:spring-security-web:jar:3.2.8.RELEASE:compile - org.springframework:spring-aop:jar:4.1.8.RELEASE:compile - org.springframework:spring-aspects:jar:4.1.8.RELEASE:compile - org.springframework:spring-beans:jar:4.1.8.RELEASE:compile - org.springframework:spring-context-support:jar:4.1.8.RELEASE:compile - org.springframework:spring-context:jar:4.1.8.RELEASE:compile - org.springframework:spring-core:jar:4.1.8.RELEASE:compile - org.springframework:spring-expression:jar:4.1.8.RELEASE:compile - org.springframework:spring-jdbc:jar:4.1.8.RELEASE:compile - org.springframework:spring-messaging:jar:4.1.8.RELEASE:compile - org.springframework:spring-orm:jar:4.1.8.RELEASE:compile - org.springframework:spring-tx:jar:4.1.8.RELEASE:compile - org.springframework:spring-web:jar:4.1.8.RELEASE:compile - org.springframework:spring-webmvc:jar:4.1.8.RELEASE:compile + org.springframework.security:spring-security-aspects:jar:3.2.9.RELEASE:compile + org.springframework.security:spring-security-config:jar:3.2.9.RELEASE:compile + org.springframework.security:spring-security-core:jar:3.2.9.RELEASE:compile + org.springframework.security:spring-security-crypto:jar:3.2.9.RELEASE:compile + org.springframework.security:spring-security-web:jar:3.2.9.RELEASE:compile + org.springframework:spring-aop:jar:4.1.9.RELEASE:compile + org.springframework:spring-aspects:jar:4.1.9.RELEASE:compile + org.springframework:spring-beans:jar:4.1.9.RELEASE:compile + org.springframework:spring-context-support:jar:4.1.9.RELEASE:compile + org.springframework:spring-context:jar:4.1.9.RELEASE:compile + org.springframework:spring-core:jar:4.1.9.RELEASE:compile + org.springframework:spring-expression:jar:4.1.9.RELEASE:compile + org.springframework:spring-jdbc:jar:4.1.9.RELEASE:compile + org.springframework:spring-messaging:jar:4.1.9.RELEASE:compile + org.springframework:spring-orm:jar:4.1.9.RELEASE:compile + org.springframework:spring-tx:jar:4.1.9.RELEASE:compile + org.springframework:spring-web:jar:4.1.9.RELEASE:compile + org.springframework:spring-webmvc:jar:4.1.9.RELEASE:compile org.vaadin.addons.lazyquerycontainer:vaadin-lazyquerycontainer:jar:7.4.0.1:compile + org.vaadin.addons:contextmenu:jar:4.5:compile org.vaadin.addons:flexibleoptiongroup:jar:2.2.0:compile org.vaadin.addons:tokenfield:jar:7.0.1:compile + org.vaadin.alump.distributionbar:dbar-addon:jar:1.2.0:compile org.vaadin.spring.addons:vaadin-spring-addon-eventbus:jar:0.0.6.RELEASE:compile org.vaadin.spring.extensions:vaadin-spring-ext-core:jar:0.0.6.RELEASE:compile org.vaadin.spring.extensions:vaadin-spring-ext-security:jar:0.0.6.RELEASE:compile org.w3c.css:sac:jar:1.3:compile org.yaml:snakeyaml:jar:1.14:compile redis.clients:jedis:jar:2.5.2:compile + rhino:js:jar:1.7R2:compile diff --git a/3rd-dependencies/listDeps.sh b/3rd-dependencies/listDeps.sh index 3f6dddbd5..dfb5b942c 100755 --- a/3rd-dependencies/listDeps.sh +++ b/3rd-dependencies/listDeps.sh @@ -6,3 +6,6 @@ find . -name dependencies.txt|while read i; do cat $i;done|grep '.*:.*:test'|sor find . -name dependencies.txt|while read i; do cat $i;done|grep '.*:.*:provided'|sort|uniq > 3rd-dependencies/provided.txt find . -name dependencies.txt|while read i; do rm $i;done cd 3rd-dependencies/ +cat compile.txt provided.txt|cut -d':' -f1-4|while read i; do grep -h $i test.txt;done|sort|uniq|while read x; do sed -i.bak -e s/$x// test.txt ;done +sed -i.bak '/^[[:space:]]*$/d' test.txt +rm *.bak diff --git a/3rd-dependencies/provided.txt b/3rd-dependencies/provided.txt index 84074c22c..48ea20ba5 100644 --- a/3rd-dependencies/provided.txt +++ b/3rd-dependencies/provided.txt @@ -1,6 +1,6 @@ javax.servlet:javax.servlet-api:jar:3.1.0:provided - org.apache.tomcat.embed:tomcat-embed-core:jar:8.0.28:provided - org.apache.tomcat.embed:tomcat-embed-el:jar:8.0.28:provided - org.apache.tomcat.embed:tomcat-embed-logging-juli:jar:8.0.28:provided - org.apache.tomcat.embed:tomcat-embed-websocket:jar:8.0.28:provided - org.springframework.boot:spring-boot-starter-tomcat:jar:1.2.7.RELEASE:provided + org.apache.tomcat.embed:tomcat-embed-core:jar:8.0.30:provided + org.apache.tomcat.embed:tomcat-embed-el:jar:8.0.30:provided + org.apache.tomcat.embed:tomcat-embed-logging-juli:jar:8.0.30:provided + org.apache.tomcat.embed:tomcat-embed-websocket:jar:8.0.30:provided + org.springframework.boot:spring-boot-starter-tomcat:jar:1.2.8.RELEASE:provided diff --git a/3rd-dependencies/test.txt b/3rd-dependencies/test.txt index 83d519ac8..dc4f579ea 100644 --- a/3rd-dependencies/test.txt +++ b/3rd-dependencies/test.txt @@ -1,55 +1,36 @@ - com.fasterxml.jackson.core:jackson-core:jar:2.5.0:test - com.fasterxml.jackson.core:jackson-databind:jar:2.5.0:test com.github.fge:btf:jar:1.2:test com.github.fge:jackson-coreutils:jar:1.6:test com.github.fge:json-patch:jar:1.7:test com.github.fge:msg-simple:jar:1.1:test com.google.code.findbugs:jsr305:jar:2.0.1:test - com.h2database:h2:jar:1.4.186:test - com.jayway.jsonpath:json-path:jar:0.9.1:test + com.sun.jersey:jersey-client:jar:1.18.1:test + com.sun.jersey:jersey-core:jar:1.13:test commons-beanutils:commons-beanutils-core:jar:1.8.3:test - commons-io:commons-io:jar:2.4:test commons-logging:commons-logging:jar:1.1.1:test - de.flapdoodle.embed:de.flapdoodle.embed.mongo:jar:1.50.0:test - de.flapdoodle.embed:de.flapdoodle.embed.process:jar:1.50.0:test - io.swagger:swagger-annotations:jar:1.5.0:test + de.flapdoodle.embed:de.flapdoodle.embed.mongo:jar:1.50.2:test + de.flapdoodle.embed:de.flapdoodle.embed.process:jar:1.50.1:test javax.el:javax.el-api:jar:2.2.4:test junit:junit:jar:4.12:test net.java.dev.jna:jna-platform:jar:4.0.0:test - net.java.dev.jna:jna:jar:3.3.0:test net.java.dev.jna:jna:jar:4.0.0:test - net.java.dev.jna:jna:jar:platform:3.3.0:test - net.minidev:json-smart:jar:1.2:test org.apache.commons:commons-compress:jar:1.3:test - org.apache.commons:commons-lang3:jar:3.1:test org.apache.tika:tika-core:jar:1.7:test - org.aspectj:aspectjrt:jar:1.8.5:test org.atteo:evo-inflector:jar:1.2.1:test org.easytesting:fest-assert-core:jar:2.0M10:test org.easytesting:fest-assert:jar:1.4:test org.easytesting:fest-util:jar:1.2.5:test org.hamcrest:hamcrest-core:jar:1.3:test org.hamcrest:hamcrest-library:jar:1.3:test - org.json:json:jar:20141113:test org.jvnet.jaxb2_commons:jaxb2-basics-runtime:jar:0.9.3:test - org.mariadb.jdbc:mariadb-java-client:jar:1.2.3:test + org.mariadb.jdbc:mariadb-java-client:jar:1.3.5:test org.mockito:mockito-core:jar:1.10.19:test - org.objenesis:objenesis:jar:2.1:test - org.springframework.boot:spring-boot-starter-test:jar:1.2.7.RELEASE:test + org.springframework.boot:spring-boot-starter-test:jar:1.2.8.RELEASE:test org.springframework.data:spring-data-rest-core:jar:2.3.1.RELEASE:test org.springframework.data:spring-data-rest-webmvc:jar:2.3.1.RELEASE:test - org.springframework.hateoas:spring-hateoas:jar:0.16.0.RELEASE:test - org.springframework.plugin:spring-plugin-core:jar:1.1.0.RELEASE:test - org.springframework.security:spring-security-aspects:jar:3.2.8.RELEASE:test - org.springframework.security:spring-security-config:jar:3.2.8.RELEASE:test - org.springframework.security:spring-security-web:jar:3.2.8.RELEASE:test - org.springframework:spring-context-support:jar:4.1.8.RELEASE:test - org.springframework:spring-test:jar:4.1.8.RELEASE:test - org.springframework:spring-web:jar:4.1.8.RELEASE:test - org.springframework:spring-webmvc:jar:4.1.8.RELEASE:test - ru.yandex.qatools.allure:allure-java-adaptor-api:jar:1.4.15:test - ru.yandex.qatools.allure:allure-java-annotations:jar:1.4.15:test - ru.yandex.qatools.allure:allure-java-aspects:jar:1.4.15:test - ru.yandex.qatools.allure:allure-junit-adaptor:jar:1.4.15:test - ru.yandex.qatools.allure:allure-model:jar:1.4.15:test + org.springframework:spring-test:jar:4.1.9.RELEASE:test + ru.yandex.qatools.allure:allure-java-adaptor-api:jar:1.4.22:test + ru.yandex.qatools.allure:allure-java-annotations:jar:1.4.22:test + ru.yandex.qatools.allure:allure-java-aspects:jar:1.4.22:test + ru.yandex.qatools.allure:allure-junit-adaptor:jar:1.4.22:test + ru.yandex.qatools.allure:allure-model:jar:1.4.22:test ru.yandex.qatools.properties:properties-loader:jar:1.5:test diff --git a/CONTRUBUTING.md b/CONTRUBUTING.md index bc04d2abd..626e7f0f1 100644 --- a/CONTRUBUTING.md +++ b/CONTRUBUTING.md @@ -4,7 +4,9 @@ Please read this if you intend to contribute to the project. -## Code Conventions +## Conventions + +### Code Style * Java files: * we follow the standard eclipse IDE (built in) code formatter with the following changes: @@ -19,6 +21,37 @@ Please read this if you intend to contribute to the project. * Sonarqube: * Our rule set is defined [here](http://sonar.eu-gb.mybluemix.net) +### Test documentation + +Please documented the test cases that you contribute by means of [Allure](http://allure.qatools.ru) annotations and proper test method naming. + +All test classes are documented with [Allure's](https://github.com/allure-framework/allure-core/wiki/Features-and-Stories) **@Features** and **@Stories** annotations in the following format: +``` +@Features("TEST_TYPE - HAWKBIT_COMPONENT") +@Stories("Test class description") +``` + +Test types are: +* Unit Tests - for single units tests with a mocked environment +* Component Tests - for complete components including lower layers, e.g. Spring MVC test on rest API including repository and database. +* Integration Tests - including clients, e.g. Selenium UI tests with various browsers. +* System Tests - on target environments, e.g. Cloud Foundry. + +Examples for hawkBit components: +* Management API +* Direct Device Integration API +* Device Management Federation API +* Management UI +* Repository +* Security + +``` +@Features("Component Tests - Management API") +@Stories("Distribution Set Type Resource") +``` + +In addition all test method's name describes in **camel case** what the test is all about and has a long description aith Allures **@Description** annotation. + ## Legal considerations for your contribution The following steps are necessary to comply with the Eclipse Foundation's IP policy. diff --git a/DDIA.md b/DDIA.md deleted file mode 100644 index 4754ae8c1..000000000 --- a/DDIA.md +++ /dev/null @@ -1,131 +0,0 @@ -## Direct Device Integration API (HTTP) -This API is based on HTTP standards and based on a polling mechanism. -The hawkbit update server provides REST resources which are consumed by the device to retrieve software update tasks. - - -##### Base Poll Resource -``` -GET /{tenant}/controller/v1/{controllerId} -``` -In the answer to the baseUrl polling the backend can send action links. A possible action is a deploy command. -The client makes a GET request on the given link: - -_Example Response_ -``` -{ - "config": { - "polling": { - "sleep": "00:05:00" - } - }, - "_links": { - "deploymentBase": { - "href": "http://localhost:8080/default/controller/v1/example/deploymentBase/1?c=644088541" - }, - "configData": { - "href": "http://localhost:8080/default/controller/v1/example/configData" - } - } -} -``` - -##### Deployment Base Resource -``` -GET /{tenant}/controller/v1/{controllerId}/deploymentBase/{id} -``` -_Example Response_ -``` -{ - "deployment": { - "download": "forced", - "update": "forced", - "chunks": [ - { - "part": "os", - "version": "1.0.0", - "name": "Linux", - "artifacts": [ - { - "filename": "linux.zip", - "hashes": { - "sha1": "46fc56de883ec027759d8513458fe1010aa7e793", - "md5": "5813e9655bd6871d0c25b8d510fd8605" - }, - "size": 52167, - "_links": { - "download": { - "href": "http://localhost:8080/default/controller/v1/example/softwaremodules/1/artifacts/linux.zip" - }, - "md5sum": { - "href": "http://localhost:8080/default/controller/v1/example/softwaremodules/1/artifacts/linux.zip.MD5SUM" - } - } - } - ] - } - ] - }, - "id": "1" -} -``` - - -##### Deployment Feedback Resource -To every deployment the client can post feedback back to the update-server about the deployment status. -``` -POST /{tenant}/controller/v1/{controllerId}/deploymentBase/{id}/feedback -Content-Type: application/json -``` -_Example Body Deployment Success_ -``` -{ - "id": 1, - "time": "20140511T121314", - "status": { - "execution": "closed", - "result": { - "finished": "success", - "progress": {} - } - } -} -``` - -_Example Body Deployment Proceeding_ -``` -{ - "id": "1", - "time": "20140511T121314", - "status": { - "execution": "proceeding", - "result": { - "finished": "none", - "progress": { - "cnt": 2, - "of": 5 - } - }, - "details": [ - "checking hash sums" - ] - } -} -``` - -_Example Body Deployment Error_ -``` -{ - "id": 1, - "time": "20140511T121314", - "status": { - "execution": "rejected", - "result": { - "finished": "failure", - "progress": {} - }, - "details": [ - "something bad happend" - ] - } -} -``` \ No newline at end of file diff --git a/DMFA.md b/DMFA.md deleted file mode 100644 index 6653f3d9f..000000000 --- a/DMFA.md +++ /dev/null @@ -1,127 +0,0 @@ -# !!! DRAFT !!! # -# Device Management Federation API # - -This document describes the **DMF API**. This API is based on AMQP [(AMQP-Specification)](ht1tp://www.amqp.org/sites/amqp.org/files/amqp.pdf "AMQP-Specification"). The SP Update server provides a message queue (default: "*dmf_receiver*") on the corresponding Rabbit MQ service, which accepts all incoming messages. - -Every Service, which wants to communicate, has to offer a own queue, to which the SP update server can send messages. The name of this queue is sent - -## Authentication ## -??? - -## Regular update process ## - -### Step 1: Creating a Thing on SP ### - -At the beginning every connecting service has to register its targets at the Sp update server. During the creating process the service has to send the name of the queue, which accepts messages, which are sent back from the Sp update server. - -The message which is sent by the connecting service to the SP update service has to fulfill the following format. The connecting service has to set the attributes in the header. The name of the tenent (*tenant*, an unique identifier of the thing/target (*thingId*) and an string which identifies the sending service (*sender*). Furthermore it has to set the *replyTo*-Property which defines the name of the queue, which receives messages sent by the SP Update Server. The body of the message is not analyzed and can be empty. - -**Header** -
-type=THING_CREATED
-tenant=#TENANTNAME#
-thingId=#ExampleDevice001#
-sender=#CONNECTORSERVICE#
-
-**Properties** -
-contentType=application/json
-replyTo=#queue.which.accepts.messages.from.sp#
-
-**Example Body** -
 
- -### Step 2: Begin of update process ### -Afterwards an distribution set can be assigned to the created thing. This can be done on the UI or with the REST API. To start the update process the SP update server sends a message over the registered queue back to the connecting service. - -Inside the message send from the Sp update service the header specifies the target which has to be updated. Therefor the *tenent* and the unique *thingId* are used. The body contains an identifier which defines the update process (*actionId*) and the information of the software modules. The *actionId* is important to send the progress of the update process to the server. The information about the software describes all modules which are included in the current update process, their identifier, their type, their version and the corresponding artifacts with the urls where the target can download the artifacts. - -**Header** -
-type=EVENT 
-tenant=#TENANTNAME#
-thingId=#ExampleDevice001#
-topic=DOWNLOAD_AND_INSTALL
-
-**Properties** -
-contentType=application/json
-
-**Example Body** -
-{
-"actionId":172,
-"softwareModules":[
-    {
-    "moduleId":6,
-    "moduleType":"firmware",
-    "moduleVersion":"7.7.7",
-    "artifacts":[
-        {
-        "urls":{
-            "coap":"coap://bumlux.test",
-            "http":"coap://bumlux.test"}
-        }]
-    }]
-}
-
- -### Step 3: Sending the state update process ### - -During the update progress the connected service can inform the sp update service about the progress and eventual warnings or errors. Therefore the *actionId* and the *softwareModuleId* are used to specify for which process and for which software module the progress update is for. Additionally in the Body there is a field *actionStatus*. This field is used to describe the type of the progress update and there are several allowed values: - -- **RETRIEVED**: general acknowledge message -- **DOWNLOAD**: describes all messages related to the download prorgess -- **RUNNING**: -- **WARNING**: A smaller problem which influence the update process, but no need for stop it. -- **ERROR**: A big issues which not allows continuing the update process. -- **FINISHED**: Successfully finished the update process. -- **CANCELED**: Response to a cancel request, when the process is canceled. -- **CANCEL_REJECTED**: Response to a cancel request, when canceling is not possible. - -The update process is marked as running until the connected Server sends a **FINISHED**, an **ERROR** or a **CANCELED** message. Additionally the connected service can put some human readable information into the message, which describes the state of the action more detailed. - -**Header** -
-type=EVENT 
-tenant=#TENANTNAME#
-topic=UPDATE_ACTION_STATUS
-
-**Properties** -
-contentType=application/json
-
-**Example Body** - -
-{
-"actionId":115,
-"softwareModuleId":3,
-"actionStatus":"DOWNLOAD",
-"message":["The download has started"]
-}
-
- -## Cancel process ## - -During the update process the SP update server has the chance to cancel an update. Therefore it sends the following message. This message includes the *tenant* and the corresponding *thingId* on theheader. - -**Header** -
-type=EVENT
-tenant=#TENANTNAME#
-thingId=#ExampleDevice001#
-topic=CANCEL_DOWNLOAD 
-
-**Properties** -
-contentType=application/json
-
-**Example Body** -
-
-
- -Receiving this message the connected service has to decide if an update can be canceled or not. If it is the update has to be canceled and a **CANCELED** message has to be sent to the SP server. Otherwise a **CANCEL_REJECTED** message has to be sent. - - ![](src/images/dmfa_cancel_response.png) \ No newline at end of file diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 000000000..eacce864d --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,9 @@ +# hawkBit Migration Guides +## Release 0.2 +### Configuration Property changes +- hawkbit.server.controller._ have changed to hawkbit.server.ddi._ +- info.build._ have changed to hawkbit.server.build._ +- hawkbit.server.demo._ have changed to hawkbit.server.ui.demo._ +- hawkbit.server.email.support has changed to hawkbit.server.ui.links.support +- hawkbit.server.email.request.account has changed to hawkbit.server.ui.links.requestAccount +- hawkbit.server.im.login.url has changed to hawkbit.server.ui.links.userManagement diff --git a/README.md b/README.md index e824971e9..fb649633b 100644 --- a/README.md +++ b/README.md @@ -4,21 +4,43 @@ Build: [![Circle CI](https://circleci.com/gh/eclipse/hawkbit.svg?style=svg)](htt [hawkBit](https://projects.eclipse.org/projects/iot.hawkbit) is an domain independent back end solution for rolling out software updates to constrained edge devices as well as more powerful controllers and gateways connected to IP based networking infrastructure. +# Documentation + +see [hawkBit Wiki](https://github.com/eclipse/hawkbit/wiki) + # Contact us * Want to chat with the team behind hawkBit? [![Join the chat at https://gitter.im/eclipse/hawkbit](https://badges.gitter.im/eclipse/hawkbit.svg)](https://gitter.im/eclipse/hawkbit?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +* Having issues with hawkBit? Open a [GitHub issue](https://github.com/eclipse/hawkbit/issues). * You can also check out our [Project Homepage](https://projects.eclipse.org/projects/iot.hawkbit) for further contact options. -# Compile -``` -mvn install -``` - -# Run and use +# Compile, Run and Getting Started We are not providing an off the shelf installation ready hawkBit update server. However, we recommend to check out the [Example Application](examples/hawkbit-example-app) for a runtime ready Spring Boot based update server that is empowered by hawkBit. +#### Clone and build hawkBit +``` +$ git clone https://github.com/eclipse/hawkbit.git +$ cd hawkbit +$ mvn clean install +``` +#### Start hawkBit example app +[Example Application](examples/hawkbit-example-app) +``` +$ java -jar ./examples/hawkbit-example-app/target/hawkbit-example-app-#version#.jar +``` +#### Start hawkBit device simulator +[Device Simulator](examples/hawkbit-device-simulator) +``` +$ java -jar ./examples/hawkbit-device-simulator/target/hawkbit-device-simulator-#version#.jar +``` +#### Generate Getting Started data +[Example Management API Client](examples/hawkbit-mgmt-api-client) +``` +$ java -jar ./examples/hawkbit-mgmt-api-client/target/hawkbit-mgmt-api-client-#version#.jar +``` + # Releases and Roadmap * We are currently working on the first formal release under the Eclipse banner: 0.1 (see [Release 0.1 branch](https://github.com/eclipse/hawkbit/tree/release-train-0.1)). @@ -29,12 +51,6 @@ We are not providing an off the shelf installation ready hawkBit update server. * And of course tons of usability improvements and bug fixes. -## Try out examples -#### Standalone Test Application Server -[Example Application](examples/hawkbit-example-app) -#### Device Simulator using the DMF AMQP API -[Device Simulator](examples/hawkbit-device-simulator) - # Modules `hawkbit-core` : core elements. `hawkbit-security-core` : core security elements. @@ -49,9 +65,3 @@ We are not providing an off the shelf installation ready hawkBit update server. `hawkbit-rest-resource` : HTTP REST endpoints for the Management and the Direct Device API. `hawkbit-ui` : Vaadin UI. `hawkbit-cache-redis` : spring cache manager configuration and implementation with redis, distributed cache and distributed events. - - -# Device Integration -There are two device integration APIs provided by the hawkbit update server. -* [Direct Device Integration API (HTTP)](DDIA.md) -* [Device Management Federation API (AMQP)](DMFA.md) diff --git a/examples/hawkbit-device-simulator/README.md b/examples/hawkbit-device-simulator/README.md index 610a1d256..869f80511 100644 --- a/examples/hawkbit-device-simulator/README.md +++ b/examples/hawkbit-device-simulator/README.md @@ -13,7 +13,7 @@ run org.eclipse.hawkbit.simulator.DeviceSimulator ## Notes -The simulator has user authentication enabled by default. Default credentials: +The simulator has user authentication enabled in **cloud profile**. Default credentials: * username : admin * passwd : admin @@ -29,8 +29,6 @@ The UI can be accessed via the URL: http://localhost:8083 ``` -`Basic Authentication Credentials are admin / admin` - ![](src/main/images/generateScreenshot.png) ![](src/main/images/updateProcessScreenshot.png) @@ -45,6 +43,10 @@ Optional parameters: * name : name prefix simulated devices (default: "dmfSimulated"), followed by counter * amount : number of simulated devices (default: 20, capped at: 4000) * tenant : in a multi-tenenat ready hawkBit installation (default: "DEFAULT") +* api : the API which should be used for the simulated device either `dmf` or `ddi` (default: "ddi") +* endpoint : URL which defines the hawkbit DDI base endpoint (deffault: "http://localhost:8080") +* polldelay : number in milliseconds of the delay when DDI simulated devices should poll the endpoint (default: "30") +* gatewaytoken : an hawkbit gateway token to be used in case hawkbit does not allow anonymous access for DDI devices (default: "") Example: for 20 simulated devices (default) @@ -56,3 +58,8 @@ Example: for 10 simulated devices that start with the name prefix "activeSim": ``` http://localhost:8083/start?amount=10&name=activeSim ``` + +Example: for 5 simulated devices that start with the name prefix "ddi" using the Direct Device Integration API (http): +``` +http://localhost:8083/start?amount=5&name=ddi?api=ddi +``` diff --git a/examples/hawkbit-device-simulator/pom.xml b/examples/hawkbit-device-simulator/pom.xml index 9a84d13f5..a2575e9db 100644 --- a/examples/hawkbit-device-simulator/pom.xml +++ b/examples/hawkbit-device-simulator/pom.xml @@ -100,7 +100,6 @@ com.google.guava guava - 19.0 com.netflix.feign @@ -116,13 +115,18 @@ com.jayway.jsonpath json-path + + org.springframework.boot + spring-boot-configuration-processor + true + com.vaadin vaadin-bom - 7.5.5 + 7.6.3 pom import diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DDISimulatedDevice.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DDISimulatedDevice.java index 1417c3153..b58d3a413 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DDISimulatedDevice.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DDISimulatedDevice.java @@ -10,7 +10,6 @@ package org.eclipse.hawkbit.simulator; import java.util.concurrent.ScheduledExecutorService; -import org.eclipse.hawkbit.simulator.DeviceSimulatorUpdater.UpdaterCallback; import org.eclipse.hawkbit.simulator.http.ControllerResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -82,28 +81,27 @@ public class DDISimulatedDevice extends AbstractSimulatedDevice { final String deploymentJson = controllerResource.getDeployment(getTenant(), getId(), actionId); final String swVersion = JsonPath.parse(deploymentJson).read("deployment.chunks[0].version"); currentActionId = actionId; - deviceUpdater.startUpdate(getTenant(), getId(), actionId, swVersion, new UpdaterCallback() { - @Override - public void updateFinished(final AbstractSimulatedDevice device, final Long actionId) { - switch (device.getResponseStatus()) { - case SUCCESSFUL: - controllerResource.postSuccessFeedback(getTenant(), getId(), actionId); - break; - case ERROR: - controllerResource.postErrorFeedback(getTenant(), getId(), actionId); - break; - default: - throw new IllegalStateException("simulated device has an unknown response status + " - + device.getResponseStatus()); - } - currentActionId = null; + deviceUpdater.startUpdate(getTenant(), getId(), actionId, swVersion, (device, actionId1) -> { + switch (device.getResponseStatus()) { + case SUCCESSFUL: + controllerResource.postSuccessFeedback(getTenant(), getId(), + actionId1); + break; + case ERROR: + controllerResource.postErrorFeedback(getTenant(), getId(), + actionId1); + break; + default: + throw new IllegalStateException( + "simulated device has an unknown response status + " + device.getResponseStatus()); } + currentActionId = null; }); } } catch (final PathNotFoundException e) { // href might not be in the json response, so ignore // exception here. - LOGGER.trace("Response does not contain a deploymentbase href link, ignoring."); + LOGGER.trace("Response does not contain a deploymentbase href link, ignoring.", e); } } diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java index 6e93b7d04..b8b5011aa 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/DeviceSimulatorUpdater.java @@ -8,6 +8,7 @@ */ package org.eclipse.hawkbit.simulator; +import java.security.SecureRandom; import java.util.Random; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -41,7 +42,7 @@ public class DeviceSimulatorUpdater { /** * Starting an simulated update process of an simulated device. - * + * * @param tenant * the tenant of the device * @param id @@ -61,12 +62,13 @@ public class DeviceSimulatorUpdater { device.setProgress(0.0); device.setSwversion(swVersion); eventbus.post(new InitUpdate(device)); + threadPool.schedule(new DeviceSimulatorUpdateThread(device, spSenderService, actionId, eventbus, callback), - 2000, TimeUnit.MILLISECONDS); + 2_000, TimeUnit.MILLISECONDS); } private static final class DeviceSimulatorUpdateThread implements Runnable { - private static final Random rndSleep = new Random(); + private static final Random rndSleep = new SecureRandom(); private final AbstractSimulatedDevice device; private final SpSenderService spSenderService; @@ -74,9 +76,8 @@ public class DeviceSimulatorUpdater { private final EventBus eventbus; private final UpdaterCallback callback; - private DeviceSimulatorUpdateThread(final AbstractSimulatedDevice device, - final SpSenderService spSenderService, final long actionId, final EventBus eventbus, - final UpdaterCallback callback) { + private DeviceSimulatorUpdateThread(final AbstractSimulatedDevice device, final SpSenderService spSenderService, + final long actionId, final EventBus eventbus, final UpdaterCallback callback) { this.device = device; this.spSenderService = spSenderService; this.actionId = actionId; @@ -89,8 +90,9 @@ public class DeviceSimulatorUpdater { final double newProgress = device.getProgress() + 0.2; device.setProgress(newProgress); if (newProgress < 1.0) { - threadPool.schedule(new DeviceSimulatorUpdateThread(device, spSenderService, actionId, eventbus, - callback), rndSleep.nextInt(3000), TimeUnit.MILLISECONDS); + threadPool.schedule( + new DeviceSimulatorUpdateThread(device, spSenderService, actionId, eventbus, callback), + rndSleep.nextInt(5_000), TimeUnit.MILLISECONDS); } else { callback.updateFinished(device, actionId); } @@ -102,7 +104,7 @@ public class DeviceSimulatorUpdater { * Callback interface which is called when the simulated update process has * been finished and the caller of starting the simulated update process can * send the result to the hawkbit update server back. - * + * * @author Michael Hirsch * */ @@ -111,7 +113,7 @@ public class DeviceSimulatorUpdater { /** * Callback method to indicate that the simulated update process has * been finished. - * + * * @param device * the device which has been updated * @param actionId diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/NextPollTimeController.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/NextPollTimeController.java index 81acf897e..9c78ec65a 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/NextPollTimeController.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/NextPollTimeController.java @@ -16,6 +16,8 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import org.eclipse.hawkbit.simulator.event.NextPollCounterUpdate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -24,13 +26,15 @@ import com.google.common.eventbus.EventBus; /** * Poll time trigger which executes the {@link DDISimulatedDevice#poll()} every * second. - * + * * @author Michael Hirsch * */ @Component public class NextPollTimeController { + private static final Logger LOGGER = LoggerFactory.getLogger(NextPollTimeController.class); + private static final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); private static final ExecutorService pollService = Executors.newFixedThreadPool(1); @@ -58,14 +62,9 @@ public class NextPollTimeController { if (nextCounter < 0) { if (device instanceof DDISimulatedDevice) { try { - pollService.submit(new Runnable() { - @Override - public void run() { - ((DDISimulatedDevice) device).poll(); - } - }); - } catch (final Exception e) { - + pollService.submit(() -> ((DDISimulatedDevice) device).poll()); + } catch (final IllegalStateException e) { + LOGGER.trace("Device could not be polled", e); } nextCounter = ((DDISimulatedDevice) device).getPollDelaySec(); } diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java index 6f94bd319..c1f358d89 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/SimulationController.java @@ -8,9 +8,13 @@ */ package org.eclipse.hawkbit.simulator; +import java.net.MalformedURLException; +import java.net.URL; + import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol; import org.eclipse.hawkbit.simulator.amqp.SpSenderService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -42,19 +46,49 @@ public class SimulationController { * the amount of devices to be created * @param tenant * the tenant to create the device to + * @param api + * the api-protocol to be used either {@code dmf} or {@code ddi} + * @param endpoint + * the URL endpoint to be used of the hawkbit-update-server for + * DDI devices + * @param pollDelay + * number of delay in milliseconds to delay polling of DDI + * devices + * @param gatewayToken + * the hawkbit-update-server gatwaytoken in case authentication + * is enforced in hawkbit * @return a response string that devices has been created + * @throws MalformedURLException */ @RequestMapping("/start") - String start(@RequestParam(value = "name", defaultValue = "dmfSimulated") final String name, + ResponseEntity start(@RequestParam(value = "name", defaultValue = "simulated") final String name, @RequestParam(value = "amount", defaultValue = "20") final int amount, - @RequestParam(value = "tenant", defaultValue = "DEFAULT") final String tenant) { + @RequestParam(value = "tenant", defaultValue = "DEFAULT") final String tenant, + @RequestParam(value = "api", defaultValue = "dmf") final String api, + @RequestParam(value = "endpoint", defaultValue = "http://localhost:8080") final String endpoint, + @RequestParam(value = "polldelay", defaultValue = "30") final int pollDelay, + @RequestParam(value = "gatewaytoken", defaultValue = "") final String gatewayToken) + throws MalformedURLException { + + final Protocol protocol; + switch (api.toLowerCase()) { + case "dmf": + protocol = Protocol.DMF_AMQP; + break; + case "ddi": + protocol = Protocol.DDI_HTTP; + break; + default: + return ResponseEntity.badRequest().body("query param api only allows value of 'dmf' or 'ddi'"); + } for (int i = 0; i < amount; i++) { final String deviceId = name + i; - repository.add(deviceFactory.createSimulatedDevice(deviceId, tenant, Protocol.DMF_AMQP)); + repository.add(deviceFactory.createSimulatedDevice(deviceId, tenant, protocol, pollDelay, new URL(endpoint), + gatewayToken)); spSenderService.createOrUpdateThing(tenant, deviceId); } - return "Updated " + amount + " DMF connected targets!"; + return ResponseEntity.ok("Updated " + amount + " DMF connected targets!"); } } diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/AmqpProperties.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/AmqpProperties.java index ff9762c5d..f9e6ab23d 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/AmqpProperties.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/AmqpProperties.java @@ -9,21 +9,35 @@ package org.eclipse.hawkbit.simulator.amqp; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; /** * Bean which holds the necessary properties for configuring the AMQP * connection. * - * - * */ +@Component @ConfigurationProperties("hawkbit.device.simulator.amqp") public class AmqpProperties { + /** + * Queue for receiving DMF messages from update server. + */ private String receiverConnectorQueueFromSp; + + /** + * Exchange for sending DMF messages to update server. + */ private String senderForSpExchange; + /** + * Simulator dead letter queue. + */ private String deadLetterQueue; + + /** + * Simulator dead letter exchange. + */ private String deadLetterExchange; public String getReceiverConnectorQueueFromSp() { diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpReceiverService.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpReceiverService.java index 6f0ac732e..1c314d56f 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpReceiverService.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpReceiverService.java @@ -13,11 +13,8 @@ import java.util.Map; import org.eclipse.hawkbit.dmf.amqp.api.EventTopic; import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey; import org.eclipse.hawkbit.dmf.amqp.api.MessageType; -import org.eclipse.hawkbit.dmf.json.model.ActionStatus; import org.eclipse.hawkbit.dmf.json.model.DownloadAndUpdateRequest; -import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice; import org.eclipse.hawkbit.simulator.DeviceSimulatorUpdater; -import org.eclipse.hawkbit.simulator.DeviceSimulatorUpdater.UpdaterCallback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.core.Message; @@ -139,31 +136,22 @@ public class SpReceiverService extends ReceiverService { DownloadAndUpdateRequest.class); final Long actionId = downloadAndUpdateRequest.getActionId(); - try { - Thread.sleep(1_000); - } catch (final InterruptedException e) { - LOGGER.error("Sleep interrupted", e); - } - - spSenderService.sendActionStatusMessage(tenant, ActionStatus.RUNNING, - "device Simulator retrieved update request. proceeding with simulation.", actionId); - deviceUpdater.startUpdate(tenant, thingId, actionId, downloadAndUpdateRequest.getSoftwareModules().get(0) - .getModuleVersion(), new UpdaterCallback() { - @Override - public void updateFinished(final AbstractSimulatedDevice device, final Long actionId) { - switch (device.getResponseStatus()) { - case SUCCESSFUL: - spSenderService.finishUpdateProcess(new SimulatedUpdate(device.getTenant(), device.getId(), - actionId), "Simulation complete!"); - break; - case ERROR: - spSenderService.finishUpdateProcessWithError(new SimulatedUpdate(device.getTenant(), - device.getId(), actionId), "Simulation complete with error!"); - break; - default: - break; - } - } - }); + deviceUpdater.startUpdate(tenant, thingId, actionId, + downloadAndUpdateRequest.getSoftwareModules().get(0).getModuleVersion(), (device, actionId1) -> { + switch (device.getResponseStatus()) { + case SUCCESSFUL: + spSenderService.finishUpdateProcess( + new SimulatedUpdate(device.getTenant(), device.getId(), actionId1), + "Simulation complete!"); + break; + case ERROR: + spSenderService.finishUpdateProcessWithError( + new SimulatedUpdate(device.getTenant(), device.getId(), actionId1), + "Simulation complete with error!"); + break; + default: + break; + } + }); } } diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java index 1400ec0e1..e337e86e7 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/GenerateDialog.java @@ -15,16 +15,15 @@ import org.eclipse.hawkbit.simulator.AbstractSimulatedDevice.Protocol; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.vaadin.data.Property.ValueChangeEvent; -import com.vaadin.data.Property.ValueChangeListener; +import com.vaadin.data.Property; +import com.vaadin.data.Validator; import com.vaadin.data.util.ObjectProperty; import com.vaadin.data.validator.NullValidator; import com.vaadin.data.validator.RangeValidator; import com.vaadin.data.validator.RegexpValidator; import com.vaadin.server.FontAwesome; +import com.vaadin.server.Resource; import com.vaadin.ui.Button; -import com.vaadin.ui.Button.ClickEvent; -import com.vaadin.ui.Button.ClickListener; import com.vaadin.ui.FormLayout; import com.vaadin.ui.OptionGroup; import com.vaadin.ui.TextField; @@ -44,6 +43,15 @@ public class GenerateDialog extends Window { private final FormLayout formLayout = new FormLayout(); + private final TextField namePrefixTextField; + private final TextField amountTextField; + private final TextField tenantTextField; + private final TextField pollDelayTextField; + private final TextField pollUrlTextField; + private final TextField gatewayTokenTextField; + private final OptionGroup protocolGroup; + private final Button buttonOk; + /** * Creates a new pop window for setting the configuration of simulating * devices. @@ -57,97 +65,43 @@ public class GenerateDialog extends Window { formLayout.setSpacing(true); formLayout.setMargin(true); - final TextField tf1 = new TextField("name prefix", "dmfSimulated"); - tf1.setIcon(FontAwesome.INFO); - tf1.setRequired(true); - tf1.addValidator(new NullValidator("Must be given", false)); + namePrefixTextField = createRequiredTextfield("name prefix", "dmfSimulated", FontAwesome.INFO, + new NullValidator("Must be given", false)); - final TextField tf2 = new TextField("amount", new ObjectProperty(10)); - tf2.setIcon(FontAwesome.GEAR); - tf2.setRequired(true); - tf2.addValidator(new RangeValidator("Must be between 1 and 30000", Integer.class, 1, 30000)); + amountTextField = createRequiredTextfield("amount", new ObjectProperty(10), FontAwesome.GEAR, + new RangeValidator("Must be between 1 and 30000", Integer.class, 1, 30000)); - final TextField tf3 = new TextField("tenant", "default"); - tf3.setIcon(FontAwesome.USER); - tf3.setRequired(true); - tf3.addValidator(new NullValidator("Must be given", false)); + tenantTextField = createRequiredTextfield("tenant", "default", FontAwesome.USER, + new NullValidator("Must be given", false)); - final TextField tf4 = new TextField("poll delay (sec)", new ObjectProperty(10)); - tf4.setIcon(FontAwesome.CLOCK_O); - tf4.setRequired(true); - tf4.setVisible(false); - tf4.addValidator(new RangeValidator("Must be between 1 and 60", Integer.class, 1, 60)); + pollDelayTextField = createRequiredTextfield("poll delay (sec)", new ObjectProperty(10), + FontAwesome.CLOCK_O, new RangeValidator("Must be between 1 and 60", Integer.class, 1, 60)); + pollDelayTextField.setVisible(false); - final TextField tf5 = new TextField("base poll URL endpoint", "http://localhost:8080"); - tf5.setColumns(50); - tf5.setIcon(FontAwesome.FLAG_O); - tf5.setRequired(true); - tf5.setVisible(false); - tf5.addValidator(new RegexpValidator( - "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]", "is not an URL")); + pollUrlTextField = createRequiredTextfield("base poll URL endpoint", "http://localhost:8080", + FontAwesome.FLAG_O, new RegexpValidator( + "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]", "is not an URL")); + pollUrlTextField.setColumns(50); + pollUrlTextField.setVisible(false); - final TextField tf6 = new TextField("gateway token", ""); - tf6.setColumns(50); - tf6.setIcon(FontAwesome.FLAG_O); - tf6.setRequired(true); - tf6.setVisible(false); + gatewayTokenTextField = createRequiredTextfield("gateway token", "", FontAwesome.FLAG_O, null); + gatewayTokenTextField.setColumns(50); + gatewayTokenTextField.setVisible(false); - final OptionGroup protocolGroup = new OptionGroup("Simulated Device Protocol"); - protocolGroup.addItem(Protocol.DMF_AMQP); - protocolGroup.addItem(Protocol.DDI_HTTP); - protocolGroup.setItemCaption(Protocol.DMF_AMQP, "Device Management Federation API (AMQP push)"); - protocolGroup.setItemCaption(Protocol.DDI_HTTP, "Direct Device Interface (HTTP poll)"); - protocolGroup.setNullSelectionAllowed(false); - protocolGroup.select(Protocol.DMF_AMQP); - protocolGroup.addValueChangeListener(new ValueChangeListener() { - private static final long serialVersionUID = 1L; + protocolGroup = createProtocolGroup(); + buttonOk = createOkButton(callback); - @Override - public void valueChange(final ValueChangeEvent event) { - if (event.getProperty().getValue().equals(Protocol.DDI_HTTP)) { - tf4.setVisible(true); - tf5.setVisible(true); - tf6.setVisible(true); - } else { - tf4.setVisible(false); - tf5.setVisible(false); - tf6.setVisible(false); - } - } - }); + namePrefixTextField.addValueChangeListener(event -> checkValid()); + amountTextField.addValueChangeListener(event -> checkValid()); + tenantTextField.addValueChangeListener(event -> checkValid()); - final Button buttonOk = new Button("generate"); - buttonOk.setImmediate(true); - buttonOk.setIcon(FontAwesome.GEARS); - buttonOk.addClickListener(new ClickListener() { - private static final long serialVersionUID = 1L; - - @Override - public void buttonClick(final ClickEvent event) { - try { - callback.okButton(tf1.getValue(), tf3.getValue(), Integer.valueOf(tf2.getValue().replace(".", "")), - Integer.valueOf(tf4.getValue().replace(".", "")), new URL(tf5.getValue()), tf6.getValue(), - (Protocol) protocolGroup.getValue()); - } catch (final NumberFormatException e) { - LOGGER.info(e.getMessage(), e); - } catch (final MalformedURLException e) { - LOGGER.info(e.getMessage(), e); - } - GenerateDialog.this.close(); - } - }); - - tf1.addValueChangeListener(event -> checkValid(tf1, tf2, tf3, tf4, buttonOk)); - tf2.addValueChangeListener(event -> checkValid(tf1, tf2, tf3, tf4, buttonOk)); - tf3.addValueChangeListener(event -> checkValid(tf1, tf2, tf3, tf4, buttonOk)); - - formLayout.addComponent(tf1); - formLayout.addComponent(tf2); - formLayout.addComponent(tf3); + formLayout.addComponent(namePrefixTextField); + formLayout.addComponent(amountTextField); + formLayout.addComponent(tenantTextField); formLayout.addComponent(protocolGroup); - formLayout.addComponent(tf4); - formLayout.addComponent(tf5); - formLayout.addComponent(tf6); + formLayout.addComponent(pollDelayTextField); + formLayout.addComponent(pollUrlTextField); + formLayout.addComponent(gatewayTokenTextField); formLayout.addComponent(buttonOk); setCaption("Simulate Devices"); @@ -156,13 +110,10 @@ public class GenerateDialog extends Window { center(); } - private void checkValid(final TextField tf1, final TextField tf2, final TextField tf3, final TextField tf4, - final Button buttonOk) { - if (tf1.isValid() && tf2.isValid() && tf3.isValid() && tf4.isValid()) { - buttonOk.setEnabled(true); - } else { - buttonOk.setEnabled(false); - } + private void checkValid() { + buttonOk.setEnabled(namePrefixTextField.isValid() && amountTextField.isValid() && tenantTextField.isValid() + && pollDelayTextField.isValid()); + } @Override @@ -230,4 +181,66 @@ public class GenerateDialog extends Window { void okButton(final String namePrefix, final String tenant, final int amount, final int pollDelay, final URL basePollURL, final String gatewayToken, final Protocol protocol); } + + private OptionGroup createProtocolGroup() { + + final OptionGroup protocolGroup = new OptionGroup("Simulated Device Protocol"); + protocolGroup.addItem(Protocol.DMF_AMQP); + protocolGroup.addItem(Protocol.DDI_HTTP); + protocolGroup.setItemCaption(Protocol.DMF_AMQP, "Device Management Federation API (AMQP push)"); + protocolGroup.setItemCaption(Protocol.DDI_HTTP, "Direct Device Interface (HTTP poll)"); + protocolGroup.setNullSelectionAllowed(false); + protocolGroup.select(Protocol.DMF_AMQP); + protocolGroup.addValueChangeListener(event -> { + final boolean directDeviceOptionSelected = event.getProperty().getValue().equals(Protocol.DDI_HTTP); + pollDelayTextField.setVisible(directDeviceOptionSelected); + pollUrlTextField.setVisible(directDeviceOptionSelected); + gatewayTokenTextField.setVisible(directDeviceOptionSelected); + }); + return protocolGroup; + } + + private Button createOkButton(final GenerateDialogCallback callback) { + + final Button buttonOk = new Button("generate"); + buttonOk.setImmediate(true); + buttonOk.setIcon(FontAwesome.GEARS); + buttonOk.addClickListener(event -> { + try { + callback.okButton(namePrefixTextField.getValue(), tenantTextField.getValue(), + Integer.valueOf(amountTextField.getValue().replace(".", "").replace(",", "")), + Integer.valueOf(pollDelayTextField.getValue().replace(".", "")), + new URL(pollUrlTextField.getValue()), gatewayTokenTextField.getValue(), + (Protocol) protocolGroup.getValue()); + } catch (final NumberFormatException e) { + LOGGER.info(e.getMessage(), e); + } catch (final MalformedURLException e) { + LOGGER.info(e.getMessage(), e); + } + GenerateDialog.this.close(); + }); + return buttonOk; + } + + private TextField createRequiredTextfield(final String caption, final String value, final Resource icon, + final Validator validator) { + final TextField textField = new TextField(caption, value); + return addTextFieldValues(textField, icon, validator); + } + + private TextField createRequiredTextfield(final String caption, final Property dataSource, final Resource icon, + final Validator validator) { + final TextField textField = new TextField(caption, dataSource); + return addTextFieldValues(textField, icon, validator); + } + + private TextField addTextFieldValues(final TextField textField, final Resource icon, final Validator validator) { + textField.setIcon(icon); + textField.setRequired(true); + if (validator != null) { + textField.addValidator(validator); + } + return textField; + } + } diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorView.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorView.java index 25498cea7..e1d6bd15b 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorView.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/ui/SimulatorView.java @@ -28,8 +28,6 @@ import org.springframework.beans.factory.annotation.Autowired; import com.google.common.collect.Lists; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; -import com.vaadin.data.Property.ValueChangeEvent; -import com.vaadin.data.Property.ValueChangeListener; import com.vaadin.data.util.BeanContainer; import com.vaadin.data.util.BeanItem; import com.vaadin.data.util.converter.Converter; @@ -76,8 +74,8 @@ public class SimulatorView extends VerticalLayout implements View { private final Label caption = new Label("DMF/DDI Simulated Devices"); private final HorizontalLayout toolbar = new HorizontalLayout(); private final Grid grid = new Grid(); - private final ComboBox responseComboBox = new ComboBox("", Lists.newArrayList(ResponseStatus.SUCCESSFUL, - ResponseStatus.ERROR)); + private final ComboBox responseComboBox = new ComboBox("", + Lists.newArrayList(ResponseStatus.SUCCESSFUL, ResponseStatus.ERROR)); private BeanContainer beanContainer; @@ -97,6 +95,9 @@ public class SimulatorView extends VerticalLayout implements View { grid.setSizeFull(); grid.setCellStyleGenerator(new CellStyleGenerator() { + + private static final long serialVersionUID = 1L; + @Override public String getStyle(final CellReference cellReference) { return cellReference.getPropertyId().equals("status") ? "centeralign" : null; @@ -118,86 +119,8 @@ public class SimulatorView extends VerticalLayout implements View { grid.getColumn("swversion").setHeaderCaption("SW Version"); grid.getColumn("responseStatus").setHeaderCaption("Response Update Status"); grid.getColumn("progress").setRenderer(new ProgressBarRenderer()); - grid.getColumn("protocol").setConverter(new Converter() { - @Override - public Protocol convertToModel(final String value, final Class targetType, - final Locale locale) { - return null; - } - - @Override - public String convertToPresentation(final Protocol value, final Class targetType, - final Locale locale) { - switch (value) { - case DDI_HTTP: - return "DDI API (http)"; - case DMF_AMQP: - return "DMF API (amqp)"; - default: - return "unknown"; - } - } - - @Override - public Class getModelType() { - return Protocol.class; - } - - @Override - public Class getPresentationType() { - return String.class; - } - }); - grid.getColumn("status").setRenderer(new HtmlRenderer(), new Converter() { - private static final long serialVersionUID = 1L; - - @Override - public Status convertToModel(final String value, final Class targetType, - final Locale locale) { - return null; - } - - @Override - public String convertToPresentation(final Status value, final Class targetType, - final Locale locale) { - String style = null; - switch (value) { - case UNKNWON: - style = "&#x" - + Integer.toHexString(FontAwesome.QUESTION_CIRCLE.getCodepoint()) + ";"; - break; - case PEDNING: - style = "&#x" + Integer.toHexString(FontAwesome.REFRESH.getCodepoint()) - + ";"; - break; - case FINISH: - style = "&#x" - + Integer.toHexString(FontAwesome.CHECK_CIRCLE.getCodepoint()) + ";"; - break; - case ERROR: - style = "&#x" - + Integer.toHexString(FontAwesome.EXCLAMATION_CIRCLE.getCodepoint()) + ";"; - break; - default: - throw new IllegalStateException("unknown value"); - } - return style; - } - - @Override - public Class getModelType() { - return Status.class; - } - - @Override - public Class getPresentationType() { - return String.class; - } - }); + grid.getColumn("protocol").setConverter(createProtocolConverter()); + grid.getColumn("status").setRenderer(new HtmlRenderer(), createStatusConverter()); grid.removeColumn("tenant"); // grid combobox @@ -205,13 +128,9 @@ public class SimulatorView extends VerticalLayout implements View { responseComboBox.setItemIcon(ResponseStatus.ERROR, FontAwesome.EXCLAMATION_CIRCLE); responseComboBox.setNullSelectionAllowed(false); responseComboBox.setValue(ResponseStatus.SUCCESSFUL); - responseComboBox.addValueChangeListener(new ValueChangeListener() { - @Override - public void valueChange(final ValueChangeEvent event) { - beanContainer.getItemIds().forEach( - itemId -> beanContainer.getItem(itemId).getItemProperty("responseStatus") - .setValue(event.getProperty().getValue())); - } + responseComboBox.addValueChangeListener(valueChangeEvent -> { + beanContainer.getItemIds().forEach(itemId -> beanContainer.getItem(itemId).getItemProperty("responseStatus") + .setValue(valueChangeEvent.getProperty().getValue())); }); // add all components @@ -338,4 +257,90 @@ public class SimulatorView extends VerticalLayout implements View { } })); } + + private Converter createProtocolConverter() { + + return new Converter() { + + private static final long serialVersionUID = 1L; + + @Override + public Protocol convertToModel(final String value, final Class targetType, + final Locale locale) { + return null; + } + + @Override + public String convertToPresentation(final Protocol value, final Class targetType, + final Locale locale) { + switch (value) { + case DDI_HTTP: + return "DDI API (http)"; + case DMF_AMQP: + return "DMF API (amqp)"; + default: + return "unknown"; + } + } + + @Override + public Class getModelType() { + return Protocol.class; + } + + @Override + public Class getPresentationType() { + return String.class; + } + }; + + } + + private Converter createStatusConverter() { + return new Converter() { + private static final long serialVersionUID = 1L; + + @Override + public Status convertToModel(final String value, final Class targetType, + final Locale locale) { + return null; + } + + @Override + public String convertToPresentation(final Status value, final Class targetType, + final Locale locale) { + switch (value) { + case UNKNWON: + return "&#x" + + Integer.toHexString(FontAwesome.QUESTION_CIRCLE.getCodepoint()) + ";"; + case PEDNING: + return "&#x" + Integer.toHexString(FontAwesome.REFRESH.getCodepoint()) + + ";"; + case FINISH: + return "&#x" + + Integer.toHexString(FontAwesome.CHECK_CIRCLE.getCodepoint()) + ";"; + case ERROR: + return "&#x" + + Integer.toHexString(FontAwesome.EXCLAMATION_CIRCLE.getCodepoint()) + ";"; + default: + throw new IllegalStateException("unknown value"); + } + } + + @Override + public Class getModelType() { + return Status.class; + } + + @Override + public Class getPresentationType() { + return String.class; + } + }; + } + } diff --git a/examples/hawkbit-device-simulator/src/main/resources/application-cloud.properties b/examples/hawkbit-device-simulator/src/main/resources/application-cloud.properties new file mode 100644 index 000000000..3e3edc90c --- /dev/null +++ b/examples/hawkbit-device-simulator/src/main/resources/application-cloud.properties @@ -0,0 +1,28 @@ +# +# Copyright (c) 2015 Bosch Software Innovations GmbH and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# + +# SECURITY (SecurityProperties) +security.basic.enabled=true +security.user.name=${BASIC_USERNAME:admin} +security.user.password=${BASIC_PASSWORD:admin} +security.user.role=USER +security.require-ssl=false +security.enable-csrf=false +security.basic.enabled=true +security.basic.realm=DeviceSimulator +security.basic.path= /** +security.basic.authorize-mode=ROLE +security.filter-order=0 +security.headers.xss=false +security.headers.cache=false +security.headers.frame=false +security.headers.content-type=false +security.headers.hsts=all +security.sessions=stateless +security.ignored=/VAADIN/** \ No newline at end of file diff --git a/examples/hawkbit-device-simulator/src/main/resources/application.properties b/examples/hawkbit-device-simulator/src/main/resources/application.properties index 402f71bfe..56d0190a7 100644 --- a/examples/hawkbit-device-simulator/src/main/resources/application.properties +++ b/examples/hawkbit-device-simulator/src/main/resources/application.properties @@ -27,23 +27,5 @@ spring.rabbitmq.port=5672 spring.rabbitmq.dynamic=true spring.rabbitmq.listener.prefetch=100 -# SECURITY (SecurityProperties) -security.user.name=${BASIC_USERNAME:admin} -security.user.password=${BASIC_PASSWORD:admin} -security.user.role=USER -security.require-ssl=false -security.enable-csrf=false -security.basic.enabled=true -security.basic.realm=DeviceSimulator -security.basic.path= /** -security.basic.authorize-mode=ROLE -security.filter-order=0 -security.headers.xss=false -security.headers.cache=false -security.headers.frame=false -security.headers.content-type=false -security.headers.hsts=all -security.sessions=stateless -security.ignored=/VAADIN/** - +security.basic.enabled=false server.port=8083 diff --git a/examples/hawkbit-example-app/src/main/java/org/eclipse/hawkbit/app/MyUI.java b/examples/hawkbit-example-app/src/main/java/org/eclipse/hawkbit/app/MyUI.java index cafe0749d..e55cb02d2 100644 --- a/examples/hawkbit-example-app/src/main/java/org/eclipse/hawkbit/app/MyUI.java +++ b/examples/hawkbit-example-app/src/main/java/org/eclipse/hawkbit/app/MyUI.java @@ -8,21 +8,12 @@ */ package org.eclipse.hawkbit.app; -import org.eclipse.hawkbit.eventbus.EventSubscriber; -import org.eclipse.hawkbit.eventbus.event.EntityEvent; -import org.eclipse.hawkbit.ui.DispatcherRunnable; import org.eclipse.hawkbit.ui.HawkbitUI; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.context.HttpSessionSecurityContextRepository; -import org.vaadin.spring.events.EventBus.SessionEventBus; +import org.eclipse.hawkbit.ui.push.DelayedEventBusPushStrategy; +import org.springframework.beans.factory.annotation.Autowired; -import com.google.common.eventbus.AllowConcurrentEvents; -import com.google.common.eventbus.Subscribe; +import com.google.common.eventbus.EventBus; import com.vaadin.annotations.Push; -import com.vaadin.server.VaadinSession; -import com.vaadin.server.VaadinSession.State; -import com.vaadin.server.WrappedSession; import com.vaadin.shared.communication.PushMode; import com.vaadin.shared.ui.ui.Transport; import com.vaadin.spring.annotation.SpringUI; @@ -33,45 +24,16 @@ import com.vaadin.spring.annotation.SpringUI; * A {@link SpringUI} annotated class must be present in the classpath. The * easiest way to get an hawkBit UI running is to extend the {@link HawkbitUI} * and to annotated it with {@link SpringUI} as in this example. - * - * * */ @SpringUI @Push(value = PushMode.AUTOMATIC, transport = Transport.WEBSOCKET) -@EventSubscriber public class MyUI extends HawkbitUI { private static final long serialVersionUID = 1L; - /** - * An {@link com.google.common.eventbus.EventBus} subscriber which - * subscribes {@link EntityEvent} from the repository to dispatch these - * events to the UI {@link SessionEventBus}. - * - * @param event - * the entity event which has been published from the repository - */ - @Override - @Subscribe - @AllowConcurrentEvents - public void dispatch(final org.eclipse.hawkbit.eventbus.event.Event event) { - final VaadinSession session = getSession(); - if (session != null && session.getState() == State.OPEN) { - final WrappedSession wrappedSession = session.getSession(); - if (wrappedSession != null) { - final SecurityContext userContext = (SecurityContext) wrappedSession - .getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); - if (eventSecurityCheck(userContext, event)) { - final SecurityContext oldContext = SecurityContextHolder.getContext(); - try { - access(new DispatcherRunnable(eventBus, session, userContext, event)); - } finally { - SecurityContextHolder.setContext(oldContext); - } - } - } - } + @Autowired + public MyUI(final EventBus systemEventBus, final org.vaadin.spring.events.EventBus.SessionEventBus eventBus) { + super(new DelayedEventBusPushStrategy(eventBus, systemEventBus)); } - } diff --git a/examples/hawkbit-example-app/src/main/resources/application.properties b/examples/hawkbit-example-app/src/main/resources/application.properties index 7149dbb9c..7864087c8 100644 --- a/examples/hawkbit-example-app/src/main/resources/application.properties +++ b/examples/hawkbit-example-app/src/main/resources/application.properties @@ -7,23 +7,35 @@ # http://www.eclipse.org/legal/epl-v10.html # -# need to re-name these properties in the defaulthawkbit.properties and code! -hawkbit.server.controller.security.authentication.anonymous.enabled=true -hawkbit.server.controller.security.authentication.header.enabled=false -hawkbit.server.controller.security.authentication.targettoken.enabled=false -hawkbit.server.controller.security.authentication.gatewaytoken.enabled=false +hawkbit.server.ddi.security.authentication.anonymous.enabled=true +hawkbit.server.ddi.security.authentication.targettoken.enabled=false +hawkbit.server.ddi.security.authentication.gatewaytoken.enabled=false spring.profiles.active=amqp -vaadin.servlet.params.productionMode=false -vaadin.static.servlet.params.productionMode=false +vaadin.servlet.productionMode=false ## Configuration for RabbitMQ integration -hawkbit.server.amqp.username=guest -hawkbit.server.amqp.password=guest -hawkbit.server.amqp.virtualHost=/ -hawkbit.server.amqp.host=localhost -hawkbit.server.amqp.port=5672 -hawkbit.server.amqp.deadLetterQueue=sp_deadletter -hawkbit.server.amqp.deadLetterExchange=sp.deadletter -hawkbit.server.amqp.receiverQueue=sp_receiver +spring.rabbitmq.username=guest +spring.rabbitmq.password=guest +spring.rabbitmq.virtualHost=/ +spring.rabbitmq.host=localhost +spring.rabbitmq.port=5672 +hawkbit.dmf.rabbitmq.deadLetterQueue=dmf_connector_deadletter +hawkbit.dmf.rabbitmq.deadLetterExchange=dmf.connector.deadletter +hawkbit.dmf.rabbitmq.receiverQueue=dmf_receiver + +# UI demo account +hawkbit.server.ui.demo.password=admin +hawkbit.server.ui.demo.user=admin +hawkbit.server.ui.demo.tenant=DEFAULT + +# UI help links +hawkbit.server.ui.links.documentation.root=https://github.com/eclipse/hawkbit +hawkbit.server.ui.links.documentation.deployment-view=https://github.com/eclipse/hawkbit +hawkbit.server.ui.links.documentation.distribution-view=https://github.com/eclipse/hawkbit +hawkbit.server.ui.links.documentation.rollout-view=https://github.com/eclipse/hawkbit +hawkbit.server.ui.links.documentation.security=https://github.com/eclipse/hawkbit +hawkbit.server.ui.links.documentation.system-configuration-view=https://github.com/eclipse/hawkbit +hawkbit.server.ui.links.documentation.targetfilter-view=https://github.com/eclipse/hawkbit +hawkbit.server.ui.links.documentation.upload-view=https://github.com/eclipse/hawkbit diff --git a/examples/hawkbit-mgmt-api-client/.gitignore b/examples/hawkbit-mgmt-api-client/.gitignore index 0f630157f..4b9b73073 100644 --- a/examples/hawkbit-mgmt-api-client/.gitignore +++ b/examples/hawkbit-mgmt-api-client/.gitignore @@ -1,2 +1,3 @@ /target/ /bin/ +/.apt_generated/ diff --git a/examples/hawkbit-mgmt-api-client/README.md b/examples/hawkbit-mgmt-api-client/README.md index 7eb32f9e6..eff301e20 100644 --- a/examples/hawkbit-mgmt-api-client/README.md +++ b/examples/hawkbit-mgmt-api-client/README.md @@ -1,3 +1,39 @@ +# HawkBit management API example + Example client that shows how to efficiently use the hawkBit management API. -Powered by [Feign](https://github.com/Netflix/feign). \ No newline at end of file +Powered by [Feign](https://github.com/Netflix/feign). + +## How to run the example client + +Run getting started example + + + + $ java -jar hawkbit-mgmt-api-client-#version#.jar + + +Run create and start rollout example + + + $ java -jar hawkbit-mgmt-api-client-#version#.jar --createrollout + + +## This example shows + +In getting started example: +* creating software modules type +* creating distribution set type +* creating distribution sets +* creating software modules +* assigning software modules to distribution sets + +In rollout mode: +* creating software modules type +* creating distribution set type +* creating distribution sets +* creating software modules +* assigning software modules to distribution sets +* creating a rollout +* starting a rollout + diff --git a/examples/hawkbit-mgmt-api-client/pom.xml b/examples/hawkbit-mgmt-api-client/pom.xml index cced3dfbb..9aaf53dc6 100644 --- a/examples/hawkbit-mgmt-api-client/pom.xml +++ b/examples/hawkbit-mgmt-api-client/pom.xml @@ -16,9 +16,43 @@ hawkbit-examples-parent 0.2.0-SNAPSHOT + jar hawkbit-mgmt-api-client hawkBit Management API example client + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + ${baseDir} + false + org.eclipse.hawkbit.mgmt.client.Application + JAR + + + + + + + + + + + org.springframework.cloud + spring-cloud-netflix + 1.0.7.RELEASE + pom + import + + + @@ -26,45 +60,37 @@ hawkbit-rest-api ${project.version} - - com.netflix.feign - feign-jackson - 8.12.1 - com.netflix.feign feign-core - 8.12.1 + + 8.14.2 - - - org.eclipse.hawkbit - hawkbit-example-app - ${project.version} - test + hibernate-validator + org.hibernate org.springframework.boot - spring-boot-starter-test - test + spring-boot-starter - ru.yandex.qatools.allure - allure-junit-adaptor - test + org.springframework.cloud + spring-cloud-starter-feign - org.easytesting - fest-assert-core - test + org.springframework.boot + spring-boot-starter-logging - org.easytesting - fest-assert - test + com.google.collections + google-collections + 1.0-rc2 + + + org.springframework.boot + spring-boot-configuration-processor + true - - \ No newline at end of file diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/api/client/DistributionSetResource.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/api/client/DistributionSetResource.java deleted file mode 100644 index 62c987ae8..000000000 --- a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/api/client/DistributionSetResource.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.eclipse.hawkbit.mgmt.api.client; - -import java.util.List; - -import org.eclipse.hawkbit.rest.resource.RestConstants; -import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetRequestBodyPost; -import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetsRest; - -import feign.Headers; -import feign.RequestLine; - -/** - * Client binding for the Distribution resource of the management API. - */ -@FunctionalInterface -public interface DistributionSetResource { - - /** - * Creates a list of distribution sets. - * - * @param sets - * the request body java bean containing the necessary attributes - * for creating a distribution set. - * @return the list of targets which have been created - */ - @RequestLine("POST " + RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING) - @Headers("Content-Type: application/json") - DistributionSetsRest createDistributionSets(final List sets); - -} diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/api/client/DistrubutionSetTagResource.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/api/client/DistrubutionSetTagResource.java deleted file mode 100644 index fe7a147a8..000000000 --- a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/api/client/DistrubutionSetTagResource.java +++ /dev/null @@ -1,138 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.eclipse.hawkbit.mgmt.api.client; - -import java.util.List; - -import org.eclipse.hawkbit.rest.resource.RestConstants; -import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetsRest; -import org.eclipse.hawkbit.rest.resource.model.tag.AssignedDistributionSetRequestBody; -import org.eclipse.hawkbit.rest.resource.model.tag.DistributionSetTagAssigmentResultRest; -import org.eclipse.hawkbit.rest.resource.model.tag.TagRequestBodyPut; -import org.eclipse.hawkbit.rest.resource.model.tag.TagRest; -import org.eclipse.hawkbit.rest.resource.model.tag.TagsRest; -import org.eclipse.hawkbit.rest.resource.model.target.TargetsRest; - -import feign.Headers; -import feign.Param; -import feign.RequestLine; - -/** - * Client binding for the DistributionSetTag resource of the management API. - */ -public interface DistrubutionSetTagResource { - - /** - * Retrieves a single distributionset tag based on the given ID. - * - * @param dsTagId - * the ID of the distributionset tag to retrieve - * @return a deserialized java bean containing the attributes of the - * returned distributionset tag - */ - @RequestLine("GET " + RestConstants.DISTRIBUTIONSET_TAG_V1_REQUEST_MAPPING + "/{dsTagId}") - TagRest getDistributionSetTag(@Param("dsTagId") Long dsTagId); - - /** - * Creates a list of distributionset tags. - * - * @param tags - * the tags to be created - * @return the created tag list - */ - @RequestLine("POST " + RestConstants.DISTRIBUTIONSET_TAG_V1_REQUEST_MAPPING) - @Headers("Content-Type: application/json") - TagsRest createDistributionSetTags(List tags); - - /** - * Update attributes of a distributionset tag. - * - * @param dsTagId - * the distributionset tag id to be updated - * @param tag - * the request body - * @return the updated distributionset tag - */ - @RequestLine("PUT " + RestConstants.DISTRIBUTIONSET_TAG_V1_REQUEST_MAPPING + "/{dsTagId}") - @Headers("Content-Type: application/json") - TagRest updateDistributionSetTag(@Param("dsTagId") Long dsTagId, TagRequestBodyPut tag); - - /** - * Deletes given distributionset tag on given ID. - * - * @param dsTagId - * to be deleted - */ - @RequestLine("DELETE " + RestConstants.DISTRIBUTIONSET_TAG_V1_REQUEST_MAPPING + "/{dsTagId}") - void deleteDistributionSetTag(@Param("dsTagId") final Long dsTagId); - - /** - * Retrieves a all assigned targets on the given distributionset tag id. - * - * @param dsTagId - * the ID of the distributionset tag to retrieve - * @return a list of targets - */ - @RequestLine("GET " + RestConstants.DISTRIBUTIONSET_TAG_V1_REQUEST_MAPPING - + RestConstants.DISTRIBUTIONSET_REQUEST_MAPPING) - DistributionSetsRest getAssignedDistributionSets(@Param("dsTagId") final Long dsTagId); - - /** - * Toggle the tag assignment all assigned targets will be unassigned and all - * unassigned targets will be assigned. - * - * @param dsTagId - * the ID of the distributionset tag to toggle - * @param assignedTargetRequestBodies - * a list of controller ids - * @return a list of assigned and unassigned targets - */ - @RequestLine("POST " + RestConstants.DISTRIBUTIONSET_TAG_V1_REQUEST_MAPPING - + RestConstants.DISTRIBUTIONSET_REQUEST_MAPPING + "/toggleTagAssignment") - @Headers("Content-Type: application/json") - DistributionSetTagAssigmentResultRest toggleTagAssignment(@Param("dsTagId") final Long dsTagId, - final List assignedTargetRequestBodies); - - /** - * Assign targets to a given distributionset tag id. - * - * @param dsTagId - * the ID of the distributionset tag to add the targets - * @param assignedTargetRequestBodies - * a list of controller ids - * @return a list of assigned targets - */ - @RequestLine("POST " + RestConstants.DISTRIBUTIONSET_TAG_V1_REQUEST_MAPPING - + RestConstants.DISTRIBUTIONSET_REQUEST_MAPPING) - @Headers("Content-Type: application/json") - TargetsRest assignDistributionSets(@Param("dsTagId") final Long dsTagId, - final List assignedTargetRequestBodies); - - /** - * Unassign targets to a given distributionset tag id. - * - * @param dsTagId - * the ID of the distributionset tag to add the targets - */ - @RequestLine("DELETE " + RestConstants.DISTRIBUTIONSET_TAG_V1_REQUEST_MAPPING - + RestConstants.DISTRIBUTIONSET_REQUEST_MAPPING) - void unassignDistributionSets(@Param("dsTagId") final Long dsTagId); - - /** - * Unassign one target to a given distributionset tag id. - * - * @param dsTagId - * the ID of the distributionset tag to add the targets param - * @param dsId - * the distributionset id - */ - @RequestLine("DELETE " + RestConstants.DISTRIBUTIONSET_TAG_V1_REQUEST_MAPPING - + RestConstants.DISTRIBUTIONSET_REQUEST_MAPPING + "/{dsId}") - void unassignDistributionSet(@Param("dsTagId") final Long dsTagId, @Param("dsId") final Long dsId); -} diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/api/client/TargetResource.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/api/client/TargetResource.java deleted file mode 100644 index 66c147426..000000000 --- a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/api/client/TargetResource.java +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.eclipse.hawkbit.mgmt.api.client; - -import java.util.List; - -import org.eclipse.hawkbit.rest.resource.model.target.TargetPagedList; -import org.eclipse.hawkbit.rest.resource.model.target.TargetRequestBody; -import org.eclipse.hawkbit.rest.resource.model.target.TargetRest; -import org.eclipse.hawkbit.rest.resource.model.target.TargetsRest; - -import feign.Headers; -import feign.Param; -import feign.RequestLine; - -/** - * Client binding for the Target resource of the management API. - */ -public interface TargetResource { - - /** - * Retrieves a single target based on the given ID. - * - * @param targetId - * the ID of the target to retrieve - * @return a deserialized java bean containing the attributes of the - * returned target - */ - @RequestLine("GET /rest/v1/targets/{targetId}") - TargetRest getTarget(@Param("targetId") final String targetId); - - /** - * Paged query of targets resource. - * - * @param pagingOffsetParam - * of the paged query - * @param pagingLimitParam - * of the paged query - * @return paged list of target entries - */ - @RequestLine("GET /rest/v1/targets?offset={pagingOffsetParam}&limit={pagingLimitParam}") - TargetPagedList getTargets(@Param("pagingOffsetParam") int pagingOffsetParam, - @Param("pagingLimitParam") int pagingLimitParam); - - /** - * Paged query of targets resource with default offset and limit. - * - * @return paged list of target entries - */ - @RequestLine("GET /rest/v1/targets") - TargetPagedList getTargets(); - - /** - * Deletes given target based on given ID. - * - * @param targetId - * to be deleted - */ - @RequestLine("DELETE /rest/v1/targets/{targetId}") - void deleteTarget(@Param("targetId") final String targetId); - - /** - * Creates a list of targets. - * - * @param targets - * the request body java bean containing the necessary attributes - * for creating a target. - * @return the list of targets which have been created - */ - @RequestLine("POST /rest/v1/targets/") - @Headers("Content-Type: application/json") - TargetsRest createTargets(List targets); - -} diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/api/client/TargetTagResource.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/api/client/TargetTagResource.java deleted file mode 100644 index bd74e52d7..000000000 --- a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/api/client/TargetTagResource.java +++ /dev/null @@ -1,137 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.eclipse.hawkbit.mgmt.api.client; - -import java.util.List; - -import org.eclipse.hawkbit.rest.resource.RestConstants; -import org.eclipse.hawkbit.rest.resource.model.tag.AssignedTargetRequestBody; -import org.eclipse.hawkbit.rest.resource.model.tag.TagRequestBodyPut; -import org.eclipse.hawkbit.rest.resource.model.tag.TagRest; -import org.eclipse.hawkbit.rest.resource.model.tag.TagsRest; -import org.eclipse.hawkbit.rest.resource.model.tag.TargetTagAssigmentResultRest; -import org.eclipse.hawkbit.rest.resource.model.target.TargetsRest; - -import feign.Headers; -import feign.Param; -import feign.RequestLine; - -/** - * Client binding for the Target resource of the management API. - */ -public interface TargetTagResource { - - /** - * Retrieves a single target tag based on the given ID. - * - * @param targetTagId - * the ID of the target tag to retrieve - * @return a deserialized java bean containing the attributes of the - * returned target tag - */ - @RequestLine("GET " + RestConstants.TARGET_TAG_V1_REQUEST_MAPPING + "/{targetTagId}") - TagRest getTargetTag(@Param("targetTagId") Long targetTagId); - - /** - * Creates a list of target tags. - * - * @param tags - * the tags to be created - * @return the created tag list - */ - @RequestLine("POST " + RestConstants.TARGET_TAG_V1_REQUEST_MAPPING) - @Headers("Content-Type: application/json") - TagsRest createTargetTag(List tags); - - /** - * Update attributes of a target tag. - * - * @param targetTagId - * the target tag id to be updated - * @param tag - * the request body - * @return the updated target tag - */ - @RequestLine("PUT " + RestConstants.TARGET_TAG_V1_REQUEST_MAPPING + "/{targetTagId}") - @Headers("Content-Type: application/json") - TagRest updateTagretTag(@Param("targetTagId") Long targetTagId, TagRequestBodyPut tag); - - /** - * Deletes given target tag on given ID. - * - * @param targetTagId - * to be deleted - */ - @RequestLine("DELETE " + RestConstants.TARGET_TAG_V1_REQUEST_MAPPING + "/{targetTagId}") - void deleteTargetTag(@Param("targetTagId") final Long targetTagId); - - /** - * Retrieves a all assigned targets on the given target tag id. - * - * @param targetTagId - * the ID of the target tag to retrieve - * @return a list of targets - */ - @RequestLine("GET " + RestConstants.TARGET_TAG_V1_REQUEST_MAPPING - + RestConstants.TARGET_TAG_TAGERTS_REQUEST_MAPPING) - TargetsRest getAssignedTargets(@Param("targetTagId") final Long targetTagId); - - /** - * Toggle the tag assignment all assigned targets will be unassigned and all - * unassigned targets will be assigned. - * - * @param targetTagId - * the ID of the target tag to toggle - * @param assignedTargetRequestBodies - * a list of controller ids - * @return a list of assigned and unassigned targets - */ - @RequestLine("POST " + RestConstants.TARGET_TAG_V1_REQUEST_MAPPING - + RestConstants.TARGET_TAG_TAGERTS_REQUEST_MAPPING + "/toggleTagAssignment") - @Headers("Content-Type: application/json") - TargetTagAssigmentResultRest toggleTagAssignment(@Param("targetTagId") final Long targetTagId, - final List assignedTargetRequestBodies); - - /** - * Assign targets to a given target tag id. - * - * @param targetTagId - * the ID of the target tag to add the targets - * @param assignedTargetRequestBodies - * a list of controller ids - * @return a list of assigned targets - */ - @RequestLine("POST " + RestConstants.TARGET_TAG_V1_REQUEST_MAPPING - + RestConstants.TARGET_TAG_TAGERTS_REQUEST_MAPPING) - @Headers("Content-Type: application/json") - TargetsRest assignTargets(@Param("targetTagId") final Long targetTagId, - final List assignedTargetRequestBodies); - - /** - * Unassign targets to a given target tag id. - * - * @param targetTagId - * the ID of the target tag to add the targets - */ - @RequestLine("DELETE " + RestConstants.TARGET_TAG_V1_REQUEST_MAPPING - + RestConstants.TARGET_TAG_TAGERTS_REQUEST_MAPPING) - void unassignTargets(@Param("targetTagId") final Long targetTagId); - - /** - * Unassign one target to a given target tag id. - * - * @param targetTagId - * the ID of the target tag to add the targets param - * @param controllerId - * the controller id - */ - @RequestLine("DELETE " + RestConstants.TARGET_TAG_V1_REQUEST_MAPPING - + RestConstants.TARGET_TAG_TAGERTS_REQUEST_MAPPING + "/{controllerId}") - void unassignTarget(@Param("targetTagId") final Long targetTagId, @Param("controllerId") final String controllerId); -} diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/Application.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/Application.java new file mode 100644 index 000000000..27584b50a --- /dev/null +++ b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/Application.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.mgmt.client; + +import org.eclipse.hawkbit.mgmt.client.scenarios.CreateStartedRolloutExample; +import org.eclipse.hawkbit.mgmt.client.scenarios.GettingStartedDefaultScenario; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.netflix.feign.EnableFeignClients; +import org.springframework.context.annotation.Bean; + +import feign.Contract; +import feign.auth.BasicAuthRequestInterceptor; + +@SpringBootApplication +@EnableFeignClients +@EnableConfigurationProperties(ClientConfigurationProperties.class) +public class Application implements CommandLineRunner { + + @Autowired + private ClientConfigurationProperties configuration; + + @Autowired + private GettingStartedDefaultScenario gettingStarted; + + @Autowired + private CreateStartedRolloutExample gettingStartedRolloutScenario; + + public static void main(final String[] args) { + new SpringApplicationBuilder().showBanner(false).sources(Application.class).run(args); + } + + @Override + public void run(final String... args) throws Exception { + if (containsArg("--createrollout", args)) { + // run the create and start rollout example + gettingStartedRolloutScenario.run(); + } else { + // run the getting started scenario which creates a setup of + // distribution set and software modules to be used + gettingStarted.run(); + } + } + + @Bean + public BasicAuthRequestInterceptor basicAuthRequestInterceptor() { + return new BasicAuthRequestInterceptor(configuration.getUsername(), configuration.getPassword()); + } + + @Bean + public ApplicationJsonRequestHeaderInterceptor jsonHeaderInterceptor() { + return new ApplicationJsonRequestHeaderInterceptor(); + } + + @Bean + public Contract feignContract() { + return new IgnoreMultipleConsumersProducersSpringMvcContract(); + } + + private boolean containsArg(final String containsArg, final String... args) { + for (final String arg : args) { + if (arg.equalsIgnoreCase(containsArg)) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/ApplicationJsonRequestHeaderInterceptor.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/ApplicationJsonRequestHeaderInterceptor.java new file mode 100644 index 000000000..75f3b0dd1 --- /dev/null +++ b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/ApplicationJsonRequestHeaderInterceptor.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.mgmt.client; + +import org.springframework.http.MediaType; + +import feign.RequestInterceptor; +import feign.RequestTemplate; + +/** + * An feign request interceptor to set the defined {@code Accept} and + * {@code Content-Type} headers for each request to {@code application/json}. + */ +public class ApplicationJsonRequestHeaderInterceptor implements RequestInterceptor { + + @Override + public void apply(final RequestTemplate template) { + template.header("Accept", MediaType.APPLICATION_JSON_VALUE); + template.header("Content-Type", MediaType.APPLICATION_JSON_VALUE); + } + +} diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/ClientConfigurationProperties.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/ClientConfigurationProperties.java new file mode 100644 index 000000000..68f35b550 --- /dev/null +++ b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/ClientConfigurationProperties.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.mgmt.client; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration bean which holds the configuration of the client e.g. the base + * URL of the hawkbit-server and the credentials to use the RESTful Management + * API. + */ +@ConfigurationProperties(prefix = "hawkbit") +public class ClientConfigurationProperties { + + /** + * Update server URI. + */ + private String url = "localhost:8080"; + + /** + * Update server user name. + */ + private String username = "admin"; + + /** + * Update server password. + */ + private String password = "admin"; // NOSONAR this password is only used for + // examples + + public String getUrl() { + return url; + } + + public void setUrl(final String url) { + this.url = url; + } + + public String getUsername() { + return username; + } + + public void setUsername(final String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(final String password) { + this.password = password; + } +} diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/IgnoreMultipleConsumersProducersSpringMvcContract.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/IgnoreMultipleConsumersProducersSpringMvcContract.java new file mode 100644 index 000000000..c796aa019 --- /dev/null +++ b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/IgnoreMultipleConsumersProducersSpringMvcContract.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.mgmt.client; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.netflix.feign.support.SpringMvcContract; + +import feign.MethodMetadata; + +/** + * Own implementation of the {@link SpringMvcContract} which catches the + * {@link IllegalStateException} which occurs due multiple produces and consumes + * values in the request-mapping + * annoation.https://github.com/spring-cloud/spring-cloud-netflix/issues/808 + */ +public class IgnoreMultipleConsumersProducersSpringMvcContract extends SpringMvcContract { + + private static final Logger LOGGER = LoggerFactory + .getLogger(IgnoreMultipleConsumersProducersSpringMvcContract.class); + + @Override + protected void processAnnotationOnMethod(final MethodMetadata data, final Annotation methodAnnotation, + final Method method) { + try { + super.processAnnotationOnMethod(data, methodAnnotation, method); + } catch (final IllegalStateException e) { + // ignore illegalstateexception here because it's thrown because of + // multiple consumers and produces, see + // https://github.com/spring-cloud/spring-cloud-netflix/issues/808 + LOGGER.trace(e.getMessage(), e); + } + } +} diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/DistributionSetResourceClient.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/DistributionSetResourceClient.java new file mode 100644 index 000000000..00a9b3fba --- /dev/null +++ b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/DistributionSetResourceClient.java @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.mgmt.client.resource; + +import org.eclipse.hawkbit.rest.resource.api.DistributionSetRestApi; +import org.springframework.cloud.netflix.feign.FeignClient; + +/** + * Client binding for the DistributionSet resource of the management API. + */ +@FeignClient(url = "${hawkbit.endpoint.url:localhost:8080}/rest/v1/distributionsets") +public interface DistributionSetResourceClient extends DistributionSetRestApi { + +} diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/DistributionSetTagResourceClient.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/DistributionSetTagResourceClient.java new file mode 100644 index 000000000..ea9f5d28a --- /dev/null +++ b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/DistributionSetTagResourceClient.java @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.mgmt.client.resource; + +import org.eclipse.hawkbit.rest.resource.api.DistributionSetTagRestApi; +import org.springframework.cloud.netflix.feign.FeignClient; + +/** + * Client binding for the DistributionSetTag resource of the management API. + */ +@FeignClient(url = "${hawkbit.endpoint.url:localhost:8080}/rest/v1/distributionsettags") +public interface DistributionSetTagResourceClient extends DistributionSetTagRestApi { + +} diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/DistributionSetTypeResourceClient.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/DistributionSetTypeResourceClient.java new file mode 100644 index 000000000..08d40dfa5 --- /dev/null +++ b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/DistributionSetTypeResourceClient.java @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.mgmt.client.resource; + +import org.eclipse.hawkbit.rest.resource.api.DistributionSetTypeRestApi; +import org.springframework.cloud.netflix.feign.FeignClient; + +/** + * Client binding for the DistributionSetType resource of the management API. + * + */ +@FeignClient(url = "${hawkbit.endpoint.url:localhost:8080}/rest/v1/distributionsettypes") +public interface DistributionSetTypeResourceClient extends DistributionSetTypeRestApi { + +} diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/RolloutResourceClient.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/RolloutResourceClient.java new file mode 100644 index 000000000..78b7413e7 --- /dev/null +++ b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/RolloutResourceClient.java @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.mgmt.client.resource; + +import org.eclipse.hawkbit.rest.resource.api.RolloutRestApi; +import org.springframework.cloud.netflix.feign.FeignClient; + +/** + * Client binding for the Rollout resource of the management API. + */ +@FeignClient(url = "${hawkbit.endpoint.url:localhost:8080}/rest/v1/rollouts") +public interface RolloutResourceClient extends RolloutRestApi { + +} diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/SoftwareModuleResourceClient.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/SoftwareModuleResourceClient.java new file mode 100644 index 000000000..88e664d78 --- /dev/null +++ b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/SoftwareModuleResourceClient.java @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.mgmt.client.resource; + +import org.eclipse.hawkbit.rest.resource.api.SoftwareModuleRestAPI; +import org.springframework.cloud.netflix.feign.FeignClient; + +/** + * Client binding for the SoftwareModule resource of the management API. + */ +@FeignClient(url = "${hawkbit.endpoint.url:localhost:8080}/rest/v1/softwaremodules") +public interface SoftwareModuleResourceClient extends SoftwareModuleRestAPI { + +} diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/SoftwareModuleTypeResourceClient.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/SoftwareModuleTypeResourceClient.java new file mode 100644 index 000000000..4896cb8d8 --- /dev/null +++ b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/SoftwareModuleTypeResourceClient.java @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.mgmt.client.resource; + +import org.eclipse.hawkbit.rest.resource.api.SoftwareModuleTypeRestApi; +import org.springframework.cloud.netflix.feign.FeignClient; + +/** + * Client binding for the oftwareModuleType resource of the management API. + */ +@FeignClient(url = "${hawkbit.endpoint.url:localhost:8080}/rest/v1/softwaremoduletypes") +public interface SoftwareModuleTypeResourceClient extends SoftwareModuleTypeRestApi { + +} diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/TargetResourceClient.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/TargetResourceClient.java new file mode 100644 index 000000000..a82aa5443 --- /dev/null +++ b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/TargetResourceClient.java @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.mgmt.client.resource; + +import org.eclipse.hawkbit.rest.resource.api.TargetRestApi; +import org.springframework.cloud.netflix.feign.FeignClient; + +/** + * Client binding for the Target resource of the management API. + */ +@FeignClient(url = "${hawkbit.endpoint.url:localhost:8080}/rest/v1/targets") +public interface TargetResourceClient extends TargetRestApi { + +} diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/TargetTagResourceClient.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/TargetTagResourceClient.java new file mode 100644 index 000000000..fee30c686 --- /dev/null +++ b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/TargetTagResourceClient.java @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.mgmt.client.resource; + +import org.eclipse.hawkbit.rest.resource.api.TargetTagRestApi; +import org.springframework.cloud.netflix.feign.FeignClient; + +/** + * Client binding for the TargetTag resource of the management API. + */ +@FeignClient(url = "${hawkbit.endpoint.url:localhost:8080}/rest/v1/targettags") +public interface TargetTagResourceClient extends TargetTagRestApi { + +} diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/DistributionSetBuilder.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/DistributionSetBuilder.java new file mode 100644 index 000000000..c821b106c --- /dev/null +++ b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/DistributionSetBuilder.java @@ -0,0 +1,99 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.mgmt.client.resource.builder; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetRequestBodyPost; + +import com.google.common.collect.Lists; + +/** + * + * Builder pattern for building {@link DistributionSetRequestBodyPost}. + * + * @author Jonathan Knoblauch + * + */ +public class DistributionSetBuilder { + + private String name; + private String version; + private String type; + + /** + * @param name + * the name of the distribution set + * @return the builder itself + */ + public DistributionSetBuilder name(final String name) { + this.name = name; + return this; + } + + /** + * @param version + * the version of the distribution set + * @return the builder itself + */ + public DistributionSetBuilder version(final String version) { + this.version = version; + return this; + } + + /** + * @param type + * the distribution set type name for this distribution set + * @return the builder itself + */ + public DistributionSetBuilder type(final String type) { + this.type = type; + return this; + } + + /** + * Builds a list with a single entry of + * {@link DistributionSetRequestBodyPost} which can directly be used to post + * on the RESTful-API. + * + * @return a single entry list of {@link DistributionSetRequestBodyPost} + */ + public List build() { + return Lists.newArrayList(doBuild(name)); + } + + /** + * Builds a list of multiple {@link DistributionSetRequestBodyPost} to + * create multiple distribution sets at once. An increasing number will be + * added to the name of the distribution set. The version and type will + * remain the same. + * + * @param count + * the amount of distribution sets body which should be created + * @return a list of {@link DistributionSetRequestBodyPost} + */ + public List buildAsList(final int count) { + final ArrayList bodyList = Lists.newArrayList(); + for (int index = 0; index < count; index++) { + bodyList.add(doBuild(name + index)); + } + + return bodyList; + } + + private DistributionSetRequestBodyPost doBuild(final String prefixName) { + final DistributionSetRequestBodyPost body = new DistributionSetRequestBodyPost(); + body.setName(prefixName); + body.setVersion(version); + body.setType(type); + return body; + } + +} diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/DistributionSetTypeBuilder.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/DistributionSetTypeBuilder.java new file mode 100644 index 000000000..e1da1f048 --- /dev/null +++ b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/DistributionSetTypeBuilder.java @@ -0,0 +1,124 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.mgmt.client.resource.builder; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.hawkbit.rest.resource.model.distributionsettype.DistributionSetTypeRequestBodyPost; +import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypeAssigmentRest; + +import com.google.common.collect.Lists; + +/** + * + * Builder pattern for building {@link DistributionSetTypeRequestBodyPost}. + * + * @author Jonathan Knoblauch + * + */ +public class DistributionSetTypeBuilder { + + private String key; + private String name; + private final List mandatorymodules = Lists.newArrayList(); + private final List optionalmodules = Lists.newArrayList(); + + /** + * @param key + * the key of the distribution set type + * @return the builder itself + */ + public DistributionSetTypeBuilder key(final String key) { + this.key = key; + return this; + } + + /** + * @param name + * the name of the distribution set type + * @return the builder itself + */ + public DistributionSetTypeBuilder name(final String name) { + this.name = name; + return this; + } + + /** + * @param softwareModuleTypeIds + * the IDs of the software module types which should be mandatory + * for the distribution set type + * @return the builder itself + */ + public DistributionSetTypeBuilder mandatorymodules(final Long... softwareModuleTypeIds) { + for (final Long id : softwareModuleTypeIds) { + final SoftwareModuleTypeAssigmentRest softwareModuleTypeAssigmentRest = new SoftwareModuleTypeAssigmentRest(); + softwareModuleTypeAssigmentRest.setId(id); + this.mandatorymodules.add(softwareModuleTypeAssigmentRest); + } + return this; + } + + /** + * + * @param softwareModuleTypeIds + * the IDs of the software module types which should be optional + * for the distribution set type + * @return the builder itself + */ + public DistributionSetTypeBuilder optionalmodules(final Long... softwareModuleTypeIds) { + for (final Long id : softwareModuleTypeIds) { + final SoftwareModuleTypeAssigmentRest softwareModuleTypeAssigmentRest = new SoftwareModuleTypeAssigmentRest(); + softwareModuleTypeAssigmentRest.setId(id); + this.optionalmodules.add(softwareModuleTypeAssigmentRest); + } + return this; + } + + /** + * Builds a list with a single entry of + * {@link DistributionSetTypeRequestBodyPost} which can directly be used in + * the RESTful-API. + * + * @return a single entry list of {@link DistributionSetTypeRequestBodyPost} + */ + public List build() { + return Lists.newArrayList(doBuild(name, key)); + } + + /** + * Builds a list of multiple {@link DistributionSetTypeRequestBodyPost} to + * create multiple distribution set types at once. An increasing number will + * be added to the name and key of the distribution set type. The optional + * and mandatory software module types will remain the same. + * + * @param count + * the amount of distribution sets type body which should be + * created + * @return a list of {@link DistributionSetTypeRequestBodyPost} + */ + public List buildAsList(final int count) { + final ArrayList bodyList = Lists.newArrayList(); + for (int index = 0; index < count; index++) { + bodyList.add(doBuild(name + index, key + index)); + } + return bodyList; + + } + + private DistributionSetTypeRequestBodyPost doBuild(final String prefixName, final String prefixKey) { + final DistributionSetTypeRequestBodyPost body = new DistributionSetTypeRequestBodyPost(); + body.setKey(prefixKey); + body.setName(prefixName); + body.setMandatorymodules(mandatorymodules); + body.setOptionalmodules(optionalmodules); + return body; + } + +} diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/RolloutBuilder.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/RolloutBuilder.java new file mode 100644 index 000000000..bea0fd9a4 --- /dev/null +++ b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/RolloutBuilder.java @@ -0,0 +1,115 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.mgmt.client.resource.builder; + +import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutCondition; +import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutCondition.Condition; +import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutRestRequestBody; + +/** + * + * Builder pattern for building {@link RolloutRestRequestBody}. + * + * @author Jonathan Knoblauch + * + */ +public class RolloutBuilder { + + private String name; + private int groupSize; + private String targetFilterQuery; + private long distributionSetId; + private String successThreshold; + private String errorThreshold; + + /** + * @param name + * the name of the rollout + * @return the builder itself + */ + public RolloutBuilder name(final String name) { + this.name = name; + return this; + } + + /** + * @param groupSize + * the amount of groups the rollout should be split into + * @return the builder itself + */ + public RolloutBuilder groupSize(final int groupSize) { + this.groupSize = groupSize; + return this; + } + + /** + * @param targetFilterQuery + * the FIQL query language to filter targets to contain in the + * rollout + * @return the builder itself + */ + public RolloutBuilder targetFilterQuery(final String targetFilterQuery) { + this.targetFilterQuery = targetFilterQuery; + return this; + } + + /** + * @param distributionSetId + * the ID of the distribution set to assign to the target in the + * rollout + * @return the builder itself + */ + public RolloutBuilder distributionSetId(final long distributionSetId) { + this.distributionSetId = distributionSetId; + return this; + } + + /** + * @param successThreshold + * the threshold to be used to indicate if a deployment group is + * successful, to trigger the success action + * @return the builder itself + */ + public RolloutBuilder successThreshold(final String successThreshold) { + this.successThreshold = successThreshold; + return this; + } + + /** + * @param errorThreshold + * the threshold to be used to indicate if a deployment group is + * failing, to trigger the error action + * @return the builder itself + */ + public RolloutBuilder errorThreshold(final String errorThreshold) { + this.errorThreshold = errorThreshold; + return this; + } + + /** + * Builds the rollout rest body to creating a rollout. + * + * @return the rest request body for creating a rollout + */ + public RolloutRestRequestBody build() { + return doBuild(); + } + + private RolloutRestRequestBody doBuild() { + final RolloutRestRequestBody body = new RolloutRestRequestBody(); + body.setName(name); + body.setAmountGroups(groupSize); + body.setTargetFilterQuery(targetFilterQuery); + body.setDistributionSetId(distributionSetId); + body.setSuccessCondition(new RolloutCondition(Condition.THRESHOLD, successThreshold)); + body.setErrorCondition(new RolloutCondition(Condition.THRESHOLD, errorThreshold)); + return body; + } + +} diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/SoftwareModuleAssigmentBuilder.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/SoftwareModuleAssigmentBuilder.java new file mode 100644 index 000000000..b209dbe8b --- /dev/null +++ b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/SoftwareModuleAssigmentBuilder.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.mgmt.client.resource.builder; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.hawkbit.rest.resource.model.softwaremodule.SoftwareModuleAssigmentRest; + +/** + * + * Builder pattern for building {@link SoftwareModuleAssigmentRest}. + * + * @author Jonathan Knoblauch + * + */ +public class SoftwareModuleAssigmentBuilder { + + private final List ids; + + public SoftwareModuleAssigmentBuilder() { + ids = new ArrayList<>(); + } + + /** + * @param id + * the id of the software module + * @return the builder itself + */ + public SoftwareModuleAssigmentBuilder id(final Long id) { + ids.add(id); + return this; + } + + /** + * Builds a list with a single entry of {@link SoftwareModuleAssigmentRest} + * which can directly be used in the RESTful-API. + * + * @return a single entry list of {@link SoftwareModuleAssigmentRest} + */ + public List build() { + final List softwareModuleAssigmentRestList = new ArrayList<>(); + for (final Long id : ids) { + final SoftwareModuleAssigmentRest softwareModuleAssigmentRest = new SoftwareModuleAssigmentRest(); + softwareModuleAssigmentRest.setId(id); + softwareModuleAssigmentRestList.add(softwareModuleAssigmentRest); + } + return softwareModuleAssigmentRestList; + } + +} diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/SoftwareModuleBuilder.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/SoftwareModuleBuilder.java new file mode 100644 index 000000000..30b85d901 --- /dev/null +++ b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/SoftwareModuleBuilder.java @@ -0,0 +1,101 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.mgmt.client.resource.builder; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.hawkbit.rest.resource.model.distributionsettype.DistributionSetTypeRequestBodyPost; +import org.eclipse.hawkbit.rest.resource.model.softwaremodule.SoftwareModuleRequestBodyPost; + +import com.google.common.collect.Lists; + +/** + * + * Builder pattern for building {@link SoftwareModuleRequestBodyPost}. + * + * @author Jonathan Knoblauch + * + */ +public class SoftwareModuleBuilder { + + private String name; + private String version; + private String type; + + /** + * @param name + * the name of the software module + * @return the builder itself + */ + public SoftwareModuleBuilder name(final String name) { + this.name = name; + return this; + } + + /** + * @param version + * the version of the software module + * @return the builder itsefl + */ + public SoftwareModuleBuilder version(final String version) { + this.version = version; + return this; + } + + /** + * @param type + * the key of the software module type to be used for this + * software module + * @return the builder itself + */ + public SoftwareModuleBuilder type(final String type) { + this.type = type; + return this; + } + + /** + * Builds a list with a single entry of + * {@link SoftwareModuleRequestBodyPost} which can directly be used in the + * RESTful-API. + * + * @return a single entry list of {@link SoftwareModuleRequestBodyPost} + */ + public List build() { + return Lists.newArrayList(doBuild(name)); + } + + /** + * Builds a list of multiple {@link SoftwareModuleRequestBodyPost} to create + * multiple software module at once. An increasing number will be added to + * the name of the software module. The version and type will remain the + * same. + * + * @param count + * the amount of software module body which should be created + * @return a list of {@link DistributionSetTypeRequestBodyPost} + */ + public List buildAsList(final int count) { + final ArrayList bodyList = Lists.newArrayList(); + for (int index = 0; index < count; index++) { + bodyList.add(doBuild(name + index)); + } + + return bodyList; + } + + private SoftwareModuleRequestBodyPost doBuild(final String prefixName) { + final SoftwareModuleRequestBodyPost body = new SoftwareModuleRequestBodyPost(); + body.setName(prefixName); + body.setVersion(version); + body.setType(type); + return body; + } + +} diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/SoftwareModuleTypeBuilder.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/SoftwareModuleTypeBuilder.java new file mode 100644 index 000000000..ce128d592 --- /dev/null +++ b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/SoftwareModuleTypeBuilder.java @@ -0,0 +1,101 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.mgmt.client.resource.builder; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.hawkbit.rest.resource.model.softwaremodule.SoftwareModuleRequestBodyPost; +import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypeRequestBodyPost; + +import com.google.common.collect.Lists; + +/** + * + * Builder pattern for building {@link SoftwareModuleRequestBodyPost}. + * + * @author Jonathan Knoblauch + * + */ +public class SoftwareModuleTypeBuilder { + + private String key; + private String name; + private String description; + private int maxAssignments; + + /** + * @param key + * the key of the software module type + * @return the builder itself + */ + public SoftwareModuleTypeBuilder key(final String key) { + this.key = key; + return this; + } + + /** + * @param name + * the name of the software module type + * @return the builder itself + */ + public SoftwareModuleTypeBuilder name(final String name) { + this.name = name; + return this; + } + + public SoftwareModuleTypeBuilder description(final String description) { + this.description = description; + return this; + } + + public SoftwareModuleTypeBuilder maxAssignments(final int maxAssignments) { + this.maxAssignments = maxAssignments; + return this; + } + + /** + * Builds a list with a single entry of + * {@link SoftwareModuleTypeRequestBodyPost} which can directly be used in + * the RESTful-API. + * + * @return a single entry list of {@link SoftwareModuleTypeRequestBodyPost} + */ + public List build() { + return Lists.newArrayList(doBuild(key, name)); + } + + /** + * Builds a list of multiple {@link SoftwareModuleTypeRequestBodyPost} to + * create multiple software module types at once. An increasing number will + * be added to the name and key of the software module type. + * + * @param count + * the amount of software module type bodies which should be + * created + * @return a list of {@link SoftwareModuleTypeRequestBodyPost} + */ + public List buildAsList(final int count) { + final ArrayList bodyList = Lists.newArrayList(); + for (int index = 0; index < count; index++) { + bodyList.add(doBuild(key + index, name + index)); + } + return bodyList; + } + + private SoftwareModuleTypeRequestBodyPost doBuild(final String prefixKey, final String prefixName) { + final SoftwareModuleTypeRequestBodyPost body = new SoftwareModuleTypeRequestBodyPost(); + body.setKey(prefixKey); + body.setName(prefixName); + body.setDescription(description); + body.setMaxAssignments(maxAssignments); + return body; + } + +} \ No newline at end of file diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/TagBuilder.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/TagBuilder.java new file mode 100644 index 000000000..f3888de54 --- /dev/null +++ b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/TagBuilder.java @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.mgmt.client.resource.builder; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.hawkbit.rest.resource.model.tag.TagRequestBodyPut; + +import com.google.common.collect.Lists; + +/** + * Builder pattern for building {@link TagRequestBodyPut}. + * + * @author Jonathan Knoblauch + * + */ +public class TagBuilder { + + private String name; + private String description; + private String color; + + /** + * @param name + * the name of the tag + * @return the builder itself + */ + public TagBuilder name(final String name) { + this.name = name; + return this; + } + + /** + * @param description + * the description of the tag + * @return the builder itself + */ + public TagBuilder description(final String description) { + this.description = description; + return this; + } + + /** + * @param color + * the colour of the tag + * @return the builder itself + */ + public TagBuilder color(final String color) { + this.color = color; + return this; + } + + /** + * Builds a list with a single entry of {@link TagRequestBodyPut} which can + * directly be used in the RESTful-API. + * + * @return a single entry list of {@link TagRequestBodyPut} + */ + public List build() { + return Lists.newArrayList(doBuild(name)); + } + + /** + * Builds a list of multiple {@link TagRequestBodyPut} to create multiple + * tags at once. An increasing number will be added to the name of the tag. + * The color and description will remain the same. + * + * @param count + * the amount of distribution sets body which should be created + * @return a list of {@link TagRequestBodyPut} + */ + public List buildAsList(final int count) { + final ArrayList bodyList = Lists.newArrayList(); + for (int index = 0; index < count; index++) { + bodyList.add(doBuild(name + index)); + } + + return bodyList; + } + + private TagRequestBodyPut doBuild(final String prefixName) { + final TagRequestBodyPut body = new TagRequestBodyPut(); + body.setName(prefixName); + body.setDescription(description); + body.setColour(color); + return body; + } + +} diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/TargetBuilder.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/TargetBuilder.java new file mode 100644 index 000000000..5123903d9 --- /dev/null +++ b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/resource/builder/TargetBuilder.java @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.mgmt.client.resource.builder; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypeRequestBodyPost; +import org.eclipse.hawkbit.rest.resource.model.target.TargetRequestBody; + +import com.google.common.collect.Lists; + +/** + * + * Builder pattern for building {@link TargetRequestBody}. + * + * @author Jonathan Knoblauch + * + */ +public class TargetBuilder { + + private String controllerId; + private String name; + private String description; + + /** + * @param controllerId + * the ID of the controller/target + * @return the builder itself + */ + public TargetBuilder controllerId(final String controllerId) { + this.controllerId = controllerId; + return this; + } + + /** + * @param name + * the name of the target + * @return the builder itself + */ + public TargetBuilder name(final String name) { + this.name = name; + return this; + } + + /** + * @param description + * the description of the target + * @return the builder itself + */ + public TargetBuilder description(final String description) { + this.description = description; + return this; + } + + /** + * Builds a list with a single entry of {@link TargetRequestBody} which can + * directly be used in the RESTful-API. + * + * @return a single entry list of {@link TargetRequestBody} + */ + public List build() { + return Lists.newArrayList(doBuild(controllerId)); + } + + /** + * Builds a list of multiple {@link TargetRequestBody} to create multiple + * targets at once. An increasing number will be added to the controllerId + * of the target. The name and description will remain. + * + * @param count + * the amount of software module type bodies which should be + * created + * @return a list of {@link SoftwareModuleTypeRequestBodyPost} + */ + public List buildAsList(final int count) { + final ArrayList bodyList = Lists.newArrayList(); + for (int index = 0; index < count; index++) { + bodyList.add(doBuild(controllerId + index)); + } + return bodyList; + } + + private TargetRequestBody doBuild(final String prefixControllerId) { + final TargetRequestBody body = new TargetRequestBody(); + body.setControllerId(prefixControllerId); + body.setName(name); + body.setDescription(description); + return body; + } + +} diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/CreateStartedRolloutExample.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/CreateStartedRolloutExample.java new file mode 100644 index 000000000..35b0f31ea --- /dev/null +++ b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/CreateStartedRolloutExample.java @@ -0,0 +1,107 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.mgmt.client.scenarios; + +import org.eclipse.hawkbit.mgmt.client.resource.DistributionSetResourceClient; +import org.eclipse.hawkbit.mgmt.client.resource.DistributionSetTypeResourceClient; +import org.eclipse.hawkbit.mgmt.client.resource.RolloutResourceClient; +import org.eclipse.hawkbit.mgmt.client.resource.SoftwareModuleResourceClient; +import org.eclipse.hawkbit.mgmt.client.resource.SoftwareModuleTypeResourceClient; +import org.eclipse.hawkbit.mgmt.client.resource.TargetResourceClient; +import org.eclipse.hawkbit.mgmt.client.resource.builder.DistributionSetBuilder; +import org.eclipse.hawkbit.mgmt.client.resource.builder.DistributionSetTypeBuilder; +import org.eclipse.hawkbit.mgmt.client.resource.builder.RolloutBuilder; +import org.eclipse.hawkbit.mgmt.client.resource.builder.SoftwareModuleAssigmentBuilder; +import org.eclipse.hawkbit.mgmt.client.resource.builder.SoftwareModuleBuilder; +import org.eclipse.hawkbit.mgmt.client.resource.builder.SoftwareModuleTypeBuilder; +import org.eclipse.hawkbit.mgmt.client.resource.builder.TargetBuilder; +import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetsRest; +import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutResponseBody; +import org.eclipse.hawkbit.rest.resource.model.softwaremodule.SoftwareModulesRest; +import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypesRest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Example for creating and starting a Rollout. + * + */ +@Component +public class CreateStartedRolloutExample { + + /* known software module type name and key */ + private static final String SM_MODULE_TYPE = "firmware"; + + /* known distribution set type name and key */ + private static final String DS_MODULE_TYPE = "firmware"; + + @Autowired + private DistributionSetResourceClient distributionSetResource; + + @Autowired + private SoftwareModuleResourceClient softwareModuleResource; + + @Autowired + private TargetResourceClient targetResource; + + @Autowired + private RolloutResourceClient rolloutResource; + + @Autowired + private DistributionSetTypeResourceClient distributionSetTypeResource; + + @Autowired + private SoftwareModuleTypeResourceClient softwareModuleTypeResource; + + /** + * Run the Rollout scenario. + */ + public void run() { + + // create three SoftwareModuleTypes + final SoftwareModuleTypesRest createdSoftwareModuleTypes = softwareModuleTypeResource.createSoftwareModuleTypes( + new SoftwareModuleTypeBuilder().key(SM_MODULE_TYPE).name(SM_MODULE_TYPE).maxAssignments(1).build()) + .getBody(); + + // create one DistributionSetType + distributionSetTypeResource.createDistributionSetTypes(new DistributionSetTypeBuilder().key(DS_MODULE_TYPE) + .name(DS_MODULE_TYPE).mandatorymodules(createdSoftwareModuleTypes.get(0).getModuleId()).build()) + .getBody(); + + // create one DistributionSet + final DistributionSetsRest distributionSetsRest = distributionSetResource.createDistributionSets( + new DistributionSetBuilder().name("rollout-example").version("1.0.0").type(DS_MODULE_TYPE).build()) + .getBody(); + + // create three SoftwareModules + final SoftwareModulesRest softwareModulesRest = softwareModuleResource + .createSoftwareModules( + new SoftwareModuleBuilder().name("firmware").version("1.0.0").type(SM_MODULE_TYPE).build()) + .getBody(); + + // Assign SoftwareModule to DistributionSet + distributionSetResource.assignSoftwareModules(distributionSetsRest.get(0).getDsId(), + new SoftwareModuleAssigmentBuilder().id(softwareModulesRest.get(0).getModuleId()).build()); + + // create ten targets + targetResource.createTargets(new TargetBuilder().controllerId("00-FF-AA-0").name("00-FF-AA-0") + .description("Targets used for rollout example").buildAsList(10)); + + // create a Rollout + final RolloutResponseBody rolloutResponseBody = rolloutResource + .create(new RolloutBuilder().name("MyRollout").groupSize(2).targetFilterQuery("name==00-FF-AA-0*") + .distributionSetId(distributionSetsRest.get(0).getDsId()).successThreshold("80") + .errorThreshold("50").build()) + .getBody(); + + // start the created Rollout + rolloutResource.start(rolloutResponseBody.getRolloutId(), false); + } + +} diff --git a/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/GettingStartedDefaultScenario.java b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/GettingStartedDefaultScenario.java new file mode 100644 index 000000000..5767ab9e7 --- /dev/null +++ b/examples/hawkbit-mgmt-api-client/src/main/java/org/eclipse/hawkbit/mgmt/client/scenarios/GettingStartedDefaultScenario.java @@ -0,0 +1,134 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.mgmt.client.scenarios; + +import org.eclipse.hawkbit.mgmt.client.resource.DistributionSetResourceClient; +import org.eclipse.hawkbit.mgmt.client.resource.DistributionSetTypeResourceClient; +import org.eclipse.hawkbit.mgmt.client.resource.SoftwareModuleResourceClient; +import org.eclipse.hawkbit.mgmt.client.resource.SoftwareModuleTypeResourceClient; +import org.eclipse.hawkbit.mgmt.client.resource.builder.DistributionSetBuilder; +import org.eclipse.hawkbit.mgmt.client.resource.builder.DistributionSetTypeBuilder; +import org.eclipse.hawkbit.mgmt.client.resource.builder.SoftwareModuleAssigmentBuilder; +import org.eclipse.hawkbit.mgmt.client.resource.builder.SoftwareModuleBuilder; +import org.eclipse.hawkbit.mgmt.client.resource.builder.SoftwareModuleTypeBuilder; +import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetsRest; +import org.eclipse.hawkbit.rest.resource.model.softwaremodule.SoftwareModulesRest; +import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypesRest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * + * Default getting started scenario. + * + */ +@Component +public class GettingStartedDefaultScenario { + + private static final Logger LOGGER = LoggerFactory.getLogger(GettingStartedDefaultScenario.class); + + /* known software module type name and key */ + private static final String SM_MODULE_TYPE = "gettingstarted"; + + /* known distribution set type name and key */ + private static final String DS_MODULE_TYPE = "gettingstarted"; + + /* known distribution name of this getting started example */ + private static final String SM_EXAMPLE_NAME = "gettingstarted-example"; + + /* known distribution name of this getting started example */ + private static final String DS_EXAMPLE_NAME = "gettingstarted-example"; + + @Autowired + private DistributionSetResourceClient distributionSetResource; + + @Autowired + private DistributionSetTypeResourceClient distributionSetTypeResource; + + @Autowired + private SoftwareModuleResourceClient softwareModuleResource; + + @Autowired + private SoftwareModuleTypeResourceClient softwareModuleTypeResource; + + /** + * Run the default getting started scenario. + */ + public void run() { + + LOGGER.info("Running Getting-Started-Scenario..."); + + // create one SoftwareModuleTypes + LOGGER.info("Creating software module type {}", SM_MODULE_TYPE); + final SoftwareModuleTypesRest createdSoftwareModuleTypes = softwareModuleTypeResource.createSoftwareModuleTypes( + new SoftwareModuleTypeBuilder().key(SM_MODULE_TYPE).name(SM_MODULE_TYPE).maxAssignments(1).build()) + .getBody(); + + // create one DistributionSetType + LOGGER.info("Creating distribution set type {}", DS_MODULE_TYPE); + distributionSetTypeResource.createDistributionSetTypes(new DistributionSetTypeBuilder().key(DS_MODULE_TYPE) + .name(DS_MODULE_TYPE).mandatorymodules(createdSoftwareModuleTypes.get(0).getModuleId()).build()); + + // create three DistributionSet + final String dsVersion1 = "1.0.0"; + final String dsVersion2 = "2.0.0"; + final String dsVersion3 = "2.1.0"; + + LOGGER.info("Creating distribution set {}:{}", DS_EXAMPLE_NAME, dsVersion1); + final DistributionSetsRest distributionSetsRest1 = distributionSetResource.createDistributionSets( + new DistributionSetBuilder().name(DS_EXAMPLE_NAME).version(dsVersion1).type(DS_MODULE_TYPE).build()) + .getBody(); + + LOGGER.info("Creating distribution set {}:{}", DS_EXAMPLE_NAME, dsVersion2); + final DistributionSetsRest distributionSetsRest2 = distributionSetResource.createDistributionSets( + new DistributionSetBuilder().name(DS_EXAMPLE_NAME).version(dsVersion2).type(DS_MODULE_TYPE).build()) + .getBody(); + + LOGGER.info("Creating distribution set {}:{}", DS_EXAMPLE_NAME, dsVersion3); + final DistributionSetsRest distributionSetsRest3 = distributionSetResource.createDistributionSets( + new DistributionSetBuilder().name(DS_EXAMPLE_NAME).version(dsVersion3).type(DS_MODULE_TYPE).build()) + .getBody(); + + // create three SoftwareModules + final String swVersion1 = "1"; + final String swVersion2 = "2"; + final String swVersion3 = "3"; + + LOGGER.info("Creating distribution set {}:{}", SM_EXAMPLE_NAME, swVersion1); + final SoftwareModulesRest softwareModulesRest1 = softwareModuleResource.createSoftwareModules( + new SoftwareModuleBuilder().name(SM_EXAMPLE_NAME).version(swVersion1).type(SM_MODULE_TYPE).build()) + .getBody(); + LOGGER.info("Creating distribution set {}:{}", SM_EXAMPLE_NAME, swVersion2); + final SoftwareModulesRest softwareModulesRest2 = softwareModuleResource.createSoftwareModules( + new SoftwareModuleBuilder().name(SM_EXAMPLE_NAME).version(swVersion2).type(SM_MODULE_TYPE).build()) + .getBody(); + LOGGER.info("Creating distribution set {}:{}", SM_EXAMPLE_NAME, swVersion3); + final SoftwareModulesRest softwareModulesRest3 = softwareModuleResource.createSoftwareModules( + new SoftwareModuleBuilder().name(SM_EXAMPLE_NAME).version(swVersion3).type(SM_MODULE_TYPE).build()) + .getBody(); + + // Assign SoftwareModules to DistributionSet + LOGGER.info("Assign software module {}:{} to distribution set {}:{}", SM_EXAMPLE_NAME, swVersion1, + DS_EXAMPLE_NAME, dsVersion1); + distributionSetResource.assignSoftwareModules(distributionSetsRest1.get(0).getDsId(), + new SoftwareModuleAssigmentBuilder().id(softwareModulesRest1.get(0).getModuleId()).build()); + + LOGGER.info("Assign software module {}:{} to distribution set {}:{}", SM_EXAMPLE_NAME, swVersion2, + DS_EXAMPLE_NAME, dsVersion2); + distributionSetResource.assignSoftwareModules(distributionSetsRest2.get(0).getDsId(), + new SoftwareModuleAssigmentBuilder().id(softwareModulesRest2.get(0).getModuleId()).build()); + + LOGGER.info("Assign software module {}:{} to distribution set {}:{}", SM_EXAMPLE_NAME, swVersion3, + DS_EXAMPLE_NAME, dsVersion3); + distributionSetResource.assignSoftwareModules(distributionSetsRest3.get(0).getDsId(), + new SoftwareModuleAssigmentBuilder().id(softwareModulesRest3.get(0).getModuleId()).build()); + } +} diff --git a/examples/hawkbit-mgmt-api-client/src/main/resources/application.properties b/examples/hawkbit-mgmt-api-client/src/main/resources/application.properties new file mode 100644 index 000000000..d3a3eb969 --- /dev/null +++ b/examples/hawkbit-mgmt-api-client/src/main/resources/application.properties @@ -0,0 +1,14 @@ +# +# Copyright (c) 2015 Bosch Software Innovations GmbH and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# + +hawkbit.url=localhost:8080 +hawkbit.username=admin +hawkbit.password=admin + +spring.main.show-banner=false \ No newline at end of file diff --git a/examples/hawkbit-mgmt-api-client/src/main/resources/logback.xml b/examples/hawkbit-mgmt-api-client/src/main/resources/logback.xml new file mode 100644 index 000000000..0174611e6 --- /dev/null +++ b/examples/hawkbit-mgmt-api-client/src/main/resources/logback.xml @@ -0,0 +1,27 @@ + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + \ No newline at end of file diff --git a/examples/hawkbit-mgmt-api-client/src/test/java/org/eclipse/hawkbit/mgmt/api/client/DistributionSetTagTest.java b/examples/hawkbit-mgmt-api-client/src/test/java/org/eclipse/hawkbit/mgmt/api/client/DistributionSetTagTest.java deleted file mode 100644 index 17384c211..000000000 --- a/examples/hawkbit-mgmt-api-client/src/test/java/org/eclipse/hawkbit/mgmt/api/client/DistributionSetTagTest.java +++ /dev/null @@ -1,186 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.eclipse.hawkbit.mgmt.api.client; - -import static org.fest.assertions.api.Assertions.assertThat; - -import java.util.ArrayList; -import java.util.List; - -import org.eclipse.hawkbit.app.Start; -import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetRequestBodyPost; -import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetRest; -import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetsRest; -import org.eclipse.hawkbit.rest.resource.model.tag.AssignedDistributionSetRequestBody; -import org.eclipse.hawkbit.rest.resource.model.tag.DistributionSetTagAssigmentResultRest; -import org.eclipse.hawkbit.rest.resource.model.tag.TagRequestBodyPut; -import org.eclipse.hawkbit.rest.resource.model.tag.TagRest; -import org.eclipse.hawkbit.rest.resource.model.tag.TagsRest; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; -import org.springframework.boot.SpringApplication; -import org.springframework.context.annotation.Description; - -import ru.yandex.qatools.allure.annotations.Features; -import ru.yandex.qatools.allure.annotations.Stories; -import feign.Feign; -import feign.Logger; -import feign.auth.BasicAuthRequestInterceptor; -import feign.jackson.JacksonDecoder; -import feign.jackson.JacksonEncoder; - -@Features("Example Tests - Management RESTful API Client") -@Stories("DistrubutionSet Tag Resource") -public class DistributionSetTagTest { - - private DistrubutionSetTagResource distrubutionSetTagResource; - - private static List assignedTargetRequestBodies; - - @BeforeClass - public static void startupServer() { - SpringApplication.run(Start.class, new String[0]); - createTargetsAssignment(); - assignedTargetRequestBodies.add(new AssignedDistributionSetRequestBody().setDistributionSetId(100L)); - } - - @Before - public void setup() { - this.distrubutionSetTagResource = createDistrubutionSetTagResource(); - } - - // disabled as this runs not on CI environments. - @Test - @Description("Simple request of distrubutionset tag by ID") - @Ignore - public void getDistributionSetTag() { - final TagsRest result = createDistributionSetTags(2); - - assertThat(distrubutionSetTagResource.getDistributionSetTag(result.get(0).getTagId()).getName()).isEqualTo( - "Tag0"); - - deleteDistributionSets(result); - } - - // disabled as this runs not on CI environments. - @Test - @Description("Simple update of a distrubutionset tag") - @Ignore - public void updateDistributionSetTag() { - final TagsRest created = createDistributionSetTags(10); - - distrubutionSetTagResource.updateDistributionSetTag(created.get(0).getTagId(), new TagRequestBodyPut() - .setDescription("Test").setName("Test").setColour("Green")); - - final TagRest targetTag = distrubutionSetTagResource.getDistributionSetTag(created.get(0).getTagId()); - assertThat(targetTag.getName()).isEqualTo("Test"); - assertThat(targetTag.getDescription()).isEqualTo("Test"); - assertThat(targetTag.getColour()).isEqualTo("Green"); - - deleteDistributionSets(created); - } - - // disabled as this runs not on CI environments. - @Test - @Description("Simple request of all assigned distrubutionsets by a distrubutionset tag.") - @Ignore - public void getDistributionSetByTagId() { - final TagsRest created = createDistributionSetTags(10); - distrubutionSetTagResource.assignDistributionSets(created.get(2).getTagId(), assignedTargetRequestBodies); - - final DistributionSetsRest distributionSetsRest = distrubutionSetTagResource - .getAssignedDistributionSets(created.get(2).getTagId()); - assertThat(distributionSetsRest).hasSize(5); - - distrubutionSetTagResource.unassignDistributionSets(created.get(2).getTagId()); - deleteDistributionSets(created); - } - - @Test - @Description("Toggle request to unassigned all assigned distrubutionset and assign all unassigned distrubutionset.") - @Ignore - public void toggleTagAssignment() { - final TagsRest created = createDistributionSetTags(10); - final Long id = created.get(2).getTagId(); - - distrubutionSetTagResource.assignDistributionSets(id, assignedTargetRequestBodies); - distrubutionSetTagResource.unassignDistributionSet(id, assignedTargetRequestBodies.get(0) - .getDistributionSetId()); - - DistributionSetTagAssigmentResultRest assigmentResultRest = distrubutionSetTagResource.toggleTagAssignment(id, - assignedTargetRequestBodies); - - final TagRest targetTag = distrubutionSetTagResource.getDistributionSetTag(created.get(2).getTagId()); - assertThat(assigmentResultRest.getAssignedDistributionSets()).hasSize(1); - assertThat(assigmentResultRest.getUnassignedDistributionSets()).hasSize(0); - - assigmentResultRest = distrubutionSetTagResource.toggleTagAssignment(id, assignedTargetRequestBodies); - assertThat(assigmentResultRest.getAssignedDistributionSets()).hasSize(0); - assertThat(assigmentResultRest.getUnassignedDistributionSets()).hasSize(5); - - distrubutionSetTagResource.unassignDistributionSets(targetTag.getTagId()); - deleteDistributionSets(created); - } - - private void deleteDistributionSets(final List tags) { - for (final TagRest tag : tags) { - distrubutionSetTagResource.deleteDistributionSetTag(tag.getTagId()); - } - } - - private TagsRest createDistributionSetTags(final int number) { - - final List tags = new ArrayList<>(); - for (int i = 0; i < number; i++) { - tags.add(new TagRequestBodyPut().setDescription("Tag " + i).setName("Tag" + i).setColour("Red")); - } - - final TagsRest result = distrubutionSetTagResource.createDistributionSetTags(tags); - - assertThat(result).hasSize(number); - return result; - } - - private DistrubutionSetTagResource createDistrubutionSetTagResource() { - final DistrubutionSetTagResource distrubutionSetTagResource = Feign.builder().logger(new Logger.ErrorLogger()) - .logLevel(Logger.Level.BASIC).decoder(new JacksonDecoder()).encoder(new JacksonEncoder()) - .requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin")) - .target(DistrubutionSetTagResource.class, "http://localhost:8080"); - return distrubutionSetTagResource; - } - - private static void createTargetsAssignment() { - - final List sets = new ArrayList<>(); - assignedTargetRequestBodies = new ArrayList<>(); - for (int i = 0; i < 5; i++) { - final DistributionSetRequestBodyPost bodyPost = (DistributionSetRequestBodyPost) new DistributionSetRequestBodyPost() - .setName("Ds" + i).setDescription("Ds" + i).setVersion("" + i); - sets.add(bodyPost); - } - - final DistributionSetsRest result = createDistributionSetResource().createDistributionSets(sets); - for (final DistributionSetRest rest : result) { - assignedTargetRequestBodies.add(new AssignedDistributionSetRequestBody().setDistributionSetId(rest - .getDsId())); - } - - } - - private static DistributionSetResource createDistributionSetResource() { - final DistributionSetResource distributionSetResource = Feign.builder().logger(new Logger.ErrorLogger()) - .logLevel(Logger.Level.BASIC).decoder(new JacksonDecoder()).encoder(new JacksonEncoder()) - .requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin")) - .target(DistributionSetResource.class, "http://localhost:8080"); - return distributionSetResource; - } - -} diff --git a/examples/hawkbit-mgmt-api-client/src/test/java/org/eclipse/hawkbit/mgmt/api/client/TargetTagTest.java b/examples/hawkbit-mgmt-api-client/src/test/java/org/eclipse/hawkbit/mgmt/api/client/TargetTagTest.java deleted file mode 100644 index 10f18b704..000000000 --- a/examples/hawkbit-mgmt-api-client/src/test/java/org/eclipse/hawkbit/mgmt/api/client/TargetTagTest.java +++ /dev/null @@ -1,182 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.eclipse.hawkbit.mgmt.api.client; - -import static org.fest.assertions.api.Assertions.assertThat; - -import java.util.ArrayList; -import java.util.List; - -import org.eclipse.hawkbit.app.Start; -import org.eclipse.hawkbit.rest.resource.model.tag.AssignedTargetRequestBody; -import org.eclipse.hawkbit.rest.resource.model.tag.TagRequestBodyPut; -import org.eclipse.hawkbit.rest.resource.model.tag.TagRest; -import org.eclipse.hawkbit.rest.resource.model.tag.TagsRest; -import org.eclipse.hawkbit.rest.resource.model.tag.TargetTagAssigmentResultRest; -import org.eclipse.hawkbit.rest.resource.model.target.TargetRequestBody; -import org.eclipse.hawkbit.rest.resource.model.target.TargetRest; -import org.eclipse.hawkbit.rest.resource.model.target.TargetsRest; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; -import org.springframework.boot.SpringApplication; -import org.springframework.context.annotation.Description; - -import ru.yandex.qatools.allure.annotations.Features; -import ru.yandex.qatools.allure.annotations.Stories; -import feign.Feign; -import feign.Logger; -import feign.auth.BasicAuthRequestInterceptor; -import feign.jackson.JacksonDecoder; -import feign.jackson.JacksonEncoder; - -@Features("Example Tests - Management RESTful API Client") -@Stories("Target Tag Resource") -public class TargetTagTest { - - private TargetTagResource targetTagResource; - - private static List assignedTargetRequestBodies; - - @BeforeClass - public static void startupServer() { - SpringApplication.run(Start.class, new String[0]); - createTargetsAssignment(); - assignedTargetRequestBodies.add(new AssignedTargetRequestBody().setControllerId("NotExist")); - } - - @Before - public void setup() { - this.targetTagResource = createTargetTagResource(); - } - - // disabled as this runs not on CI environments. - @Test - @Description("Simple request of target tag by ID") - @Ignore - public void getTargetTag() { - final TagsRest result = createTargetTags(2); - - assertThat(targetTagResource.getTargetTag(result.get(0).getTagId()).getName()).isEqualTo("Tag0"); - - deleteTargets(result); - } - - // disabled as this runs not on CI environments. - @Test - @Description("Simple update of a target tag") - @Ignore - public void updateTargetTag() { - final TagsRest created = createTargetTags(10); - - targetTagResource.updateTagretTag(created.get(0).getTagId(), new TagRequestBodyPut().setDescription("Test") - .setName("Test").setColour("Green")); - - final TagRest targetTag = targetTagResource.getTargetTag(created.get(0).getTagId()); - assertThat(targetTag.getName()).isEqualTo("Test"); - assertThat(targetTag.getDescription()).isEqualTo("Test"); - assertThat(targetTag.getColour()).isEqualTo("Green"); - - deleteTargets(created); - } - - // disabled as this runs not on CI environments. - @Test - @Description("Simple request of all assigned targets by a target tag.") - @Ignore - public void getTargetsByTargetTagId() { - final TagsRest created = createTargetTags(10); - final Long tagId = created.get(2).getTagId(); - targetTagResource.assignTargets(tagId, assignedTargetRequestBodies); - - final TagRest targetTag = targetTagResource.getTargetTag(tagId); - assertThat(targetTagResource.getAssignedTargets(tagId)).hasSize(5); - - targetTagResource.unassignTargets(targetTag.getTagId()); - deleteTargets(created); - } - - @Test - @Description("Toggle request to unassigned all assigned targets and assign all unassigned targets.") - @Ignore - public void toggleTagAssignment() { - final TagsRest created = createTargetTags(10); - final Long id = created.get(2).getTagId(); - - targetTagResource.assignTargets(id, assignedTargetRequestBodies); - targetTagResource.unassignTarget(id, assignedTargetRequestBodies.get(0).getControllerId()); - - TargetTagAssigmentResultRest assigmentResultRest = targetTagResource.toggleTagAssignment(id, - assignedTargetRequestBodies); - - final TagRest targetTag = targetTagResource.getTargetTag(created.get(2).getTagId()); - assertThat(assigmentResultRest.getAssignedTargets()).hasSize(1); - assertThat(assigmentResultRest.getUnassignedTargets()).hasSize(0); - - assigmentResultRest = targetTagResource.toggleTagAssignment(id, assignedTargetRequestBodies); - assertThat(assigmentResultRest.getAssignedTargets()).hasSize(0); - assertThat(assigmentResultRest.getUnassignedTargets()).hasSize(5); - - targetTagResource.unassignTargets(targetTag.getTagId()); - deleteTargets(created); - } - - private void deleteTargets(final List tags) { - for (final TagRest tag : tags) { - targetTagResource.deleteTargetTag(tag.getTagId()); - } - } - - private TagsRest createTargetTags(final int number) { - - final List tags = new ArrayList<>(); - for (int i = 0; i < number; i++) { - tags.add(new TagRequestBodyPut().setDescription("Tag " + i).setName("Tag" + i).setColour("Red")); - } - - final TagsRest result = targetTagResource.createTargetTag(tags); - - assertThat(result).hasSize(number); - return result; - } - - private TargetTagResource createTargetTagResource() { - final TargetTagResource targetResource = Feign.builder().logger(new Logger.ErrorLogger()) - .logLevel(Logger.Level.BASIC).decoder(new JacksonDecoder()).encoder(new JacksonEncoder()) - .requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin")) - .target(TargetTagResource.class, "http://localhost:8080"); - return targetResource; - } - - private static void createTargetsAssignment() { - - final List targets = new ArrayList<>(); - assignedTargetRequestBodies = new ArrayList<>(); - for (int i = 0; i < 5; i++) { - - targets.add(new TargetRequestBody().setControllerId("test" + i).setName("testDevice")); - } - - final TargetsRest result = createTargetResource().createTargets(targets); - for (final TargetRest rest : result) { - assignedTargetRequestBodies.add(new AssignedTargetRequestBody().setControllerId(rest.getControllerId())); - } - - } - - private static TargetResource createTargetResource() { - final TargetResource targetResource = Feign.builder().logger(new Logger.ErrorLogger()) - .logLevel(Logger.Level.BASIC).decoder(new JacksonDecoder()).encoder(new JacksonEncoder()) - .requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin")) - .target(TargetResource.class, "http://localhost:8080"); - return targetResource; - } - -} diff --git a/examples/hawkbit-mgmt-api-client/src/test/java/org/eclipse/hawkbit/mgmt/api/client/TargetTest.java b/examples/hawkbit-mgmt-api-client/src/test/java/org/eclipse/hawkbit/mgmt/api/client/TargetTest.java deleted file mode 100644 index 745b119f6..000000000 --- a/examples/hawkbit-mgmt-api-client/src/test/java/org/eclipse/hawkbit/mgmt/api/client/TargetTest.java +++ /dev/null @@ -1,119 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.eclipse.hawkbit.mgmt.api.client; - -import static org.fest.assertions.api.Assertions.assertThat; - -import java.util.ArrayList; -import java.util.List; - -import org.eclipse.hawkbit.app.Start; -import org.eclipse.hawkbit.rest.resource.model.target.TargetPagedList; -import org.eclipse.hawkbit.rest.resource.model.target.TargetRequestBody; -import org.eclipse.hawkbit.rest.resource.model.target.TargetRest; -import org.eclipse.hawkbit.rest.resource.model.target.TargetsRest; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; -import org.springframework.boot.SpringApplication; -import org.springframework.context.annotation.Description; - -import feign.Feign; -import feign.Logger; -import feign.auth.BasicAuthRequestInterceptor; -import feign.jackson.JacksonDecoder; -import feign.jackson.JacksonEncoder; -import ru.yandex.qatools.allure.annotations.Features; -import ru.yandex.qatools.allure.annotations.Stories; - -@Features("Example Tests - Management RESTful API Client") -@Stories("Target Resource") -public class TargetTest { - - @BeforeClass - public static void startupServer() { - SpringApplication.run(Start.class, new String[0]); - } - - // disabled as this runs not on CI environments. - @Test - @Description("Simple request of target by ID") - @Ignore - public void getTarget() { - final TargetResource targetResource = createTargetResource(); - final TargetsRest result = createTargets(targetResource, 1); - - assertThat(targetResource.getTarget("test0").getName()).isEqualTo("testDevice"); - - deleteTargets(targetResource, result); - } - - // disabled as this runs not on CI environments. - @Test - @Description("Simple request of all targets with defined page sizing information (i.e. offset and limit).") - @Ignore - public void getTargetsAsPagedListWithDefinedPageSizing() { - final TargetResource targetResource = createTargetResource(); - final TargetsRest created = createTargets(targetResource, 20); - - final TargetPagedList queryResult = targetResource.getTargets(0, 10); - - assertThat(queryResult.getContent()).hasSize(10); - assertThat(queryResult.getTotal()).isEqualTo(20); - assertThat(queryResult.getSize()).isEqualTo(10); - - deleteTargets(targetResource, created); - } - - // disabled as this runs not on CI environments. - @Test - @Description("Simple request of all targets with defualt paging parameters.") - @Ignore - public void getTargetsAsPagedListWithDefaultPageSizing() { - final TargetResource targetResource = createTargetResource(); - final TargetsRest created = createTargets(targetResource, 20); - - final TargetPagedList queryResult = targetResource.getTargets(); - - assertThat(queryResult.getContent()).hasSize(20); - assertThat(queryResult.getTotal()).isEqualTo(20); - assertThat(queryResult.getSize()).isEqualTo(20); - - deleteTargets(targetResource, created); - } - - private void deleteTargets(final TargetResource targetResource, final List targets) { - for (final TargetRest targetRest : targets) { - targetResource.deleteTarget(targetRest.getControllerId()); - } - } - - private TargetsRest createTargets(final TargetResource targetResource, final int number) { - - final List targets = new ArrayList<>(); - for (int i = 0; i < number; i++) { - - targets.add(new TargetRequestBody().setControllerId("test" + i).setName("testDevice")); - } - - final TargetsRest result = targetResource.createTargets(targets); - - assertThat(result).hasSize(number); - return result; - } - - private TargetResource createTargetResource() { - final TargetResource targetResource = Feign.builder().logger(new Logger.ErrorLogger()) - .logLevel(Logger.Level.BASIC).decoder(new JacksonDecoder()).encoder(new JacksonEncoder()) - .requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin")) - .target(TargetResource.class, "http://localhost:8080"); - return targetResource; - } - -} diff --git a/hawkbit-artifact-repository-mongo/src/test/java/org/eclipse/hawkbit/artifact/MongoDBTestRule.java b/hawkbit-artifact-repository-mongo/src/test/java/org/eclipse/hawkbit/artifact/MongoDBTestRule.java index d8485787e..f5d88ceaa 100644 --- a/hawkbit-artifact-repository-mongo/src/test/java/org/eclipse/hawkbit/artifact/MongoDBTestRule.java +++ b/hawkbit-artifact-repository-mongo/src/test/java/org/eclipse/hawkbit/artifact/MongoDBTestRule.java @@ -88,7 +88,7 @@ public class MongoDBTestRule implements TestRule { System.setProperty("spring.data.mongodb.port", String.valueOf(port)); } - Version version = Version.V3_1_0; + Version version = Version.V3_0_8; if (System.getProperty("inf.mongodb.version") != null) { version = Version.valueOf("V" + System.getProperty("inf.mongodb.version").trim().replaceAll("\\.", "_")); } diff --git a/hawkbit-artifact-repository-mongo/src/test/java/org/eclipse/hawkbit/artifact/repository/ArtifactStoreTest.java b/hawkbit-artifact-repository-mongo/src/test/java/org/eclipse/hawkbit/artifact/repository/ArtifactStoreTest.java index 2a8441ee6..c5aca8e1a 100644 --- a/hawkbit-artifact-repository-mongo/src/test/java/org/eclipse/hawkbit/artifact/repository/ArtifactStoreTest.java +++ b/hawkbit-artifact-repository-mongo/src/test/java/org/eclipse/hawkbit/artifact/repository/ArtifactStoreTest.java @@ -34,6 +34,12 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.google.common.io.BaseEncoding; import com.mongodb.gridfs.GridFSDBFile; +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Component Tests - Repository") +@Stories("Artifact Store MongoDB") @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = { ArtifactStoreAutoConfiguration.class, TestConfiguration.class }) public class ArtifactStoreTest { @@ -48,6 +54,7 @@ public class ArtifactStoreTest { private GridFsOperations gridFs; @Test + @Description("Ensures that storage in MongoDB is correctly executed.s") public void storeArtifactInMongoDB() { final int filelengthBytes = 128; final String filename = "testfile.json"; @@ -60,6 +67,7 @@ public class ArtifactStoreTest { } @Test + @Description("Ensures that search by SHA1 hash (which is used by hawkBit as artifact ID) finds the expected results.") public void findArtifactBySHA1Hash() throws NoSuchAlgorithmException { final int filelengthBytes = 128; final String filename = "testfile.json"; @@ -72,6 +80,7 @@ public class ArtifactStoreTest { } @Test + @Description("Ensures that search by MD5 hash finds the expected results.") public void findArtifactByMD5Hash() throws NoSuchAlgorithmException { final int filelengthBytes = 128; final String filename = "testfile.json"; @@ -84,6 +93,7 @@ public class ArtifactStoreTest { } @Test + @Description("Ensures that artifact content can be read through InputStream.") public void getInputStreamFromArtifact() throws IOException { final int filelengthBytes = 128; final String filename = "testfile.json"; @@ -103,7 +113,8 @@ public class ArtifactStoreTest { } @Test - public void deleteArtifactWithOnlyOneTenantLast() throws NoSuchAlgorithmException { + @Description("Ensures that artifact delete actually results in deletion from database.") + public void deleteArtifact() throws NoSuchAlgorithmException { final int filelengthBytes = 128; final String filename = "testfile.json"; final String contentType = "application/json"; diff --git a/hawkbit-autoconfigure/pom.xml b/hawkbit-autoconfigure/pom.xml index 11a106a4b..7670da406 100644 --- a/hawkbit-autoconfigure/pom.xml +++ b/hawkbit-autoconfigure/pom.xml @@ -72,5 +72,10 @@ org.springframework spring-context-support + + org.springframework.boot + spring-boot-configuration-processor + true + diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/conf/ControllerPollAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/conf/ControllerPollAutoConfiguration.java deleted file mode 100644 index bcbc5ec16..000000000 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/conf/ControllerPollAutoConfiguration.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.eclipse.hawkbit.autoconfigure.conf; - -import org.eclipse.hawkbit.ControllerPollProperties; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -/** - * Enable the Controlle Poll. - * - * - * - */ -@Configuration -@ConditionalOnClass(ControllerPollProperties.class) -@EnableConfigurationProperties(ControllerPollProperties.class) -public class ControllerPollAutoConfiguration { - -} diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/eventbus/EventBusAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/eventbus/EventBusAutoConfiguration.java index e09c0a68e..37e72880c 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/eventbus/EventBusAutoConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/eventbus/EventBusAutoConfiguration.java @@ -12,6 +12,7 @@ import java.util.concurrent.Executor; import org.eclipse.hawkbit.eventbus.EventBusSubscriberProcessor; import org.eclipse.hawkbit.eventbus.EventSubscriber; +import org.eclipse.hawkbit.repository.model.helper.EventBusHolder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -54,4 +55,12 @@ public class EventBusAutoConfiguration { return new EventBusSubscriberProcessor(); } + /** + * @return the singleton instance of the {@link EventBusHolder} + */ + @Bean + public EventBusHolder eventBusHolder() { + return EventBusHolder.getInstance(); + } + } diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/scheduling/AsyncConfigurerThreadpoolProperties.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/scheduling/AsyncConfigurerThreadpoolProperties.java index f7f18f1b0..35996a114 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/scheduling/AsyncConfigurerThreadpoolProperties.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/scheduling/AsyncConfigurerThreadpoolProperties.java @@ -13,17 +13,29 @@ import org.springframework.boot.context.properties.ConfigurationProperties; /** * Properties for the async configurer. * - * */ @ConfigurationProperties("hawkbit.threadpool") public class AsyncConfigurerThreadpoolProperties { + /** + * Max queue size for central event executor. + */ private Integer queuesize = 250; + /** + * Core processing threads for central event executor. + */ private Integer corethreads = 5; + /** + * Maximum thread pool size for central event executor. + */ private Integer maxthreads = 50; + /** + * When the number of threads is greater than the core, this is the maximum + * time that excess idle threads will wait for new tasks before terminating. + */ private Long idletimeout = 10000L; public Integer getQueuesize() { diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/scheduling/ExecutorAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/scheduling/ExecutorAutoConfiguration.java index f8a86d1bc..43a096e7d 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/scheduling/ExecutorAutoConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/scheduling/ExecutorAutoConfiguration.java @@ -26,10 +26,9 @@ import org.springframework.security.concurrent.DelegatingSecurityContextExecutor import com.google.common.util.concurrent.ThreadFactoryBuilder; /** - * + * Central event processors inside update server. * */ - @Configuration @EnableConfigurationProperties(AsyncConfigurerThreadpoolProperties.class) public class ExecutorAutoConfiguration { @@ -40,7 +39,7 @@ public class ExecutorAutoConfiguration { private AsyncConfigurerThreadpoolProperties asyncConfigurerProperties; /** - * @return ExecutorService for general pupose multi threaded operations + * @return ExecutorService for general purpose multi threaded operations */ @Bean @ConditionalOnMissingBean diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java index 326ff4774..8a3a50ad2 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java @@ -20,7 +20,7 @@ import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails; import org.eclipse.hawkbit.im.authentication.UserAuthenticationFilter; import org.eclipse.hawkbit.security.SecurityContextTenantAware; -import org.eclipse.hawkbit.security.SecurityProperties; +import org.eclipse.hawkbit.security.DdiSecurityProperties; import org.eclipse.hawkbit.security.SpringSecurityAuditorAware; import org.eclipse.hawkbit.tenancy.TenantAware; import org.slf4j.Logger; @@ -53,7 +53,7 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationFi * */ @Configuration -@EnableConfigurationProperties(SecurityProperties.class) +@EnableConfigurationProperties(DdiSecurityProperties.class) public class SecurityAutoConfiguration { /** diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java index ca55f1711..24e9667c2 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityManagedConfiguration.java @@ -29,31 +29,31 @@ import org.eclipse.hawkbit.im.authentication.TenantUserPasswordAuthenticationTok import org.eclipse.hawkbit.im.authentication.UserAuthenticationFilter; import org.eclipse.hawkbit.repository.ControllerManagement; import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.rest.resource.RestConstants; import org.eclipse.hawkbit.security.ControllerTenantAwareAuthenticationDetailsSource; +import org.eclipse.hawkbit.security.DdiSecurityProperties; import org.eclipse.hawkbit.security.DosFilter; +import org.eclipse.hawkbit.security.HawkbitSecurityProperties; import org.eclipse.hawkbit.security.HttpControllerPreAuthenticateSecurityTokenFilter; import org.eclipse.hawkbit.security.HttpControllerPreAuthenticatedGatewaySecurityTokenFilter; import org.eclipse.hawkbit.security.HttpControllerPreAuthenticatedSecurityHeaderFilter; import org.eclipse.hawkbit.security.HttpDownloadAuthenticationFilter; import org.eclipse.hawkbit.security.PreAuthTokenSourceTrustAuthenticationProvider; -import org.eclipse.hawkbit.security.SecurityProperties; +import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.tenancy.TenantAware; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.boot.context.embedded.FilterRegistrationBean; import org.springframework.boot.context.embedded.ServletListenerRegistrationBean; import org.springframework.cache.Cache; -import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.AdviceMode; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; -import org.springframework.core.env.Environment; import org.springframework.http.HttpStatus; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -94,17 +94,11 @@ import org.vaadin.spring.security.web.authentication.VaadinUrlAuthenticationSucc @EnableGlobalMethodSecurity(prePostEnabled = true, mode = AdviceMode.ASPECTJ, proxyTargetClass = true, securedEnabled = true) @EnableWebMvcSecurity @Order(value = Ordered.HIGHEST_PRECEDENCE) -public class SecurityManagedConfiguration implements EnvironmentAware { +public class SecurityManagedConfiguration { private static final Logger LOG = LoggerFactory.getLogger(SecurityManagedConfiguration.class); - private static final String SP_SERVER_CONFIG_PREFIX = "hawkbit.server."; - private RelaxedPropertyResolver environment; - - @Override - public void setEnvironment(final Environment environment) { - this.environment = new RelaxedPropertyResolver(environment, SP_SERVER_CONFIG_PREFIX); - - } + @Autowired + private HawkbitSecurityProperties securityProperties; /** * {@link WebSecurityConfigurer} for the internal SP controller API. @@ -119,33 +113,36 @@ public class SecurityManagedConfiguration implements EnvironmentAware { @Autowired private ControllerManagement controllerManagement; @Autowired - private SystemManagement systemManagement; + private TenantConfigurationManagement tenantConfigurationManagement; @Autowired private TenantAware tenantAware; @Autowired - private SecurityProperties securityConfiguration; + private DdiSecurityProperties ddiSecurityConfiguration; @Autowired private org.springframework.boot.autoconfigure.security.SecurityProperties springSecurityProperties; + @Autowired + private SystemSecurityContext systemSecurityContext; @Override protected void configure(final HttpSecurity http) throws Exception { final ControllerTenantAwareAuthenticationDetailsSource authenticationDetailsSource = new ControllerTenantAwareAuthenticationDetailsSource(); final HttpControllerPreAuthenticatedSecurityHeaderFilter securityHeaderFilter = new HttpControllerPreAuthenticatedSecurityHeaderFilter( - securityConfiguration.getRpCnHeader(), securityConfiguration.getRpSslIssuerHashHeader(), - systemManagement, tenantAware); + ddiSecurityConfiguration.getRp().getCnHeader(), + ddiSecurityConfiguration.getRp().getSslIssuerHashHeader(), tenantConfigurationManagement, + tenantAware, systemSecurityContext); securityHeaderFilter.setAuthenticationManager(authenticationManager()); securityHeaderFilter.setCheckForPrincipalChanges(true); securityHeaderFilter.setAuthenticationDetailsSource(authenticationDetailsSource); final HttpControllerPreAuthenticateSecurityTokenFilter securityTokenFilter = new HttpControllerPreAuthenticateSecurityTokenFilter( - systemManagement, tenantAware, controllerManagement); + tenantConfigurationManagement, tenantAware, controllerManagement, systemSecurityContext); securityTokenFilter.setAuthenticationManager(authenticationManager()); securityTokenFilter.setCheckForPrincipalChanges(true); securityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource); final HttpControllerPreAuthenticatedGatewaySecurityTokenFilter gatewaySecurityTokenFilter = new HttpControllerPreAuthenticatedGatewaySecurityTokenFilter( - systemManagement, tenantAware); + tenantConfigurationManagement, tenantAware, systemSecurityContext); gatewaySecurityTokenFilter.setAuthenticationManager(authenticationManager()); gatewaySecurityTokenFilter.setCheckForPrincipalChanges(true); gatewaySecurityTokenFilter.setAuthenticationDetailsSource(authenticationDetailsSource); @@ -158,7 +155,7 @@ public class SecurityManagedConfiguration implements EnvironmentAware { httpSec = httpSec.requiresChannel().anyRequest().requiresSecure().and(); } - if (securityConfiguration.getAnonymousEnabled()) { + if (ddiSecurityConfiguration.getAuthentication().getAnonymous().isEnabled()) { LOG.info( "******************\n** Anonymous controller security enabled, should only use for developing purposes **\n******************"); final AnonymousAuthenticationFilter anoymousFilter = new AnonymousAuthenticationFilter( @@ -177,19 +174,10 @@ public class SecurityManagedConfiguration implements EnvironmentAware { } } - /* - * (non-Javadoc) - * - * @see - * org.springframework.security.config.annotation.web.configuration. - * WebSecurityConfigurerAdapter - * #configure(org.springframework.security.config.annotation. - * authentication.builders. AuthenticationManagerBuilder) - */ @Override protected void configure(final AuthenticationManagerBuilder auth) throws Exception { - auth.authenticationProvider( - new PreAuthTokenSourceTrustAuthenticationProvider(securityConfiguration.getRpTrustedIPs())); + auth.authenticationProvider(new PreAuthTokenSourceTrustAuthenticationProvider( + ddiSecurityConfiguration.getRp().getTrustedIPs())); } } @@ -204,13 +192,10 @@ public class SecurityManagedConfiguration implements EnvironmentAware { public FilterRegistrationBean dosFilter() { final FilterRegistrationBean filterRegBean = new FilterRegistrationBean(); - filterRegBean - .setFilter( - new DosFilter(environment.getProperty("security.dos.filter.maxRead", Integer.class, 200), - environment.getProperty("security.dos.filter.maxWrite", Integer.class, 50), - environment.getProperty("security.dos.filter.whitelist"), environment - .getProperty("security.clients.blacklist"), - environment.getProperty("security.rp.remote_ip_header", String.class, "X-Forwarded-For"))); + filterRegBean.setFilter(new DosFilter(securityProperties.getDos().getFilter().getMaxRead(), + securityProperties.getDos().getFilter().getMaxWrite(), + securityProperties.getDos().getFilter().getWhitelist(), securityProperties.getClients().getBlacklist(), + securityProperties.getClients().getRemoteIpHeader())); filterRegBean.addUrlPatterns("/{tenant}/controller/v1/*", "/rest/*"); return filterRegBean; } @@ -310,8 +295,7 @@ public class SecurityManagedConfiguration implements EnvironmentAware { @Configuration @Order(400) @EnableVaadinSecurity - public static class UISecurityConfigurationAdapter extends WebSecurityConfigurerAdapter - implements EnvironmentAware { + public static class UISecurityConfigurationAdapter extends WebSecurityConfigurerAdapter { private static final String XFRAME_OPTION_DENY = "DENY"; private static final String XFRAME_OPTION_SAMEORIGIN = "SAMEORIGIN"; @@ -320,13 +304,8 @@ public class SecurityManagedConfiguration implements EnvironmentAware { private VaadinSecurityContext vaadinSecurityContext; @Autowired private org.springframework.boot.autoconfigure.security.SecurityProperties springSecurityProperties; - - private RelaxedPropertyResolver environment; - - @Override - public void setEnvironment(final Environment environment) { - this.environment = new RelaxedPropertyResolver(environment, SP_SERVER_CONFIG_PREFIX); - } + @Autowired + private HawkbitSecurityProperties securityProperties; /** * post construct for setting the authentication success handler for the @@ -379,13 +358,13 @@ public class SecurityManagedConfiguration implements EnvironmentAware { protected void configure(final HttpSecurity http) throws Exception { // configuration xframe-option - final String confXframeOption = environment.getProperty("security.xframe.option", XFRAME_OPTION_DENY); - final String confAllowFromUri = environment.getProperty("security.xframe.option.allowfrom"); - if (confXframeOption.equals(XFAME_OPTION_ALLOW_FROM) && confAllowFromUri == null) { + final String confXframeOption = securityProperties.getXframe().getOption(); + final String confAllowFromUri = securityProperties.getXframe().getAllowfrom(); + if (confXframeOption.equals(XFAME_OPTION_ALLOW_FROM) && confAllowFromUri.isEmpty()) { // if allow-from option is specified but no allowFromUri throw // exception throw new IllegalStateException("hawkbit.server.security.xframe.option has been specified as ALLOW-FROM" - + " but no hawkbit.server.security.xframe.option.allowfrom has been set, " + + " but no hawkbit.server.security.xframe.allowfrom has been set, " + "please ensure to set allow from URIs"); } @@ -461,7 +440,7 @@ public class SecurityManagedConfiguration implements EnvironmentAware { public static class IdRestSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter { @Autowired - private SecurityProperties securityConfiguration; + private DdiSecurityProperties ddiSecurityConfiguration; @Autowired @Qualifier(CacheConstants.DOWNLOAD_ID_CACHE) @@ -483,8 +462,8 @@ public class SecurityManagedConfiguration implements EnvironmentAware { @Override protected void configure(final AuthenticationManagerBuilder auth) throws Exception { - auth.authenticationProvider( - new PreAuthTokenSourceTrustAuthenticationProvider(securityConfiguration.getRpTrustedIPs())); + auth.authenticationProvider(new PreAuthTokenSourceTrustAuthenticationProvider( + ddiSecurityConfiguration.getRp().getTrustedIPs())); } } diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/url/PropertyHostnameResolverAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/url/PropertyHostnameResolverAutoConfiguration.java index eb925e281..0bd4a8240 100644 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/url/PropertyHostnameResolverAutoConfiguration.java +++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/url/PropertyHostnameResolverAutoConfiguration.java @@ -11,6 +11,7 @@ package org.eclipse.hawkbit.autoconfigure.url; import java.net.MalformedURLException; import java.net.URL; +import org.eclipse.hawkbit.HawkbitServerProperties; import org.eclipse.hawkbit.api.HostnameResolver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -27,11 +28,11 @@ import com.google.common.base.Throwables; * */ @Configuration -@EnableConfigurationProperties(ServerProperties.class) +@EnableConfigurationProperties(HawkbitServerProperties.class) public class PropertyHostnameResolverAutoConfiguration { @Autowired - private ServerProperties serverProperties; + private HawkbitServerProperties serverProperties; /** * @return the default autoconfigure hostname resolver implementation which diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/url/ServerProperties.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/url/ServerProperties.java deleted file mode 100644 index 24c9dfb0e..000000000 --- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/url/ServerProperties.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.eclipse.hawkbit.autoconfigure.url; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Properties for the server e.g. the server's URL which must be configured. - * - * - */ -@ConfigurationProperties("hawkbit.server") -public class ServerProperties { - - private String url = "http://localhost:8080"; - - public String getUrl() { - return url; - } - - public void setUrl(final String url) { - this.url = url; - } -} diff --git a/hawkbit-autoconfigure/src/main/resources/META-INF/spring.factories b/hawkbit-autoconfigure/src/main/resources/META-INF/spring.factories index 5d8d005d9..335054585 100644 --- a/hawkbit-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/hawkbit-autoconfigure/src/main/resources/META-INF/spring.factories @@ -11,5 +11,4 @@ org.eclipse.hawkbit.autoconfigure.eventbus.EventBusAutoConfiguration,\ org.eclipse.hawkbit.autoconfigure.scheduling.AsyncConfigurerAutoConfiguration,\ org.eclipse.hawkbit.autoconfigure.cache.RedisAutoConfiguration,\ org.eclipse.hawkbit.autoconfigure.scheduling.ExecutorAutoConfiguration,\ -org.eclipse.hawkbit.autoconfigure.conf.ControllerPollAutoConfiguration,\ org.eclipse.hawkbit.autoconfigure.amqp.AmqpAutoConfiguration diff --git a/hawkbit-autoconfigure/src/main/resources/hawkbitdefaults.properties b/hawkbit-autoconfigure/src/main/resources/hawkbitdefaults.properties index 69c9a47d2..198fce255 100644 --- a/hawkbit-autoconfigure/src/main/resources/hawkbitdefaults.properties +++ b/hawkbit-autoconfigure/src/main/resources/hawkbitdefaults.properties @@ -7,12 +7,6 @@ # http://www.eclipse.org/legal/epl-v10.html # -# Tomcat / Server -server.tomcat.compression=on -spring.http.gzip.mime-types=text/html,text/xml,text/plain,application/json,application/javascript,text/css,application/x-javascript,text/javascript,application/vnd.ms-fontobject,application/x-font-opentype,application/x-font-truetype,application/x-font-ttf,application/xml,font/eot,font/opentype,font/otf,image/svg+xml,image/vnd.microsoft.icon -server.tomcat.compressable-mime-types=${spring.http.gzip.mime-types} -spring.http.gzip.min-gzip-size=256 - # JPA / Datasource spring.jpa.eclipselink.eclipselink.weaving=false spring.jpa.database=H2 @@ -21,7 +15,6 @@ spring.datasource.driverClassName=org.h2.Driver # MongoDB for artifact-repository spring.data.mongodb.uri=mongodb://localhost/artifactrepo -spring.data.mongo.repositories.enabled=true # Flyway DDL flyway.enabled=true @@ -29,29 +22,26 @@ flyway.initOnMigrate=true flyway.sqlMigrationSuffix=${spring.jpa.database}.sql # Vaadin Servlet -vaadin.static.servlet.params.resourceCacheTime=${spring.resources.cache-period} -vaadin.static.servlet.params.productionMode=true -vaadin.servlet.params.productionMode=true -vaadin.servlet.params.resourceCacheTime=${spring.resources.cache-period} +vaadin.servlet.productionMode=true vaadin.servlet.urlMapping=/UI/* -vaadin.servlet.params.heartbeatInterval=60 -vaadin.servlet.params.closeIdleSessions=false - -# Spring MVC -spring.mvc.favicon.enabled=false - +vaadin.servlet.heartbeatInterval=60 +vaadin.servlet.closeIdleSessions=false # Defines the thread pool executor hawkbit.threadpool.corethreads=5 hawkbit.threadpool.maxthreads=20 hawkbit.threadpool.idletimeout=10000 -hawkbit.threadpool.queuesize=250 +hawkbit.threadpool.queuesize=20000 # Defines the polling time for the controllers in HH:MM:SS notation hawkbit.controller.pollingTime=00:05:00 hawkbit.controller.pollingOverdueTime=00:05:00 +hawkbit.controller.maxPollingTime=23:59:59 +hawkbit.controller.minPollingTime=00:00:30 +# Attention: if you want to use a maximumPollingTime greater 23:59:59 you have to update the DurationField in the configuration window -## Configuration for RabbitMQ integration + +# Configuration for RabbitMQ integration hawkbit.dmf.rabbitmq.deadLetterQueue=dmf_connector_deadletter hawkbit.dmf.rabbitmq.deadLetterExchange=dmf.connector.deadletter -hawkbit.dmf.rabbitmq.receiverQueue=dmf_receiver +hawkbit.dmf.rabbitmq.receiverQueue=dmf_receiver \ No newline at end of file diff --git a/hawkbit-cache-redis/pom.xml b/hawkbit-cache-redis/pom.xml index 99c8328b5..09567291b 100644 --- a/hawkbit-cache-redis/pom.xml +++ b/hawkbit-cache-redis/pom.xml @@ -37,6 +37,11 @@ redis.clients jedis + + org.springframework.boot + spring-boot-configuration-processor + true + diff --git a/hawkbit-cache-redis/src/main/java/org/eclipse/hawkbit/cache/RedisConfiguration.java b/hawkbit-cache-redis/src/main/java/org/eclipse/hawkbit/cache/RedisConfiguration.java index 2d1b99c98..edc183b17 100644 --- a/hawkbit-cache-redis/src/main/java/org/eclipse/hawkbit/cache/RedisConfiguration.java +++ b/hawkbit-cache-redis/src/main/java/org/eclipse/hawkbit/cache/RedisConfiguration.java @@ -26,8 +26,6 @@ import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer * The spring Redis configuration which is enabled by using the profile * {@code redis} to use a Redis server as cache. * - * - * */ @Configuration @EnableConfigurationProperties(RedisProperties.class) @@ -75,7 +73,7 @@ public class RedisConfiguration { */ @Bean public RedisTemplate redisTemplate() { - final RedisTemplate redisTemplate = new RedisTemplate(); + final RedisTemplate redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(jedisConnectionFactory()); redisTemplate.setKeySerializer(new JdkSerializationRedisSerializer()); redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer()); diff --git a/hawkbit-cache-redis/src/main/java/org/eclipse/hawkbit/cache/RedisProperties.java b/hawkbit-cache-redis/src/main/java/org/eclipse/hawkbit/cache/RedisProperties.java index c228cde4c..ab409bbf5 100644 --- a/hawkbit-cache-redis/src/main/java/org/eclipse/hawkbit/cache/RedisProperties.java +++ b/hawkbit-cache-redis/src/main/java/org/eclipse/hawkbit/cache/RedisProperties.java @@ -14,14 +14,18 @@ import org.springframework.boot.context.properties.ConfigurationProperties; * Bean which holds the necessary properties for configuring the Redis * connection. * - * - * - * */ @ConfigurationProperties("hawkbit.server.redis") public class RedisProperties { + /** + * Redis server hostname. + */ private String host; + + /** + * Redis server port. + */ private int port; /** diff --git a/hawkbit-cache-redis/src/main/java/org/eclipse/hawkbit/cache/eventbus/EventDistributor.java b/hawkbit-cache-redis/src/main/java/org/eclipse/hawkbit/cache/eventbus/EventDistributor.java index e52128cbc..8749340f0 100644 --- a/hawkbit-cache-redis/src/main/java/org/eclipse/hawkbit/cache/eventbus/EventDistributor.java +++ b/hawkbit-cache-redis/src/main/java/org/eclipse/hawkbit/cache/eventbus/EventDistributor.java @@ -95,7 +95,7 @@ public class EventDistributor { * wants to subscribe */ public Collection getTopics() { - final List topics = new ArrayList(); + final List topics = new ArrayList<>(); topics.add(new PatternTopic(SUB_DISTRIBUTION_CHANNEL)); return topics; } diff --git a/hawkbit-cache-redis/src/test/java/org/eclipse/hawkbit/cache/eventbus/EventDistributorTest.java b/hawkbit-cache-redis/src/test/java/org/eclipse/hawkbit/cache/eventbus/EventDistributorTest.java index e44889f60..c1ff54961 100644 --- a/hawkbit-cache-redis/src/test/java/org/eclipse/hawkbit/cache/eventbus/EventDistributorTest.java +++ b/hawkbit-cache-redis/src/test/java/org/eclipse/hawkbit/cache/eventbus/EventDistributorTest.java @@ -29,7 +29,13 @@ import org.springframework.hateoas.Identifiable; import com.google.common.eventbus.EventBus; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Unit Tests - Cluster Cache") +@Stories("EventDistributor Test") @RunWith(MockitoJUnitRunner.class) +// TODO: create description annotations public class EventDistributorTest { @Mock diff --git a/hawkbit-cache-redis/src/test/java/org/eclipse/hawkbit/cache/redis/RedisPropertiesTest.java b/hawkbit-cache-redis/src/test/java/org/eclipse/hawkbit/cache/redis/RedisPropertiesTest.java index 6aac1b03f..ab57dd541 100644 --- a/hawkbit-cache-redis/src/test/java/org/eclipse/hawkbit/cache/redis/RedisPropertiesTest.java +++ b/hawkbit-cache-redis/src/test/java/org/eclipse/hawkbit/cache/redis/RedisPropertiesTest.java @@ -13,6 +13,11 @@ import static org.fest.assertions.api.Assertions.assertThat; import org.eclipse.hawkbit.cache.RedisProperties; import org.junit.Test; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Unit Tests - Cluster Cache") +@Stories("Redis Properties Test") public class RedisPropertiesTest { @Test diff --git a/hawkbit-core/.gitignore b/hawkbit-core/.gitignore new file mode 100644 index 000000000..c71ea97ab --- /dev/null +++ b/hawkbit-core/.gitignore @@ -0,0 +1 @@ +/.apt_generated/ diff --git a/hawkbit-core/pom.xml b/hawkbit-core/pom.xml index e5d0f65fd..f9e140d40 100644 --- a/hawkbit-core/pom.xml +++ b/hawkbit-core/pom.xml @@ -32,7 +32,6 @@ com.google.guava guava - org.springframework.boot @@ -44,6 +43,11 @@ allure-junit-adaptor test + + org.springframework.boot + spring-boot-configuration-processor + true + \ No newline at end of file diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/ControllerPollProperties.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/ControllerPollProperties.java index 694d5c016..c923963d2 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/ControllerPollProperties.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/ControllerPollProperties.java @@ -8,35 +8,51 @@ */ package org.eclipse.hawkbit; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.EnvironmentAware; +import org.springframework.stereotype.Component; /** - * Defines the polling time for the controllers in HH:MM:SS notation. + * Defines global configuration for the controllers/clients on the provisioning + * targets/devices. + * + * + * Note: many of the controller related properties can be overridden on tenant + * level. As a result they are not defined here but in + * {@link TenantConfigurationKey} and injected using {@link EnvironmentAware}. * - * - * */ - +@Component @ConfigurationProperties(prefix = "hawkbit.controller") public class ControllerPollProperties { - private String pollingTime = "00:05:00"; - private String pollingOverdueTime = "00:05:00"; + /** + * Maximum polling time that can be configured by a tenant in HH:MM:SS + * notation. + */ + private String maxPollingTime = "23:59:00"; - public String getPollingTime() { - return pollingTime; + /** + * Minimum polling time that can be configured by a tenant in HH:MM:SS + * notation. + */ + private String minPollingTime = "00:00:30"; + + public String getMaxPollingTime() { + return maxPollingTime; } - public void setPollingTime(final String pollingTime) { - this.pollingTime = pollingTime; + public void setMaxPollingTime(final String maxPollingTime) { + this.maxPollingTime = maxPollingTime; } - public String getPollingOverdueTime() { - return pollingOverdueTime; + public String getMinPollingTime() { + return minPollingTime; } - public void setPollingOverdueTime(final String pollingOverdue) { - this.pollingOverdueTime = pollingOverdue; + public void setMinPollingTime(final String minPollingTime) { + this.minPollingTime = minPollingTime; } } diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/HawkbitServerProperties.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/HawkbitServerProperties.java new file mode 100644 index 000000000..878965102 --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/HawkbitServerProperties.java @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Properties for the server e.g. the server's URL which must be configured. + * + */ +@ConfigurationProperties("hawkbit.server") +public class HawkbitServerProperties { + /** + * Defines under which URI the update server can be reached. Used to + * calculate download URLs for DMF transmitted update actions. + */ + private String url = "http://localhost:8080"; + + private final Build build = new Build(); + + public Build getBuild() { + return build; + } + + /** + * Build information of the hawkBit instance. Influenced by maven. + * + */ + public static class Build { + /** + * Project artifact ID. + */ + private String artifact = ""; + + /** + * Project name. + */ + private String name = ""; + + /** + * Project description. + */ + private String description = ""; + + /** + * Project version. + */ + private String version = ""; + + public String getArtifact() { + return artifact; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getVersion() { + return version; + } + + public void setArtifact(final String artifact) { + this.artifact = artifact; + } + + public void setName(final String name) { + this.name = name; + } + + public void setDescription(final String description) { + this.description = description; + } + + public void setVersion(final String version) { + this.version = version; + } + + } + + public String getUrl() { + return url; + } + + public void setUrl(final String url) { + this.url = url; + } +} diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/cache/TenantAwareCacheManager.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/cache/TenantAwareCacheManager.java index 06d6e1719..435f1b2e1 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/cache/TenantAwareCacheManager.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/cache/TenantAwareCacheManager.java @@ -51,7 +51,12 @@ public class TenantAwareCacheManager implements TenancyCacheManager { @Override public Cache getCache(final String name) { - final String currentTenant = tenantAware.getCurrentTenant().toUpperCase(); + String currentTenant = tenantAware.getCurrentTenant(); + if (currentTenant == null) { + return null; + } + + currentTenant = currentTenant.toUpperCase(); if (currentTenant.contains(TENANT_CACHE_DELIMITER)) { return null; } @@ -60,7 +65,12 @@ public class TenantAwareCacheManager implements TenancyCacheManager { @Override public Collection getCacheNames() { - final String currentTenant = tenantAware.getCurrentTenant().toUpperCase(); + String currentTenant = tenantAware.getCurrentTenant(); + if (currentTenant == null) { + return null; + } + + currentTenant = currentTenant.toUpperCase(); if (currentTenant.contains(TENANT_CACHE_DELIMITER)) { return Collections.emptyList(); } diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/eventbus/event/AbstractEvent.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/eventbus/event/AbstractEvent.java new file mode 100644 index 000000000..498418169 --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/eventbus/event/AbstractEvent.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.eventbus.event; + +/** + * Abstract event definition class which holds the necessary revsion and tenant + * information which every event needs. + * + * @author Michael Hirsch + * @see AbstractDistributedEvent for events which should be distributed to other + * cluster nodes + */ +public class AbstractEvent implements Event { + + private final long revision; + private final String tenant; + + /** + * @param revision + * the revision number of the event + * @param tenant + * the tenant of the event + */ + protected AbstractEvent(final long revision, final String tenant) { + this.revision = revision; + this.tenant = tenant; + } + + @Override + public long getRevision() { + return revision; + } + + @Override + public String getTenant() { + return tenant; + } + +} diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/eventbus/event/CancelTargetAssignmentEvent.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/eventbus/event/CancelTargetAssignmentEvent.java index ad421b266..da93cc1a3 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/eventbus/event/CancelTargetAssignmentEvent.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/eventbus/event/CancelTargetAssignmentEvent.java @@ -17,7 +17,7 @@ import java.net.URI; * * */ -public class CancelTargetAssignmentEvent { +public class CancelTargetAssignmentEvent extends AbstractEvent { private final String controllerId; private final Long actionId; @@ -26,6 +26,10 @@ public class CancelTargetAssignmentEvent { /** * Creates a new {@link CancelTargetAssignmentEvent}. * + * @param revision + * the revision for this event + * @param tenant + * the tenant for this event * @param controllerId * the ID of the controller * @param actionId @@ -33,7 +37,9 @@ public class CancelTargetAssignmentEvent { * @param targetAdress * the targetAdress of the target */ - public CancelTargetAssignmentEvent(final String controllerId, final Long actionId, final URI targetAdress) { + public CancelTargetAssignmentEvent(final long revision, final String tenant, final String controllerId, + final Long actionId, final URI targetAdress) { + super(revision, tenant); this.controllerId = controllerId; this.actionId = actionId; this.targetAdress = targetAdress; diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java index 91559cc88..003b31cd7 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerError.java @@ -65,6 +65,7 @@ public enum SpServerError { */ SP_REST_SORT_PARAM_INVALID_DIRECTION("hawkbit.server.error.rest.param.invalidDirection", "The given sort parameter direction does not exist"), + /** * */ @@ -174,7 +175,23 @@ public enum SpServerError { * */ SP_REPO_ENTITY_READ_ONLY("hawkbit.server.error.entityreadonly", - "The given entity is read only and the change cannot be completed."); + "The given entity is read only and the change cannot be completed."), + + /** + * + */ + SP_CONFIGURATION_VALUE_INVALID("hawkbit.server.error.configValueInvalid", + "The given configuration value is invalid."), + /** + * + */ + SP_CONFIGURATION_KEY_INVALID("hawkbit.server.error.configKeyInvalid", "The given configuration key is invalid."), + + /** + * + */ + SP_ROLLOUT_ILLEGAL_STATE("hawkbit.server.error.rollout.illegalstate", + "The rollout is currently in the wrong state for the current operation"); private final String key; private final String message; diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerRtException.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerRtException.java index a605f7745..ba691bb5f 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerRtException.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/exception/SpServerRtException.java @@ -74,6 +74,9 @@ public abstract class SpServerRtException extends RuntimeException { this.error = error; } + /** + * @return the SpServerError which is wrapped by this exception + */ public SpServerError getError() { return error; } diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/ActionStatusFields.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/ActionStatusFields.java index 22fa42474..ef8bf3c98 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/ActionStatusFields.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/ActionStatusFields.java @@ -20,7 +20,12 @@ public enum ActionStatusFields implements FieldNameProvider { /** * The id field. */ - ID("id"); + ID("id"), + + /** + * The reportedAt field. + */ + REPORTEDAT("createdAt"); private final String fieldName; diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/FieldNameProvider.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/FieldNameProvider.java index eef36f552..fb9166529 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/FieldNameProvider.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/FieldNameProvider.java @@ -20,7 +20,7 @@ import java.util.Map; */ public interface FieldNameProvider { /** - * Seperator for the sub attributes + * Separator for the sub attributes */ public static final String SUB_ATTRIBUTE_SEPERATOR = "."; diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/RolloutFields.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/RolloutFields.java new file mode 100644 index 000000000..2c7f7b971 --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/RolloutFields.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository; + +/** + * Describing the fields of the Rollout model which can be used in the REST API + * e.g. for sorting etc. + * + */ +public enum RolloutFields implements FieldNameProvider { + /** + * The name field. + */ + NAME("name"), + /** + * The description field. + */ + DESCRIPTION("description"), + /** + * The id field. + */ + ID("id"); + + private final String fieldName; + + private RolloutFields(final String fieldName) { + this.fieldName = fieldName; + } + + @Override + public String getFieldName() { + return fieldName; + } +} diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupFields.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupFields.java new file mode 100644 index 000000000..2c8ebf8ac --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupFields.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository; + +/** + * Describing the fields of the RolloutGroup model which can be used in the REST + * API e.g. for sorting etc. + * + */ +public enum RolloutGroupFields implements FieldNameProvider { + /** + * The name field. + */ + NAME("name"), + /** + * The description field. + */ + DESCRIPTION("description"), + /** + * The id field. + */ + ID("id"); + + private final String fieldName; + + private RolloutGroupFields(final String fieldName) { + this.fieldName = fieldName; + } + + @Override + public String getFieldName() { + return fieldName; + } +} diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleTypeFields.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleTypeFields.java index beb46a9d7..68e4f4427 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleTypeFields.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/SoftwareModuleTypeFields.java @@ -36,7 +36,7 @@ public enum SoftwareModuleTypeFields implements FieldNameProvider { /** * The max ds assignments field. */ - MAX("maxAssignments"); + MAXASSIGNMENTS("maxAssignments"); private final String fieldName; diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFields.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFields.java index 1b4daaf66..0a696fdb6 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFields.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFields.java @@ -51,7 +51,16 @@ public enum TargetFields implements FieldNameProvider { */ ATTRIBUTE("targetInfo.controllerAttributes", true), + /** + * distribution sets which is assigned to the target. + */ ASSIGNEDDS("assignedDistributionSet", "name", "version"), + + /** + * distribution sets which is installed on the target. + */ + INSTALLEDDS("targetInfo.installedDistributionSet", "name", "version"), + /** * The tags field. */ diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/DurationHelper.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/DurationHelper.java new file mode 100644 index 000000000..8e7c46223 --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/DurationHelper.java @@ -0,0 +1,115 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.tenancy.configuration; + +import java.time.Duration; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.temporal.TemporalAccessor; + +/** + * This class is a helper for converting a duration into a string and for the + * other way. The string is in the format expected in configuration and database + * {@link #DURATION_FORMAT}. + * + */ +public final class DurationHelper { + + /** + * Duration validation utility class. Checks if the requested duration is in + * the defined min/max range. + * + */ + public static class DurationRangeValidator { + final Duration min; + final Duration max; + + private DurationRangeValidator(final Duration min, final Duration max) { + this.min = min; + this.max = max; + } + + public boolean isWithinRange(final Duration duration) { + return duration.compareTo(min) > 0 && duration.compareTo(max) < 0; + } + } + + /** + * Format of the String expected in configuration file and in the database. + */ + public static final String DURATION_FORMAT = "HH:mm:ss"; + + private DurationHelper() { + // utility class + } + + /** + * Creates a {@link DurationRangeValidator}. + * + * @param min + * imum of range. + * @param max + * imum of range. + * @return {@link DurationRangeValidator} range. + */ + public static DurationRangeValidator durationRangeValidator(final Duration min, final Duration max) { + return new DurationRangeValidator(min, max); + } + + /** + * Converts a Duration into a formatted String + * + * @param duration + * duration, which will be converted into a formatted String + * @return String in the duration format, specified at + * {@link #DURATION_FORMAT} + */ + public static String durationToFormattedString(final Duration duration) { + if (duration == null) { + return null; + } + + return LocalTime.ofNanoOfDay(duration.toNanos()).format(DateTimeFormatter.ofPattern(DURATION_FORMAT)); + } + + /** + * Converts a formatted String into a Duration object. + * + * @param formattedDuration + * String in {@link #DURATION_FORMAT} + * @return duration + * @throws DateTimeParseException + * when String is in wrong format + */ + public static Duration formattedStringToDuration(final String formattedDuration) { + if (formattedDuration == null) { + return null; + } + + final TemporalAccessor ta = DateTimeFormatter.ofPattern(DURATION_FORMAT).parse(formattedDuration.trim()); + return Duration.between(LocalTime.MIDNIGHT, LocalTime.from(ta)); + } + + /** + * converts values of time constants to a Duration object.. + * + * @param hours + * count of hours + * @param minutes + * count of minutes + * @param seconds + * count of seconds + * @return duration + */ + public static Duration getDurationByTimeValues(final long hours, final long minutes, final long seconds) { + return Duration.ofHours(hours).plusMinutes(minutes).plusSeconds(seconds); + } + +} diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/InvalidTenantConfigurationKeyException.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/InvalidTenantConfigurationKeyException.java new file mode 100644 index 000000000..dcc42065c --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/InvalidTenantConfigurationKeyException.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.tenancy.configuration; + +import org.eclipse.hawkbit.exception.SpServerError; +import org.eclipse.hawkbit.exception.SpServerRtException; + +/** + * The {@link #InvalidTenantConfigurationKeyException} is thrown when an invalid + * configuration key is used. + * + */ +public class InvalidTenantConfigurationKeyException extends SpServerRtException { + + private static final long serialVersionUID = 1L; + private static final SpServerError THIS_ERROR = SpServerError.SP_CONFIGURATION_KEY_INVALID; + + /** + * Default constructor. + */ + public InvalidTenantConfigurationKeyException() { + super(THIS_ERROR); + } + + /** + * Parameterized constructor. + * + * @param cause + * of the exception + */ + public InvalidTenantConfigurationKeyException(final Throwable cause) { + super(THIS_ERROR, cause); + } + + /** + * Parameterized constructor. + * + * @param message + * of the exception + * @param cause + * of the exception + */ + public InvalidTenantConfigurationKeyException(final String message, final Throwable cause) { + super(message, THIS_ERROR, cause); + } + + /** + * Parameterized constructor. + * + * @param message + * of the exception + */ + public InvalidTenantConfigurationKeyException(final String message) { + super(message, THIS_ERROR); + } + +} diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/TenantConfigurationKey.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/TenantConfigurationKey.java index 477cd654e..c83f24dc2 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/TenantConfigurationKey.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/TenantConfigurationKey.java @@ -8,12 +8,21 @@ */ package org.eclipse.hawkbit.tenancy.configuration; +import java.util.Arrays; +import java.util.Optional; + +import org.eclipse.hawkbit.ControllerPollProperties; +import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator; +import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationPollingDurationValidator; +import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationStringValidator; +import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationValidator; +import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationValidatorException; +import org.springframework.context.ApplicationContext; + /** * An enum which defines the tenant specific configurations which can be - * configured for each tenant seperately. - * - * - * + * configured for each tenant separately. The non overridable properties are + * configured in {@link ControllerPollProperties} instead. * */ public enum TenantConfigurationKey { @@ -21,51 +30,68 @@ public enum TenantConfigurationKey { /** * boolean value {@code true} {@code false}. */ - AUTHENTICATION_MODE_HEADER_ENABLED("authentication.header.enabled", - "hawkbit.server.controller.security.authentication.header.enabled", Boolean.FALSE.toString()), + AUTHENTICATION_MODE_HEADER_ENABLED("authentication.header.enabled", "hawkbit.server.ddi.security.authentication.header.enabled", Boolean.class, Boolean.FALSE.toString(), TenantConfigurationBooleanValidator.class), /** * */ - AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME("authentication.header.authority", - "hawkbit.server.controller.security.authentication.header.authority", Boolean.FALSE.toString()), + AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME("authentication.header.authority", "hawkbit.server.ddi.security.authentication.header.authority", String.class, Boolean.FALSE.toString(), TenantConfigurationStringValidator.class), /** * boolean value {@code true} {@code false}. */ - AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED("authentication.targettoken.enabled", - "hawkbit.server.controller.security.authentication.targettoken.enabled", Boolean.FALSE.toString()), + AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED("authentication.targettoken.enabled", "hawkbit.server.ddi.security.authentication.targettoken.enabled", Boolean.class, Boolean.FALSE.toString(), TenantConfigurationBooleanValidator.class), /** * boolean value {@code true} {@code false}. */ - AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED("authentication.gatewaytoken.enabled", - "hawkbit.server.controller.security.authentication.gatewaytoken.enabled", Boolean.FALSE.toString()), + AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED("authentication.gatewaytoken.enabled", "hawkbit.server.ddi.security.authentication.gatewaytoken.enabled", Boolean.class, Boolean.FALSE.toString(), TenantConfigurationBooleanValidator.class), /** * string value which holds the name of the security token key. */ - AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_NAME("authentication.gatewaytoken.name", - "hawkbit.server.controller.security.authentication.gatewaytoken.name", null), + AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_NAME("authentication.gatewaytoken.name", "hawkbit.server.ddi.security.authentication.gatewaytoken.name", String.class, null, TenantConfigurationStringValidator.class), /** * string value which holds the actual security-key of the gateway security * token. */ - AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY("authentication.gatewaytoken.key", - "hawkbit.server.controller.security.authentication.gatewaytoken.key", null); + AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY("authentication.gatewaytoken.key", "hawkbit.server.ddi.security.authentication.gatewaytoken.key", String.class, null, TenantConfigurationStringValidator.class), + + /** + * string value which holds the polling time interval in the format HH:mm:ss + */ + POLLING_TIME_INTERVAL("pollingOverdueTime", "hawkbit.controller.pollingOverdueTime", String.class, null, TenantConfigurationPollingDurationValidator.class), + + /** + * string value which holds the polling time interval in the format HH:mm:ss + */ + POLLING_OVERDUE_TIME_INTERVAL("pollingTime", "hawkbit.controller.pollingTime", String.class, null, TenantConfigurationPollingDurationValidator.class); private final String keyName; private final String defaultKeyName; + private final Class dataType; private final String defaultValue; + private final Class validator; /** + * * @param key * the property key name - * @param allowedValues + * @param defaultKeyName * the allowed values for this specific key + * @param dataType + * the class of the property + * @param defaultValue + * value which should be returned, in case there is no value in + * the database + * @param validator + * Validator which validates, that property is of correct format */ - private TenantConfigurationKey(final String key, final String defaultKeyName, final String defaultValue) { + private TenantConfigurationKey(final String key, final String defaultKeyName, final Class dataType, + final String defaultValue, final Class validator) { this.keyName = key; + this.dataType = dataType; this.defaultKeyName = defaultKeyName; this.defaultValue = defaultValue; + this.validator = validator; } /** @@ -88,4 +114,49 @@ public enum TenantConfigurationKey { public String getDefaultValue() { return defaultValue; } + + /** + * + * @return the data type of the tenant configuration value. (e.g. + * Integer.class, String.class) + */ + public Class getDataType() { + return dataType; + } + + /** + * validates if a object matches the allowed data format of the + * corresponding key + * + * @param context + * application context + * @param value + * which will be validated + * @throws TenantConfigurationValidatorException + * is thrown, when object is invalid + */ + public void validate(final ApplicationContext context, final Object value) { + final TenantConfigurationValidator createdBean = context.getAutowireCapableBeanFactory().createBean(validator); + try { + createdBean.validate(value); + } finally { + context.getAutowireCapableBeanFactory().destroyBean(createdBean); + } + } + + /** + * @param keyName + * name of the TenantConfigurationKey + * @return the TenantConfigurationKey with the name keyName + */ + public static TenantConfigurationKey fromKeyName(final String keyName) { + + final Optional optKey = Arrays.stream(TenantConfigurationKey.values()) + .filter(conf -> conf.getKeyName().equals(keyName)).findFirst(); + + if (optKey.isPresent()) { + return optKey.get(); + } + throw new InvalidTenantConfigurationKeyException("The given configuration key name does not exist."); + } } diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationBooleanValidator.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationBooleanValidator.java new file mode 100644 index 000000000..8836d8578 --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationBooleanValidator.java @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.tenancy.configuration.validator; + +/** + * specific tenant configuration validator, which validates that the given value + * is a booleans. + */ +public class TenantConfigurationBooleanValidator implements TenantConfigurationValidator { + + @Override + public Class validateToClass() { + return Boolean.class; + } + +} diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationPollingDurationValidator.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationPollingDurationValidator.java new file mode 100644 index 000000000..11c9666e8 --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationPollingDurationValidator.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.tenancy.configuration.validator; + +import java.time.Duration; +import java.time.format.DateTimeParseException; + +import org.eclipse.hawkbit.ControllerPollProperties; +import org.eclipse.hawkbit.tenancy.configuration.DurationHelper; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * This class is used to validate, that the property is a String and that it is + * in the correct duration format. + * + */ +public class TenantConfigurationPollingDurationValidator implements TenantConfigurationValidator { + + private final Duration minDuration; + + private final Duration maxDuration; + + /** + * Constructor. + * + * @param properties + * property accessor for poll configuration + */ + @Autowired + public TenantConfigurationPollingDurationValidator(final ControllerPollProperties properties) { + minDuration = DurationHelper.formattedStringToDuration(properties.getMinPollingTime()); + maxDuration = DurationHelper.formattedStringToDuration(properties.getMaxPollingTime()); + } + + @Override + public void validate(final Object tenantConfigurationObject) { + TenantConfigurationValidator.super.validate(tenantConfigurationObject); + final String tenantConfigurationString = (String) tenantConfigurationObject; + + final Duration tenantConfigurationValue; + try { + tenantConfigurationValue = DurationHelper.formattedStringToDuration(tenantConfigurationString); + } catch (final DateTimeParseException ex) { + throw new TenantConfigurationValidatorException( + String.format("The given configuration value is expected as a string in the format %s.", + DurationHelper.DURATION_FORMAT)); + } + + if (!DurationHelper.durationRangeValidator(minDuration, maxDuration).isWithinRange(tenantConfigurationValue)) { + throw new TenantConfigurationValidatorException( + String.format("The given configuration value is not in the allowed range from %s to %s.", + DurationHelper.durationToFormattedString(minDuration), + DurationHelper.durationToFormattedString(maxDuration))); + } + } + + @Override + public Class validateToClass() { + return String.class; + } +} diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationStringValidator.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationStringValidator.java new file mode 100644 index 000000000..fc427f4ac --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationStringValidator.java @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.eclipse.hawkbit.tenancy.configuration.validator; + +/** + * specific tenant configuration validator, which validates Strings. + */ +public class TenantConfigurationStringValidator implements TenantConfigurationValidator { + + @Override + public Class validateToClass() { + return String.class; + } +} diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationValidator.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationValidator.java new file mode 100644 index 000000000..dccf515e6 --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationValidator.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.tenancy.configuration.validator; + +/** + * base interface for clases which can validate tenant configuration values. + * + */ +public interface TenantConfigurationValidator { + + /** + * validates the tenant configuration value + * + * @param tenantConfigurationValue + * value which will be validated. + * @throws TenantConfigurationValidatorException + * is thrown, when parameter is invalid. + */ + default void validate(final Object tenantConfigurationValue) throws TenantConfigurationValidatorException { + if (tenantConfigurationValue != null + && validateToClass().isAssignableFrom(tenantConfigurationValue.getClass())) { + return; + } + throw new TenantConfigurationValidatorException( + "The given configuration value is expected as a " + validateToClass().getSimpleName()); + } + + /** + * Return the generic class to check the object + * + * @return the class to check the value + */ + default Class validateToClass() { + return Object.class; + } +} diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationValidatorException.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationValidatorException.java new file mode 100644 index 000000000..dffc13dcb --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/configuration/validator/TenantConfigurationValidatorException.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.tenancy.configuration.validator; + +import org.eclipse.hawkbit.exception.SpServerError; +import org.eclipse.hawkbit.exception.SpServerRtException; + +/** + * Exception which is thrown, when the validation of the configuration value has + * not been successful. + * + */ +public class TenantConfigurationValidatorException extends SpServerRtException { + + private static final long serialVersionUID = 1L; + private static final SpServerError THIS_ERROR = SpServerError.SP_CONFIGURATION_VALUE_INVALID; + + /** + * Default constructor. + */ + public TenantConfigurationValidatorException() { + super(THIS_ERROR); + } + + /** + * Parameterized constructor. + * + * @param cause + * of the exception + */ + public TenantConfigurationValidatorException(final Throwable cause) { + super(THIS_ERROR, cause); + } + + /** + * Parameterized constructor. + * + * @param message + * of the exception + * @param cause + * of the exception + */ + public TenantConfigurationValidatorException(final String message, final Throwable cause) { + super(message, THIS_ERROR, cause); + } + + /** + * Parameterized constructor. + * + * @param message + * of the exception + */ + public TenantConfigurationValidatorException(final String message) { + super(message, THIS_ERROR); + } + +} diff --git a/hawkbit-core/src/test/java/org/eclipse/hawkbit/eventbus/EventBusSubscriberProcessorTest.java b/hawkbit-core/src/test/java/org/eclipse/hawkbit/eventbus/EventBusSubscriberProcessorTest.java index ac02aaad1..548bf7e7e 100644 --- a/hawkbit-core/src/test/java/org/eclipse/hawkbit/eventbus/EventBusSubscriberProcessorTest.java +++ b/hawkbit-core/src/test/java/org/eclipse/hawkbit/eventbus/EventBusSubscriberProcessorTest.java @@ -21,7 +21,13 @@ import org.mockito.runners.MockitoJUnitRunner; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Unit Tests - Cluster Event Bus") +@Stories("EventBus Subscriber Processor Test") @RunWith(MockitoJUnitRunner.class) +// TODO: create description annotations public class EventBusSubscriberProcessorTest { @Mock diff --git a/hawkbit-dmf-amqp/pom.xml b/hawkbit-dmf-amqp/pom.xml index a7dfc5b42..2fded8559 100644 --- a/hawkbit-dmf-amqp/pom.xml +++ b/hawkbit-dmf-amqp/pom.xml @@ -60,6 +60,11 @@ org.slf4j slf4j-api + + org.springframework.boot + spring-boot-configuration-processor + true + diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpConfiguration.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpConfiguration.java index 988a68ada..acbcb453b 100644 --- a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpConfiguration.java +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpConfiguration.java @@ -21,8 +21,8 @@ import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; -import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -30,8 +30,6 @@ import org.springframework.context.annotation.Bean; * The spring AMQP configuration which is enabled by using the profile * {@code amqp} to use a AMQP for communication with SP enabled devices. * - * - * */ @EnableConfigurationProperties(AmqpProperties.class) public class AmqpConfiguration { @@ -42,19 +40,16 @@ public class AmqpConfiguration { @Autowired private ConnectionFactory connectionFactory; - @Autowired - private RabbitTemplate rabbitTemplate; - /** * Method to set the Jackson2JsonMessageConverter. * * @return the Jackson2JsonMessageConverter */ @Bean - public MessageConverter jsonMessageConverter() { - final Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter(); - rabbitTemplate.setMessageConverter(jackson2JsonMessageConverter); - return jackson2JsonMessageConverter; + public RabbitTemplate rabbitTemplate() { + final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); + rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter()); + return rabbitTemplate; } /** @@ -121,11 +116,22 @@ public class AmqpConfiguration { /** * Create amqp handler service bean. * - * @return + * @return handler service bean */ @Bean public AmqpMessageHandlerService amqpMessageHandlerService() { - return new AmqpMessageHandlerService(); + return new AmqpMessageHandlerService(rabbitTemplate()); + } + + /** + * Create default amqp sender service bean. + * + * @return the default amqp sender service bean + */ + @Bean + @ConditionalOnMissingBean + public AmqpSenderService amqpSenderServiceBean() { + return new DefaultAmqpSenderService(rabbitTemplate()); } /** diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthentfication.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthentfication.java index dd36ef1fd..8f19d9f02 100644 --- a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthentfication.java +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthentfication.java @@ -16,14 +16,15 @@ import javax.annotation.PostConstruct; import org.eclipse.hawkbit.dmf.json.model.TenantSecruityToken; import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails; import org.eclipse.hawkbit.repository.ControllerManagement; -import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.security.CoapAnonymousPreAuthenticatedFilter; import org.eclipse.hawkbit.security.ControllerPreAuthenticateSecurityTokenFilter; import org.eclipse.hawkbit.security.ControllerPreAuthenticatedGatewaySecurityTokenFilter; import org.eclipse.hawkbit.security.ControllerPreAuthenticatedSecurityHeaderFilter; +import org.eclipse.hawkbit.security.DdiSecurityProperties; import org.eclipse.hawkbit.security.PreAuthTokenSourceTrustAuthenticationProvider; import org.eclipse.hawkbit.security.PreAuthenficationFilter; -import org.eclipse.hawkbit.security.SecurityProperties; +import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.tenancy.TenantAware; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,13 +50,16 @@ public class AmqpControllerAuthentfication { private ControllerManagement controllerManagement; @Autowired - private SystemManagement systemManagement; + private TenantConfigurationManagement tenantConfigurationManagement; @Autowired private TenantAware tenantAware; @Autowired - private SecurityProperties secruityProperties; + private DdiSecurityProperties ddiSecruityProperties; + + @Autowired + private SystemSecurityContext systemSecurityContext; /** * Constructor. @@ -74,16 +78,16 @@ public class AmqpControllerAuthentfication { private void addFilter() { final ControllerPreAuthenticatedGatewaySecurityTokenFilter gatewaySecurityTokenFilter = new ControllerPreAuthenticatedGatewaySecurityTokenFilter( - systemManagement, tenantAware); + tenantConfigurationManagement, tenantAware, systemSecurityContext); filterChain.add(gatewaySecurityTokenFilter); final ControllerPreAuthenticatedSecurityHeaderFilter securityHeaderFilter = new ControllerPreAuthenticatedSecurityHeaderFilter( - secruityProperties.getRpCnHeader(), secruityProperties.getRpSslIssuerHashHeader(), systemManagement, - tenantAware); + ddiSecruityProperties.getRp().getCnHeader(), ddiSecruityProperties.getRp().getSslIssuerHashHeader(), + tenantConfigurationManagement, tenantAware, systemSecurityContext); filterChain.add(securityHeaderFilter); final ControllerPreAuthenticateSecurityTokenFilter securityTokenFilter = new ControllerPreAuthenticateSecurityTokenFilter( - systemManagement, controllerManagement, tenantAware); + tenantConfigurationManagement, controllerManagement, tenantAware, systemSecurityContext); filterChain.add(securityTokenFilter); filterChain.add(new CoapAnonymousPreAuthenticatedFilter()); @@ -127,26 +131,26 @@ public class AmqpControllerAuthentfication { LOGGER.debug("preAuthenticatedPrincipal = {} trying to authenticate", principal); - final PreAuthenticatedAuthenticationToken authRequest = new PreAuthenticatedAuthenticationToken(principal, - credentials); - - return authRequest; + return new PreAuthenticatedAuthenticationToken(principal, credentials); } public void setControllerManagement(final ControllerManagement controllerManagement) { this.controllerManagement = controllerManagement; } - public void setSecruityProperties(final SecurityProperties secruityProperties) { - this.secruityProperties = secruityProperties; + public void setSecruityProperties(final DdiSecurityProperties secruityProperties) { + this.ddiSecruityProperties = secruityProperties; } - public void setSystemManagement(final SystemManagement systemManagement) { - this.systemManagement = systemManagement; + public void setTenantConfigurationManagement(final TenantConfigurationManagement tenantConfigurationManagement) { + this.tenantConfigurationManagement = tenantConfigurationManagement; } public void setTenantAware(final TenantAware tenantAware) { this.tenantAware = tenantAware; } + void setSystemSecurityContext(final SystemSecurityContext systemSecurityContext) { + this.systemSecurityContext = systemSecurityContext; + } } diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java index 7469e6768..b9e6fe9da 100644 --- a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java @@ -25,35 +25,43 @@ import org.eclipse.hawkbit.eventbus.EventSubscriber; import org.eclipse.hawkbit.eventbus.event.CancelTargetAssignmentEvent; import org.eclipse.hawkbit.eventbus.event.TargetAssignDistributionSetEvent; import org.eclipse.hawkbit.repository.model.LocalArtifact; -import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.util.ArtifactUrlHandler; import org.eclipse.hawkbit.util.IpUtil; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessageProperties; import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.support.converter.AbstractJavaTypeMapper; import org.springframework.beans.factory.annotation.Autowired; import com.google.common.eventbus.Subscribe; /** - * {@link AmqpMessageDispatcherService} handles all outgoing AMQP messages. - * - * + * {@link AmqpMessageDispatcherService} create all outgoing AMQP messages and + * delegate the messages to a {@link AmqpSenderService}. + * + * Additionally the dispatcher listener/subscribe for some target events e.g. + * assignment. * */ @EventSubscriber -public class AmqpMessageDispatcherService { - - @Autowired - private RabbitTemplate rabbitTemplate; - - @Autowired - private TenantAware tenantAware; +public class AmqpMessageDispatcherService extends BaseAmqpService { @Autowired private ArtifactUrlHandler artifactUrlHandler; + @Autowired + private AmqpSenderService amqpSenderService; + + /** + * Constructor. + * + * @param messageConverter + * message converter + */ + @Autowired + public AmqpMessageDispatcherService(final RabbitTemplate rabbitTemplate) { + super(rabbitTemplate); + } + /** * Method to send a message to a RabbitMQ Exchange after the Distribution * set has been assign to a Target. @@ -79,9 +87,10 @@ public class AmqpMessageDispatcherService { downloadAndUpdateRequest.addSoftwareModule(amqpSoftwareModule); } - final Message message = rabbitTemplate.getMessageConverter().toMessage(downloadAndUpdateRequest, - createConnectorMessageProperties(controllerId, EventTopic.DOWNLOAD_AND_INSTALL)); - sendMessage(targetAdress.getHost(), message); + final Message message = getMessageConverter().toMessage(downloadAndUpdateRequest, + createConnectorMessageProperties(targetAssignDistributionSetEvent.getTenant(), controllerId, + EventTopic.DOWNLOAD_AND_INSTALL)); + amqpSenderService.sendMessage(message, targetAdress); } /** @@ -96,32 +105,19 @@ public class AmqpMessageDispatcherService { final CancelTargetAssignmentEvent cancelTargetAssignmentDistributionSetEvent) { final String controllerId = cancelTargetAssignmentDistributionSetEvent.getControllerId(); final Long actionId = cancelTargetAssignmentDistributionSetEvent.getActionId(); - final Message message = rabbitTemplate.getMessageConverter().toMessage(actionId, - createConnectorMessageProperties(controllerId, EventTopic.CANCEL_DOWNLOAD)); + final Message message = getMessageConverter().toMessage(actionId, createConnectorMessageProperties( + cancelTargetAssignmentDistributionSetEvent.getTenant(), controllerId, EventTopic.CANCEL_DOWNLOAD)); - sendMessage(cancelTargetAssignmentDistributionSetEvent.getTargetAdress().getHost(), message); + amqpSenderService.sendMessage(message, cancelTargetAssignmentDistributionSetEvent.getTargetAdress()); } - /** - * Send message to exchange. - * - * @param exchange - * the exchange - * @param message - * the message - */ - public void sendMessage(final String exchange, final Message message) { - message.getMessageProperties().getHeaders().remove(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME); - rabbitTemplate.setExchange(exchange); - rabbitTemplate.send(message); - } - - private MessageProperties createConnectorMessageProperties(final String controllerId, final EventTopic topic) { + private MessageProperties createConnectorMessageProperties(final String tenant, final String controllerId, + final EventTopic topic) { final MessageProperties messageProperties = createMessageProperties(); messageProperties.setHeader(MessageHeaderKey.TOPIC, topic); messageProperties.setHeader(MessageHeaderKey.THING_ID, controllerId); - messageProperties.setHeader(MessageHeaderKey.TENANT, tenantAware.getCurrentTenant()); + messageProperties.setHeader(MessageHeaderKey.TENANT, tenant); messageProperties.setHeader(MessageHeaderKey.TYPE, MessageType.EVENT); return messageProperties; } @@ -150,9 +146,8 @@ public class AmqpMessageDispatcherService { return Collections.emptyList(); } - final List convertedArtifacts = localArtifacts.stream() - .map(localArtifact -> convertArtifact(targetId, localArtifact)).collect(Collectors.toList()); - return convertedArtifacts; + return localArtifacts.stream().map(localArtifact -> convertArtifact(targetId, localArtifact)) + .collect(Collectors.toList()); } private Artifact convertArtifact(final String targetId, final LocalArtifact localArtifact) { @@ -170,15 +165,11 @@ public class AmqpMessageDispatcherService { return artifact; } - public void setTenantAware(final TenantAware tenantAware) { - this.tenantAware = tenantAware; - } - - public void setRabbitTemplate(final RabbitTemplate rabbitTemplate) { - this.rabbitTemplate = rabbitTemplate; - } - public void setArtifactUrlHandler(final ArtifactUrlHandler artifactUrlHandler) { this.artifactUrlHandler = artifactUrlHandler; } + + public void setAmqpSenderService(final AmqpSenderService amqpSenderService) { + this.amqpSenderService = amqpSenderService; + } } diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java index 640c0e270..cfd5485a6 100644 --- a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java @@ -12,7 +12,6 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.UUID; import org.apache.commons.lang3.StringUtils; @@ -50,8 +49,6 @@ import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessageProperties; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.rabbit.core.RabbitTemplate; -import org.springframework.amqp.support.converter.AbstractJavaTypeMapper; -import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.cache.Cache; @@ -72,19 +69,14 @@ import com.google.common.eventbus.EventBus; /** * - * {@link AmqpMessageHandlerService} handles all incoming AMQP messages. - * - * - * + * {@link AmqpMessageHandlerService} handles all incoming AMQP messages for the + * queue which is configure for the property hawkbit.dmf.rabbitmq.receiverQueue. * */ -public class AmqpMessageHandlerService { +public class AmqpMessageHandlerService extends BaseAmqpService { private static final Logger LOG = LoggerFactory.getLogger(AmqpMessageHandlerService.class); - @Autowired - private RabbitTemplate rabbitTemplate; - @Autowired private ControllerManagement controllerManagement; @@ -105,7 +97,23 @@ public class AmqpMessageHandlerService { private HostnameResolver hostnameResolver; /** - * /** Method to handle all incoming amqp messages. + * Constructor. + * + * @param defaultTemplate + * the configured amqp template. + */ + public AmqpMessageHandlerService(final RabbitTemplate defaultTemplate) { + super(defaultTemplate); + } + + @RabbitListener(queues = "${hawkbit.dmf.rabbitmq.receiverQueue}", containerFactory = "listenerContainerFactory") + private Message onMessage(final Message message, @Header(MessageHeaderKey.TYPE) final String type, + @Header(MessageHeaderKey.TENANT) final String tenant) { + return onMessage(message, type, tenant, getRabbitTemplate().getConnectionFactory().getVirtualHost()); + } + + /** + * Method to handle all incoming amqp messages. * * @param message * incoming message @@ -115,11 +123,11 @@ public class AmqpMessageHandlerService { * the contentType of the message * @param tenant * the contentType of the message + * @param virtualHost + * the virtual host * @return a message if no message is send back to sender */ - @RabbitListener(queues = "${hawkbit.dmf.rabbitmq.receiverQueue}", containerFactory = "listenerContainerFactory") - public Message onMessage(final Message message, @Header(MessageHeaderKey.TYPE) final String type, - @Header(MessageHeaderKey.TENANT) final String tenant) { + public Message onMessage(final Message message, final String type, final String tenant, final String virtualHost) { checkContentTypeJson(message); final SecurityContext oldContext = SecurityContextHolder.getContext(); try { @@ -127,7 +135,7 @@ public class AmqpMessageHandlerService { switch (messageType) { case THING_CREATED: setTenantSecurityContext(tenant); - registerTarget(message); + registerTarget(message, virtualHost); break; case EVENT: setTenantSecurityContext(tenant); @@ -196,7 +204,7 @@ public class AmqpMessageHandlerService { authentificationResponse.setMessage(errorMessage); } - return rabbitTemplate.getMessageConverter().toMessage(authentificationResponse, messageProperties); + return getMessageConverter().toMessage(authentificationResponse, messageProperties); } private static Artifact convertDbArtifact(final DbArtifact dbArtifact) { @@ -207,11 +215,6 @@ public class AmqpMessageHandlerService { return artifact; } - protected void logAndThrowMessageError(final Message message, final String error) { - LOG.error("Error \"{}\" reported by message {}", error, message.getMessageProperties().getMessageId()); - throw new IllegalArgumentException(error); - } - private static void setSecurityContext(final Authentication authentication) { final SecurityContextImpl securityContextImpl = new SecurityContextImpl(); securityContextImpl.setAuthentication(authentication); @@ -226,15 +229,6 @@ public class AmqpMessageHandlerService { setSecurityContext(authenticationToken); } - private String getStringHeaderKey(final Message message, final String key, final String errorMessageIfNull) { - final Map header = message.getMessageProperties().getHeaders(); - final Object value = header.get(key); - if (value == null) { - logAndThrowMessageError(message, errorMessageIfNull); - } - return value.toString(); - } - /** * Method to create a new target or to find the target if it already exists. * @@ -243,14 +237,15 @@ public class AmqpMessageHandlerService { * @param ip * the ip of the target/thing */ - private void registerTarget(final Message message) { + private void registerTarget(final Message message, final String virtualHost) { final String thingId = getStringHeaderKey(message, MessageHeaderKey.THING_ID, "ThingId is null"); final String replyTo = message.getMessageProperties().getReplyTo(); if (StringUtils.isEmpty(replyTo)) { logAndThrowMessageError(message, "No ReplyTo was set for the createThing Event."); } - final URI amqpUri = IpUtil.createAmqpUri(replyTo); + + final URI amqpUri = IpUtil.createAmqpUri(virtualHost, replyTo); final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotexist(thingId, amqpUri); LOG.debug("Target {} reported online state.", thingId); @@ -267,8 +262,8 @@ public class AmqpMessageHandlerService { final DistributionSet distributionSet = action.getDistributionSet(); final List softwareModuleList = controllerManagement .findSoftwareModulesByDistributionSet(distributionSet); - eventBus.post(new TargetAssignDistributionSetEvent(target.getControllerId(), action.getId(), softwareModuleList, - target.getTargetInfo().getAddress())); + eventBus.post(new TargetAssignDistributionSetEvent(target.getOptLockRevision(), target.getTenant(), + target.getControllerId(), action.getId(), softwareModuleList, target.getTargetInfo().getAddress())); } @@ -281,13 +276,11 @@ public class AmqpMessageHandlerService { * the topic of the event. */ private void handleIncomingEvent(final Message message, final EventTopic topic) { - switch (topic) { - case UPDATE_ACTION_STATUS: + if (EventTopic.UPDATE_ACTION_STATUS.equals(topic)) { updateActionStatus(message); return; - default: - logAndThrowMessageError(message, "Got event without appropriate topic."); } + logAndThrowMessageError(message, "Got event without appropriate topic."); } /** @@ -298,20 +291,7 @@ public class AmqpMessageHandlerService { */ private void updateActionStatus(final Message message) { final ActionUpdateStatus actionUpdateStatus = convertMessage(message, ActionUpdateStatus.class); - final Long actionId = actionUpdateStatus.getActionId(); - LOG.debug("Target notifies intermediate about action {} with status {}.", actionId, - actionUpdateStatus.getActionStatus().name()); - - if (actionId == null) { - logAndThrowMessageError(message, "Invalid message no action id"); - } - - final Action action = controllerManagement.findActionWithDetails(actionId); - - if (action == null) { - logAndThrowMessageError(message, - "Got intermediate notification about action " + actionId + " but action does not exist"); - } + final Action action = checkActionExist(message, actionUpdateStatus); final ActionStatus actionStatus = new ActionStatus(); final List messageText = actionUpdateStatus.getMessage(); @@ -349,19 +329,38 @@ public class AmqpMessageHandlerService { logAndThrowMessageError(message, "Status for action does not exisit."); } - Action addUpdateActionStatus; - - if (!actionStatus.getStatus().equals(Status.CANCELED)) { - addUpdateActionStatus = controllerManagement.addUpdateActionStatus(actionStatus, action); - } else { - addUpdateActionStatus = controllerManagement.addCancelActionStatus(actionStatus, action); - } + final Action addUpdateActionStatus = getUpdateActionStatus(action, actionStatus); if (!addUpdateActionStatus.isActive()) { lookIfUpdateAvailable(action.getTarget()); } } + private Action getUpdateActionStatus(final Action action, final ActionStatus actionStatus) { + if (actionStatus.getStatus().equals(Status.CANCELED)) { + return controllerManagement.addCancelActionStatus(actionStatus, action); + } + return controllerManagement.addUpdateActionStatus(actionStatus, action); + } + + private Action checkActionExist(final Message message, final ActionUpdateStatus actionUpdateStatus) { + final Long actionId = actionUpdateStatus.getActionId(); + LOG.debug("Target notifies intermediate about action {} with status {}.", actionId, + actionUpdateStatus.getActionStatus().name()); + + if (actionId == null) { + logAndThrowMessageError(message, "Invalid message no action id"); + } + + final Action action = controllerManagement.findActionWithDetails(actionId); + + if (action == null) { + logAndThrowMessageError(message, + "Got intermediate notification about action " + actionId + " but action does not exist"); + } + return action; + } + private void handleCancelRejected(final Message message, final Action action, final ActionStatus actionStatus) { if (action.isCancelingOrCanceled()) { @@ -372,37 +371,11 @@ public class AmqpMessageHandlerService { } else { logAndThrowMessageError(message, - "Cancel Recjected message is not allowed, if action is on state: " + action.getStatus()); + "Cancel recjected message is not allowed, if action is on state: " + action.getStatus()); } } - /** - * Is needed to convert a incoming message to is originally object type. - * - * @param message - * the message to convert. - * @param clazz - * the class of the originally object. - * @return - */ - @SuppressWarnings("unchecked") - private T convertMessage(final Message message, final Class clazz) { - message.getMessageProperties().getHeaders().put(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME, - clazz.getTypeName()); - return (T) rabbitTemplate.getMessageConverter().fromMessage(message); - } - - /** - * Is needed to verify if an incoming message has the content type json. - * - * @param message - * the to verify - * @param contentType - * the content type - * @return true if the content type has json, false it not. - */ - - private static void checkContentTypeJson(final Message message) { + private void checkContentTypeJson(final Message message) { final MessageProperties messageProperties = message.getMessageProperties(); if (messageProperties.getContentType() != null && messageProperties.getContentType().contains("json")) { return; @@ -418,14 +391,6 @@ public class AmqpMessageHandlerService { this.hostnameResolver = hostnameResolver; } - void setRabbitTemplate(final RabbitTemplate rabbitTemplate) { - this.rabbitTemplate = rabbitTemplate; - } - - MessageConverter getMessageConverter() { - return rabbitTemplate.getMessageConverter(); - } - void setAuthenticationManager(final AmqpControllerAuthentfication authenticationManager) { this.authenticationManager = authenticationManager; } diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpProperties.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpProperties.java index ecd2dc3d7..38c6d34b3 100644 --- a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpProperties.java +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpProperties.java @@ -15,16 +15,27 @@ import org.springframework.boot.context.properties.ConfigurationProperties; * Bean which holds the necessary properties for configuring the AMQP * connection. * - * - * - * */ @ConfigurationProperties("hawkbit.dmf.rabbitmq") public class AmqpProperties { - + /** + * DMF API dead letter queue. + */ private String deadLetterQueue = "dmf_connector_deadletter"; + + /** + * DMF API dead letter exchange. + */ private String deadLetterExchange = "dmf.connector.deadletter"; + + /** + * DMF API receiving queue. + */ private String receiverQueue = "dmf_receiver"; + + /** + * Missing queue fatal. + */ private boolean missingQueuesFatal = false; /** diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpSenderService.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpSenderService.java new file mode 100644 index 000000000..6cb3dd9be --- /dev/null +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpSenderService.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.amqp; + +import java.net.URI; + +import org.springframework.amqp.core.Message; + +/** + * Interface to send a amqp message. + */ +@FunctionalInterface +public interface AmqpSenderService { + + /** + * Send the given message to the given uri. The uri contains the (virtual) + * host and exchange e.g amqp://host/exchange. + * + * @param message + * the amqp message + * @param uri + * the reply to uri + */ + void sendMessage(Message message, URI uri); + + /** + * Extract the exchange from the uri. Default implementation removes the + * first /. + * + * @param amqpUri + * the amqp uri + * @return the exchange. + */ + default String extractExchange(final URI amqpUri) { + return amqpUri.getPath().substring(1); + } + +} diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/BaseAmqpService.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/BaseAmqpService.java new file mode 100644 index 000000000..8a054165b --- /dev/null +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/BaseAmqpService.java @@ -0,0 +1,115 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.amqp; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.support.converter.AbstractJavaTypeMapper; +import org.springframework.amqp.support.converter.MessageConverter; + +/** + * A base class which provide basis amqp staff. + */ +public class BaseAmqpService { + + private static final Logger LOGGER = LoggerFactory.getLogger(BaseAmqpService.class); + private final RabbitTemplate rabbitTemplate; + + /** + * Constructor. + * + * @param rabbitTemplate + * the rabbit template. + */ + public BaseAmqpService(final RabbitTemplate rabbitTemplate) { + this.rabbitTemplate = rabbitTemplate; + } + + /** + * Clean message properties before sending a message. + * + * @param message + * the message to cleaned up + */ + protected void cleanMessageHeaderProperties(final Message message) { + message.getMessageProperties().getHeaders().remove(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME); + } + + /** + * Is needed to convert a incoming message to is originally object type. + * + * @param message + * the message to convert. + * @param clazz + * the class of the originally object. + * @return the converted object + */ + @SuppressWarnings("unchecked") + public T convertMessage(final Message message, final Class clazz) { + if (message == null || message.getBody() == null) { + return null; + } + message.getMessageProperties().getHeaders().put(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME, + clazz.getName()); + return (T) rabbitTemplate.getMessageConverter().fromMessage(message); + } + + /** + * Is needed to convert a incoming message to is originally list object + * type. + * + * @param message + * the message to convert. + * @param clazz + * the class of the list content. + * @return the list of converted objects + */ + @SuppressWarnings("unchecked") + public List convertMessageList(final Message message, final Class clazz) { + if (message == null || message.getBody() == null) { + return Collections.emptyList(); + } + message.getMessageProperties().getHeaders().put(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME, + ArrayList.class.getName()); + message.getMessageProperties().getHeaders().put(AbstractJavaTypeMapper.DEFAULT_CONTENT_CLASSID_FIELD_NAME, + clazz.getName()); + return (List) rabbitTemplate.getMessageConverter().fromMessage(message); + } + + public MessageConverter getMessageConverter() { + return rabbitTemplate.getMessageConverter(); + } + + protected final String getStringHeaderKey(final Message message, final String key, + final String errorMessageIfNull) { + final Map header = message.getMessageProperties().getHeaders(); + final Object value = header.get(key); + if (value == null) { + logAndThrowMessageError(message, errorMessageIfNull); + return null; + } + return value.toString(); + } + + protected final void logAndThrowMessageError(final Message message, final String error) { + LOGGER.error("Error \"{}\" reported by message {}", error, message.getMessageProperties().getMessageId()); + throw new IllegalArgumentException(error); + } + + protected RabbitTemplate getRabbitTemplate() { + return rabbitTemplate; + } +} diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/DefaultAmqpSenderService.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/DefaultAmqpSenderService.java new file mode 100644 index 000000000..9586633bf --- /dev/null +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/DefaultAmqpSenderService.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.amqp; + +import java.net.URI; + +import org.springframework.amqp.core.Message; +import org.springframework.amqp.rabbit.core.RabbitTemplate; + +/** + * A default implementation for the sender service. The service sends all amqp + * message to the configured spring rabbitmq connections. The exchange is + * extracted from the uri. + */ +public class DefaultAmqpSenderService implements AmqpSenderService { + + private final RabbitTemplate internalAmqpTemplate; + + /** + * Constructor. + * + * @param internalAmqpTemplate + * the amqp template + */ + public DefaultAmqpSenderService(final RabbitTemplate internalAmqpTemplate) { + this.internalAmqpTemplate = internalAmqpTemplate; + } + + @Override + public void sendMessage(final Message message, final URI uri) { + internalAmqpTemplate.send(extractExchange(uri), null, message); + } + +} diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/AmqpTestConfiguration.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/AmqpTestConfiguration.java new file mode 100644 index 000000000..a1dd54710 --- /dev/null +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/AmqpTestConfiguration.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit; + +import org.eclipse.hawkbit.amqp.AmqpSenderService; +import org.eclipse.hawkbit.amqp.DefaultAmqpSenderService; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * + */ +@Configuration +public class AmqpTestConfiguration { + + /** + * Method to set the Jackson2JsonMessageConverter. + * + * @return the Jackson2JsonMessageConverter + */ + @Bean + public MessageConverter jsonMessageConverter() { + return new Jackson2JsonMessageConverter(); + } + + /** + * Create default amqp sender service bean. + * + * @param rabbitTemplate + * + * @return the default amqp sender service bean + */ + @Bean + @Autowired + public AmqpSenderService amqpSenderServiceBean(final RabbitTemplate rabbitTemplate) { + return new DefaultAmqpSenderService(rabbitTemplate); + } +} diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthentficationTest.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java similarity index 71% rename from hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthentficationTest.java rename to hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java index 5a77c5fce..66527727a 100644 --- a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthentficationTest.java +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java @@ -22,9 +22,12 @@ import org.eclipse.hawkbit.dmf.json.model.DownloadResponse; import org.eclipse.hawkbit.dmf.json.model.TenantSecruityToken; import org.eclipse.hawkbit.repository.ArtifactManagement; import org.eclipse.hawkbit.repository.ControllerManagement; -import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.repository.model.TenantConfigurationValue; +import org.eclipse.hawkbit.security.DdiSecurityProperties; +import org.eclipse.hawkbit.security.DdiSecurityProperties.Rp; import org.eclipse.hawkbit.security.SecurityContextTenantAware; -import org.eclipse.hawkbit.security.SecurityProperties; +import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.junit.Before; import org.junit.Test; @@ -45,74 +48,97 @@ import ru.yandex.qatools.allure.annotations.Stories; /** * - * + * Test Amqp controller authentication. */ -@Features("AMQP Authenfication Test") -@Stories("Tests the authenfication") -public class AmqpControllerAuthentficationTest { +@Features("Component Tests - Device Management Federation API") +@Stories("AmqpController Authentication Test") +public class AmqpControllerAuthenticationTest { private static final String TENANT = "DEFAULT"; private static String CONTROLLLER_ID = "123"; private AmqpMessageHandlerService amqpMessageHandlerService; private MessageConverter messageConverter; - private SystemManagement systemManagement; + private TenantConfigurationManagement tenantConfigurationManagement; private AmqpControllerAuthentfication authenticationManager; + private static final TenantConfigurationValue CONFIG_VALUE_FALSE = TenantConfigurationValue + . builder().value(Boolean.FALSE).build(); + + private static final TenantConfigurationValue CONFIG_VALUE_TRUE = TenantConfigurationValue + . builder().value(Boolean.TRUE).build(); + @Before public void before() throws Exception { - amqpMessageHandlerService = new AmqpMessageHandlerService(); messageConverter = new Jackson2JsonMessageConverter(); - final RabbitTemplate rabbitTemplate = new RabbitTemplate(); - rabbitTemplate.setMessageConverter(messageConverter); - amqpMessageHandlerService.setRabbitTemplate(rabbitTemplate); + final RabbitTemplate rabbitTemplate = mock(RabbitTemplate.class); + when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter); + amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate); authenticationManager = new AmqpControllerAuthentfication(); authenticationManager.setControllerManagement(mock(ControllerManagement.class)); - final SecurityProperties secruityProperties = mock(SecurityProperties.class); - when(secruityProperties.getRpSslIssuerHashHeader()).thenReturn("X-Ssl-Issuer-Hash-%d"); + + final DdiSecurityProperties secruityProperties = mock(DdiSecurityProperties.class); + final Rp rp = mock(Rp.class); + when(secruityProperties.getRp()).thenReturn(rp); + when(rp.getSslIssuerHashHeader()).thenReturn("X-Ssl-Issuer-Hash-%d"); authenticationManager.setSecruityProperties(secruityProperties); - systemManagement = mock(SystemManagement.class); - authenticationManager.setSystemManagement(systemManagement); - when(systemManagement.getConfigurationValue(any(), any())).thenReturn(Boolean.FALSE); + + tenantConfigurationManagement = mock(TenantConfigurationManagement.class); + authenticationManager.setTenantConfigurationManagement(tenantConfigurationManagement); + + when(tenantConfigurationManagement.getConfigurationValue(any(), eq(Boolean.class))) + .thenReturn(CONFIG_VALUE_FALSE); final ControllerManagement controllerManagement = mock(ControllerManagement.class); when(controllerManagement.getSecurityTokenByControllerId(anyString())).thenReturn(CONTROLLLER_ID); authenticationManager.setControllerManagement(controllerManagement); - amqpMessageHandlerService.setArtifactManagement(mock(ArtifactManagement.class)); - authenticationManager.setTenantAware(new SecurityContextTenantAware()); + final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(); + authenticationManager.setTenantAware(tenantAware); + final SystemSecurityContext systemSecurityContext = new SystemSecurityContext(tenantAware); + authenticationManager.setSystemSecurityContext(systemSecurityContext); authenticationManager.postConstruct(); amqpMessageHandlerService.setAuthenticationManager(authenticationManager); } - @Test(expected = BadCredentialsException.class) + @Test @Description("Tests authentication manager without principal") public void testAuthenticationeBadCredantialsWithoutPricipal() { final TenantSecruityToken securityToken = new TenantSecruityToken(TENANT, CONTROLLLER_ID, "12345"); - authenticationManager.doAuthenticate(securityToken); - fail(); + try { + authenticationManager.doAuthenticate(securityToken); + fail("BadCredentialsException was excepeted since principal was missing"); + } catch (final BadCredentialsException exception) { + // test ok - exception was excepted + } + } - @Test(expected = BadCredentialsException.class) - @Description("Tests authentication manager without wrong credential") + @Test + @Description("Tests authentication manager without wrong credential") public void testAuthenticationBadCredantialsWithWrongCredential() { final TenantSecruityToken securityToken = new TenantSecruityToken(TENANT, CONTROLLLER_ID, "12345"); - when(systemManagement.getConfigurationValue( - eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), any())) - .thenReturn(Boolean.TRUE); + when(tenantConfigurationManagement.getConfigurationValue( + eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class))) + .thenReturn(CONFIG_VALUE_TRUE); securityToken.getHeaders().put(TenantSecruityToken.AUTHORIZATION_HEADER, "TargetToken 12" + CONTROLLLER_ID); - authenticationManager.doAuthenticate(securityToken); - fail(); + try { + authenticationManager.doAuthenticate(securityToken); + fail("BadCredentialsException was excepeted due to wrong credential"); + } catch (final BadCredentialsException exception) { + // test ok - exception was excepted + } + } @Test @Description("Tests authentication successfull") public void testSuccessfullAuthentication() { final TenantSecruityToken securityToken = new TenantSecruityToken(TENANT, CONTROLLLER_ID, "12345"); - when(systemManagement.getConfigurationValue( - eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), any())) - .thenReturn(Boolean.TRUE); + when(tenantConfigurationManagement.getConfigurationValue( + eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class))) + .thenReturn(CONFIG_VALUE_TRUE); securityToken.getHeaders().put(TenantSecruityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLLER_ID); final Authentication authentication = authenticationManager.doAuthenticate(securityToken); assertThat(authentication).isNotNull(); @@ -129,7 +155,7 @@ public class AmqpControllerAuthentficationTest { // test final Message onMessage = amqpMessageHandlerService.onMessage(message, MessageType.AUTHENTIFICATION.name(), - TENANT); + TENANT, "vHost"); // verify final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage); @@ -142,16 +168,16 @@ public class AmqpControllerAuthentficationTest { public void testAuthenticationMessageBadCredantialsWithWrongCredential() { final MessageProperties messageProperties = createMessageProperties(MessageType.AUTHENTIFICATION); final TenantSecruityToken securityToken = new TenantSecruityToken(TENANT, CONTROLLLER_ID, "12345"); - when(systemManagement.getConfigurationValue( - eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), any())) - .thenReturn(Boolean.TRUE); + when(tenantConfigurationManagement.getConfigurationValue( + eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class))) + .thenReturn(CONFIG_VALUE_TRUE); securityToken.getHeaders().put(TenantSecruityToken.AUTHORIZATION_HEADER, "TargetToken 12" + CONTROLLLER_ID); final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken, messageProperties); // test final Message onMessage = amqpMessageHandlerService.onMessage(message, MessageType.AUTHENTIFICATION.name(), - TENANT); + TENANT, "vHost"); // verify final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage); @@ -164,16 +190,16 @@ public class AmqpControllerAuthentficationTest { public void testSuccessfullMessageAuthentication() { final MessageProperties messageProperties = createMessageProperties(MessageType.AUTHENTIFICATION); final TenantSecruityToken securityToken = new TenantSecruityToken(TENANT, CONTROLLLER_ID, "12345"); - when(systemManagement.getConfigurationValue( - eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), any())) - .thenReturn(Boolean.TRUE); + when(tenantConfigurationManagement.getConfigurationValue( + eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class))) + .thenReturn(CONFIG_VALUE_TRUE); securityToken.getHeaders().put(TenantSecruityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLLER_ID); final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken, messageProperties); // test final Message onMessage = amqpMessageHandlerService.onMessage(message, MessageType.AUTHENTIFICATION.name(), - TENANT); + TENANT, "vHost"); // verify final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage); diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java index c423b76bf..46ddd35cc 100644 --- a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java @@ -19,6 +19,7 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import java.net.URI; import java.util.ArrayList; import java.util.List; @@ -45,7 +46,6 @@ import org.springframework.amqp.core.MessageProperties; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.support.converter.AbstractJavaTypeMapper; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; -import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.test.context.ActiveProfiles; import ru.yandex.qatools.allure.annotations.Description; @@ -53,45 +53,47 @@ import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Stories; @ActiveProfiles({ "test" }) -@Features("AMQP Dispatcher Test") -@Stories("Tests send messages") +@Features("Component Tests - Device Management Federation API") +@Stories("AmqpMessage Dispatcher Service Test") public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWithMongoDB { private AmqpMessageDispatcherService amqpMessageDispatcherService; - private MessageConverter messageConverter; - private RabbitTemplate rabbitTemplate; + private DefaultAmqpSenderService senderService; + private static final String CONTROLLER_ID = "1"; @Override public void before() throws Exception { super.before(); - amqpMessageDispatcherService = new AmqpMessageDispatcherService(); + this.rabbitTemplate = Mockito.mock(RabbitTemplate.class); + when(rabbitTemplate.getMessageConverter()).thenReturn(new Jackson2JsonMessageConverter()); + amqpMessageDispatcherService = new AmqpMessageDispatcherService(rabbitTemplate); amqpMessageDispatcherService = spy(amqpMessageDispatcherService); - messageConverter = new Jackson2JsonMessageConverter(); + + senderService = Mockito.mock(DefaultAmqpSenderService.class); + amqpMessageDispatcherService.setAmqpSenderService(senderService); final ArtifactUrlHandler artifactUrlHandlerMock = Mockito.mock(ArtifactUrlHandler.class); when(artifactUrlHandlerMock.getUrl(anyString(), any(), anyObject())).thenReturn("http://mockurl"); - this.rabbitTemplate = Mockito.mock(RabbitTemplate.class); - when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter); - - amqpMessageDispatcherService.setRabbitTemplate(rabbitTemplate); - amqpMessageDispatcherService.setTenantAware(tenantAware); amqpMessageDispatcherService.setArtifactUrlHandler(artifactUrlHandlerMock); + } @Test @Description("Verfies that download and install event with no software modul works") public void testSendDownloadRequesWithEmptySoftwareModules() { final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent( - CONTROLLER_ID, 1l, new ArrayList(), IpUtil.createAmqpUri("mytest")); + 1L, "default", CONTROLLER_ID, 1l, new ArrayList(), + IpUtil.createAmqpUri("vHost", "mytest")); amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent); - final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress().getHost()); + final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress()); final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage); - assertTrue(downloadAndUpdateRequest.getSoftwareModules().isEmpty()); + assertTrue("No softwaremmodule should be contained in the request", + downloadAndUpdateRequest.getSoftwareModules().isEmpty()); } @Test @@ -100,21 +102,26 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, distributionSetManagement); final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent( - CONTROLLER_ID, 1l, dsA.getModules(), IpUtil.createAmqpUri("mytest")); + 1L, "default", CONTROLLER_ID, 1l, dsA.getModules(), IpUtil.createAmqpUri("vHost", "mytest")); amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent); - final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress().getHost()); + final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress()); final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage); - assertEquals(3, downloadAndUpdateRequest.getSoftwareModules().size()); + assertEquals("Expecting a size of 3 software modules in the reuqest", 3, + downloadAndUpdateRequest.getSoftwareModules().size()); for (final org.eclipse.hawkbit.dmf.json.model.SoftwareModule softwareModule : downloadAndUpdateRequest .getSoftwareModules()) { - assertTrue(softwareModule.getArtifacts().isEmpty()); + assertTrue("Artifact list for softwaremodule should be empty", softwareModule.getArtifacts().isEmpty()); for (final SoftwareModule softwareModule2 : dsA.getModules()) { - assertNotNull(softwareModule.getModuleId()); + assertNotNull("Sofware module ID should be set", softwareModule.getModuleId()); if (!softwareModule.getModuleId().equals(softwareModule2.getId())) { continue; } - assertEquals(softwareModule.getModuleType(), softwareModule2.getType().getKey()); - assertEquals(softwareModule.getModuleVersion(), softwareModule2.getVersion()); + assertEquals( + "Software module type in event should be the same as the softwaremodule in the distribution set", + softwareModule.getModuleType(), softwareModule2.getType().getKey()); + assertEquals( + "Software module version in event should be the same as the softwaremodule in the distribution set", + softwareModule.getModuleVersion(), softwareModule2.getVersion()); } } } @@ -134,17 +141,18 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit Mockito.when(rabbitTemplate.convertSendAndReceive(any())).thenReturn(receivedList); final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = new TargetAssignDistributionSetEvent( - CONTROLLER_ID, 1l, dsA.getModules(), IpUtil.createAmqpUri("mytest")); + 1L, "default", CONTROLLER_ID, 1l, dsA.getModules(), IpUtil.createAmqpUri("vHost", "mytest")); amqpMessageDispatcherService.targetAssignDistributionSet(targetAssignDistributionSetEvent); - final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress().getHost()); + final Message sendMessage = createArgumentCapture(targetAssignDistributionSetEvent.getTargetAdress()); final DownloadAndUpdateRequest downloadAndUpdateRequest = assertDownloadAndInstallMessage(sendMessage); - assertEquals(3, downloadAndUpdateRequest.getSoftwareModules().size()); + assertEquals("DownloadAndUpdateRequest event should contains 3 software modules", 3, + downloadAndUpdateRequest.getSoftwareModules().size()); for (final org.eclipse.hawkbit.dmf.json.model.SoftwareModule softwareModule : downloadAndUpdateRequest .getSoftwareModules()) { if (!softwareModule.getModuleId().equals(module.getId())) { continue; } - assertFalse(softwareModule.getArtifacts().isEmpty()); + assertFalse("The software module artifacts should not be empty", softwareModule.getArtifacts().isEmpty()); } } @@ -152,11 +160,10 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit @Description("Verfies that send cancel event works") public void testSendCancelRequest() { final CancelTargetAssignmentEvent cancelTargetAssignmentDistributionSetEvent = new CancelTargetAssignmentEvent( - CONTROLLER_ID, 1l, IpUtil.createAmqpUri("mytest")); + 1L, "default", CONTROLLER_ID, 1l, IpUtil.createAmqpUri("vHost", "mytest")); amqpMessageDispatcherService .targetCancelAssignmentToDistributionSet(cancelTargetAssignmentDistributionSetEvent); - final Message sendMessage = createArgumentCapture( - cancelTargetAssignmentDistributionSetEvent.getTargetAdress().getHost()); + final Message sendMessage = createArgumentCapture(cancelTargetAssignmentDistributionSetEvent.getTargetAdress()); assertCancelMessage(sendMessage); } @@ -164,8 +171,8 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit private void assertCancelMessage(final Message sendMessage) { assertEventMessage(sendMessage); final Long actionId = convertMessage(sendMessage, Long.class); - assertEquals(actionId, Long.valueOf(1)); - assertEquals(EventTopic.CANCEL_DOWNLOAD, + assertEquals("Action ID should be 1", actionId, Long.valueOf(1)); + assertEquals("The topc in the message should be a CANCEL_DOWNLOAD value", EventTopic.CANCEL_DOWNLOAD, sendMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.TOPIC)); } @@ -174,8 +181,9 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit assertEventMessage(sendMessage); final DownloadAndUpdateRequest downloadAndUpdateRequest = convertMessage(sendMessage, DownloadAndUpdateRequest.class); - assertEquals(downloadAndUpdateRequest.getActionId(), Long.valueOf(1)); - assertEquals(EventTopic.DOWNLOAD_AND_INSTALL, + assertEquals("The action ID of the downloadAndUpdateRequest event shuold be 1", + downloadAndUpdateRequest.getActionId(), Long.valueOf(1)); + assertEquals("The topic of the event shuold contain DOWNLOAD_AND_INSTALL", EventTopic.DOWNLOAD_AND_INSTALL, sendMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.TOPIC)); return downloadAndUpdateRequest; @@ -185,16 +193,19 @@ public class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTestWit * @param sendMessage */ private void assertEventMessage(final Message sendMessage) { - assertNotNull(sendMessage); + assertNotNull("The message should not be null", sendMessage); - assertEquals(CONTROLLER_ID, sendMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.THING_ID)); - assertEquals(MessageType.EVENT, sendMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.TYPE)); - assertEquals(MessageProperties.CONTENT_TYPE_JSON, sendMessage.getMessageProperties().getContentType()); + assertEquals("The value of the message header THING_ID should be " + CONTROLLER_ID, CONTROLLER_ID, + sendMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.THING_ID)); + assertEquals("The value of the message header TYPE should be EVENT", MessageType.EVENT, + sendMessage.getMessageProperties().getHeaders().get(MessageHeaderKey.TYPE)); + assertEquals("The content type message should be " + MessageProperties.CONTENT_TYPE_JSON, + MessageProperties.CONTENT_TYPE_JSON, sendMessage.getMessageProperties().getContentType()); } - protected Message createArgumentCapture(final String exchange) { + protected Message createArgumentCapture(final URI uri) { final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Message.class); - Mockito.verify(amqpMessageDispatcherService).sendMessage(eq(exchange), argumentCaptor.capture()); + Mockito.verify(senderService).sendMessage(argumentCaptor.capture(), eq(uri)); return argumentCaptor.getValue(); } diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java index bea75e9d6..9d6ae3ba7 100644 --- a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java @@ -68,8 +68,8 @@ import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Stories; @RunWith(MockitoJUnitRunner.class) -@Features("AMQP Controller Test") -@Stories("Tests the servcies for message handler and dispatcher") +@Features("Component Tests - Device Management Federation API") +@Stories("AmqpMessage Handler Service Test") public class AmqpMessageHandlerServiceTest { private static final String TENANT = "DEFAULT"; @@ -99,14 +99,15 @@ public class AmqpMessageHandlerServiceTest { @Mock private EventBus eventBus; + @Mock + private RabbitTemplate rabbitTemplate; + @Before public void before() throws Exception { - amqpMessageHandlerService = new AmqpMessageHandlerService(); - amqpMessageHandlerService.setControllerManagement(controllerManagementMock); messageConverter = new Jackson2JsonMessageConverter(); - final RabbitTemplate rabbitTemplate = new RabbitTemplate(); - rabbitTemplate.setMessageConverter(messageConverter); - amqpMessageHandlerService.setRabbitTemplate(rabbitTemplate); + when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter); + amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate); + amqpMessageHandlerService.setControllerManagement(controllerManagementMock); amqpMessageHandlerService.setAuthenticationManager(authenticationManagerMock); amqpMessageHandlerService.setArtifactManagement(artifactManagementMock); amqpMessageHandlerService.setCache(cacheMock); @@ -115,14 +116,17 @@ public class AmqpMessageHandlerServiceTest { } - @Test(expected = IllegalArgumentException.class) + @Test @Description("Tests not allowed content-type in message") public void testWrongContentType() { final MessageProperties messageProperties = new MessageProperties(); messageProperties.setContentType("xml"); final Message message = new Message(new byte[0], messageProperties); - amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT); - fail(); + try { + amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, "vHost"); + fail("IllegalArgumentException was excepeted due to worng content type"); + } catch (final IllegalArgumentException e) { + } } @Test @@ -133,18 +137,16 @@ public class AmqpMessageHandlerServiceTest { messageProperties.setHeader(MessageHeaderKey.THING_ID, "1"); final Message message = messageConverter.toMessage(new byte[0], messageProperties); - // mock final ArgumentCaptor targetIdCaptor = ArgumentCaptor.forClass(String.class); final ArgumentCaptor uriCaptor = ArgumentCaptor.forClass(URI.class); when(controllerManagementMock.findOrRegisterTargetIfItDoesNotexist(targetIdCaptor.capture(), uriCaptor.capture())).thenReturn(null); - // test - amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT); + amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, "vHost"); // verify - assertThat(targetIdCaptor.getValue()).isEqualTo(knownThingId); - assertThat(uriCaptor.getValue().toString()).isEqualTo("amqp://MyTest"); + assertThat(targetIdCaptor.getValue()).as("Thing id is wrong").isEqualTo(knownThingId); + assertThat(uriCaptor.getValue().toString()).as("Uri is not right").isEqualTo("amqp://vHost/MyTest"); } @@ -156,7 +158,7 @@ public class AmqpMessageHandlerServiceTest { final Message message = messageConverter.toMessage("", messageProperties); try { - amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT); + amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, "vHost"); fail("IllegalArgumentException was excepeted since no replyTo header was set"); } catch (final IllegalArgumentException exception) { // test ok - exception was excepted @@ -170,7 +172,7 @@ public class AmqpMessageHandlerServiceTest { final MessageProperties messageProperties = createMessageProperties(MessageType.THING_CREATED); final Message message = messageConverter.toMessage(new byte[0], messageProperties); try { - amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT); + amqpMessageHandlerService.onMessage(message, MessageType.THING_CREATED.name(), TENANT, "vHost"); fail("IllegalArgumentException was excepeted since no thingID was set"); } catch (final IllegalArgumentException exception) { // test ok - exception was excepted @@ -186,7 +188,7 @@ public class AmqpMessageHandlerServiceTest { final Message message = messageConverter.toMessage(new byte[0], messageProperties); try { - amqpMessageHandlerService.onMessage(message, type, TENANT); + amqpMessageHandlerService.onMessage(message, type, TENANT, "vHost"); fail("IllegalArgumentException was excepeted due to unknown message type"); } catch (final IllegalArgumentException exception) { // test ok - exception was excepted @@ -199,21 +201,21 @@ public class AmqpMessageHandlerServiceTest { final MessageProperties messageProperties = createMessageProperties(MessageType.EVENT); final Message message = new Message(new byte[0], messageProperties); try { - amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT); - fail(); + amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost"); + fail("IllegalArgumentException was excepeted due to unknown message type"); } catch (final IllegalArgumentException e) { } try { messageProperties.setHeader(MessageHeaderKey.TOPIC, "wrongTopic"); - amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT); - fail(); + amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost"); + fail("IllegalArgumentException was excepeted due to unknown topic"); } catch (final IllegalArgumentException e) { } messageProperties.setHeader(MessageHeaderKey.TOPIC, EventTopic.CANCEL_DOWNLOAD.name()); try { - amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT); + amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost"); fail("IllegalArgumentException was excepeted because there was no event topic"); } catch (final IllegalArgumentException exception) { // test ok - exception was excepted @@ -232,7 +234,7 @@ public class AmqpMessageHandlerServiceTest { messageProperties); try { - amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT); + amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost"); fail("IllegalArgumentException was excepeted since no action id was set"); } catch (final IllegalArgumentException exception) { // test ok - exception was excepted @@ -249,7 +251,7 @@ public class AmqpMessageHandlerServiceTest { messageProperties); try { - amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT); + amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost"); fail("IllegalArgumentException was excepeted since no action id was set"); } catch (final IllegalArgumentException exception) { // test ok - exception was excepted @@ -267,12 +269,13 @@ public class AmqpMessageHandlerServiceTest { // test final Message onMessage = amqpMessageHandlerService.onMessage(message, MessageType.AUTHENTIFICATION.name(), - TENANT); + TENANT, "vHost"); // verify final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage); - assertThat(downloadResponse).isNotNull(); - assertThat(downloadResponse.getResponseCode()).isEqualTo(HttpStatus.NOT_FOUND.value()); + assertThat(downloadResponse).as("Message body should not null").isNotNull(); + assertThat(downloadResponse.getResponseCode()).as("Message body response code is wrong") + .isEqualTo(HttpStatus.NOT_FOUND.value()); } @Test @@ -290,12 +293,13 @@ public class AmqpMessageHandlerServiceTest { // test final Message onMessage = amqpMessageHandlerService.onMessage(message, MessageType.AUTHENTIFICATION.name(), - TENANT); + TENANT, "vHost"); // verify final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage); - assertThat(downloadResponse).isNotNull(); - assertThat(downloadResponse.getResponseCode()).isEqualTo(HttpStatus.NOT_FOUND.value()); + assertThat(downloadResponse).as("Message body should not null").isNotNull(); + assertThat(downloadResponse.getResponseCode()).as("Message body response code is wrong") + .isEqualTo(HttpStatus.NOT_FOUND.value()); } @Test @@ -321,14 +325,16 @@ public class AmqpMessageHandlerServiceTest { // test final Message onMessage = amqpMessageHandlerService.onMessage(message, MessageType.AUTHENTIFICATION.name(), - TENANT); + TENANT, "vHost"); // verify final DownloadResponse downloadResponse = (DownloadResponse) messageConverter.fromMessage(onMessage); - assertThat(downloadResponse).isNotNull(); - assertThat(downloadResponse.getResponseCode()).isEqualTo(HttpStatus.OK.value()); - assertThat(downloadResponse.getArtifact().getSize()).isEqualTo(1L); - assertThat(downloadResponse.getDownloadUrl()).startsWith("http://localhost/api/v1/downloadserver/downloadId/"); + assertThat(downloadResponse).as("Message body should not null").isNotNull(); + assertThat(downloadResponse.getResponseCode()).as("Message body response code is wrong") + .isEqualTo(HttpStatus.OK.value()); + assertThat(downloadResponse.getArtifact().getSize()).as("Wrong artifact size in message body").isEqualTo(1L); + assertThat(downloadResponse.getDownloadUrl()).as("download url is wrong") + .startsWith("http://localhost/api/v1/downloadserver/downloadId/"); } @Test @@ -355,7 +361,7 @@ public class AmqpMessageHandlerServiceTest { messageProperties); // test - amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT); + amqpMessageHandlerService.onMessage(message, MessageType.EVENT.name(), TENANT, "vHost"); // verify final ArgumentCaptor captorTargetAssignDistributionSetEvent = ArgumentCaptor @@ -364,9 +370,11 @@ public class AmqpMessageHandlerServiceTest { final TargetAssignDistributionSetEvent targetAssignDistributionSetEvent = captorTargetAssignDistributionSetEvent .getValue(); - assertThat(targetAssignDistributionSetEvent.getControllerId()).isEqualTo("target1"); - assertThat(targetAssignDistributionSetEvent.getActionId()).isEqualTo(22L); - assertThat(targetAssignDistributionSetEvent.getSoftwareModules()).isEqualTo(softwareModuleList); + assertThat(targetAssignDistributionSetEvent.getControllerId()).as("event has wrong controller id") + .isEqualTo("target1"); + assertThat(targetAssignDistributionSetEvent.getActionId()).as("event has wrong action id").isEqualTo(22L); + assertThat(targetAssignDistributionSetEvent.getSoftwareModules()).as("event has wrong sofware modules") + .isEqualTo(softwareModuleList); } diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/BaseAmqpServiceTest.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/BaseAmqpServiceTest.java new file mode 100644 index 000000000..0bd8c164b --- /dev/null +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/BaseAmqpServiceTest.java @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.amqp; + +import static org.fest.assertions.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.hawkbit.dmf.json.model.ActionUpdateStatus; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; + +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@RunWith(MockitoJUnitRunner.class) +@Features("Component Tests - Device Management Federation API") +@Stories("Base Amqp Service Test") +public class BaseAmqpServiceTest { + + @Mock + private RabbitTemplate rabbitTemplate; + + private BaseAmqpService baseAmqpService; + + @Before + public void setup() { + when(rabbitTemplate.getMessageConverter()).thenReturn(new Jackson2JsonMessageConverter()); + baseAmqpService = new BaseAmqpService(rabbitTemplate); + + } + + @Test + @Description("Verify that the message conversion works") + public void convertMessageTest() { + final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus(); + actionUpdateStatus.setActionId(1L); + actionUpdateStatus.setSoftwareModuleId(2L); + + final Message message = rabbitTemplate.getMessageConverter().toMessage(actionUpdateStatus, + new MessageProperties()); + ActionUpdateStatus convertedActionUpdateStatus = baseAmqpService.convertMessage(message, + ActionUpdateStatus.class); + + assertThat(convertedActionUpdateStatus).as("Converted Action Status is wrong") + .isEqualsToByComparingFields(actionUpdateStatus); + + convertedActionUpdateStatus = baseAmqpService.convertMessage(null, ActionUpdateStatus.class); + assertThat(convertedActionUpdateStatus).as("Converted Object should be null when message is null").isNull(); + + convertedActionUpdateStatus = baseAmqpService.convertMessage(new Message(null, new MessageProperties()), + ActionUpdateStatus.class); + assertThat(convertedActionUpdateStatus).as("Converted Object should be null when message body is null") + .isNull(); + } + + @Test + @Description("Verify that a conversion of a list from a message works") + public void convertMessageListTest() { + final List actionUpdateStatusList = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus(); + actionUpdateStatus.setActionId(Long.valueOf(i)); + actionUpdateStatus.setSoftwareModuleId(Long.valueOf(i)); + actionUpdateStatusList.add(actionUpdateStatus); + } + + final Message message = rabbitTemplate.getMessageConverter().toMessage(actionUpdateStatusList, + new MessageProperties()); + List convertedActionUpdateStatus = baseAmqpService.convertMessageList(message, + ActionUpdateStatus.class); + + assertThat(convertedActionUpdateStatus).as("Converted Action Status list is wrong") + .hasSameClassAs(actionUpdateStatusList); + assertThat(convertedActionUpdateStatus).as("Converted Action Status list is wrong") + .hasSameSizeAs(actionUpdateStatusList); + + convertedActionUpdateStatus = baseAmqpService.convertMessageList(null, ActionUpdateStatus.class); + assertThat(convertedActionUpdateStatus).as("Converted list should be empty when message is null").isEmpty(); + + convertedActionUpdateStatus = baseAmqpService.convertMessageList(new Message(null, new MessageProperties()), + ActionUpdateStatus.class); + assertThat(convertedActionUpdateStatus).as("Converted list should be empty when message body is null") + .isEmpty(); + } + +} diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/util/PropertyBasedArtifactUrlHandlerTest.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/util/PropertyBasedArtifactUrlHandlerTest.java index fcafb23e4..e7ba06d19 100644 --- a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/util/PropertyBasedArtifactUrlHandlerTest.java +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/util/PropertyBasedArtifactUrlHandlerTest.java @@ -11,6 +11,9 @@ package org.eclipse.hawkbit.util; import static org.junit.Assert.assertEquals; import org.eclipse.hawkbit.AbstractIntegrationTestWithMongoDB; +import org.eclipse.hawkbit.AmqpTestConfiguration; +import org.eclipse.hawkbit.RepositoryApplicationConfiguration; +import org.eclipse.hawkbit.TestConfiguration; import org.eclipse.hawkbit.TestDataUtil; import org.eclipse.hawkbit.dmf.json.model.Artifact; import org.eclipse.hawkbit.repository.model.DistributionSet; @@ -20,6 +23,7 @@ import org.eclipse.hawkbit.tenancy.TenantAware; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.SpringApplicationConfiguration; import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Features; @@ -31,6 +35,8 @@ import ru.yandex.qatools.allure.annotations.Stories; */ @Features("Component Tests - Artifact URL Handler") @Stories("Test to generate the artifact download URL") +@SpringApplicationConfiguration(classes = { RepositoryApplicationConfiguration.class, TestConfiguration.class, + AmqpTestConfiguration.class }) public class PropertyBasedArtifactUrlHandlerTest extends AbstractIntegrationTestWithMongoDB { @Autowired @@ -50,29 +56,33 @@ public class PropertyBasedArtifactUrlHandlerTest extends AbstractIntegrationTest } @Test - @Description("Tests generate the http download url") + @Description("Tests the generation of http download url.") public void testHttpUrl() { final String url = urlHandlerProperties.getUrl(controllerId, localArtifact, Artifact.UrlProtocol.HTTP); - assertEquals("http://localhost/" + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId - + "/softwaremodules/" + localArtifact.getSoftwareModule().getId() + "/artifacts/" - + localArtifact.getFilename(), url); + assertEquals("http is build incorrect", + "http://localhost/" + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId + + "/softwaremodules/" + localArtifact.getSoftwareModule().getId() + "/artifacts/" + + localArtifact.getFilename(), + url); } @Test - @Description("Tests generate the https download url") + @Description("Tests the generation of https download url.") public void testHttpsUrl() { final String url = urlHandlerProperties.getUrl(controllerId, localArtifact, Artifact.UrlProtocol.HTTPS); - assertEquals("https://localhost/" + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId - + "/softwaremodules/" + localArtifact.getSoftwareModule().getId() + "/artifacts/" - + localArtifact.getFilename(), url); + assertEquals("https is build incorrect", + "https://localhost/" + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId + + "/softwaremodules/" + localArtifact.getSoftwareModule().getId() + "/artifacts/" + + localArtifact.getFilename(), + url); } @Test - @Description("Tests generate the coap download url") + @Description("Tests the generation of coap download url.") public void testCoapUrl() { final String url = urlHandlerProperties.getUrl(controllerId, localArtifact, Artifact.UrlProtocol.COAP); - assertEquals("coap://127.0.0.1:5683/fw/" + tenantAware.getCurrentTenant() + "/" + controllerId + "/sha1/" - + localArtifact.getSha1Hash(), url); + assertEquals("coap is build incorrect", "coap://127.0.0.1:5683/fw/" + tenantAware.getCurrentTenant() + "/" + + controllerId + "/sha1/" + localArtifact.getSha1Hash(), url); } } diff --git a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/AbstractHttpControllerAuthenticationFilter.java b/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/AbstractHttpControllerAuthenticationFilter.java index 5782a48db..adb2858b9 100644 --- a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/AbstractHttpControllerAuthenticationFilter.java +++ b/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/AbstractHttpControllerAuthenticationFilter.java @@ -18,7 +18,7 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import org.eclipse.hawkbit.dmf.json.model.TenantSecruityToken; -import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.tenancy.TenantAware; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,8 +55,9 @@ public abstract class AbstractHttpControllerAuthenticationFilter extends Abstrac private static final String CONTROLLER_DL_REQUEST_ANT_PATTERN = "/{" + TENANT_PLACE_HOLDER + "}/controller/artifacts/v1/**"; - protected SystemManagement systemManagement; + protected TenantConfigurationManagement tenantConfigurationManagement; protected TenantAware tenantAware; + protected SystemSecurityContext systemSecurityContext; private final AntPathMatcher pathExtractor; @@ -70,10 +71,11 @@ public abstract class AbstractHttpControllerAuthenticationFilter extends Abstrac * @param tenantAware * the tenant aware service */ - public AbstractHttpControllerAuthenticationFilter(final SystemManagement systemManagement, - final TenantAware tenantAware) { - this.systemManagement = systemManagement; + public AbstractHttpControllerAuthenticationFilter(final TenantConfigurationManagement tenantConfigurationManagement, + final TenantAware tenantAware, final SystemSecurityContext systemSecurityContext) { + this.tenantConfigurationManagement = tenantConfigurationManagement; this.tenantAware = tenantAware; + this.systemSecurityContext = systemSecurityContext; pathExtractor = new AntPathMatcher(); } diff --git a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticateSecurityTokenFilter.java b/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticateSecurityTokenFilter.java index 91a0cdfa6..1eb155b9a 100644 --- a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticateSecurityTokenFilter.java +++ b/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticateSecurityTokenFilter.java @@ -9,7 +9,7 @@ package org.eclipse.hawkbit.security; import org.eclipse.hawkbit.repository.ControllerManagement; -import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.tenancy.TenantAware; /** @@ -35,7 +35,7 @@ public class HttpControllerPreAuthenticateSecurityTokenFilter extends AbstractHt /** * Constructor. * - * @param systemManagement + * @param tenantConfigurationManagement * the system management service to retrieve configuration * properties * @param tenantAware @@ -44,16 +44,20 @@ public class HttpControllerPreAuthenticateSecurityTokenFilter extends AbstractHt * @param controllerManagement * the controller management to retrieve the specific target * security token to verify + * @param systemSecurityContext + * the system security context */ - public HttpControllerPreAuthenticateSecurityTokenFilter(final SystemManagement systemManagement, - final TenantAware tenantAware, final ControllerManagement controllerManagement) { - super(systemManagement, tenantAware); + public HttpControllerPreAuthenticateSecurityTokenFilter( + final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware, + final ControllerManagement controllerManagement, final SystemSecurityContext systemSecurityContext) { + super(tenantConfigurationManagement, tenantAware, systemSecurityContext); this.controllerManagement = controllerManagement; } @Override protected PreAuthenficationFilter createControllerAuthenticationFilter() { - return new ControllerPreAuthenticateSecurityTokenFilter(systemManagement, controllerManagement, tenantAware); + return new ControllerPreAuthenticateSecurityTokenFilter(tenantConfigurationManagement, controllerManagement, + tenantAware, systemSecurityContext); } } diff --git a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedGatewaySecurityTokenFilter.java b/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedGatewaySecurityTokenFilter.java index 2b1a4d78f..930c66dca 100644 --- a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedGatewaySecurityTokenFilter.java +++ b/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedGatewaySecurityTokenFilter.java @@ -8,7 +8,7 @@ */ package org.eclipse.hawkbit.security; -import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.tenancy.TenantAware; /** @@ -27,21 +27,25 @@ public class HttpControllerPreAuthenticatedGatewaySecurityTokenFilter /** * Constructor. * - * @param systemManagement + * @param tenantConfigurationManagement * the system management service to retrieve configuration * properties * @param tenantAware * the tenant aware service to get configuration for the specific * tenant + * @param systemSecurityContext + * * @param systemSecurityContext the system security context */ - public HttpControllerPreAuthenticatedGatewaySecurityTokenFilter(final SystemManagement systemManagement, - final TenantAware tenantAware) { - super(systemManagement, tenantAware); + public HttpControllerPreAuthenticatedGatewaySecurityTokenFilter( + final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware, + final SystemSecurityContext systemSecurityContext) { + super(tenantConfigurationManagement, tenantAware, systemSecurityContext); } @Override protected PreAuthenficationFilter createControllerAuthenticationFilter() { - return new ControllerPreAuthenticatedGatewaySecurityTokenFilter(systemManagement, tenantAware); + return new ControllerPreAuthenticatedGatewaySecurityTokenFilter(tenantConfigurationManagement, tenantAware, + systemSecurityContext); } } diff --git a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedSecurityHeaderFilter.java b/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedSecurityHeaderFilter.java index 3d6b43657..162e2688d 100644 --- a/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedSecurityHeaderFilter.java +++ b/hawkbit-http-security/src/main/java/org/eclipse/hawkbit/security/HttpControllerPreAuthenticatedSecurityHeaderFilter.java @@ -8,7 +8,7 @@ */ package org.eclipse.hawkbit.security; -import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.tenancy.TenantAware; /** @@ -35,18 +35,20 @@ public class HttpControllerPreAuthenticatedSecurityHeaderFilter extends Abstract * @param caAuthorityNameHeader * the http-header which holds the ca-authority name of the * certificate - * @param systemManagement - * the system management service to retrieve configuration - * properties to check if the header authentication is enabled - * for this tenant + * @param tenantConfigurationManagement + * the tenant configuration management service to retrieve + * configuration properties to check if the header authentication + * is enabled for this tenant * @param tenantAware * the tenant aware service to get configuration for the specific * tenant + * @param systemSecurityContext + * the system security context */ public HttpControllerPreAuthenticatedSecurityHeaderFilter(final String caCommonNameHeader, - final String caAuthorityNameHeader, final SystemManagement systemManagement, - final TenantAware tenantAware) { - super(systemManagement, tenantAware); + final String caAuthorityNameHeader, final TenantConfigurationManagement tenantConfigurationManagement, + final TenantAware tenantAware, final SystemSecurityContext systemSecurityContext) { + super(tenantConfigurationManagement, tenantAware, systemSecurityContext); this.caCommonNameHeader = caCommonNameHeader; this.caAuthorityNameHeader = caAuthorityNameHeader; } @@ -54,7 +56,7 @@ public class HttpControllerPreAuthenticatedSecurityHeaderFilter extends Abstract @Override protected PreAuthenficationFilter createControllerAuthenticationFilter() { return new ControllerPreAuthenticatedSecurityHeaderFilter(caCommonNameHeader, caAuthorityNameHeader, - systemManagement, tenantAware); + tenantConfigurationManagement, tenantAware, systemSecurityContext); } } diff --git a/hawkbit-http-security/src/test/java/org/eclipse/hawkbit/security/PreAuthTokenSourceTrustAuthenticationProviderTest.java b/hawkbit-http-security/src/test/java/org/eclipse/hawkbit/security/PreAuthTokenSourceTrustAuthenticationProviderTest.java index f2ffe5c4c..fb961f9b2 100644 --- a/hawkbit-http-security/src/test/java/org/eclipse/hawkbit/security/PreAuthTokenSourceTrustAuthenticationProviderTest.java +++ b/hawkbit-http-security/src/test/java/org/eclipse/hawkbit/security/PreAuthTokenSourceTrustAuthenticationProviderTest.java @@ -20,7 +20,13 @@ import org.springframework.security.authentication.InsufficientAuthenticationExc import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Unit Tests - Security") +@Stories("PreAuthToken Source TrustAuthentication Provider Test") @RunWith(MockitoJUnitRunner.class) +// TODO: create description annotations public class PreAuthTokenSourceTrustAuthenticationProviderTest { private static final String REQUEST_SOURCE_IP = "127.0.0.1"; diff --git a/hawkbit-repository/README.md b/hawkbit-repository/README.md index 6c1e35c5d..2d68a4e73 100644 --- a/hawkbit-repository/README.md +++ b/hawkbit-repository/README.md @@ -9,4 +9,39 @@ The repository is in charge for managing the meta data of the update server, e.g $ mvn clean install ---- -Note, in order to build correctly in your IDE, you have to add ./target/generated-sources/apt to your build path. \ No newline at end of file +Note, in order to build correctly in your IDE, you have to add ./target/generated-sources/apt to your build path. + +#Concepts + +## Rollout +A rollout consists of the distribution set and a target-query-filter which defines the targets to be updated in this rollout. The targets within this filter are split up in configured amount of _Deployment Groups_ at creation time. + +When starting a rollout, for all targets within this rollout deployment actions will be created. The deployment actions of the first group will be started immediately all other deployment actions will be scheduled. + +> Due rollouts might include a large number of targets and deployment group, creation as well as starting a rollout might take some time and therefore the creation and starting of an rollout is executed asynchronously. The creation and starting progress is reflected by the rollout's status attribute + +### Rollout Creation +The targets reflected by the target-query-filter is interpreted at creation time. Only targets which have been created at that time are included in the rollout. Targets which are created after the rollout creation will not be included. At rollout creation time all necessary deployment groups containing the targets will be created. Dynamically adding targets to a created or running rollout is currently not supported. + +### Rollout Starting +The rollout is using the same concept based on _deployment acionts_ per target. All necessary _deployment acionts_ will be created at starting point but not all _deployment acionts_ will be set to running. Only the _deployment acionts_ of the first _deployment group_ is set to running, all other actions are created in _scheduled_ state. +If targets having not finished _deployment actions_ at rollout starting time, these action are tried to cancel (state: canceling). Scheduled actions from another rollout are canceled directly on server side because they were never running before and can be safely canceled. + +>Multiple rollouts can be running at the same time, but if the rollouts effect the same targets they will interfer the targets _deployment actions_ e.g. canceling/cancel already running/scheduled _deployment actions_. + +### Deployment Group Condition/Action +#### Success Condition/Action +A _success condition_ defines when a deployment group is interpreted as success and triggers the _success action_. E.g. a threshold of successfully updated targets within the group. + +The _success action_ is executed if the _success_condition_ is fullfilled. Currently only the _success action_ starting the next following deployment group is available. + +#### Error Condition/Action +A _error condition_ defines when a deployment group is interpreted as failure and triggers the _error action_. E.g. a threshold of update failures of targets within the group. + +The _error action_ is executed if the _error condition_ is fullfilled. Currently only the _error action_ pausing the whole rollout is available, a paused rollout can be resumed by a user. + +### Rollout Scheduler +The rollout is managed by a scheduler task which is checking for rollouts in _running_ state. This is done atomic by updating the row in the database, so only the rollouts are checked and processed for the current instance to avoid that multiple instances are processing a rollout. + +>Due the scheduler, it might take some time until a rollouts is processed and switching is state or starting the next deployment group. + diff --git a/hawkbit-repository/pom.xml b/hawkbit-repository/pom.xml index 62d234ddb..7259262de 100644 --- a/hawkbit-repository/pom.xml +++ b/hawkbit-repository/pom.xml @@ -99,6 +99,11 @@ org.flywaydb flyway-core + + org.springframework.boot + spring-boot-configuration-processor + true + @@ -215,7 +220,7 @@ com.ethlo.persistence.tools eclipselink-maven-plugin - 1.1-SNAPSHOT + 2.6.2 process-classes diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/MultiTenantJpaTransactionManager.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/MultiTenantJpaTransactionManager.java index 2675510c4..7b97f5037 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/MultiTenantJpaTransactionManager.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/MultiTenantJpaTransactionManager.java @@ -11,6 +11,7 @@ package org.eclipse.hawkbit; import javax.persistence.EntityManager; import javax.transaction.Transaction; +import org.eclipse.hawkbit.repository.RolloutManagement; import org.eclipse.hawkbit.repository.SystemManagement; import org.eclipse.hawkbit.repository.exception.TenantNotExistException; import org.eclipse.hawkbit.tenancy.TenantAware; @@ -26,8 +27,6 @@ import org.springframework.transaction.support.TransactionSynchronizationManager * {@link TenantAware#getCurrentTenant()} in the eclipselink session. This has * to be done in eclipselink after a {@link Transaction} has been started. * - * - * */ public class MultiTenantJpaTransactionManager extends JpaTransactionManager { private static final long serialVersionUID = 1L; @@ -43,10 +42,8 @@ public class MultiTenantJpaTransactionManager extends JpaTransactionManager { .getResource(getEntityManagerFactory()); final EntityManager em = emHolder.getEntityManager(); - if (!definition.getName().startsWith(SystemManagement.class.getCanonicalName() + ".findTenants") - && !definition.getName().startsWith(SystemManagement.class.getCanonicalName() + ".deleteTenant") - && !definition.getName() - .startsWith(SystemManagement.class.getCanonicalName() + ".currentTenantKeyGenerator")) { + if (notTenantManagement(definition) && notCurrentTenantKeyGenerator(definition) + && notRolloutScheduler(definition) && notGetOrCreateTenantMetadata(definition)) { final String currentTenant = tenantAware.getCurrentTenant(); if (currentTenant == null) { @@ -56,4 +53,23 @@ public class MultiTenantJpaTransactionManager extends JpaTransactionManager { em.setProperty(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT, currentTenant.toUpperCase()); } } + + private boolean notGetOrCreateTenantMetadata(final TransactionDefinition definition) { + return !definition.getName() + .startsWith(SystemManagement.class.getCanonicalName() + ".getOrCreateTenantMetadata"); + } + + private boolean notRolloutScheduler(final TransactionDefinition definition) { + return !definition.getName().startsWith(RolloutManagement.class.getCanonicalName() + ".rolloutScheduler"); + } + + private boolean notCurrentTenantKeyGenerator(final TransactionDefinition definition) { + return !definition.getName() + .startsWith(SystemManagement.class.getCanonicalName() + ".currentTenantKeyGenerator"); + } + + private boolean notTenantManagement(final TransactionDefinition definition) { + return !definition.getName().startsWith(SystemManagement.class.getCanonicalName() + ".deleteTenant") + && !definition.getName().startsWith(SystemManagement.class.getCanonicalName() + ".findTenants"); + } } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java index a621e94fc..bf805f3dd 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java @@ -13,17 +13,16 @@ import java.util.Map; import org.eclipse.hawkbit.aspects.ExceptionMappingAspectHandler; import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.model.helper.AfterTransactionCommitExecutorHolder; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.model.helper.CacheManagerHolder; -import org.eclipse.hawkbit.repository.model.helper.PollConfigurationHelper; import org.eclipse.hawkbit.repository.model.helper.SecurityTokenGeneratorHolder; import org.eclipse.hawkbit.repository.model.helper.SystemManagementHolder; import org.eclipse.hawkbit.repository.model.helper.TenantAwareHolder; import org.eclipse.hawkbit.security.SecurityTokenGenerator; import org.eclipse.hawkbit.tenancy.TenantAware; -import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration; -import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -39,10 +38,6 @@ import org.springframework.validation.beanvalidation.MethodValidationPostProcess /** * General configuration for the SP Repository. * - * - * - * - * */ @EnableJpaRepositories(basePackages = { "org.eclipse.hawkbit.repository" }) @EnableTransactionManagement @@ -54,21 +49,13 @@ import org.springframework.validation.beanvalidation.MethodValidationPostProcess public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { /** - * @return simple default {@link AsyncUncaughtExceptionHandler} - * implementation - * @Bean public SimpleAsyncUncaughtExceptionHandler - * simpleAsyncUncaughtExceptionHandler() { return new - * SimpleAsyncUncaughtExceptionHandler(); } - * - * /** - * @return the {@link PollConfigurationHelper} singleton bean which holds - * the polling and polling overdue configuration and make it - * accessible in beans which cannot not be autowired or retrieve - * environment variables due {@link EnvironmentAware} interface. + * @return the {@link TenantConfigurationManagement} singleton bean which + * make it accessible in beans which cannot access the service + * directly, e.g. JPA entities. */ @Bean - public PollConfigurationHelper pollConfigurationHelper() { - return PollConfigurationHelper.getInstance(); + public TenantConfigurationManagement tenantConfigurationManagement() { + return TenantConfigurationManagement.getInstance(); } /** @@ -112,6 +99,16 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { return CacheManagerHolder.getInstance(); } + /** + * + * @return the singleton instance of the + * {@link AfterTransactionCommitExecutorHolder} + */ + @Bean + public AfterTransactionCommitExecutorHolder afterTransactionCommitExecutorHolder() { + return AfterTransactionCommitExecutorHolder.getInstance(); + } + /** * Defines the validation processor bean. * @@ -150,7 +147,7 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { @Override protected Map getVendorProperties() { - final Map properties = new HashMap(); + final Map properties = new HashMap<>(); // Turn off dynamic weaving to disable LTW lookup in static weaving mode properties.put("eclipselink.weaving", "false"); // needed for reports @@ -174,5 +171,4 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { public PlatformTransactionManager transactionManager() { return new MultiTenantJpaTransactionManager(); } - } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/cache/CacheKeys.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/cache/CacheKeys.java index 8eca2744a..9cb53a317 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/cache/CacheKeys.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/cache/CacheKeys.java @@ -28,6 +28,8 @@ public final class CacheKeys { * event. */ public static final String DOWNLOAD_PROGRESS_PERCENT = "download.progress.percent"; + public static final String ROLLOUT_GROUP_CREATED = "rollout.group.created"; + public static final String ROLLOUT_GROUP_TOTAL = "rollout.group.total"; /** * utility class only private constructor. diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/cache/CacheWriteNotify.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/cache/CacheWriteNotify.java index 812f4ca85..bf690e07e 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/cache/CacheWriteNotify.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/cache/CacheWriteNotify.java @@ -9,8 +9,10 @@ package org.eclipse.hawkbit.cache; import org.eclipse.hawkbit.eventbus.event.DownloadProgressEvent; +import org.eclipse.hawkbit.eventbus.event.RolloutGroupCreatedEvent; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.ActionStatus; +import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.tenancy.TenantAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.Cache; @@ -70,6 +72,43 @@ public class CacheWriteNotify { eventBus.post(new DownloadProgressEvent(tenantAware.getCurrentTenant(), statusId, progressPercent)); } + /** + * Writes the {@link CacheKeys#ROLLOUT_GROUP_CREATED} and + * {@link CacheKeys#ROLLOUT_GROUP_TOTAL} into the cache and notfies the + * {@link EventBus} with a {@link RolloutGroupCreatedEvent}. + * + * @param revision + * the revision of the event + * @param rolloutId + * the ID of the rollout the group has been created + * @param rolloutGroupId + * the ID of the rollout group which has been created + * @param totalRolloutGroup + * the total number of rollout groups for this rollout + * @param createdRolloutGroup + * the number of already created groups of the rollout + */ + public void rolloutGroupCreated(final long revision, final Long rolloutId, final Long rolloutGroupId, + final int totalRolloutGroup, final int createdRolloutGroup) { + + final Cache cache = cacheManager.getCache(Rollout.class.getName()); + final String cacheKeyGroupTotal = CacheKeys.entitySpecificCacheKey(String.valueOf(rolloutId), + CacheKeys.ROLLOUT_GROUP_TOTAL); + final String cacheKeyGroupCreated = CacheKeys.entitySpecificCacheKey(String.valueOf(rolloutId), + CacheKeys.ROLLOUT_GROUP_CREATED); + if (createdRolloutGroup < totalRolloutGroup) { + cache.put(cacheKeyGroupTotal, totalRolloutGroup); + cache.put(cacheKeyGroupCreated, createdRolloutGroup); + } else { + // in case we reached progress 100 delete the cache value again + // because otherwise he will keep there forever + cache.evict(cacheKeyGroupTotal); + cache.evict(cacheKeyGroupCreated); + } + eventBus.post(new RolloutGroupCreatedEvent(tenantAware.getCurrentTenant(), revision, rolloutId, rolloutGroupId, + totalRolloutGroup, createdRolloutGroup)); + } + /** * @param cacheManager * the cacheManager to set diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/EntityChangeEventListener.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/EntityChangeEventListener.java index e1ff377e5..b25a72fbc 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/EntityChangeEventListener.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/EntityChangeEventListener.java @@ -144,4 +144,5 @@ public class EntityChangeEventListener { private boolean isTargetInfoNew(final Object targetInfo) { return ((TargetInfo) targetInfo).isNew(); } + } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/EntityPropertyChangeListener.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/EntityPropertyChangeListener.java new file mode 100644 index 000000000..8b632ee6a --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/EntityPropertyChangeListener.java @@ -0,0 +1,107 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.eventbus; + +import java.util.Map; +import java.util.stream.Collectors; + +import org.eclipse.hawkbit.eventbus.event.AbstractPropertyChangeEvent; +import org.eclipse.hawkbit.eventbus.event.ActionCreatedEvent; +import org.eclipse.hawkbit.eventbus.event.ActionPropertyChangeEvent; +import org.eclipse.hawkbit.eventbus.event.RolloutGroupPropertyChangeEvent; +import org.eclipse.hawkbit.eventbus.event.RolloutPropertyChangeEvent; +import org.eclipse.hawkbit.executor.AfterTransactionCommitExecutor; +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.BaseEntity; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.helper.AfterTransactionCommitExecutorHolder; +import org.eclipse.hawkbit.repository.model.helper.EventBusHolder; +import org.eclipse.persistence.descriptors.DescriptorEvent; +import org.eclipse.persistence.descriptors.DescriptorEventAdapter; +import org.eclipse.persistence.internal.sessions.ObjectChangeSet; +import org.eclipse.persistence.queries.UpdateObjectQuery; +import org.eclipse.persistence.sessions.changesets.DirectToFieldChangeRecord; + +import com.google.common.eventbus.EventBus; + +/** + * Listens to change in property values of an entity. + * + */ +public class EntityPropertyChangeListener extends DescriptorEventAdapter { + /* + * (non-Javadoc) + * + * @see + * org.eclipse.persistence.descriptors.DescriptorEventAdapter#postInsert + * (org.eclipse.persistence.descriptors.DescriptorEvent) + */ + @Override + public void postInsert(final DescriptorEvent event) { + if (event.getObject().getClass().equals(Action.class)) { + final Action action = (Action) event.getObject(); + if (action.getRollout() != null) { + final EventBus eventBus = getEventBus(); + final AfterTransactionCommitExecutor afterCommit = getAfterTransactionCommmitExecutor(); + afterCommit.afterCommit(() -> eventBus.post(new ActionCreatedEvent(action))); + } + } + + } + + /* + * (non-Javadoc) + * + * @see + * org.eclipse.persistence.descriptors.DescriptorEventAdapter#postUpdate + * (org.eclipse.persistence.descriptors.DescriptorEvent) + */ + @Override + public void postUpdate(final DescriptorEvent event) { + if (event.getObject().getClass().equals(Action.class)) { + getAfterTransactionCommmitExecutor().afterCommit( + () -> getEventBus() + .post(new ActionPropertyChangeEvent((Action) event.getObject(), getChangeSet(Action.class, + event)))); + } else if (event.getObject().getClass().equals(Rollout.class)) { + getAfterTransactionCommmitExecutor().afterCommit( + () -> getEventBus().post( + new RolloutPropertyChangeEvent((Rollout) event.getObject(), getChangeSet(Rollout.class, + event)))); + } else if (event.getObject().getClass().equals(RolloutGroup.class)) { + getAfterTransactionCommmitExecutor().afterCommit( + () -> getEventBus().post( + new RolloutGroupPropertyChangeEvent((RolloutGroup) event.getObject(), getChangeSet( + RolloutGroup.class, event)))); + } + } + + private Map.Values> getChangeSet( + final Class clazz, final DescriptorEvent event) { + final T rolloutGroup = clazz.cast(event.getObject()); + final ObjectChangeSet changeSet = ((UpdateObjectQuery) event.getQuery()).getObjectChangeSet(); + return changeSet + .getChanges() + .stream() + .filter(record -> record instanceof DirectToFieldChangeRecord) + .map(record -> (DirectToFieldChangeRecord) record) + .collect( + Collectors.toMap(record -> record.getAttribute(), record -> new AbstractPropertyChangeEvent( + rolloutGroup, null).new Values(record.getOldValue(), record.getNewValue()))); + } + + private AfterTransactionCommitExecutor getAfterTransactionCommmitExecutor() { + return AfterTransactionCommitExecutorHolder.getInstance().getAfterCommit(); + } + + private EventBus getEventBus() { + return EventBusHolder.getInstance().getEventBus(); + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/AbstractPropertyChangeEvent.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/AbstractPropertyChangeEvent.java new file mode 100644 index 000000000..8a596eeb5 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/AbstractPropertyChangeEvent.java @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.eventbus.event; + +import java.util.Map; + +import org.eclipse.hawkbit.repository.model.BaseEntity; + +/** + * Property change event. + * + * @param + */ +public class AbstractPropertyChangeEvent extends AbstractBaseEntityEvent { + + private static final long serialVersionUID = -3671601415138242311L; + private final transient Map changeSet; + + /** + * Initialize base entity and property changed with old and new value. + * + * @param baseEntity + * entity changed + * @param changeSetValues + * details of properties changed and old value and new value of + * the changed properties + */ + public AbstractPropertyChangeEvent(final E baseEntity, final Map changeSetValues) { + super(baseEntity); + this.changeSet = changeSetValues; + } + + /** + * @return the changeSet + */ + public Map getChangeSet() { + return changeSet; + } + + /** + * Carries old value and new value of a property . + * + */ + public class Values { + private final Object oldValue; + private final Object newValue; + + /** + * Initialize old value and new changes value of property. + * + * @param oldValue + * old value before change + * @param newValue + * new value after change + */ + public Values(final Object oldValue, final Object newValue) { + super(); + this.oldValue = oldValue; + this.newValue = newValue; + } + + /** + * @return the oldValue + */ + public Object getOldValue() { + return oldValue; + } + + /** + * @return the newValue + */ + public Object getNewValue() { + return newValue; + } + + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/ActionCreatedEvent.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/ActionCreatedEvent.java new file mode 100644 index 000000000..5efdca2a9 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/ActionCreatedEvent.java @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.eventbus.event; + +import org.eclipse.hawkbit.repository.model.Action; + +/** + * Defines the {@link AbstractBaseEntityEvent} of creating a new {@link Action}. + */ +public class ActionCreatedEvent extends AbstractBaseEntityEvent { + private static final long serialVersionUID = 181780358321768629L; + + /** + * @param action + */ + public ActionCreatedEvent(final Action action) { + super(action); + } + +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/ActionPropertyChangeEvent.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/ActionPropertyChangeEvent.java new file mode 100644 index 000000000..822f9bbeb --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/ActionPropertyChangeEvent.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.eventbus.event; + +import java.util.Map; + +import org.eclipse.hawkbit.repository.model.Action; + +/** + * Defines the {@link AbstractPropertyChangeEvent} of {@link Action}. + */ +public class ActionPropertyChangeEvent extends AbstractPropertyChangeEvent { + private static final long serialVersionUID = 181780358321768629L; + + /** + * @param action + * @param changeSetValues + */ + public ActionPropertyChangeEvent(final Action action, + final Map.Values> changeSetValues) { + super(action, changeSetValues); + } + +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/EventMerger.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/EventMerger.java new file mode 100644 index 000000000..fe4cf4b34 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/EventMerger.java @@ -0,0 +1,185 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.eventbus.event; + +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.hawkbit.eventbus.EventSubscriber; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; + +import com.google.common.eventbus.AllowConcurrentEvents; +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; + +/** + * Collects and merges fine grained events to more generic events and push them + * in a fixed delay on the events bus. This helps for code which are not + * interested in all fine grained events, e.g. UI code. The UI code is not + * interested in handling the flood of events, so collecting the events and + * merge them to one event together and post them in a fixed interval is easier + * to consume e.g. for push notifcations on UI. + * + * @author Michael Hirsch + * + */ +@EventSubscriber +public class EventMerger { + + private static final Set rolloutEvents = ConcurrentHashMap.newKeySet(); + private static final Set rolloutGroupEvents = ConcurrentHashMap.newKeySet(); + + @Autowired + private EventBus eventBus; + + /** + * Checks if there are events to publish in the fixed interval. + */ + @Scheduled(initialDelay = 10000, fixedDelay = 2000) + public void rolloutEventScheduler() { + final Iterator rolloutIterator = rolloutEvents.iterator(); + while (rolloutIterator.hasNext()) { + final RolloutEventKey eventKey = rolloutIterator.next(); + eventBus.post(new RolloutChangeEvent(1, eventKey.tenant, eventKey.rolloutId)); + rolloutIterator.remove(); + } + + final Iterator rolloutGroupIterator = rolloutGroupEvents.iterator(); + while (rolloutGroupIterator.hasNext()) { + final RolloutEventKey eventKey = rolloutGroupIterator.next(); + eventBus.post(new RolloutGroupChangeEvent(1, eventKey.tenant, eventKey.rolloutId, eventKey.rolloutGroupId)); + rolloutGroupIterator.remove(); + } + } + + /** + * Called by the event bus to retrieve all necessary events to collect and + * merge. + * + * @param event + * the event on the event bus + */ + @Subscribe + @AllowConcurrentEvents + public void onEvent(final Event event) { + Long rolloutId = null; + Long rolloutGroupId = null; + if (event instanceof ActionCreatedEvent) { + rolloutId = getRolloutId(((ActionCreatedEvent) event).getEntity().getRollout()); + rolloutGroupId = getRolloutGroupId(((ActionCreatedEvent) event).getEntity().getRolloutGroup()); + } else if (event instanceof ActionPropertyChangeEvent) { + rolloutId = getRolloutId(((ActionPropertyChangeEvent) event).getEntity().getRollout()); + rolloutGroupId = getRolloutGroupId(((ActionPropertyChangeEvent) event).getEntity().getRolloutGroup()); + } else if (event instanceof RolloutPropertyChangeEvent) { + rolloutId = ((RolloutPropertyChangeEvent) event).getEntity().getId(); + } else if (event instanceof RolloutGroupCreatedEvent) { + rolloutId = ((RolloutGroupCreatedEvent) event).getRolloutId(); + rolloutGroupId = ((RolloutGroupCreatedEvent) event).getRolloutGroupId(); + } else if (event instanceof RolloutGroupPropertyChangeEvent) { + final RolloutGroup rolloutGroup = ((RolloutGroupPropertyChangeEvent) event).getEntity(); + rolloutId = rolloutGroup.getRollout().getId(); + rolloutGroupId = rolloutGroup.getId(); + } + + if (rolloutId != null) { + rolloutEvents.add(new RolloutEventKey(rolloutId, event.getTenant())); + if (rolloutGroupId != null) { + rolloutGroupEvents.add(new RolloutEventKey(rolloutId, rolloutGroupId, event.getTenant())); + } + } + } + + private Long getRolloutGroupId(final RolloutGroup rolloutGroup) { + if (rolloutGroup != null) { + return rolloutGroup.getId(); + } + return null; + } + + private Long getRolloutId(final Rollout rollout) { + if (rollout != null) { + return rollout.getId(); + } + return null; + } + + /** + * The rollout key in the concurrent set to be hold. + * + * @author Michael Hirsch + * + */ + private static final class RolloutEventKey { + private final Long rolloutId; + private final String tenant; + private final Long rolloutGroupId; + + private RolloutEventKey(final Long rolloutId, final Long rolloutGroupId, final String tenant) { + this.rolloutGroupId = rolloutGroupId; + this.rolloutId = rolloutId; + this.tenant = tenant; + } + + private RolloutEventKey(final Long rolloutId, final String tenant) { + this(rolloutId, null, tenant); + } + + @Override + public int hashCode() {// NOSONAR - as this is generated + final int prime = 31; + int result = 1; + result = prime * result + ((rolloutGroupId == null) ? 0 : rolloutGroupId.hashCode()); + result = prime * result + ((rolloutId == null) ? 0 : rolloutId.hashCode()); + result = prime * result + ((tenant == null) ? 0 : tenant.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) {// NOSONAR - as this is + // generated + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final RolloutEventKey other = (RolloutEventKey) obj; + if (rolloutGroupId == null) { + if (other.rolloutGroupId != null) { + return false; + } + } else if (!rolloutGroupId.equals(other.rolloutGroupId)) { + return false; + } + if (rolloutId == null) { + if (other.rolloutId != null) { + return false; + } + } else if (!rolloutId.equals(other.rolloutId)) { + return false; + } + if (tenant == null) { + if (other.tenant != null) { + return false; + } + } else if (!tenant.equals(other.tenant)) { + return false; + } + return true; + } + + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutChangeEvent.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutChangeEvent.java new file mode 100644 index 000000000..8f15602b5 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutChangeEvent.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.eventbus.event; + +import org.eclipse.hawkbit.eventbus.event.AbstractEvent; + +/** + * Event declaration for the UI to notify the UI that a rollout has been + * changed. + * + * @author Michael Hirsch + * + */ +public class RolloutChangeEvent extends AbstractEvent { + + private final Long rolloutId; + + /** + * @param revision + * the revision of the event + * @param tenant + * the tenant of the event + * @param rolloutId + * the ID of the rollout which has been changed + */ + public RolloutChangeEvent(final long revision, final String tenant, final Long rolloutId) { + super(revision, tenant); + this.rolloutId = rolloutId; + } + + public Long getRolloutId() { + return rolloutId; + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutGroupChangeEvent.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutGroupChangeEvent.java new file mode 100644 index 000000000..cbda44ef5 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutGroupChangeEvent.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.eventbus.event; + +/** + * Event declaration for the UI to notify the UI that a rollout has been + * changed. + * + * @author Michael Hirsch + * + */ +public class RolloutGroupChangeEvent extends AbstractEvent { + + private final Long rolloutId; + private final Long rolloutGroupId; + + /** + * @param revision + * the revision of the event + * @param tenant + * the tenant of the event + * @param rolloutId + * the ID of the rollout which has been changed + * @param rolloutGroupId + * the ID of the rollout group which has been changed + */ + public RolloutGroupChangeEvent(final long revision, final String tenant, final Long rolloutId, + final Long rolloutGroupId) { + super(revision, tenant); + this.rolloutId = rolloutId; + this.rolloutGroupId = rolloutGroupId; + } + + public Long getRolloutId() { + return rolloutId; + } + + public Long getRolloutGroupId() { + return rolloutGroupId; + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutGroupCreatedEvent.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutGroupCreatedEvent.java new file mode 100644 index 000000000..465ff6088 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutGroupCreatedEvent.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.eventbus.event; + +/** + * Event definition which is been published in case a rollout group has been + * created for a specific rollout. + * + * @author Michael Hirsch + * + */ +public class RolloutGroupCreatedEvent extends AbstractDistributedEvent { + + private static final long serialVersionUID = 1L; + private final Long rolloutId; + private final Long rolloutGroupId; + private final int totalRolloutGroup; + private final int createdRolloutGroup; + + /** + * Creating a new rollout group created event for a specific rollout. + * + * @param tenant + * the tenant of this event + * @param revision + * the revision of the event + * @param rolloutId + * the ID of the rollout the group has been created + * @param totalRolloutGroup + * the total number of rollout groups for this rollout + * @param createdRolloutGroup + * the number of already created groups of the rollout + */ + public RolloutGroupCreatedEvent(final String tenant, final long revision, final Long rolloutId, + final Long rolloutGroupId, final int totalRolloutGroup, final int createdRolloutGroup) { + super(revision, tenant); + this.rolloutId = rolloutId; + this.rolloutGroupId = rolloutGroupId; + this.totalRolloutGroup = totalRolloutGroup; + this.createdRolloutGroup = createdRolloutGroup; + + } + + public Long getRolloutId() { + return rolloutId; + } + + public int getTotalRolloutGroup() { + return totalRolloutGroup; + } + + public int getCreatedRolloutGroup() { + return createdRolloutGroup; + } + + public Long getRolloutGroupId() { + return rolloutGroupId; + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutGroupPropertyChangeEvent.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutGroupPropertyChangeEvent.java new file mode 100644 index 000000000..6caa46547 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutGroupPropertyChangeEvent.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.eventbus.event; + +import java.util.Map; + +import org.eclipse.hawkbit.repository.model.RolloutGroup; + +/** + * Defines the {@link AbstractPropertyChangeEvent} of {@link RolloutGroup}. + */ +public class RolloutGroupPropertyChangeEvent extends AbstractPropertyChangeEvent { + + private static final long serialVersionUID = 4026477044419472686L; + + /** + * + * @param rolloutGroup + * @param changeSetValues + */ + public RolloutGroupPropertyChangeEvent(final RolloutGroup rolloutGroup, + final Map.Values> changeSetValues) { + super(rolloutGroup, changeSetValues); + } + +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutPropertyChangeEvent.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutPropertyChangeEvent.java new file mode 100644 index 000000000..5f72307df --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/RolloutPropertyChangeEvent.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.eventbus.event; + +import java.util.Map; + +import org.eclipse.hawkbit.repository.model.Rollout; + +/** + * Defines the {@link AbstractPropertyChangeEvent} of {@link Rollout}. + */ +public class RolloutPropertyChangeEvent extends AbstractPropertyChangeEvent { + private static final long serialVersionUID = 1056221355466373514L; + + /** + * + * @param rollout + * @param changeSetValues + */ + public RolloutPropertyChangeEvent(final Rollout rollout, + final Map.Values> changeSetValues) { + super(rollout, changeSetValues); + } + +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/TargetAssignDistributionSetEvent.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/TargetAssignDistributionSetEvent.java index 8c13800c9..b286ac6ea 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/TargetAssignDistributionSetEvent.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/TargetAssignDistributionSetEvent.java @@ -19,7 +19,7 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule; * * */ -public class TargetAssignDistributionSetEvent { +public class TargetAssignDistributionSetEvent extends AbstractEvent { private final Collection softwareModules; private final String controllerId; @@ -29,6 +29,10 @@ public class TargetAssignDistributionSetEvent { /** * Creates a new {@link TargetAssignDistributionSetEvent}. * + * @param revision + * the revision of the event + * @param tenant + * the tenant of the event * @param controllerId * the ID of the controller * @param actionId @@ -38,8 +42,9 @@ public class TargetAssignDistributionSetEvent { * @param targetAdress * the targetAdress of the target */ - public TargetAssignDistributionSetEvent(final String controllerId, final Long actionId, - final Collection softwareModules, final URI targetAdress) { + public TargetAssignDistributionSetEvent(final long revision, final String tenant, final String controllerId, + final Long actionId, final Collection softwareModules, final URI targetAdress) { + super(revision, tenant); this.controllerId = controllerId; this.actionId = actionId; this.softwareModules = softwareModules; diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/TargetCreatedEvent.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/TargetCreatedEvent.java index 3e80e45a5..7e89565b1 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/TargetCreatedEvent.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/eventbus/event/TargetCreatedEvent.java @@ -14,8 +14,6 @@ import org.eclipse.hawkbit.repository.model.Target; * Defines the {@link AbstractBaseEntityEvent} of creating a new {@link Target}. * * - * - * */ public class TargetCreatedEvent extends AbstractBaseEntityEvent { diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/report/model/DataReportSeries.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/report/model/DataReportSeries.java index 97623d9d0..39c8f5723 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/report/model/DataReportSeries.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/report/model/DataReportSeries.java @@ -8,19 +8,22 @@ */ package org.eclipse.hawkbit.report.model; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; /** * A data report series which contains a list of {@link DataReportSeriesItem}. - * + * * * * @param * the type of the report series item */ -public class DataReportSeries extends AbstractReportSeries { +public class DataReportSeries extends AbstractReportSeries { + + private static final long serialVersionUID = 1L; private final List> data = new ArrayList<>(); @@ -46,7 +49,7 @@ public class DataReportSeries extends AbstractReportSeries { } private void setData(final List> values) { - this.data.clear(); + data.clear(); data.addAll(values); } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/report/model/DataReportSeriesItem.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/report/model/DataReportSeriesItem.java index 3369ef1c0..492e27e1e 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/report/model/DataReportSeriesItem.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/report/model/DataReportSeriesItem.java @@ -8,6 +8,8 @@ */ package org.eclipse.hawkbit.report.model; +import java.io.Serializable; + /** * An data report series item which contains a type and a value. * @@ -16,8 +18,9 @@ package org.eclipse.hawkbit.report.model; * @param * the type of the report series item */ -public class DataReportSeriesItem { +public class DataReportSeriesItem implements Serializable { + private static final long serialVersionUID = 1L; private final T type; private final Number data; diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/report/model/InnerOuterDataReportSeries.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/report/model/InnerOuterDataReportSeries.java index 1568847de..b63935b39 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/report/model/InnerOuterDataReportSeries.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/report/model/InnerOuterDataReportSeries.java @@ -8,6 +8,8 @@ */ package org.eclipse.hawkbit.report.model; +import java.io.Serializable; + /** * A double data series which contains an inner and an outer series ideal for * showing donut charts. @@ -17,7 +19,7 @@ package org.eclipse.hawkbit.report.model; * @param * The type parameter for the report series data */ -public class InnerOuterDataReportSeries { +public class InnerOuterDataReportSeries { private final DataReportSeries innerSeries; private final DataReportSeries outerSeries; diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/report/model/ListReportSeries.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/report/model/ListReportSeries.java index 55125e046..79c496480 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/report/model/ListReportSeries.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/report/model/ListReportSeries.java @@ -21,7 +21,7 @@ import java.util.List; */ public class ListReportSeries extends AbstractReportSeries { - private final List data = new ArrayList(); + private final List data = new ArrayList<>(); /** * @param name diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/ActionRepository.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/ActionRepository.java index a97799ab4..8221fa0ca 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/ActionRepository.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/ActionRepository.java @@ -12,9 +12,13 @@ import java.util.Collection; import java.util.List; import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus; import org.springframework.cache.annotation.CacheEvict; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -30,26 +34,12 @@ import org.springframework.transaction.annotation.Transactional; /** * {@link Action} repository. * - * - * - * - * */ @Transactional(readOnly = true) public interface ActionRepository extends BaseEntityRepository, JpaSpecificationExecutor { - - /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.CrudRepository#findAll() - */ - @Override - @EntityGraph(value = "Action.all", type = EntityGraphType.LOAD) - Iterable findAll(); - /** * Retrieves an Action with all lazy attributes. - * + * * @param actionId * the ID of the action * @return the found {@link Action} @@ -67,13 +57,12 @@ public interface ActionRepository extends BaseEntityRepository, Jp * the {@link DistributionSet} on which will be filtered * @return the found {@link Action}s */ - @EntityGraph(value = "Action.all", type = EntityGraphType.LOAD) Page findByDistributionSet(final Pageable pageable, final DistributionSet ds); /** * Retrieves all {@link Action}s which are referring the given * {@link Target}. - * + * * @param pageable * page parameters * @param target @@ -84,8 +73,9 @@ public interface ActionRepository extends BaseEntityRepository, Jp /** * Retrieves all {@link Action}s which are active and referring the given - * {@link Target} in a specified order. - * + * {@link Target} in a specified order. Loads also the lazy + * {@link Action#getDistributionSet()} field. + * * @param pageable * page parameters * @param target @@ -125,14 +115,13 @@ public interface ActionRepository extends BaseEntityRepository, Jp * @return the found {@link UpdateAction}s */ @Query("Select a from Action a where a.target = :target and a.distributionSet = :ds order by a.id") - @EntityGraph(value = "Action.all", type = EntityGraphType.LOAD) Page findByTargetAndDistributionSet(final Pageable pageable, @Param("target") final Target target, @Param("ds") DistributionSet ds); /** * Retrieves all {@link Action}s of a specific target, without pagination * ordered by action ID. - * + * * @param target * to search for * @return a list of actions according to the searched target @@ -143,7 +132,7 @@ public interface ActionRepository extends BaseEntityRepository, Jp /** * Retrieves all {@link Action}s of a specific target and given active flag * ordered by action ID. - * + * * @param pageable * the pagination parameter * @param target @@ -159,8 +148,9 @@ public interface ActionRepository extends BaseEntityRepository, Jp /** * Retrieves all {@link Action}s of a specific target and given active flag - * ordered by action ID. - * + * ordered by action ID. Loads also the lazy + * {@link Action#getDistributionSet()} field. + * * @param target * to search for * @param active @@ -174,7 +164,7 @@ public interface ActionRepository extends BaseEntityRepository, Jp /** * Updates all {@link Action} to inactive for all targets with given ID. - * + * * @param keySet * the list of actions to set inactive * @param targetsIds @@ -187,6 +177,47 @@ public interface ActionRepository extends BaseEntityRepository, Jp void setToInactive(@Param("keySet") List keySet, @Param("targetsIds") List targetsIds); /** + * Switches the status of actions from one specific status into another, + * only if the actions are in a specific status. This should be a atomar + * operation. + * + * @param statusToSet + * the new status the actions should get + * @param targetIds + * the IDs of the targets of the actions which are affected + * @param active + * the active flag of the actions which should be affected + * @param currentStatus + * the current status of the actions which are affected + */ + @Modifying + @Transactional + @Query("UPDATE Action a SET a.status = :statusToSet WHERE a.target IN :targetsIds AND a.active = :active AND a.status = :currentStatus AND a.distributionSet.requiredMigrationStep = false") + void switchStatus(@Param("statusToSet") Action.Status statusToSet, @Param("targetsIds") List targetIds, + @Param("active") boolean active, @Param("currentStatus") Action.Status currentStatus); + + /** + * Switches the status of actions from one specific status into another, + * only if the actions are in a specific status. This should be a atomar + * operation. + * + * @param statusToSet + * the new status the actions should get + * @param rollout + * the rollout of the actions which are affected + * @param active + * the active flag of the actions which should be affected + * @param currentStatus + * the current status of the actions which are affected + */ + @Modifying + @Transactional + @Query("UPDATE Action a SET a.status = :statusToSet WHERE a.rollout = :rollout AND a.active = :active AND a.status = :currentStatus") + void switchStatus(@Param("statusToSet") Action.Status statusToSet, @Param("rollout") Rollout rollout, + @Param("active") boolean active, @Param("currentStatus") Action.Status currentStatus); + + /** + * * Retrieves all {@link Action}s which are active and referring to the given * target Ids and distribution set required migration step. * @@ -202,39 +233,159 @@ public interface ActionRepository extends BaseEntityRepository, Jp /** * Counts all {@link Action}s referring to the given target. - * + * * @param target * the target to count the {@link Action}s * @return the count of actions referring to the given target */ Long countByTarget(Target target); - /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.CrudRepository#save(java.lang. - * Iterable) - */ @Override @CacheEvict(value = "feedbackReceivedOverTime", allEntries = true) List save(Iterable entities); - /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.CrudRepository#save(java.lang.Object) - */ @Override @CacheEvict(value = "feedbackReceivedOverTime", allEntries = true) S save(S entity); /** * Counts all {@link Action}s referring to the given DistributionSet. - * + * * @param distributionSet * DistributionSet to count the {@link Action}s from - * @return the count of actions referring to the given target + * @return the count of actions referring to the given distributionSet */ Long countByDistributionSet(DistributionSet distributionSet); + + /** + * Counts all {@link Action}s referring to the given rollout. + * + * @param rollout + * the rollout to count the {@link Action}s from + * @return the count of actions referring to the given rollout + */ + Long countByRollout(Rollout rollout); + + /** + * Counts all actions referring to a given rollout and rolloutgroup which + * are currently not in the given status. An in-clause statement does not + * work with the spring-data, so this is specific usecase regarding to the + * rollout-management to find out actions which are not in specific states. + * + * @param rollout + * the rollout the actions are belong to + * @param rolloutGroup + * the rolloutgroup the actions are belong to + * @param notStatus1 + * the status the action should not have + * @param notStatus2 + * the status the action should not have + * @param notStatus3 + * the status the action should not have + * @return the count of actions referring the rollout and rolloutgroup and + * are not in given states + */ + Long countByRolloutAndRolloutGroupAndStatusNotAndStatusNotAndStatusNot(Rollout rollout, RolloutGroup rolloutGroup, + Status notStatus1, Status notStatus2, Status notStatus3); + + /** + * Counts all actions referring to a given rollout and rolloutgroup. + * + * @param rollout + * the rollout the actions belong to + * @param rolloutGroup + * the rolloutgroup the actions belong to + * @return the count of actions referring to a rollout and rolloutgroup + */ + Long countByRolloutAndRolloutGroup(Rollout rollout, RolloutGroup rolloutGroup); + + /** + * Counts all actions referring to a given rollout, rolloutgroup and status. + * + * @param rolloutId + * the ID of rollout the actions belong to + * @param rolloutGroupId + * the ID rolloutgroup the actions belong to + * @param status + * the status the actions should have + * @return the count of actions referring to a rollout, rolloutgroup and are + * in a given status + */ + Long countByRolloutIdAndRolloutGroupIdAndStatus(Long rolloutId, Long rolloutGroupId, Action.Status status); + + /** + * Retrieving all actions referring to a given rollout with a specific + * action as parent reference and a specific status. + * + * Finding all actions of a specific rolloutgroup parent relation. + * + * @param rollout + * the rollout the actions belong to + * @param rolloutGroupParent + * the parent rolloutgroup the actions should reference + * @param actionStatus + * the status the actions have + * @return the actions referring a specific rollout and a specific parent + * rolloutgroup in a specific status + */ + List findByRolloutAndRolloutGroupParentAndStatus(Rollout rollout, RolloutGroup rolloutGroupParent, + Status actionStatus); + + /** + * Retrieves all actions for a specific rollout and in a specific status. + * + * @param rollout + * the rollout the actions beglong to + * @param actionStatus + * the status of the actions + * @return the actions referring a specific rollout an in a specific status + */ + List findByRolloutAndStatus(Rollout rollout, Status actionStatus); + + /** + * Get list of objects which has details of status and count of targets in + * each status in specified rollout. + * + * @param rolloutId + * id of {@link Rollout} + * @return list of objects with status and target count + */ + @Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus( a.rollout.id, a.status , COUNT(a.target)) FROM Action a WHERE a.rollout.id IN ?1 GROUP BY a.rollout.id,a.status") + List getStatusCountByRolloutId(List rolloutId); + + /** + * Get list of objects which has details of status and count of targets in + * each status in specified rollout. + * + * @param rolloutId + * id of {@link Rollout} + * @return list of objects with status and target count + */ + @Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus( a.rollout.id, a.status , COUNT(a.target)) FROM Action a WHERE a.rollout.id = ?1 GROUP BY a.rollout.id,a.status") + List getStatusCountByRolloutId(Long rolloutId); + + /** + * Get list of objects which has details of status and count of targets in + * each status in specified rollout group. + * + * @param rolloutGroupId + * id of {@link RolloutGroup} + * @return list of objects with status and target count + */ + @Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus(a.rolloutGroup.id, a.status , COUNT(a.target)) FROM Action a WHERE a.rolloutGroup.id = ?1 GROUP BY a.rolloutGroup.id, a.status") + List getStatusCountByRolloutGroupId(Long rolloutGroupId); + + /** + * Get list of objects which has details of status and count of targets in + * each status in specified rollout group. + * + * @param rolloutGroupId + * list of id of {@link RolloutGroup} + * @return list of objects with status and target count + */ + @Query("SELECT NEW org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus(a.rolloutGroup.id, a.status , COUNT(a.target)) FROM Action a WHERE a.rolloutGroup.id IN ?1 GROUP BY a.rolloutGroup.id, a.status") + List getStatusCountByRolloutGroupId(List rolloutGroupId); + + // Asha-ends here + } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/ActionStatusRepository.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/ActionStatusRepository.java index 5e2800755..2705b9ac6 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/ActionStatusRepository.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/ActionStatusRepository.java @@ -21,45 +21,47 @@ import org.springframework.transaction.annotation.Transactional; /** * {@link ActionStatus} repository. * - * - * - * */ @Transactional(readOnly = true) public interface ActionStatusRepository extends BaseEntityRepository, JpaSpecificationExecutor { /** - * @param target + * Counts {@link ActionStatus} entries of given {@link Action} in + * repository. + * * @param action - * @return + * to count status entries + * @return number of actions in repository */ Long countByAction(Action action); /** + * Counts {@link ActionStatus} entries of given {@link Action} with given + * {@link Status} in repository. + * * @param action - * @param retrieved - * @return + * to count status entries + * @param status + * to filter for + * @return number of actions in repository */ - Long countByActionAndStatus(Action action, Status retrieved); + Long countByActionAndStatus(Action action, Status status); /** + * Retrieves all {@link ActionStatus} entries from repository of given + * {@link Action}. + * * @param pageReq + * parameters * @param action - * @return + * of the status entries + * @return pages list of {@link ActionStatus} entries */ Page findByAction(Pageable pageReq, Action action); /** - * @param pageReq - * @param action - * @return - */ - Page findByActionOrderByIdDesc(Pageable pageReq, Action action); - - /** - * Finds all status updates for the defined action and target order by - * {@link ActionStatus#getId()} desc including + * Finds all status updates for the defined action and target including * {@link ActionStatus#getMessages()}. * * @param pageReq @@ -71,6 +73,6 @@ public interface ActionStatusRepository * @return Page with found targets */ @EntityGraph(value = "ActionStatus.withMessages", type = EntityGraphType.LOAD) - Page getByActionOrderByIdDesc(Pageable pageReq, Action action); + Page getByAction(Pageable pageReq, Action action); } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java index 844bbd097..7131bbe0a 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/ControllerManagement.java @@ -15,7 +15,6 @@ import java.util.Map; import javax.persistence.EntityManager; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import javax.validation.constraints.NotNull; @@ -34,13 +33,13 @@ import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetInfo; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.repository.model.Target_; +import org.eclipse.hawkbit.repository.model.TenantConfiguration; +import org.eclipse.hawkbit.security.HawkbitSecurityProperties; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.hibernate.validator.constraints.NotEmpty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.bind.RelaxedPropertyResolver; -import org.springframework.context.EnvironmentAware; -import org.springframework.core.env.Environment; import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.repository.Modifying; import org.springframework.security.access.prepost.PreAuthorize; @@ -58,7 +57,7 @@ import org.springframework.validation.annotation.Validated; @Transactional(readOnly = true) @Validated @Service -public class ControllerManagement implements EnvironmentAware { +public class ControllerManagement { private static final Logger LOG = LoggerFactory.getLogger(ControllerManagement.class); private static final Logger LOG_DOS = LoggerFactory.getLogger("server-security.dos"); @@ -86,9 +85,35 @@ public class ControllerManagement implements EnvironmentAware { @Autowired private ActionStatusRepository actionStatusRepository; - private Integer maxCount = 1000; + @Autowired + private HawkbitSecurityProperties securityProperties; - private Integer maxAttributes = 100; + @Autowired + private TenantConfigurationRepository tenantConfigurationRepository; + + @Autowired + private TenantConfigurationManagement tenantConfigurationManagement; + + /** + * Retrieves all {@link SoftwareModule}s which are assigned to the given + * {@link DistributionSet}. + * + * @param distributionSet + * the distribution set which should be assigned to the returned + * {@link SoftwareModule}s + * @return a list of {@link SoftwareModule}s assigned to given + * {@code distributionSet} + */ + @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) + public String findPollingTime() { + final TenantConfigurationKey configurationKey = TenantConfigurationKey.POLLING_TIME_INTERVAL; + final Class propertyType = String.class; + tenantConfigurationManagement.validateTenantConfigurationDataType(configurationKey, propertyType); + final TenantConfiguration tenantConfiguration = tenantConfigurationRepository + .findByKey(configurationKey.getKeyName()); + return tenantConfigurationManagement + .buildTenantConfigurationValueByKey(configurationKey, propertyType, tenantConfiguration).getValue(); + } /** * Refreshes the time of the last time the controller has been connected to @@ -212,14 +237,9 @@ public class ControllerManagement implements EnvironmentAware { @Modifying @Transactional @PreAuthorize(SpringEvalExpressions.IS_CONTROLLER) - public Target findOrRegisterTargetIfItDoesNotexist(@NotNull final String targetid, final URI address) { - final Specification spec = new Specification() { - @Override - public Predicate toPredicate(final Root targetRoot, final CriteriaQuery query, - final CriteriaBuilder cb) { - return cb.equal(targetRoot.get(Target_.controllerId), targetid); - } - }; + public Target findOrRegisterTargetIfItDoesNotexist(@NotEmpty final String targetid, final URI address) { + final Specification spec = (targetRoot, query, cb) -> cb.equal(targetRoot.get(Target_.controllerId), + targetid); Target target = targetRepository.findOne(spec); @@ -229,9 +249,9 @@ public class ControllerManagement implements EnvironmentAware { target.setName(targetid); return targetManagement.createTarget(target, TargetUpdateStatus.REGISTERED, System.currentTimeMillis(), address); - } else { - return updateLastTargetQuery(target.getTargetInfo(), address).getTarget(); } + + return updateLastTargetQuery(target.getTargetInfo(), address).getTarget(); } /** @@ -385,15 +405,16 @@ public class ControllerManagement implements EnvironmentAware { } private void checkForToManyStatusEntries(final Action action) { - if (maxCount > 0) { + if (securityProperties.getDos().getMaxStatusEntriesPerAction() > 0) { final Long statusCount = actionStatusRepository.countByAction(action); - if (statusCount >= maxCount) { + if (statusCount >= securityProperties.getDos().getMaxStatusEntriesPerAction()) { LOG_DOS.error( "Potential denial of service (DOS) attack identfied. More status entries in the system than permitted ({})!", - maxCount); - throw new ToManyStatusEntriesException(String.valueOf(maxCount)); + securityProperties.getDos().getMaxStatusEntriesPerAction()); + throw new ToManyStatusEntriesException( + String.valueOf(securityProperties.getDos().getMaxStatusEntriesPerAction())); } } } @@ -442,10 +463,12 @@ public class ControllerManagement implements EnvironmentAware { target.getTargetInfo().getControllerAttributes().putAll(data); - if (target.getTargetInfo().getControllerAttributes().size() > maxAttributes) { + if (target.getTargetInfo().getControllerAttributes().size() > securityProperties.getDos() + .getMaxAttributeEntriesPerTarget()) { LOG_DOS.info("Target tries to insert more than the allowed number of entries ({}). DOS attack anticipated!", - maxAttributes); - throw new ToManyAttributeEntriesException(String.valueOf(maxAttributes)); + securityProperties.getDos().getMaxAttributeEntriesPerTarget()); + throw new ToManyAttributeEntriesException( + String.valueOf(securityProperties.getDos().getMaxAttributeEntriesPerTarget())); } target.getTargetInfo().setLastTargetQuery(System.currentTimeMillis()); @@ -453,19 +476,6 @@ public class ControllerManagement implements EnvironmentAware { return targetRepository.save(target); } - /* - * (non-Javadoc) - * - * @see org.springframework.context.EnvironmentAware#setEnvironment(org. - * springframework.core.env. Environment) - */ - @Override - public void setEnvironment(final Environment environment) { - final RelaxedPropertyResolver env = new RelaxedPropertyResolver(environment, "hawkbit.server."); - maxCount = env.getProperty("security.dos.maxStatusEntriesPerAction", Integer.class, 1000); - maxAttributes = env.getProperty("security.dos.maxAttributeEntriesPerTarget", Integer.class, 100); - } - /** * Registers retrieved status for given {@link Target} and {@link Action} if * it does not exist yet. diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java index b8b8610cd..77f71bebf 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java @@ -23,7 +23,6 @@ import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Join; import javax.persistence.criteria.JoinType; import javax.persistence.criteria.ListJoin; -import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import javax.validation.constraints.NotNull; @@ -46,6 +45,9 @@ import org.eclipse.hawkbit.repository.model.Action_; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSet_; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.Rollout_; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.model.Target; @@ -228,7 +230,7 @@ public class DeploymentManagement { String.format("no %s with id %d found", DistributionSet.class.getSimpleName(), dsID)); } - return assignDistributionSetToTargets(set, targets); + return assignDistributionSetToTargets(set, targets, null, null); } /** @@ -237,8 +239,45 @@ public class DeploymentManagement { * * @param dsID * the ID of the distribution set to assign - * @param targetsWithActionType + * @param targets * a list of all targets and their action type + * @param rollout + * the rollout for this assignment + * @param rolloutgroup + * the rolloutgroup for this assignment + * @return the assignment result + * + * @throw IncompleteDistributionSetException if mandatory + * {@link SoftwareModuleType} are not assigned as define by the + * {@link DistributionSetType}. + */ + @Modifying + @Transactional + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET) + @CacheEvict(value = { "distributionUsageAssigned" }, allEntries = true) + public DistributionSetAssignmentResult assignDistributionSet(@NotNull final Long dsID, + final List targets, final Rollout rollout, final RolloutGroup rolloutGroup) { + final DistributionSet set = distributoinSetRepository.findOne(dsID); + if (set == null) { + throw new EntityNotFoundException( + String.format("no %s with id %d found", DistributionSet.class.getSimpleName(), dsID)); + } + + return assignDistributionSetToTargets(set, targets, rollout, rolloutGroup); + } + + /** + * method assigns the {@link DistributionSet} to all {@link Target}s by + * their IDs with a specific {@link ActionType} and {@code forcetime}. + * + * @param dsID + * the ID of the distribution set to assign + * @param targets + * a list of all targets and their action type + * @param rollout + * the rollout for this assignment + * @param rolloutgroup + * the rolloutgroup for this assignment * @return the assignment result * * @throw IncompleteDistributionSetException if mandatory @@ -246,7 +285,8 @@ public class DeploymentManagement { * {@link DistributionSetType}. */ private DistributionSetAssignmentResult assignDistributionSetToTargets(@NotNull final DistributionSet set, - final List targetsWithActionType) { + final List targetsWithActionType, final Rollout rollout, + final RolloutGroup rolloutGroup) { if (!set.isComplete()) { throw new IncompleteDistributionSetException( @@ -262,13 +302,10 @@ public class DeploymentManagement { .collect(Collectors.toMap(TargetWithActionType::getTargetId, Function.identity())); // split tIDs length into max entries in-statement because many database - // have constraint of - // max entries in in-statements e.g. Oracle with maximum 1000 elements, - // so we need to split - // the entries here and execute multiple statements - // we take the target only into account if the requested operation is no - // duplicate of a - // previous one + // have constraint of max entries in in-statements e.g. Oracle with + // maximum 1000 elements, so we need to split the entries here and + // execute multiple statements we take the target only into account if + // the requested operation is no duplicate of a previous one final List targets = Lists.partition(controllerIDs, Constants.MAX_ENTRIES_IN_STATEMENT).stream() .map(ids -> targetRepository .findAll(TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot(ids, set.getId()))) @@ -286,15 +323,16 @@ public class DeploymentManagement { targets.stream().map(Target::getId).collect(Collectors.toList()), Constants.MAX_ENTRIES_IN_STATEMENT); // override all active actions and set them into canceling state, we - // need to remember which - // one we have been switched to canceling state because for targets - // which we have changed to - // canceling we don't want to publish the new action update event. - final Set targetIdsCancellList = new HashSet(); + // need to remember which one we have been switched to canceling state + // because for targets which we have changed to canceling we don't want + // to publish the new action update event. + final Set targetIdsCancellList = new HashSet<>(); targetIds.forEach(ids -> targetIdsCancellList.addAll(overrideObsoleteUpdateActions(ids))); - // check for old assigments that needs cancelation - // final List canncelledTargetIds = + // cancel all scheduled actions which are in-active, these actions were + // not active before and the manual assignment which has been done + // cancels the + targetIds.forEach(tIds -> actionRepository.switchStatus(Status.CANCELED, tIds, false, Status.SCHEDULED)); // set assigned distribution set and TargetUpdateStatus final String currentUser; @@ -307,25 +345,15 @@ public class DeploymentManagement { targetIds.forEach(tIds -> targetRepository.setAssignedDistributionSet(set, System.currentTimeMillis(), currentUser, tIds)); targetIds.forEach(tIds -> targetInfoRepository.setTargetUpdateStatus(TargetUpdateStatus.PENDING, tIds)); + final Map targetIdsToActions = actionRepository + .save(targets.stream().map(t -> createTargetAction(targetsWithActionMap, t, set, rollout, rolloutGroup)) + .collect(Collectors.toList())) + .stream().collect(Collectors.toMap(a -> a.getTarget().getControllerId(), Function.identity())); - final Map targetIdsToActions = actionRepository.save(targets.stream().map(t -> { - final Action tAction = new Action(); - final TargetWithActionType targetWithActionType = targetsWithActionMap.get(t.getControllerId()); - tAction.setActionType(targetWithActionType.getActionType()); - tAction.setForcedTime(targetWithActionType.getForceTime()); - tAction.setActive(true); - tAction.setStatus(Status.RUNNING); - tAction.setTarget(t); - tAction.setDistributionSet(set); - return tAction; - }).collect(Collectors.toList())).stream() - .collect(Collectors.toMap(a -> a.getTarget().getControllerId(), Function.identity())); - - // MECS-720 create initial action status when action is created so we - // remember the initial - // running status because we will change the status of the action itself - // and with this action - // status we have a nicer action history. + // create initial action status when action is created so we remember + // the initial running status because we will change the status + // of the action itself and with this action status we have a nicer + // action history. targetIdsToActions.values().forEach(action -> { final ActionStatus actionStatus = new ActionStatus(); actionStatus.setAction(action); @@ -334,11 +362,13 @@ public class DeploymentManagement { actionStatusRepository.save(actionStatus); }); - // select updated targets in order to return them + // flush to get action IDs + entityManager.flush(); + // collect updated target and actions IDs in order to return them final DistributionSetAssignmentResult result = new DistributionSetAssignmentResult( targets.stream().map(target -> target.getControllerId()).collect(Collectors.toList()), targets.size(), - controllerIDs.size() - targets.size(), Lists.newArrayList(targetIdsToActions.values()), - targetManagement); + controllerIDs.size() - targets.size(), + targetIdsToActions.values().stream().map(Action::getId).collect(Collectors.toList()), targetManagement); LOG.debug("assignDistribution({}) finished {}", set, result); @@ -347,19 +377,37 @@ public class DeploymentManagement { // detaching as it is not necessary to persist the set itself entityManager.detach(set); - // send distribution set assignment event + sendDistributionSetAssignmentEvent(targets, targetIdsCancellList, targetIdsToActions, softwareModules); + return result; + } + + private void sendDistributionSetAssignmentEvent(final List targets, final Set targetIdsCancellList, + final Map targetIdsToActions, final List softwareModules) { targets.stream().filter(t -> !!!targetIdsCancellList.contains(t.getId())) .forEach(t -> assignDistributionSetEvent(t, targetIdsToActions.get(t.getControllerId()).getId(), softwareModules)); + } - return result; + private Action createTargetAction(final Map targetsWithActionMap, final Target target, + final DistributionSet set, final Rollout rollout, final RolloutGroup rolloutGroup) { + final Action actionForTarget = new Action(); + final TargetWithActionType targetWithActionType = targetsWithActionMap.get(target.getControllerId()); + actionForTarget.setActionType(targetWithActionType.getActionType()); + actionForTarget.setForcedTime(targetWithActionType.getForceTime()); + actionForTarget.setActive(true); + actionForTarget.setStatus(Status.RUNNING); + actionForTarget.setTarget(target); + actionForTarget.setDistributionSet(set); + actionForTarget.setRollout(rollout); + actionForTarget.setRolloutGroup(rolloutGroup); + return actionForTarget; } /** * Sends the {@link TargetAssignDistributionSetEvent} for a specific target * to the {@link EventBus}. - * + * * @param target * the Target which has been assigned to a distribution set * @param actionId @@ -372,31 +420,32 @@ public class DeploymentManagement { target.getTargetInfo().setUpdateStatus(TargetUpdateStatus.PENDING); afterCommit.afterCommit(() -> { eventBus.post(new TargetInfoUpdateEvent(target.getTargetInfo())); - eventBus.post(new TargetAssignDistributionSetEvent(target.getControllerId(), actionId, softwareModules, - target.getTargetInfo().getAddress())); + eventBus.post(new TargetAssignDistributionSetEvent(target.getOptLockRevision(), target.getTenant(), + target.getControllerId(), actionId, softwareModules, target.getTargetInfo().getAddress())); }); } /** * Removes {@link UpdateAction}s that are no longer necessary and sends - * cancellations to the controller. + * cancelations to the controller. * * @param myTarget * to override {@link UpdateAction}s */ private Set overrideObsoleteUpdateActions(final List targetsIds) { - final Set cancelledTargetIds = new HashSet(); + final Set cancelledTargetIds = new HashSet<>(); // Figure out if there are potential target/action combinations that // need to be considered - // for cancellation + // for cancelation final List activeActions = actionRepository .findByActiveAndTargetIdInAndActionStatusNotEqualToAndDistributionSetRequiredMigrationStep(targetsIds, Action.Status.CANCELING); activeActions.forEach(action -> { action.setStatus(Status.CANCELING); // document that the status has been retrieved + actionStatusRepository.save(new ActionStatus(action, Status.CANCELING, System.currentTimeMillis(), "manual cancelation requested")); @@ -404,15 +453,20 @@ public class DeploymentManagement { cancelledTargetIds.add(action.getTarget().getId()); }); + actionRepository.save(activeActions); return cancelledTargetIds; + } private DistributionSetAssignmentResult assignDistributionSetByTargetId(@NotNull final DistributionSet set, @NotEmpty final List tIDs, final ActionType actionType, final long forcedTime) { + return assignDistributionSetToTargets(set, tIDs.stream() - .map(t -> new TargetWithActionType(t, actionType, forcedTime)).collect(Collectors.toList())); + .map(t -> new TargetWithActionType(t, actionType, forcedTime)).collect(Collectors.toList()), null, + null); + } /** @@ -475,7 +529,6 @@ public class DeploymentManagement { actionStatusRepository.save(new ActionStatus(myAction, Status.CANCELING, System.currentTimeMillis(), "manual cancelation requested")); final Action saveAction = actionRepository.save(myAction); - cancelAssignDistributionSetEvent(target, myAction.getId()); return saveAction; @@ -488,15 +541,15 @@ public class DeploymentManagement { /** * Sends the {@link CancelTargetAssignmentEvent} for a specific target to * the {@link EventBus}. - * + * * @param target * the Target which has been assigned to a distribution set * @param actionId * the action id of the assignment */ private void cancelAssignDistributionSetEvent(final Target target, final Long actionId) { - afterCommit.afterCommit(() -> eventBus.post(new CancelTargetAssignmentEvent(target.getControllerId(), actionId, - target.getTargetInfo().getAddress()))); + afterCommit.afterCommit(() -> eventBus.post(new CancelTargetAssignmentEvent(target.getOptLockRevision(), + target.getTenant(), target.getControllerId(), actionId, target.getTargetInfo().getAddress()))); } /** @@ -518,7 +571,7 @@ public class DeploymentManagement { @Modifying @Transactional @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) - public Action forceQuitAction(@NotNull final Action action, @NotNull final Target target) { + public Action forceQuitAction(@NotNull final Action action) { final Action mergedAction = entityManager.merge(action); if (!mergedAction.isCancelingOrCanceled()) { @@ -542,6 +595,111 @@ public class DeploymentManagement { return actionRepository.save(mergedAction); } + /** + * Creates an action entry into the action repository. In case of existing + * scheduled actions the scheduled actions gets canceled. A scheduled action + * is created in-active. + * + * @param targets + * the targets to create scheduled actions for + * @param distributionSet + * the distribution set for the actions + * @param actionType + * the action type for the action + * @param forcedTime + * the forcedTime of the action + * @param rollout + * the rollout for this action + * @param rolloutGroup + * the rolloutgroup for this action + */ + @Modifying + @Transactional + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) + public void createScheduledAction(final List targets, final DistributionSet distributionSet, + final ActionType actionType, final long forcedTime, final Rollout rollout, + final RolloutGroup rolloutGroup) { + // cancel all current scheduled actions for this target. E.g. an action + // is already scheduled and a next action is created then cancel the + // current scheduled action to cancel. E.g. a new scheduled action is + // created. + final List targetIds = targets.stream().map(t -> t.getId()).collect(Collectors.toList()); + actionRepository.switchStatus(Action.Status.CANCELED, targetIds, false, Action.Status.SCHEDULED); + targets.forEach(target -> { + final Action action = new Action(); + action.setTarget(target); + action.setActive(false); + action.setDistributionSet(distributionSet); + action.setActionType(actionType); + action.setForcedTime(forcedTime); + action.setStatus(Status.SCHEDULED); + action.setRollout(rollout); + action.setRolloutGroup(rolloutGroup); + actionRepository.save(action); + }); + } + + /** + * Starting an action which is scheduled, e.g. in case of rollout a + * scheduled action must be started now. + * + * @param action + * the action to start now. + * @return the action which has been started + */ + @Modifying + @Transactional + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET + SpringEvalExpressions.HAS_AUTH_OR + + SpringEvalExpressions.IS_SYSTEM_CODE) + public Action startScheduledAction(@NotNull final Action action) { + + final Action mergedAction = entityManager.merge(action); + final Target mergedTarget = entityManager.merge(action.getTarget()); + + // check if we need to override running update actions + final Set overrideObsoleteUpdateActions = overrideObsoleteUpdateActions( + Collections.singletonList(action.getTarget().getId())); + + final boolean hasDistributionSetAlreadyAssigned = targetRepository + .count(TargetSpecifications.hasControllerIdAndAssignedDistributionSetIdNot( + Collections.singletonList(mergedTarget.getControllerId()), + action.getDistributionSet().getId())) == 0; + if (hasDistributionSetAlreadyAssigned) { + // the target has already the distribution set assigned, we don't + // need to start the scheduled action, just finished it. + mergedAction.setStatus(Status.FINISHED); + mergedAction.setActive(false); + return actionRepository.save(mergedAction); + } + + mergedAction.setActive(true); + mergedAction.setStatus(Status.RUNNING); + final Action savedAction = actionRepository.save(mergedAction); + + final ActionStatus actionStatus = new ActionStatus(); + actionStatus.setAction(action); + actionStatus.setOccurredAt(action.getCreatedAt()); + actionStatus.setStatus(Status.RUNNING); + actionStatusRepository.save(actionStatus); + + mergedTarget.setAssignedDistributionSet(action.getDistributionSet()); + final TargetInfo targetInfo = mergedTarget.getTargetInfo(); + targetInfo.setUpdateStatus(TargetUpdateStatus.PENDING); + targetRepository.save(mergedTarget); + targetInfoRepository.save(targetInfo); + + // in case we canceled an action before for this target, then don't fire + // assignment event + if (!overrideObsoleteUpdateActions.contains(savedAction.getId())) { + final List softwareModules = softwareModuleRepository + .findByAssignedTo(action.getDistributionSet()); + // send distribution set assignment event + + assignDistributionSetEvent(mergedAction.getTarget(), mergedAction.getId(), softwareModules); + } + return savedAction; + } + /** * Get the {@link Action} entity for given actionId. * @@ -555,8 +713,8 @@ public class DeploymentManagement { } /** - * Get the {@link Action} entity for given actionId with all lazy - * attributes. + * Get the {@link Action} entity for given actionId with all lazy attributes + * (i.e. distributionSet, target, target.assignedDs). * * @param actionId * to be id of the action @@ -609,18 +767,18 @@ public class DeploymentManagement { final Root actionRoot = query.from(Action.class); final ListJoin actionStatusJoin = actionRoot.join(Action_.actionStatus, JoinType.LEFT); final Join actionDsJoin = actionRoot.join(Action_.distributionSet); + final Join actionRolloutJoin = actionRoot.join(Action_.rollout, JoinType.LEFT); final CriteriaQuery multiselect = query.distinct(true).multiselect( actionRoot.get(Action_.id), actionRoot.get(Action_.actionType), actionRoot.get(Action_.active), actionRoot.get(Action_.forcedTime), actionRoot.get(Action_.status), actionRoot.get(Action_.createdAt), actionRoot.get(Action_.lastModifiedAt), actionDsJoin.get(DistributionSet_.id), actionDsJoin.get(DistributionSet_.name), actionDsJoin.get(DistributionSet_.version), - cb.count(actionStatusJoin)); + cb.count(actionStatusJoin), actionRolloutJoin.get(Rollout_.name)); multiselect.where(cb.equal(actionRoot.get(Action_.target), target)); multiselect.orderBy(cb.desc(actionRoot.get(Action_.id))); multiselect.groupBy(actionRoot.get(Action_.id)); - final List resultList = entityManager.createQuery(multiselect).getResultList(); - return resultList; + return entityManager.createQuery(multiselect).getResultList(); } /** @@ -640,25 +798,19 @@ public class DeploymentManagement { public Slice findActionsByTarget(final Specification specifiction, final Target target, final Pageable pageable) { - return actionRepository.findAll(new Specification() { - - @Override - public Predicate toPredicate(final Root root, final CriteriaQuery query, - final CriteriaBuilder cb) { - return cb.and(specifiction.toPredicate(root, query, cb), cb.equal(root.get(Action_.target), target)); - } - }, pageable); + return actionRepository.findAll((Specification) (root, query, cb) -> cb + .and(specifiction.toPredicate(root, query, cb), cb.equal(root.get(Action_.target), target)), pageable); } /** * Retrieves all {@link Action}s which are referring the given * {@link Target}. - * - * @param pageable - * page parameters + * * @param foundTarget - * the target to find assigned actions - * @return the found {@link Action}s + * the target to find actions for + * @param pageable + * the pageable request to limit, sort the actions + * @return a slice of actions found for a specific target */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) public Slice findActionsByTarget(final Target foundTarget, final Pageable pageable) { @@ -744,13 +896,8 @@ public class DeploymentManagement { */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) public Long countActionsByTarget(@NotNull final Specification spec, @NotNull final Target target) { - return actionRepository.count(new Specification() { - @Override - public Predicate toPredicate(final Root root, final CriteriaQuery query, - final CriteriaBuilder cb) { - return cb.and(spec.toPredicate(root, query, cb), cb.equal(root.get(Action_.target), target)); - } - }); + return actionRepository.count((root, query, cb) -> cb.and(spec.toPredicate(root, query, cb), + cb.equal(root.get(Action_.target), target))); } /** @@ -777,7 +924,7 @@ public class DeploymentManagement { /** * retrieves all the {@link ActionStatus} entries of the given - * {@link Action} and {@link Target} in the order latest first. + * {@link Action} and {@link Target}. * * @param pageReq * pagination parameter @@ -789,12 +936,12 @@ public class DeploymentManagement { * @return the corresponding {@link Page} of {@link ActionStatus} */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) - public Page findActionStatusMessagesByActionInDescOrder(final Pageable pageReq, final Action action, + public Page findActionStatusByAction(final Pageable pageReq, final Action action, final boolean withMessages) { if (withMessages) { - return actionStatusRepository.getByActionOrderByIdDesc(pageReq, action); + return actionStatusRepository.getByAction(pageReq, action); } else { - return actionStatusRepository.findByActionOrderByIdDesc(pageReq, action); + return actionStatusRepository.findByAction(pageReq, action); } } @@ -802,7 +949,7 @@ public class DeploymentManagement { * This method is called, when cancellation has been successful. It sets the * action to canceled, resets the meta data of the target and in case there * is a new action this action is triggered. - * + * * @param action * the action which is set to canceled */ @@ -824,4 +971,40 @@ public class DeploymentManagement { } targetManagement.updateTarget(target); } + + /** + * Retrieving all actions referring to a given rollout with a specific + * action as parent reference and a specific status. + * + * Finding all actions of a specific rolloutgroup parent relation. + * + * @param rollout + * the rollout the actions belong to + * @param rolloutGroupParent + * the parent rolloutgroup the actions should reference + * @param actionStatus + * the status the actions have + * @return the actions referring a specific rollout and a specific parent + * rolloutgroup in a specific status + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET + SpringEvalExpressions.HAS_AUTH_OR + + SpringEvalExpressions.IS_SYSTEM_CODE) + public List findActionsByRolloutGroupParentAndStatus(final Rollout rollout, + final RolloutGroup rolloutGroupParent, final Action.Status actionStatus) { + return actionRepository.findByRolloutAndRolloutGroupParentAndStatus(rollout, rolloutGroupParent, actionStatus); + } + + /** + * Retrieves all actions for a specific rollout and in a specific status. + * + * @param rollout + * the rollout the actions beglong to + * @param actionStatus + * the status of the actions + * @return the actions referring a specific rollout an in a specific status + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) + public List findActionsByRolloutAndStatus(final Rollout rollout, final Action.Status actionStatus) { + return actionRepository.findByRolloutAndStatus(rollout, actionStatus); + } } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/DistributionSetAssignmentResult.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/DistributionSetAssignmentResult.java index ac562131d..3e61e4499 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/DistributionSetAssignmentResult.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/DistributionSetAssignmentResult.java @@ -10,7 +10,6 @@ package org.eclipse.hawkbit.repository; import java.util.List; -import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.AssignmentResult; import org.eclipse.hawkbit.repository.model.Target; @@ -19,14 +18,11 @@ import org.eclipse.hawkbit.repository.model.Target; * information of an assignment and how much of the assignment has been done and * how much of the assignments had already been existed. * - * - * - * */ public class DistributionSetAssignmentResult extends AssignmentResult { private final List assignedTargets; - private final List actions; + private final List actions; private final TargetManagement targetManagement; @@ -48,7 +44,7 @@ public class DistributionSetAssignmentResult extends AssignmentResult { * */ public DistributionSetAssignmentResult(final List assignedTargets, final int assigned, - final int alreadyAssigned, final List actions, final TargetManagement targetManagement) { + final int alreadyAssigned, final List actions, final TargetManagement targetManagement) { super(assigned, alreadyAssigned); this.assignedTargets = assignedTargets; this.actions = actions; @@ -63,9 +59,9 @@ public class DistributionSetAssignmentResult extends AssignmentResult { } /** - * @return the actionId + * @return the actionIds */ - public List getActions() { + public List getActions() { return actions; } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/DistributionSetManagement.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/DistributionSetManagement.java index 0a3838354..8b07c9bf8 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/DistributionSetManagement.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/DistributionSetManagement.java @@ -22,10 +22,6 @@ import java.util.stream.Collectors; import javax.persistence.Entity; import javax.persistence.EntityManager; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Predicate; -import javax.persistence.criteria.Root; import javax.validation.constraints.NotNull; import org.eclipse.hawkbit.eventbus.event.DistributionSetTagAssigmentResultEvent; @@ -53,13 +49,13 @@ import org.eclipse.hawkbit.repository.model.Tag; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.specifications.DistributionSetSpecification; import org.eclipse.hawkbit.repository.specifications.DistributionSetTypeSpecification; +import org.eclipse.hawkbit.repository.specifications.SpecificationsBuilder; import org.hibernate.validator.constraints.NotEmpty; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; -import org.springframework.data.jpa.domain.Specifications; import org.springframework.data.jpa.repository.Modifying; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; @@ -203,7 +199,8 @@ public class DistributionSetManagement { } final DistributionSetTagAssigmentResult resultAssignment = result; - afterCommit.afterCommit(() -> eventBus.post(new DistributionSetTagAssigmentResultEvent(resultAssignment))); + afterCommit + .afterCommit(() -> eventBus.post(new DistributionSetTagAssigmentResultEvent(resultAssignment))); // no reason to persist the tag entityManager.detach(myTag); @@ -263,7 +260,7 @@ public class DistributionSetManagement { @Transactional @PreAuthorize(SpringEvalExpressions.HAS_AUTH_DELETE_REPOSITORY) public void deleteDistributionSet(@NotNull final DistributionSet set) { - this.deleteDistributionSet(set.getId()); + deleteDistributionSet(set.getId()); } /** @@ -297,8 +294,7 @@ public class DistributionSetManagement { // hard delete the rest if exixts if (!toHardDelete.isEmpty()) { // don't give the delete statement an empty list, JPA/Oracle cannot - // handle the empty list, - // see MECS-403 + // handle the empty list distributionSetRepository.deleteByIdIn(toHardDelete); } } @@ -321,14 +317,10 @@ public class DistributionSetManagement { @PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_REPOSITORY) public DistributionSet createDistributionSet(@NotNull final DistributionSet dSet) { prepareDsSave(dSet); - if (dSet.getType() == null) { dSet.setType(systemManagement.getTenantMetadata().getDefaultDsType()); } - - final DistributionSet result = distributionSetRepository.save(dSet); - - return result; + return distributionSetRepository.save(dSet); } private void prepareDsSave(final DistributionSet dSet) { @@ -400,7 +392,7 @@ public class DistributionSetManagement { @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) public DistributionSet unassignSoftwareModule(@NotNull final DistributionSet ds, final SoftwareModule softwareModule) { - final Set softwareModules = new HashSet(); + final Set softwareModules = new HashSet<>(); softwareModules.add(softwareModule); ds.removeModule(softwareModule); checkDistributionSetSoftwareModulesIsAllowedToModify(ds, softwareModules); @@ -491,20 +483,11 @@ public class DistributionSetManagement { @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) private DistributionSet findDistributionSetsByFiltersAndInstalledOrAssignedTarget( final DistributionSetFilter distributionSetFilter) { - final List> specList = buildDistributionSetSpecifications(distributionSetFilter); - - Specifications specs = null; - if (!specList.isEmpty()) { - specs = Specifications.where(specList.get(0)); + if (specList == null || specList.isEmpty()) { + return null; } - if (specList.size() > 1) { - specList.remove(0); - for (final Specification s : specList) { - specs = specs.and(s); - } - } - return distributionSetRepository.findOne(specs); + return distributionSetRepository.findOne(SpecificationsBuilder.combineWithAnd(specList)); } /** @@ -531,7 +514,7 @@ public class DistributionSetManagement { @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) public Page findDistributionSetsAll(@NotNull final Pageable pageReq, final Boolean deleted, final Boolean complete) { - final List> specList = new ArrayList>(); + final List> specList = new ArrayList<>(); if (deleted != null) { final Specification spec = DistributionSetSpecification.isDeleted(deleted); @@ -563,7 +546,7 @@ public class DistributionSetManagement { @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) public Page findDistributionSetsAll(@NotNull final Specification spec, @NotNull final Pageable pageReq, final Boolean deleted) { - final List> specList = new ArrayList>(); + final List> specList = new ArrayList<>(); if (deleted != null) { specList.add(DistributionSetSpecification.isDeleted(deleted)); } @@ -635,17 +618,6 @@ public class DistributionSetManagement { return new PageImpl<>(resultSet, pageable, findDistributionSetsByFilters.getTotalElements()); } - private Long countDistributionSetByCriteriaAPI(@NotEmpty final List> specList) { - Specifications specs = Specifications.where(specList.get(0)); - if (specList.size() > 1) { - for (final Specification s : specList.subList(1, specList.size())) { - specs = specs.and(s); - } - } - - return distributionSetRepository.count(specs); - } - /** * Find distribution set by name and version. * @@ -689,12 +661,12 @@ public class DistributionSetManagement { @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) public Long countDistributionSetsAll() { - final List> specList = new ArrayList>(); + final List> specList = new ArrayList<>(); final Specification spec = DistributionSetSpecification.isDeleted(Boolean.FALSE); specList.add(spec); - return countDistributionSetByCriteriaAPI(specList); + return distributionSetRepository.count(SpecificationsBuilder.combineWithAnd(specList)); } /** @@ -869,17 +841,10 @@ public class DistributionSetManagement { public Page findDistributionSetMetadataByDistributionSetId( @NotNull final Long distributionSetId, @NotNull final Pageable pageable) { - return distributionSetMetadataRepository.findAll(new Specification() { - @Override - public Predicate toPredicate(final Root root, final CriteriaQuery query, - final CriteriaBuilder cb) { - - final Predicate predicate = cb.equal( - root.get(DistributionSetMetadata_.distributionSet).get(DistributionSet_.id), distributionSetId); - - return predicate; - } - }, pageable); + return distributionSetMetadataRepository.findAll( + (Specification) (root, query, cb) -> cb.equal( + root.get(DistributionSetMetadata_.distributionSet).get(DistributionSet_.id), distributionSetId), + pageable); } @@ -899,14 +864,14 @@ public class DistributionSetManagement { public Page findDistributionSetMetadataByDistributionSetId( @NotNull final Long distributionSetId, @NotNull final Specification spec, @NotNull final Pageable pageable) { - return distributionSetMetadataRepository.findAll(new Specification() { - @Override - public Predicate toPredicate(final Root root, final CriteriaQuery query, - final CriteriaBuilder cb) { - return cb.and(cb.equal(root.get(DistributionSetMetadata_.distributionSet).get(DistributionSet_.id), - distributionSetId), spec.toPredicate(root, query, cb)); - } - }, pageable); + return distributionSetMetadataRepository + .findAll( + (Specification) (root, query, + cb) -> cb.and( + cb.equal(root.get(DistributionSetMetadata_.distributionSet) + .get(DistributionSet_.id), distributionSetId), + spec.toPredicate(root, query, cb)), + pageable); } /** @@ -956,7 +921,7 @@ public class DistributionSetManagement { /** * Checking Distribution Set is already using while assign Software module. - * + * * @param distributionSet * @param softwareModules */ @@ -969,9 +934,9 @@ public class DistributionSetManagement { private List> buildDistributionSetSpecifications( final DistributionSetFilter distributionSetFilter) { - final List> specList = new ArrayList>(); + final List> specList = new ArrayList<>(); - Specification spec = null; + Specification spec; if (null != distributionSetFilter.getIsComplete()) { spec = DistributionSetSpecification.isCompleted(distributionSetFilter.getIsComplete()); @@ -1053,21 +1018,12 @@ public class DistributionSetManagement { */ private Page findByCriteriaAPI(@NotNull final Pageable pageable, final List> specList) { - Specifications specs = null; - if (!specList.isEmpty()) { - specs = Specifications.where(specList.get(0)); - } - if (specList.size() > 1) { - for (final Specification s : specList.subList(1, specList.size())) { - specs = specs.and(s); - } + + if (specList == null || specList.isEmpty()) { + return distributionSetRepository.findAll(pageable); } - if (specs == null) { - return distributionSetRepository.findAll(pageable); - } else { - return distributionSetRepository.findAll(specs, pageable); - } + return distributionSetRepository.findAll(SpecificationsBuilder.combineWithAnd(specList), pageable); } private void checkAndThrowAlreadyIfDistributionSetMetadataExists(final DsMetadataCompositeKey metadataId) { diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/NoCountPagingRepository.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/NoCountPagingRepository.java index 4290607fd..79f54a3f0 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/NoCountPagingRepository.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/NoCountPagingRepository.java @@ -57,7 +57,7 @@ public class NoCountPagingRepository { */ public Slice findAll(final Specification spec, final Pageable pageable, final Class domainClass) { - final SimpleJpaNoCountRepository noCountDao = new SimpleJpaNoCountRepository(domainClass, em); + final SimpleJpaNoCountRepository noCountDao = new SimpleJpaNoCountRepository<>(domainClass, em); return noCountDao.findAll(spec, pageable); } @@ -76,7 +76,7 @@ public class NoCountPagingRepository { * org.springframework.data.domain.Pageable) */ public Slice findAll(final Pageable pageable, final Class domainClass) { - final SimpleJpaNoCountRepository noCountDao = new SimpleJpaNoCountRepository(domainClass, em); + final SimpleJpaNoCountRepository noCountDao = new SimpleJpaNoCountRepository<>(domainClass, em); return noCountDao.findAll(pageable); } @@ -120,7 +120,7 @@ public class NoCountPagingRepository { final List content = query.getResultList(); - return new PageImpl(content, pageable, content.size()); + return new PageImpl<>(content, pageable, content.size()); } } } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/OffsetBasedPageRequest.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/OffsetBasedPageRequest.java new file mode 100644 index 000000000..753ec6963 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/OffsetBasedPageRequest.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; + +/** + * An implementation of the {@link PageRequest} which is offset based by means + * the offset is given and not the page number as in the original + * {@link PageRequest} implemntation where the offset is generated. Due that the + * REST-API is working with {@code offset} and {@code limit} parameter we need + * an offset based page request for JPA. + * + * @author Michael Hirsch + * @since 0.2.2 + * + */ +public final class OffsetBasedPageRequest extends PageRequest { + + private static final long serialVersionUID = 1L; + private final int offset; + + /** + * Creates a new {@link OffsetBasedPageRequest}. Offsets are zero indexed, + * thus providing 0 for {@code offset} will return the first entry. + * + * @param offset + * zero-based offset index. + * @param limit + * the limit of the page to be returned. + */ + public OffsetBasedPageRequest(final int offset, final int limit) { + this(offset, limit, null); + } + + /** + * Creates a new {@link OffsetBasedPageRequest}. Offsets are zero indexed, + * thus providing 0 for {@code offset} will return the first entry. + * + * @param offset + * zero-based offset index. + * @param limit + * the limit of the page to be returned. + * @param sort + * sort can be {@literal null}. + */ + public OffsetBasedPageRequest(final int offset, final int limit, final Sort sort) { + super(0, limit, sort); + this.offset = offset; + } + + @Override + public int getOffset() { + return offset; + } + + @Override + public String toString() { + return "OffsetBasedPageRequest [offset=" + offset + ", getPageSize()=" + getPageSize() + ", getPageNumber()=" + + getPageNumber() + "]"; + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/ReportManagement.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/ReportManagement.java index 801f6c80d..eab926b4b 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/ReportManagement.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/ReportManagement.java @@ -46,7 +46,6 @@ import org.eclipse.hawkbit.repository.model.Target_; import org.eclipse.hawkbit.tenancy.TenantAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -55,14 +54,10 @@ import org.springframework.validation.annotation.Validated; /** * Service layer for generating SP reportings. * - * - * - * */ @Transactional(readOnly = true) @Validated @Service -@ConfigurationProperties public class ReportManagement { @Value("${spring.jpa.database}") @@ -213,8 +208,8 @@ public class ReportManagement { * count */ @Cacheable("targetsCreatedOverPeriod") - public DataReportSeries targetsCreatedOverPeriod(final DateType dateType, final LocalDateTime from, - final LocalDateTime to) { + public DataReportSeries targetsCreatedOverPeriod(final DateType dateType, + final LocalDateTime from, final LocalDateTime to) { final Query createNativeQuery = entityManager .createNativeQuery(getTargetsCreatedQueryTemplate(dateType, from, to)); final List resultList = createNativeQuery.getResultList(); @@ -223,8 +218,7 @@ public class ReportManagement { .map(r -> new DataReportSeriesItem<>(dateType.format((String) r[0]), ((Number) r[1]).longValue())) .collect(Collectors.toList()); - final DataReportSeries report = new DataReportSeries<>("CreatedTargets", reportItems); - return report; + return new DataReportSeries<>("CreatedTargets", reportItems); } private String getTargetsCreatedQueryTemplate(final DateType dateType, final LocalDateTime from, @@ -276,8 +270,8 @@ public class ReportManagement { * count */ @Cacheable("feedbackReceivedOverTime") - public DataReportSeries feedbackReceivedOverTime(final DateType dateType, final LocalDateTime from, - final LocalDateTime to) { + public DataReportSeries feedbackReceivedOverTime(final DateType dateType, + final LocalDateTime from, final LocalDateTime to) { final Query createNativeQuery = entityManager .createNativeQuery(getFeedbackReceivedQueryTemplate(dateType, from, to)); final List resultList = createNativeQuery.getResultList(); @@ -286,8 +280,7 @@ public class ReportManagement { .map(r -> new DataReportSeriesItem<>(dateType.format((String) r[0]), ((Number) r[1]).longValue())) .collect(Collectors.toList()); - final DataReportSeries report = new DataReportSeries<>("FeedbackRecieved", reportItems); - return report; + return new DataReportSeries<>("FeedbackRecieved", reportItems); } /** @@ -433,7 +426,7 @@ public class ReportManagement { } private DataReportSeriesItem toItem() { - return new DataReportSeriesItem(name.getName() != null ? name.getName() : "misc", count); + return new DataReportSeriesItem<>(name.getName() != null ? name.getName() : "misc", count); } } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupManagement.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupManagement.java new file mode 100644 index 000000000..54ff37e87 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupManagement.java @@ -0,0 +1,263 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.persistence.EntityManager; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Join; +import javax.persistence.criteria.JoinType; +import javax.persistence.criteria.ListJoin; +import javax.persistence.criteria.Root; +import javax.validation.constraints.NotNull; + +import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.Action_; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.RolloutGroup_; +import org.eclipse.hawkbit.repository.model.RolloutTargetGroup; +import org.eclipse.hawkbit.repository.model.RolloutTargetGroup_; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetWithActionStatus; +import org.eclipse.hawkbit.repository.model.Target_; +import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus; +import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +/** + * RolloutGroupManagement to control rollout groups. This service secures all + * the functionality based on the {@link PreAuthorize} annotation on methods. + */ +@Validated +@Service +@Transactional(readOnly = true) +public class RolloutGroupManagement { + + @Autowired + private RolloutGroupRepository rolloutGroupRepository; + + @Autowired + private ActionRepository actionRepository; + + @Autowired + private TargetRepository targetRepository; + + @Autowired + private EntityManager entityManager; + + /** + * Retrieves a single {@link RolloutGroup} by its ID. + * + * @param rolloutGroupId + * the ID of the rollout group to find + * @return the found {@link RolloutGroup} by its ID or {@code null} if it + * does not exists + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public RolloutGroup findRolloutGroupById(final Long rolloutGroupId) { + return rolloutGroupRepository.findOne(rolloutGroupId); + } + + /** + * Retrieves a page of {@link RolloutGroup}s filtered by a given + * {@link Rollout}. + * + * @param rolloutId + * the ID of the rollout to filter the {@link RolloutGroup}s + * @param page + * the page request to sort and limit the result + * @return a page of found {@link RolloutGroup}s + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Page findRolloutGroupsByRolloutId(final Long rolloutId, final Pageable page) { + return rolloutGroupRepository.findByRolloutId(rolloutId, page); + } + + /** + * Retrieves a page of {@link RolloutGroup}s filtered by a given + * {@link Rollout} and the given {@link Specification}. + * + * @param rolloutId + * the ID of the rollout to filter the {@link RolloutGroup}s + * @param specification + * the specification to filter the result set based on attributes + * of the {@link RolloutGroup} + * @param page + * the page request to sort and limit the result + * @return a page of found {@link RolloutGroup}s + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Page findRolloutGroupsByPredicate(final Rollout rollout, + final Specification specification, final Pageable page) { + return rolloutGroupRepository.findAll((root, query, criteriaBuilder) -> criteriaBuilder.and( + criteriaBuilder.equal(root.get(RolloutGroup_.rollout), rollout), + specification.toPredicate(root, query, criteriaBuilder)), page); + } + + /** + * Retrieves a page of {@link RolloutGroup}s filtered by a given + * {@link Rollout} with the detailed status. + * + * @param rolloutId + * the ID of the rollout to filter the {@link RolloutGroup}s + * @param page + * the page request to sort and limit the result + * @return a page of found {@link RolloutGroup}s + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Page findAllRolloutGroupsWithDetailedStatus(final Long rolloutId, final Pageable page) { + final Page rolloutGroups = rolloutGroupRepository.findByRolloutId(rolloutId, page); + final List rolloutGroupIds = rolloutGroups.getContent().stream().map(rollout -> rollout.getId()) + .collect(Collectors.toList()); + final Map> allStatesForRollout = getStatusCountItemForRolloutGroup( + rolloutGroupIds); + + for (final RolloutGroup rolloutGroup : rolloutGroups) { + final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus( + allStatesForRollout.get(rolloutGroup.getId()), rolloutGroup.getTotalTargets()); + rolloutGroup.setTotalTargetCountStatus(totalTargetCountStatus); + } + + return rolloutGroups; + } + + /** + * Get count of targets in different status in rollout group. + * + * @param rolloutGroupId + * rollout group id + * @return rolloutGroup with details of targets count for different statuses + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public RolloutGroup findRolloutGroupWithDetailedStatus(final Long rolloutGroupId) { + final RolloutGroup rolloutGroup = findRolloutGroupById(rolloutGroupId); + final List rolloutStatusCountItems = actionRepository + .getStatusCountByRolloutGroupId(rolloutGroupId); + + final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(rolloutStatusCountItems, + rolloutGroup.getTotalTargets()); + rolloutGroup.setTotalTargetCountStatus(totalTargetCountStatus); + return rolloutGroup; + + } + + private Map> getStatusCountItemForRolloutGroup( + final List rolloutGroupIds) { + final List resultList = actionRepository + .getStatusCountByRolloutGroupId(rolloutGroupIds); + return resultList.stream().collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId)); + } + + /** + * Get targets of specified rollout group. + * + * @param rolloutGroup + * rollout group + * @param specification + * the specification for filtering the targets of a rollout group + * @param page + * the page request to sort and limit the result + * + * @return Page list of targets of a rollout group + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Page findRolloutGroupTargets(final RolloutGroup rolloutGroup, + final Specification specification, final Pageable page) { + return targetRepository.findAll((root, query, criteriaBuilder) -> { + final ListJoin rolloutTargetJoin = root.join(Target_.rolloutTargetGroup); + return criteriaBuilder.and(specification.toPredicate(root, query, criteriaBuilder), + criteriaBuilder.equal(rolloutTargetJoin.get(RolloutTargetGroup_.rolloutGroup), rolloutGroup)); + } , page); + } + + /** + * Get targets of specified rollout group. + * + * @param rolloutGroup + * rollout group + * @param page + * the page request to sort and limit the result + * + * @return Page list of targets of a rollout group + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Page findRolloutGroupTargets(@NotNull final RolloutGroup rolloutGroup, final Pageable page) { + if (isRolloutStatusReady(rolloutGroup)) { + // in case of status ready the action has not been created yet and + // the relation information between target and rollout-group is + // stored in the #TargetRolloutGroup. + return targetRepository.findByRolloutTargetGroupRolloutGroupId(rolloutGroup.getId(), page); + } + return targetRepository.findByActionsRolloutGroup(rolloutGroup, page); + } + + private boolean isRolloutStatusReady(final RolloutGroup rolloutGroup) { + return rolloutGroup != null && RolloutStatus.READY.equals(rolloutGroup.getRollout().getStatus()); + } + + /** + * + * Find all targets with action status by rollout group id. The action + * status might be {@code null} if for the target within the rollout no + * actions as been created, e.g. the target already had assigned the same + * distribution set we do not create an action for it but the target is in + * the result list of the rollout-group. + * + * @param pageRequest + * the page request to sort and limit the result + * @param rolloutGroup + * rollout group + * @return {@link TargetWithActionStatus} target with action status + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Page findAllTargetsWithActionStatus(final PageRequest pageRequest, + @NotNull final RolloutGroup rolloutGroup) { + + final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + final CriteriaQuery query = cb.createQuery(Object[].class); + final CriteriaQuery countQuery = cb.createQuery(Long.class); + + final Root targetRoot = query.distinct(true).from(RolloutTargetGroup.class); + final Join targetJoin = targetRoot.join(RolloutTargetGroup_.target); + final ListJoin actionJoin = targetRoot.join(RolloutTargetGroup_.actions, + JoinType.LEFT); + + final Root countQueryFrom = countQuery.distinct(true).from(RolloutTargetGroup.class); + countQueryFrom.join(RolloutTargetGroup_.target); + countQueryFrom.join(RolloutTargetGroup_.actions, JoinType.LEFT); + countQuery.select(cb.count(countQueryFrom)) + .where(cb.equal(countQueryFrom.get(RolloutTargetGroup_.rolloutGroup), rolloutGroup)); + final Long totalCount = entityManager.createQuery(countQuery).getSingleResult(); + + final CriteriaQuery multiselect = query.multiselect(targetJoin, actionJoin.get(Action_.status)) + .where(cb.equal(targetRoot.get(RolloutTargetGroup_.rolloutGroup), rolloutGroup)); + final List targetWithActionStatus = entityManager.createQuery(multiselect) + .setFirstResult(pageRequest.getOffset()).setMaxResults(pageRequest.getPageSize()).getResultList() + .stream().map(o -> new TargetWithActionStatus((Target) o[0], (Action.Status) o[1])) + .collect(Collectors.toList()); + return new PageImpl<>(targetWithActionStatus, pageRequest, totalCount); + } + +} \ No newline at end of file diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupRepository.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupRepository.java new file mode 100644 index 000000000..cf3f3d729 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutGroupRepository.java @@ -0,0 +1,131 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository; + +import java.util.List; + +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; + +/** + * The repository interface for the {@link RolloutGroup} model. + */ +@Transactional(readOnly = true) +public interface RolloutGroupRepository + extends BaseEntityRepository, JpaSpecificationExecutor { + + /** + * Retrieves all {@link RolloutGroup} referring a specific rollout in the + * order of creating them. ID ASC. + * + * @param rollout + * the rollout the rolloutgroups belong to + * @return the rollout groups belonging to a rollout ordered by ID ASC. + */ + List findByRolloutOrderByIdAsc(final Rollout rollout); + + /** + * Retrieves all {@link RolloutGroup} referring a specific rollout in a + * specific {@link RolloutGroupStatus}. + * + * @param rollout + * the rollout the rolloutgroup belong to + * @param status + * the status of the rollout groups + * @return the rollout groups belonging to a rollout in a specific status + */ + List findByRolloutAndStatus(final Rollout rollout, final RolloutGroupStatus status); + + /** + * Counts all {@link RolloutGroup} referring a specific rollout. + * + * @param rollout + * the rollout the rolloutgroup belong to + * @return the count of the rollout groups for a specific rollout + */ + Long countByRollout(final Rollout rollout); + + /** + * Counts all {@link RolloutGroup} referring a specific rollout in a + * specific {@link RolloutGroupStatus}. + * + * @param rollout + * the rollout the rolloutgroup belong to + * @param rolloutGroupStatus + * the status of the rollout groups + * @return the count of rollout groups belonging to a rollout in a specific + * status + */ + Long countByRolloutAndStatus(Rollout rollout, RolloutGroupStatus rolloutGroupStatus); + + /** + * Counts all {@link RolloutGroup} referring a specific rollout in specific + * {@link RolloutGroupStatus}s. An in-clause statement does not work with + * the spring-data, so this is specific usecase regarding to the + * rollout-management to find out rolloutgroups which are in specific + * states. + * + * @param rollout + * the rollout the rolloutgroup belong to + * @param rolloutGroupStatus1 + * the status of the rollout groups + * @param rolloutGroupStatus2 + * the status of the rollout groups + * @return the count of rollout groups belonging to a rollout in specific + * status + */ + @Query("SELECT COUNT(r.id) FROM RolloutGroup r WHERE r.rollout = :rollout and (r.status = :status1 or r.status = :status2)") + Long countByRolloutAndStatusOrStatus(@Param("rollout") Rollout rollout, + @Param("status1") RolloutGroupStatus rolloutGroupStatus1, + @Param("status2") RolloutGroupStatus rolloutGroupStatus2); + + /** + * Retrieves all {@link RolloutGroup} for a specific parent in a specific + * status. Retrieves the child rolloutgroup for a specific status. + * + * @param rolloutGroup + * the parent rolloutgroup + * @param status + * the status of the rolloutgroups + * @return The child {@link RolloutGroup}s in a specific status + */ + List findByParentAndStatus(RolloutGroup rolloutGroup, RolloutGroupStatus status); + + /** + * Retrieves all {@link RolloutGroup} for a specific rollout and status not + * having ordered by ID DESC, latest top. + * + * @param rollout + * the rollout the rolloutgroup belong to + * @param notStatus + * the status which the rolloutgroup should not have + * @return rolloutgroup referring to a rollout and not having a specific + * status ordered by ID DESC. + */ + List findByRolloutAndStatusNotOrderByIdDesc(Rollout rollout, RolloutGroupStatus notStatus); + + /** + * Retrieves all {@link RolloutGroup} for a specific rollout. + * + * @param rolloutId + * the ID of the rollout to find the rollout groups + * @param page + * the page request to sort, limit the result + * @return a page of found {@link RolloutGroup} or {@code empty}. + */ + Page findByRolloutId(final Long rolloutId, Pageable page); + +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java new file mode 100644 index 000000000..1573048ec --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutManagement.java @@ -0,0 +1,877 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.stream.Collectors; + +import javax.persistence.EntityManager; +import javax.validation.constraints.NotNull; + +import org.eclipse.hawkbit.cache.CacheWriteNotify; +import org.eclipse.hawkbit.eventbus.event.RolloutGroupCreatedEvent; +import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; +import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException; +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.Action.ActionType; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupConditions; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition; +import org.eclipse.hawkbit.repository.model.RolloutTargetGroup; +import org.eclipse.hawkbit.repository.model.Rollout_; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus; +import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus; +import org.eclipse.hawkbit.rollout.condition.RolloutGroupActionEvaluator; +import org.eclipse.hawkbit.rollout.condition.RolloutGroupConditionEvaluator; +import org.hibernate.validator.constraints.NotEmpty; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationContext; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Service; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.DefaultTransactionDefinition; +import org.springframework.transaction.support.TransactionTemplate; +import org.springframework.util.Assert; +import org.springframework.validation.annotation.Validated; + +/** + * RolloutManagement to control rollouts e.g. like creating, starting, resuming + * and pausing rollouts. This service secures all the functionality based on the + * {@link PreAuthorize} annotation on methods. + */ +@Validated +@Service +@EnableScheduling +@Transactional(readOnly = true) +public class RolloutManagement { + private static final Logger LOGGER = LoggerFactory.getLogger(RolloutManagement.class); + + @Autowired + private EntityManager entityManager; + + @Autowired + private RolloutRepository rolloutRepository; + + @Autowired + private TargetManagement targetManagement; + + @Autowired + private TargetRepository targetRepository; + + @Autowired + private RolloutGroupRepository rolloutGroupRepository; + + @Autowired + private DeploymentManagement deploymentManagement; + + @Autowired + private RolloutTargetGroupRepository rolloutTargetGroupRepository; + + @Autowired + private ActionRepository actionRepository; + + @Autowired + private ApplicationContext context; + + @Autowired + private NoCountPagingRepository criteriaNoCountDao; + + @Autowired + private PlatformTransactionManager txManager; + + @Autowired + private CacheWriteNotify cacheWriteNotify; + + @Autowired + @Qualifier("asyncExecutor") + private Executor executor; + + /* + * set which stores the rollouts which are asynchronously creating. This is + * necessary to verify rollouts which maybe stuck during creationg e.g. + * because of database interruption, failures or even application crash. + * !This is not cluster aware! + */ + private static final Set creatingRollouts = ConcurrentHashMap.newKeySet(); + + /* + * set which stores the rollouts which are asynchronously starting. This is + * necessary to verify rollouts which maybe stuck during starting e.g. + * because of database interruption, failures or even application crash. + * !This is not cluster aware! + */ + private static final Set startingRollouts = ConcurrentHashMap.newKeySet(); + + /** + * Retrieves all rollouts. + * + * @param page + * the page request to sort and limit the result + * @return a page of found rollouts + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Page findAll(final Pageable page) { + return rolloutRepository.findAll(page); + } + + /** + * Retrieves all rollouts found by the given specification. + * + * @param specification + * the specification to filter rollouts + * @param page + * the page request to sort and limit the result + * @return a page of found rollouts + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Page findAllWithDetailedStatusByPredicate(final Specification specification, + final Pageable page) { + final Page findAll = rolloutRepository.findAll(specification, page); + setRolloutStatusDetails(findAll); + return findAll; + } + + /** + * Retrieves a specific rollout by its ID. + * + * @param rolloutId + * the ID of the rollout to retrieve + * @return the founded rollout or {@code null} if rollout with given ID does + * not exists + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Rollout findRolloutById(final Long rolloutId) { + return rolloutRepository.findOne(rolloutId); + } + + /** + * Persists a new rollout entity. The filter within the + * {@link Rollout#getTargetFilterQuery()} is used to retrieve the targets + * which are effected by this rollout to create. The targets will then be + * split up into groups. The size of the groups can be defined in the + * {@code groupSize} parameter. + * + * The rollout is not started. Only the preparation of the rollout is done, + * persisting and creating all the necessary groups. The Rollout and the + * groups are persisted in {@link RolloutStatus#READY} and + * {@link RolloutGroupStatus#READY} so they can be started + * {@link #startRollout(Rollout)}. + * + * @param rollout + * the rollout entity to create + * @param amountGroup + * the amount of groups to split the rollout into + * @param conditions + * the rolloutgroup conditions and actions which should be + * applied for each {@link RolloutGroup} + * @return the persisted rollout. + * + * @throws IllegalArgumentException + * in case the given groupSize is zero or lower. + */ + @Transactional + @Modifying + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE) + public Rollout createRollout(final Rollout rollout, final int amountGroup, + final RolloutGroupConditions conditions) { + final Rollout savedRollout = createRollout(rollout, amountGroup); + return createRolloutGroups(amountGroup, conditions, savedRollout); + } + + /** + * Persists a new rollout entity. The filter within the + * {@link Rollout#getTargetFilterQuery()} is used to retrieve the targets + * which are effected by this rollout to create. The creation of the rollout + * will be done synchronously and will be returned. The targets will then be + * split up into groups. The size of the groups can be defined in the + * {@code groupSize} parameter. + * + * The creation of the rollout groups is executed asynchronously due it + * might take some time to split up the targets into groups. The creation of + * the {@link RolloutGroup} is published as event + * {@link RolloutGroupCreatedEvent}. + * + * The rollout is in status {@link RolloutStatus#CREATING} until all rollout + * groups has been created and the targets are split up, then the rollout + * will change the status to {@link RolloutStatus#READY}. + * + * The rollout is not started. Only the preparation of the rollout is done, + * persisting and creating all the necessary groups. The Rollout and the + * groups are persisted in {@link RolloutStatus#READY} and + * {@link RolloutGroupStatus#READY} so they can be started + * {@link #startRollout(Rollout)}. + * + * @param rollout + * the rollout to be created + * @param amountGroup + * the number of groups should be created for the rollout and + * split up the targets + * @param conditions + * the rolloutgroup conditions and actions which should be + * applied for each {@link RolloutGroup} + * @return the created rollout entity in state + * {@link RolloutStatus#CREATING} + */ + @Transactional + @Modifying + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE) + public Rollout createRolloutAsync(final Rollout rollout, final int amountGroup, + final RolloutGroupConditions conditions) { + final Rollout savedRollout = createRollout(rollout, amountGroup); + creatingRollouts.add(savedRollout.getName()); + // need to flush the entity manager here to get the ID of the rollout, + // because entity manager is set to FlushMode#Auto, entitymanager will + // flush the Target entity, due the indirect relationship to the Rollout + // entity without set an ID JPA is throwing a Invalid + // 'org.springframework.dao.InvalidDataAccessApiUsageException: During + // synchronization aect was found through a relationship that was not + // marked cascade PERSIST' + entityManager.flush(); + executor.execute(() -> { + try { + final DefaultTransactionDefinition def = new DefaultTransactionDefinition(); + def.setName("creatingRollout"); + def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + new TransactionTemplate(txManager, def).execute(status -> { + createRolloutGroups(amountGroup, conditions, savedRollout); + return null; + }); + } finally { + creatingRollouts.remove(savedRollout.getName()); + } + }); + return savedRollout; + } + + private Rollout createRollout(final Rollout rollout, final int amountGroup) { + verifyRolloutGroupParameter(amountGroup); + final Long totalTargets = targetManagement.countTargetByTargetFilterQuery(rollout.getTargetFilterQuery()); + rollout.setTotalTargets(totalTargets.longValue()); + return rolloutRepository.save(rollout); + } + + private void verifyRolloutGroupParameter(final int amountGroup) { + if (amountGroup <= 0) { + throw new IllegalArgumentException("the amountGroup must be greater than zero"); + } else if (amountGroup > 500) { + throw new IllegalArgumentException("the amountGroup must not be greater than 500"); + } + } + + /** + * Method for creating rollout groups and calculating group sizes. Group + * sizes are calculated by dividing the total count of targets through the + * amount of given groups. In same cases this will lead to less rollout + * groups than given by client. + * + * @param amountGroup + * the amount of groups + * @param conditions + * the rollout group conditions + * @param savedRollout + * the rollout + * @return the rollout with created groups + */ + private Rollout createRolloutGroups(final int amountGroup, final RolloutGroupConditions conditions, + final Rollout savedRollout) { + int pageIndex = 0; + int groupIndex = 0; + final Long totalCount = savedRollout.getTotalTargets(); + final int groupSize = (int) Math.ceil((double) totalCount / (double) amountGroup); + // validate if the amount of groups that will be created are the amount + // of groups that the client what's to have created. + int amountGroupValidated = amountGroup; + final int amountGroupCreation = (int) (Math.ceil((double) totalCount / (double) groupSize)); + if (amountGroupCreation == (amountGroup - 1)) { + amountGroupValidated--; + } + RolloutGroup lastSavedGroup = null; + while (pageIndex < totalCount) { + groupIndex++; + final String nameAndDesc = "group-" + groupIndex; + final RolloutGroup group = new RolloutGroup(); + group.setName(nameAndDesc); + group.setDescription(nameAndDesc); + group.setRollout(savedRollout); + group.setParent(lastSavedGroup); + group.setSuccessCondition(conditions.getSuccessCondition()); + group.setSuccessConditionExp(conditions.getSuccessConditionExp()); + group.setErrorCondition(conditions.getErrorCondition()); + group.setErrorConditionExp(conditions.getErrorConditionExp()); + group.setErrorAction(conditions.getErrorAction()); + group.setErrorActionExp(conditions.getErrorActionExp()); + + final RolloutGroup savedGroup = rolloutGroupRepository.save(group); + + final Slice targetGroup = targetManagement.findTargetsAll(savedRollout.getTargetFilterQuery(), + new OffsetBasedPageRequest(pageIndex, groupSize, new Sort(Direction.ASC, "id"))); + savedGroup.setTotalTargets(targetGroup.getContent().size()); + + lastSavedGroup = savedGroup; + + targetGroup + .forEach(target -> rolloutTargetGroupRepository.save(new RolloutTargetGroup(savedGroup, target))); + cacheWriteNotify.rolloutGroupCreated(groupIndex, savedRollout.getId(), savedGroup.getId(), + amountGroupValidated, groupIndex); + pageIndex += groupSize; + } + + savedRollout.setStatus(RolloutStatus.READY); + return rolloutRepository.save(savedRollout); + } + + /** + * Starts a rollout which has been created. The rollout must be in + * {@link RolloutStatus#READY} state. The according actions will be created + * for each affected target in the rollout. The actions of the first group + * will be started immediately {@link RolloutGroupStatus#RUNNING} as the + * other groups will be {@link RolloutGroupStatus#SCHEDULED} state. + * + * The rollout itself will be then also in {@link RolloutStatus#RUNNING}. + * + * @param rollout + * the rollout to be started + * + * @throws RolloutIllegalStateException + * if given rollout is not in {@link RolloutStatus#READY}. Only + * ready rollouts can be started. + */ + @Transactional + @Modifying + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR + + SpringEvalExpressions.IS_SYSTEM_CODE) + public Rollout startRollout(final Rollout rollout) { + final Rollout mergedRollout = entityManager.merge(rollout); + checkIfRolloutCanStarted(rollout, mergedRollout); + return doStartRollout(mergedRollout); + } + + /** + * Starts a rollout asynchronously which has been created. The rollout must + * be in {@link RolloutStatus#READY} state. The according actions will be + * created asynchronously for each affected target in the rollout. The + * actions of the first group will be started immediately + * {@link RolloutGroupStatus#RUNNING} as the other groups will be + * {@link RolloutGroupStatus#SCHEDULED} state. + * + * The rollout itself will be then also in {@link RolloutStatus#RUNNING}. + * + * @param rollout + * the rollout to be started + * + * @throws RolloutIllegalStateException + * if given rollout is not in {@link RolloutStatus#READY}. Only + * ready rollouts can be started. + */ + @Transactional + @Modifying + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR + + SpringEvalExpressions.IS_SYSTEM_CODE) + public Rollout startRolloutAsync(final Rollout rollout) { + final Rollout mergedRollout = entityManager.merge(rollout); + checkIfRolloutCanStarted(rollout, mergedRollout); + mergedRollout.setStatus(RolloutStatus.STARTING); + final Rollout updatedRollout = rolloutRepository.save(mergedRollout); + startingRollouts.add(updatedRollout.getName()); + executor.execute(() -> { + try { + final DefaultTransactionDefinition def = new DefaultTransactionDefinition(); + def.setName("startingRollout"); + def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + new TransactionTemplate(txManager, def).execute(status -> { + doStartRollout(updatedRollout); + return null; + }); + } finally { + startingRollouts.remove(updatedRollout.getName()); + } + }); + return updatedRollout; + + } + + private Rollout doStartRollout(final Rollout rollout) { + final DistributionSet distributionSet = rollout.getDistributionSet(); + final ActionType actionType = rollout.getActionType(); + final long forceTime = rollout.getForcedTime(); + final List rolloutGroups = rolloutGroupRepository.findByRolloutOrderByIdAsc(rollout); + for (int iGroup = 0; iGroup < rolloutGroups.size(); iGroup++) { + final RolloutGroup rolloutGroup = rolloutGroups.get(iGroup); + final List targetGroup = targetRepository.findByRolloutTargetGroupRolloutGroup(rolloutGroup); + // firstgroup can already be started + if (iGroup == 0) { + final List targetsWithActionType = targetGroup.stream() + .map(t -> new TargetWithActionType(t.getControllerId(), actionType, forceTime)) + .collect(Collectors.toList()); + deploymentManagement.assignDistributionSet(distributionSet.getId(), targetsWithActionType, rollout, + rolloutGroup); + rolloutGroup.setStatus(RolloutGroupStatus.RUNNING); + } else { + // create only not active actions with status scheduled so they + // can be activated later + deploymentManagement.createScheduledAction(targetGroup, distributionSet, actionType, forceTime, rollout, + rolloutGroup); + rolloutGroup.setStatus(RolloutGroupStatus.SCHEDULED); + } + rolloutGroupRepository.save(rolloutGroup); + } + rollout.setStatus(RolloutStatus.RUNNING); + return rolloutRepository.save(rollout); + } + + /** + * Pauses a rollout which is currently running. The Rollout switches + * {@link RolloutStatus#PAUSED}. {@link RolloutGroup}s which are currently + * running will be untouched. {@link RolloutGroup}s which are + * {@link RolloutGroupStatus#SCHEDULED} will not be started and keep in + * {@link RolloutGroupStatus#SCHEDULED} state until the rollout is + * {@link RolloutManagement#resumeRollout(Rollout)}. + * + * Switching the rollout status to {@link RolloutStatus#PAUSED} is + * sufficient due the {@link #checkRunningRollouts(long)} will not check + * this rollout anymore. + * + * @param rollout + * the rollout to be paused. + * + * @throws RolloutIllegalStateException + * if given rollout is not in {@link RolloutStatus#RUNNING}. + * Only running rollouts can be paused. + */ + @Transactional + @Modifying + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR + + SpringEvalExpressions.IS_SYSTEM_CODE) + public void pauseRollout(final Rollout rollout) { + final Rollout mergedRollout = entityManager.merge(rollout); + if (mergedRollout.getStatus() != RolloutStatus.RUNNING) { + throw new RolloutIllegalStateException("Rollout can only be paused in state running but current state is " + + rollout.getStatus().name().toLowerCase()); + } + // setting the complete rollout only in paused state. This is sufficient + // due the currently running groups will be completed and new groups are + // not started until rollout goes back to running state again. The + // periodically check for running rollouts will skip rollouts in pause + // state. + mergedRollout.setStatus(RolloutStatus.PAUSED); + rolloutRepository.save(mergedRollout); + } + + /** + * Resumes a paused rollout. The rollout switches back to + * {@link RolloutStatus#RUNNING} state which is then picked up again by the + * {@link #checkRunningRollouts(long)}. + * + * @param rollout + * the rollout to be resumed + * @throws RolloutIllegalStateException + * if given rollout is not in {@link RolloutStatus#PAUSED}. Only + * paused rollouts can be resumed. + */ + @Transactional + @Modifying + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR + + SpringEvalExpressions.IS_SYSTEM_CODE) + public void resumeRollout(final Rollout rollout) { + final Rollout mergedRollout = entityManager.merge(rollout); + if (!(RolloutStatus.PAUSED.equals(mergedRollout.getStatus()))) { + throw new RolloutIllegalStateException("Rollout can only be resumed in state paused but current state is " + + rollout.getStatus().name().toLowerCase()); + } + mergedRollout.setStatus(RolloutStatus.RUNNING); + rolloutRepository.save(mergedRollout); + } + + /** + * Checking running rollouts. Rollouts which are checked updating the + * {@link Rollout#setLastCheck(long)} to indicate that the current instance + * is handling the specific rollout. This code should run as system-code. + * + *
+     * {@code
+     *  SystemSecurityContext.runAsSystem(new Callable() {
+     *     public Void call() throws Exception {
+     *        //run system-code
+     *     }
+     * });
+     *  }
+     * 
+ * + * This method is attend to be called by a scheduler. + * {@link RolloutScheduler}. And must be running in an transaction so it's + * splitted from the scheduler. + * + * Rollouts which are currently running are investigated, by means the + * error- and finish condition of running groups in this rollout are + * evaluated. + * + * @param delayBetweenChecks + * the time in milliseconds of the delay between the further and + * this check. This check is only applied if the last check is + * less than (lastcheck-delay). + */ + @Transactional + @Modifying + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE + SpringEvalExpressions.HAS_AUTH_OR + + SpringEvalExpressions.IS_SYSTEM_CODE) + public void checkRunningRollouts(final long delayBetweenChecks) { + verifyStuckedRollouts(); + final long lastCheck = System.currentTimeMillis(); + final int updated = rolloutRepository.updateLastCheck(lastCheck, delayBetweenChecks, RolloutStatus.RUNNING); + + if (updated == 0) { + // nothing to check, maybe another instance already checked in + // between + LOGGER.debug("No rolloutcheck necessary for current scheduled check {}, next check at {}", lastCheck, + lastCheck + delayBetweenChecks); + return; + } + + final List rolloutsToCheck = rolloutRepository.findByLastCheckAndStatus(lastCheck, + RolloutStatus.RUNNING); + LOGGER.info("Found {} running rollouts to check", rolloutsToCheck.size()); + + for (final Rollout rollout : rolloutsToCheck) { + LOGGER.debug("Checking rollout {}", rollout); + final List rolloutGroups = rolloutGroupRepository.findByRolloutAndStatus(rollout, + RolloutGroupStatus.RUNNING); + + if (rolloutGroups.isEmpty()) { + // no running rollouts, probably there was an error + // somewhere at the latest group. And the latest group has + // been switched from running into error state. So we need + // to find the latest group which + executeLatestRolloutGroup(rollout); + } else { + LOGGER.debug("Rollout {} has {} running groups", rollout.getId(), rolloutGroups.size()); + executeRolloutGroups(rollout, rolloutGroups); + } + + if (isRolloutComplete(rollout)) { + LOGGER.info("Rollout {} is finished, setting finished status", rollout); + rollout.setStatus(RolloutStatus.FINISHED); + rolloutRepository.save(rollout); + } + } + } + + /** + * Verifies and handles stucked rollouts in asynchronous creation or + * starting state. If rollouts are created or started asynchronously it + * might be that they keep in state {@link RolloutStatus#CREATING} or + * {@link RolloutStatus#STARTING} due database or application interruption. + * In case this happens, set the rollout to error state. + */ + private void verifyStuckedRollouts() { + final List rolloutsInCreatingState = rolloutRepository.findByStatus(RolloutStatus.CREATING); + rolloutsInCreatingState.stream().filter(rollout -> !creatingRollouts.contains(rollout.getName())) + .forEach(rollout -> { + LOGGER.warn( + "Determined error during rollout creation of rollout {}, stucking in creating state, setting to status", + rollout, RolloutStatus.ERROR_CREATING); + rollout.setStatus(RolloutStatus.ERROR_CREATING); + rolloutRepository.save(rollout); + }); + + final List rolloutsInStartingState = rolloutRepository.findByStatus(RolloutStatus.STARTING); + rolloutsInStartingState.stream().filter(rollout -> !startingRollouts.contains(rollout.getName())) + .forEach(rollout -> { + LOGGER.warn( + "Determined error during rollout starting of rollout {}, stucking in starting state, setting to status", + rollout, RolloutStatus.ERROR_STARTING); + rollout.setStatus(RolloutStatus.ERROR_STARTING); + rolloutRepository.save(rollout); + }); + + } + + private void executeRolloutGroups(final Rollout rollout, final List rolloutGroups) { + for (final RolloutGroup rolloutGroup : rolloutGroups) { + // error state check, do we need to stop the whole + // rollout because of error? + final RolloutGroupErrorCondition errorCondition = rolloutGroup.getErrorCondition(); + final boolean isError = checkErrorState(rollout, rolloutGroup, errorCondition); + if (isError) { + LOGGER.info("Rollout {} {} has error, calling error action", rollout.getName(), rollout.getId()); + callErrorAction(rollout, rolloutGroup); + } else { + // not in error so check finished state, do we need to + // start the next group? + final RolloutGroupSuccessCondition finishedCondition = rolloutGroup.getSuccessCondition(); + checkFinishCondition(rollout, rolloutGroup, finishedCondition); + if (isRolloutGroupComplete(rollout, rolloutGroup)) { + rolloutGroup.setStatus(RolloutGroupStatus.FINISHED); + rolloutGroupRepository.save(rolloutGroup); + } + } + } + } + + private void executeLatestRolloutGroup(final Rollout rollout) { + final List latestRolloutGroup = rolloutGroupRepository + .findByRolloutAndStatusNotOrderByIdDesc(rollout, RolloutGroupStatus.SCHEDULED); + if (latestRolloutGroup.isEmpty()) { + return; + } + executeRolloutGroupSuccessAction(rollout, latestRolloutGroup.get(0)); + } + + private void callErrorAction(final Rollout rollout, final RolloutGroup rolloutGroup) { + try { + context.getBean(rolloutGroup.getErrorAction().getBeanName(), RolloutGroupActionEvaluator.class) + .eval(rollout, rolloutGroup, rolloutGroup.getErrorActionExp()); + } catch (final BeansException e) { + LOGGER.error("Something bad happend when accessing the error action bean {}", + rolloutGroup.getErrorAction().getBeanName(), e); + } + } + + private boolean isRolloutComplete(final Rollout rollout) { + final Long groupsActiveLeft = rolloutGroupRepository.countByRolloutAndStatusOrStatus(rollout, + RolloutGroupStatus.RUNNING, RolloutGroupStatus.SCHEDULED); + return groupsActiveLeft == 0; + } + + private boolean isRolloutGroupComplete(final Rollout rollout, final RolloutGroup rolloutGroup) { + final Long actionsLeftForRollout = actionRepository + .countByRolloutAndRolloutGroupAndStatusNotAndStatusNotAndStatusNot(rollout, rolloutGroup, + Action.Status.ERROR, Action.Status.FINISHED, Action.Status.CANCELED); + return actionsLeftForRollout == 0; + } + + private boolean checkErrorState(final Rollout rollout, final RolloutGroup rolloutGroup, + final RolloutGroupErrorCondition errorCondition) { + if (errorCondition == null) { + // there is no error condition, so return false, don't have error. + return false; + } + try { + return context.getBean(errorCondition.getBeanName(), RolloutGroupConditionEvaluator.class).eval(rollout, + rolloutGroup, rolloutGroup.getErrorConditionExp()); + } catch (final BeansException e) { + LOGGER.error("Something bad happend when accessing the error condition bean {}", + errorCondition.getBeanName(), e); + return false; + } + } + + private boolean checkFinishCondition(final Rollout rollout, final RolloutGroup rolloutGroup, + final RolloutGroupSuccessCondition finishCondition) { + LOGGER.trace("Checking finish condition {} on rolloutgroup {}", finishCondition, rolloutGroup); + try { + final boolean isFinished = context + .getBean(finishCondition.getBeanName(), RolloutGroupConditionEvaluator.class) + .eval(rollout, rolloutGroup, rolloutGroup.getSuccessConditionExp()); + if (isFinished) { + LOGGER.info("Rolloutgroup {} is finished, starting next group", rolloutGroup); + executeRolloutGroupSuccessAction(rollout, rolloutGroup); + } else { + LOGGER.debug("Rolloutgroup {} is still running", rolloutGroup); + } + return isFinished; + } catch (final BeansException e) { + LOGGER.error("Something bad happend when accessing the finish condition bean {}", + finishCondition.getBeanName(), e); + return false; + } + } + + private void executeRolloutGroupSuccessAction(final Rollout rollout, final RolloutGroup rolloutGroup) { + context.getBean(rolloutGroup.getSuccessAction().getBeanName(), RolloutGroupActionEvaluator.class).eval(rollout, + rolloutGroup, rolloutGroup.getSuccessActionExp()); + } + + /** + * Counts all {@link Target}s in the repository. + * + * @return number of targets + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Long countRolloutsAll() { + return rolloutRepository.count(); + } + + /** + * Count rollouts by specified filter text. + * + * @param searchText + * name or description + * @return total count rollouts for specified filter text. + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Long countRolloutsAllByFilters(final String searchText) { + return rolloutRepository.count(likeNameOrDescription(searchText)); + } + + private static Specification likeNameOrDescription(final String searchText) { + return (rolloutRoot, query, criteriaBuilder) -> { + final String searchTextToLower = searchText.toLowerCase(); + return criteriaBuilder.or( + criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(Rollout_.name)), searchTextToLower), + criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(Rollout_.description)), + searchTextToLower)); + }; + } + + /** + * * Retrieves a specific rollout by its ID. + * + * @param pageable + * the page request to sort and limit the result + * @param searchText + * search text which matches name or description of rollout + * @return the founded rollout or {@code null} if rollout with given ID does + * not exists + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Slice findRolloutByFilters(final Pageable pageable, @NotEmpty final String searchText) { + final Specification specs = likeNameOrDescription(searchText); + final Slice findAll = criteriaNoCountDao.findAll(specs, pageable, Rollout.class); + setRolloutStatusDetails(findAll); + return findAll; + } + + /** + * Retrieves a specific rollout by its name. + * + * @param rolloutName + * the name of the rollout to retrieve + * @return the founded rollout or {@code null} if rollout with given name + * does not exists + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Rollout findRolloutByName(final String rolloutName) { + return rolloutRepository.findByName(rolloutName); + } + + /** + * Update rollout details. + * + * @param rollout + * rollout to be updated + * + * @return Rollout updated rollout + */ + @NotNull + @Transactional + @Modifying + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE) + public Rollout updateRollout(@NotNull final Rollout rollout) { + Assert.notNull(rollout.getId()); + return rolloutRepository.save(rollout); + } + + /** + * Get count of targets in different status in rollout. + * + * @param page + * the page request to sort and limit the result + * @return a list of rollouts with details of targets count for different + * statuses + * + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Page findAllRolloutsWithDetailedStatus(final Pageable page) { + final Page rollouts = findAll(page); + setRolloutStatusDetails(rollouts); + return rollouts; + + } + + /** + * Get count of targets in different status in rollout. + * + * @param rolloutId + * rollout id + * @return rollout details of targets count for different statuses + * + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ) + public Rollout findRolloutWithDetailedStatus(final Long rolloutId) { + final Rollout rollout = findRolloutById(rolloutId); + final List rolloutStatusCountItems = actionRepository + .getStatusCountByRolloutId(rolloutId); + final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(rolloutStatusCountItems, + rollout.getTotalTargets()); + rollout.setTotalTargetCountStatus(totalTargetCountStatus); + return rollout; + } + + private Map> getStatusCountItemForRollout(final List rolloutIds) { + final List resultList = actionRepository.getStatusCountByRolloutId(rolloutIds); + return resultList.stream().collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId)); + } + + private void setRolloutStatusDetails(final Slice rollouts) { + final List rolloutIds = rollouts.getContent().stream().map(rollout -> rollout.getId()) + .collect(Collectors.toList()); + final Map> allStatesForRollout = getStatusCountItemForRollout( + rolloutIds); + + for (final Rollout rollout : rollouts) { + final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus( + allStatesForRollout.get(rollout.getId()), rollout.getTotalTargets()); + rollout.setTotalTargetCountStatus(totalTargetCountStatus); + } + } + + private void checkIfRolloutCanStarted(final Rollout rollout, final Rollout mergedRollout) { + if (!(RolloutStatus.READY.equals(mergedRollout.getStatus()))) { + throw new RolloutIllegalStateException("Rollout can only be started in state ready but current state is " + + rollout.getStatus().name().toLowerCase()); + } + } + + /*** + * Get finished percentage details for a specified group which is in running + * state. + * + * @param rolloutId + * the ID of the {@link Rollout} + * @param rolloutGroup + * the ID of the {@link RolloutGroup} + * @return percentage finished + */ + public float getFinishedPercentForRunningGroup(final Long rolloutId, final RolloutGroup rolloutGroup) { + final Long totalGroup = rolloutGroup.getTotalTargets(); + final Long finished = actionRepository.countByRolloutIdAndRolloutGroupIdAndStatus(rolloutId, + rolloutGroup.getId(), Action.Status.FINISHED); + if (totalGroup == 0) { + // in case e.g. targets has been deleted we don't have any actions + // left for this group, so the group is finished + return 100; + } + // calculate threshold + return ((float) finished / (float) totalGroup) * 100; + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutProperties.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutProperties.java new file mode 100644 index 000000000..2384fa480 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutProperties.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * Rollout Management properties. + * + */ +@Component +@ConfigurationProperties("hawkbit.rollout") +public class RolloutProperties { + private final Scheduler scheduler = new Scheduler(); + + public Scheduler getScheduler() { + return scheduler; + } + + /** + * Rollout scheduler configuration. + */ + public static class Scheduler { + // used by @Scheduled annotation which needs constant + public static final String PROP_SCHEDULER_DELAY_PLACEHOLDER = "${hawkbit.rollout.scheduler.fixedDelay:30000}"; + + /** + * Schedule where the rollout scheduler looks necessary state changes in + * milliseconds. + */ + private long fixedDelay = 30000L; + + public long getFixedDelay() { + return fixedDelay; + } + + public void setFixedDelay(final long fixedDelay) { + this.fixedDelay = fixedDelay; + } + + } + +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutRepository.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutRepository.java new file mode 100644 index 000000000..9c4318a3f --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutRepository.java @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository; + +import java.util.List; + +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; + +/** + * The repository interface for the {@link Rollout} model. + */ +@Transactional(readOnly = true) +public interface RolloutRepository extends BaseEntityRepository, JpaSpecificationExecutor { + + /** + * Updates the {@code lastCheck} field of the {@link Rollout} for rollouts + * in a specific status and only if the {@code lastCheck} is overdue. + * + * @param lastCheck + * the time in milliseconds to set to the lastCheck column + * @param delay + * the delay between last checks + * @param status + * the status which the rollout should have to update the last + * check field + * @return the count of the updated rows. Zero if no row has been updated + */ + @Modifying + @Transactional + @Query("UPDATE Rollout r SET r.lastCheck = :lastCheck WHERE r.lastCheck < (:lastCheck - :delay) AND r.status=:status") + int updateLastCheck(@Param("lastCheck") final long lastCheck, @Param("delay") final long delay, + @Param("status") final RolloutStatus status); + + /** + * Retrieves all {@link Rollout} for a specific {@code lastCheck} time and + * for a specific status. + * + * @param lastCheck + * the lastCheck time to find the specific rollout. + * @param status + * the status of the rollout to find + * @return the list of {@link Rollout} for specific lastCheck time and + * status + */ + List findByLastCheckAndStatus(long lastCheck, RolloutStatus status); + + /** + * Retrieves all {@link Rollout} for a specific {@code name} + * + * @param name + * the rollout name + * @return {@link Rollout} for specific name + */ + Page findByName(final Pageable pageable, String name); + + /** + * Retrieves all {@link Rollout} for a specific {@code name} + * + * @param name + * the rollout name + * @return {@link Rollout} for specific name + */ + Rollout findByName(String name); + + /** + * Retrieves all {@link Rollout} for a specific status. + * + * @param status + * the status of the rollouts to retrieve + * @return a list of {@link Rollout} having the given status + */ + List findByStatus(final RolloutStatus status); +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutScheduler.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutScheduler.java new file mode 100644 index 000000000..6671ad896 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutScheduler.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository; + +import java.util.List; + +import org.eclipse.hawkbit.security.SystemSecurityContext; +import org.eclipse.hawkbit.tenancy.TenantAware; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +/** + * Scheduler to schedule the + * {@link RolloutManagement#checkRunningRollouts(long)}. The delay between the + * checks be be configured using the property + * {@link #PROP_SCHEDULER_DELAY_PLACEHOLDER}. + */ +@Component +// don't active the rollout scheduler in test, otherwise it is hard to test +// rolloutmanagement and leads weird side-effects maybe. +@Profile("!test") +public class RolloutScheduler { + + private static final Logger logger = LoggerFactory.getLogger(RolloutScheduler.class); + + @Autowired + private TenantAware tenantAware; + + @Autowired + private SystemManagement systemManagement; + + @Autowired + private RolloutManagement rolloutManagement; + + @Autowired + private SystemSecurityContext systemSecurityContext; + + @Autowired + private RolloutProperties rolloutProperties; + + /** + * Scheduler method called by the spring-async mechanism. Retrieves all + * tenants from the {@link SystemManagement#findTenants()} and runs for each + * tenant the {@link RolloutManagement#checkRunningRollouts(long)} in the + * {@link SystemSecurityContext}. + */ + @Scheduled(initialDelayString = RolloutProperties.Scheduler.PROP_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = RolloutProperties.Scheduler.PROP_SCHEDULER_DELAY_PLACEHOLDER) + public void rolloutScheduler() { + logger.debug("rollout schedule checker has been triggered."); + // run this code in system code privileged to have the necessary + // permission to query and create entities. + systemSecurityContext.runAsSystem(() -> { + // workaround eclipselink that is currently not possible to + // execute a query without multitenancy if MultiTenant + // annotation is used. + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=355458. So + // iterate through all tenants and execute the rollout check for + // each tenant seperately. + final List tenants = systemManagement.findTenants(); + logger.info("Checking rollouts for {} tenants", tenants.size()); + for (final String tenant : tenants) { + tenantAware.runAsTenant(tenant, () -> { + rolloutManagement.checkRunningRollouts(rolloutProperties.getScheduler().getFixedDelay()); + return null; + }); + } + return null; + }); + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutTargetGroupRepository.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutTargetGroupRepository.java new file mode 100644 index 000000000..6564e2c1f --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/RolloutTargetGroupRepository.java @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository; + +import org.eclipse.hawkbit.repository.model.RolloutTargetGroup; +import org.eclipse.hawkbit.repository.model.RolloutTargetGroupId; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.repository.CrudRepository; + +/** + * + * + */ +public interface RolloutTargetGroupRepository + extends CrudRepository, JpaSpecificationExecutor { +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/SoftwareManagement.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/SoftwareManagement.java index 98cb1f418..1f610d29c 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/SoftwareManagement.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/SoftwareManagement.java @@ -40,6 +40,7 @@ import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.model.SoftwareModule_; import org.eclipse.hawkbit.repository.model.SwMetadataCompositeKey; import org.eclipse.hawkbit.repository.specifications.SoftwareModuleSpecification; +import org.eclipse.hawkbit.repository.specifications.SpecificationsBuilder; import org.hibernate.validator.constraints.NotEmpty; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.AuditorAware; @@ -48,7 +49,6 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.SliceImpl; import org.springframework.data.jpa.domain.Specification; -import org.springframework.data.jpa.domain.Specifications; import org.springframework.data.jpa.repository.Modifying; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; @@ -198,7 +198,7 @@ public class SoftwareManagement { /** * retrieves the {@link SoftwareModule}s by their {@link SoftwareModuleType} * . - * + * * @param pageable * page parameters * @param type @@ -209,7 +209,7 @@ public class SoftwareManagement { public Slice findSoftwareModulesByType(@NotNull final Pageable pageable, @NotNull final SoftwareModuleType type) { - final List> specList = new ArrayList>(); + final List> specList = new ArrayList<>(); Specification spec = SoftwareModuleSpecification.equalType(type); specList.add(spec); @@ -230,7 +230,7 @@ public class SoftwareManagement { @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) public Long countSoftwareModulesByType(@NotNull final SoftwareModuleType type) { - final List> specList = new ArrayList>(); + final List> specList = new ArrayList<>(); Specification spec = SoftwareModuleSpecification.equalType(type); specList.add(spec); @@ -257,7 +257,7 @@ public class SoftwareManagement { /** * retrieves {@link SoftwareModule}s by their name AND version. - * + * * @param name * of the {@link SoftwareModule} * @param version @@ -273,7 +273,7 @@ public class SoftwareManagement { /** * Deletes the given {@link SoftwareModule} {@link Entity}. - * + * * @param bsm * is the {@link SoftwareModule} to be deleted */ @@ -291,26 +291,12 @@ public class SoftwareManagement { private Slice findSwModuleByCriteriaAPI(@NotNull final Pageable pageable, @NotEmpty final List> specList) { - - Specifications specs = Specifications.where(specList.get(0)); - if (specList.size() > 1) { - for (final Specification s : specList.subList(1, specList.size())) { - specs = specs.and(s); - } - } - - return criteriaNoCountDao.findAll(specs, pageable, SoftwareModule.class); + return criteriaNoCountDao.findAll(SpecificationsBuilder.combineWithAnd(specList), pageable, + SoftwareModule.class); } private Long countSwModuleByCriteriaAPI(@NotEmpty final List> specList) { - Specifications specs = Specifications.where(specList.get(0)); - if (specList.size() > 1) { - for (final Specification s : specList.subList(1, specList.size())) { - specs = specs.and(s); - } - } - - return softwareModuleRepository.count(specs); + return softwareModuleRepository.count(SpecificationsBuilder.combineWithAnd(specList)); } private void deleteGridFsArtifacts(final SoftwareModule swModule) { @@ -321,7 +307,7 @@ public class SoftwareManagement { /** * Deletes {@link SoftwareModule}s which is any if the given ids. - * + * * @param ids * of the Software Moduels to be deleted */ @@ -366,20 +352,16 @@ public class SoftwareManagement { @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) public Slice findSoftwareModulesAll(@NotNull final Pageable pageable) { - final List> specList = new ArrayList>(); + final List> specList = new ArrayList<>(); Specification spec = SoftwareModuleSpecification.isDeletedFalse(); specList.add(spec); - spec = new Specification() { - @Override - public Predicate toPredicate(final Root root, final CriteriaQuery query, - final CriteriaBuilder cb) { - if (!query.getResultType().isAssignableFrom(Long.class)) { - root.fetch(SoftwareModule_.type); - } - return cb.conjunction(); + spec = (root, query, cb) -> { + if (!query.getResultType().isAssignableFrom(Long.class)) { + root.fetch(SoftwareModule_.type); } + return cb.conjunction(); }; specList.add(spec); @@ -396,7 +378,7 @@ public class SoftwareManagement { @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) public Long countSoftwareModulesAll() { - final List> specList = new ArrayList>(); + final List> specList = new ArrayList<>(); final Specification spec = SoftwareModuleSpecification.isDeletedFalse(); specList.add(spec); @@ -479,7 +461,7 @@ public class SoftwareManagement { public Slice findSoftwareModuleByFilters(@NotNull final Pageable pageable, final String searchText, final SoftwareModuleType type) { - final List> specList = new ArrayList>(); + final List> specList = new ArrayList<>(); Specification spec = SoftwareModuleSpecification.isDeletedFalse(); specList.add(spec); @@ -494,15 +476,11 @@ public class SoftwareManagement { specList.add(spec); } - spec = new Specification() { - @Override - public Predicate toPredicate(final Root root, final CriteriaQuery query, - final CriteriaBuilder cb) { - if (!query.getResultType().isAssignableFrom(Long.class)) { - root.fetch(SoftwareModule_.type); - } - return cb.conjunction(); + spec = (root, query, cb) -> { + if (!query.getResultType().isAssignableFrom(Long.class)) { + root.fetch(SoftwareModule_.type); } + return cb.conjunction(); }; specList.add(spec); @@ -592,7 +570,7 @@ public class SoftwareManagement { private List> buildSpecificationList(final String searchText, final SoftwareModuleType type) { - final List> specList = new ArrayList>(); + final List> specList = new ArrayList<>(); if (!Strings.isNullOrEmpty(searchText)) { specList.add(SoftwareModuleSpecification.likeNameOrVersion(searchText)); } @@ -631,7 +609,7 @@ public class SoftwareManagement { @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) public Long countSoftwareModuleByFilters(final String searchText, final SoftwareModuleType type) { - final List> specList = new ArrayList>(); + final List> specList = new ArrayList<>(); Specification spec = SoftwareModuleSpecification.isDeletedFalse(); specList.add(spec); @@ -788,7 +766,7 @@ public class SoftwareManagement { /** * creates or updates a single software module meta data entry. - * + * * @param metadata * the meta data entry to create or update * @return the updated or created software module meta data entry @@ -813,7 +791,7 @@ public class SoftwareManagement { /** * creates a list of software module meta data entries. - * + * * @param metadata * the meta data entries to create or update * @return the updated or created software module meta data entries @@ -835,7 +813,7 @@ public class SoftwareManagement { /** * updates a distribution set meta data value if corresponding entry exists. - * + * * @param metadata * the meta data entry to be updated * @return the updated meta data entry @@ -858,7 +836,7 @@ public class SoftwareManagement { /** * deletes a software module meta data entry. - * + * * @param id * the ID of the software module meta data to delete */ @@ -871,7 +849,7 @@ public class SoftwareManagement { /** * finds all meta data by the given software module id. - * + * * @param swId * the software module id to retrieve the meta data from * @param pageable @@ -887,7 +865,7 @@ public class SoftwareManagement { /** * finds all meta data by the given software module id. - * + * * @param softwareModuleId * the software module id to retrieve the meta data from * @param spec @@ -900,20 +878,19 @@ public class SoftwareManagement { @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY) public Page findSoftwareModuleMetadataBySoftwareModuleId(final Long softwareModuleId, @NotNull final Specification spec, @NotNull final Pageable pageable) { - return softwareModuleMetadataRepository.findAll(new Specification() { - @Override - public Predicate toPredicate(final Root root, final CriteriaQuery query, - final CriteriaBuilder cb) { - - return cb.and(cb.equal(root.get(SoftwareModuleMetadata_.softwareModule).get(SoftwareModule_.id), - softwareModuleId), spec.toPredicate(root, query, cb)); - } - }, pageable); + return softwareModuleMetadataRepository + .findAll( + (Specification) (root, query, + cb) -> cb.and( + cb.equal(root.get(SoftwareModuleMetadata_.softwareModule) + .get(SoftwareModule_.id), softwareModuleId), + spec.toPredicate(root, query, cb)), + pageable); } /** * finds a single software module meta data by its id. - * + * * @param id * the id of the software module meta data containing the meta * data key and the ID of the software module diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java index 64865ac65..77b48a3cd 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java @@ -22,10 +22,8 @@ import org.eclipse.hawkbit.report.model.SystemUsageReport; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; -import org.eclipse.hawkbit.repository.model.TenantConfiguration; import org.eclipse.hawkbit.repository.model.TenantMetaData; import org.eclipse.hawkbit.tenancy.TenantAware; -import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.eclipse.persistence.config.PersistenceUnitProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; @@ -33,12 +31,7 @@ import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.cache.interceptor.SimpleKeyGenerator; -import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Bean; -import org.springframework.core.convert.ConversionFailedException; -import org.springframework.core.convert.support.ConfigurableConversionService; -import org.springframework.core.convert.support.DefaultConversionService; -import org.springframework.core.env.Environment; import org.springframework.data.jpa.repository.Modifying; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; @@ -53,7 +46,7 @@ import org.springframework.validation.annotation.Validated; @Transactional(readOnly = true) @Validated @Service -public class SystemManagement implements EnvironmentAware { +public class SystemManagement { @Autowired private EntityManager entityManager; @@ -96,6 +89,12 @@ public class SystemManagement implements EnvironmentAware { @Autowired private TenantConfigurationRepository tenantConfigurationRepository; + @Autowired + private RolloutRepository rolloutRepository; + + @Autowired + private RolloutGroupRepository rolloutGroupRepository; + @Autowired private TenantAware tenantAware; @@ -107,10 +106,6 @@ public class SystemManagement implements EnvironmentAware { private final ThreadLocal createInitialTenant = new ThreadLocal<>(); - private final ConfigurableConversionService conversionService = new DefaultConversionService(); - - private Environment environment; - /** * Calculated system usage statistics, both overall for the entire system * and per tenant; @@ -209,7 +204,8 @@ public class SystemManagement implements EnvironmentAware { * @return list of all tenant names in the system. */ @NotNull - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN) + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN + SpringEvalExpressions.HAS_AUTH_OR + + SpringEvalExpressions.IS_SYSTEM_CODE) // tenant independent public List findTenants() { return tenantMetaDataRepository.findAll().stream().map(md -> md.getTenant()).collect(Collectors.toList()); @@ -234,11 +230,13 @@ public class SystemManagement implements EnvironmentAware { tenantMetaDataRepository.deleteByTenantIgnoreCase(tenant); tenantConfigurationRepository.deleteByTenantIgnoreCase(tenant); targetRepository.deleteByTenantIgnoreCase(tenant); + actionRepository.deleteByTenantIgnoreCase(tenant); + rolloutGroupRepository.deleteByTenantIgnoreCase(tenant); + rolloutRepository.deleteByTenantIgnoreCase(tenant); artifactRepository.deleteByTenantIgnoreCase(tenant); externalArtifactRepository.deleteByTenantIgnoreCase(tenant); externalArtifactProviderRepository.deleteByTenantIgnoreCase(tenant); targetTagRepository.deleteByTenantIgnoreCase(tenant); - actionRepository.deleteByTenantIgnoreCase(tenant); distributionSetTagRepository.deleteByTenantIgnoreCase(tenant); distributionSetRepository.deleteByTenantIgnoreCase(tenant); distributionSetTypeRepository.deleteByTenantIgnoreCase(tenant); @@ -271,7 +269,7 @@ public class SystemManagement implements EnvironmentAware { * @return {@code true} in case the tenant exits or {@code false} if not */ @Cacheable(value = "currentTenant", keyGenerator = "currentTenantKeyGenerator") - // MECS-903 set transaction to not supported, due we call this in + // set transaction to not supported, due we call this in // BaseEntity#prePersist methods // and it seems that JPA committing the transaction when executing this // transactional method, @@ -311,88 +309,6 @@ public class SystemManagement implements EnvironmentAware { return tenantMetaDataRepository.save(metaData); } - /** - * Retrieves a configuration value from the e.g. tenant overwritten - * configuration values or in case the tenant does not a have a specific - * configuration the global default value hold in the {@link Environment}. - * - * @param configurationKey - * the key of the configuration - * @param propertyType - * the type of the configuration value, e.g. {@code String.class} - * , {@code Integer.class}, etc - * @return the converted configuration value either from the tenant specific - * configuration stored or from the fallback default values or - * {@code null} in case key has not been configured and not default - * value exists - * @throws ConversionFailedException - * if the property cannot be converted to the given - * {@code propertyType} - */ - @Cacheable(value = "tenantConfiguration", key = "#configurationKey.getKeyName()") - public T getConfigurationValue(final TenantConfigurationKey configurationKey, final Class propertyType) { - final TenantConfiguration tenantConfiguration = tenantConfigurationRepository - .findByKey(configurationKey.getKeyName()); - if (tenantConfiguration != null) { - return conversionService.convert(tenantConfiguration.getValue(), propertyType); - } else if (configurationKey.getDefaultKeyName() != null) { - final T defaultKeyNameValue = environment.getProperty(configurationKey.getDefaultKeyName(), propertyType); - return defaultKeyNameValue != null ? defaultKeyNameValue - : conversionService.convert(configurationKey.getDefaultValue(), propertyType); - } - return null; - } - - /** - * Adds or updates a specific configuration for a specific tenant. - * - * @param tenantConf - * the tenant configuration object which contains the key and - * value of the specific configuration to update - * @return the added or updated TenantConfiguration - */ - @CacheEvict(value = "tenantConfiguration", key = "#tenantConf.getKey()") - @Transactional - @Modifying - public TenantConfiguration addOrUpdateConfiguration(final TenantConfiguration tenantConf) { - TenantConfiguration tenantConfiguration = tenantConfigurationRepository.findByKey(tenantConf.getKey()); - if (tenantConfiguration != null) { - tenantConfiguration.setValue(tenantConf.getValue()); - } else { - tenantConfiguration = new TenantConfiguration(tenantConf.getKey(), tenantConf.getValue()); - } - return tenantConfigurationRepository.save(tenantConfiguration); - } - - /** - * Deletes a specific configuration for the current tenant. - * - * @param configurationKey - * the configuration key to be deleted - */ - @CacheEvict(value = "tenantConfiguration", key = "#configurationKey.getKeyName()") - @Transactional - @Modifying - public void deleteConfiguration(final TenantConfigurationKey configurationKey) { - tenantConfigurationRepository.deleteByKey(configurationKey.getKeyName()); - } - - @Transactional - public List getTenantConfigurations() { - return tenantConfigurationRepository.findAll(); - } - - /* - * (non-Javadoc) - * - * @see org.springframework.context.EnvironmentAware#setEnvironment(org. - * springframework.core.env. Environment) - */ - @Override - public void setEnvironment(final Environment environment) { - this.environment = environment; - } - private DistributionSetType createStandardSoftwareDataSetup() { // Edge Controller Linux standard setup @@ -449,7 +365,5 @@ public class SystemManagement implements EnvironmentAware { return SimpleKeyGenerator.generateKey(initialTenantCreation.toUpperCase(), initialTenantCreation.toUpperCase()); } - } - } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TagManagement.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TagManagement.java index 4d8b4ff5c..e0ab951ac 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TagManagement.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TagManagement.java @@ -160,7 +160,7 @@ public class TagManagement { public void deleteTargetTag(@NotEmpty final String targetTagName) { final TargetTag tag = targetTagRepository.findByNameEquals(targetTagName); - final List changed = new LinkedList(); + final List changed = new LinkedList<>(); for (final Target target : targetRepository.findByTag(tag)) { target.getTags().remove(tag); changed.add(target); @@ -311,7 +311,7 @@ public class TagManagement { public void deleteDistributionSetTag(@NotEmpty final String tagName) { final DistributionSetTag tag = distributionSetTagRepository.findByNameEquals(tagName); - final List changed = new LinkedList(); + final List changed = new LinkedList<>(); for (final DistributionSet set : distributionSetRepository.findByTag(tag)) { set.getTags().remove(tag); changed.add(set); diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryManagement.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryManagement.java index 11781559e..c55127d0f 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryManagement.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryManagement.java @@ -16,6 +16,7 @@ import javax.validation.constraints.NotNull; import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; +import org.eclipse.hawkbit.repository.specifications.SpecificationsBuilder; import org.eclipse.hawkbit.repository.specifications.TargetFilterQuerySpecification; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -33,7 +34,7 @@ import com.google.common.base.Strings; /** * Business service facade for managing {@link TargetFilterQuery}s. - * + * * * */ @@ -47,7 +48,7 @@ public class TargetFilterQueryManagement { /** * creating new {@link TargetFilterQuery}. - * + * * @param customTargetFilter * @return the created {@link TargetFilterQuery} */ @@ -60,13 +61,12 @@ public class TargetFilterQueryManagement { if (targetFilterQueryRepository.findByName(customTargetFilter.getName()) != null) { throw new EntityAlreadyExistsException(customTargetFilter.getName()); } - final TargetFilterQuery filter = targetFilterQueryRepository.save(customTargetFilter); - return filter; + return targetFilterQueryRepository.save(customTargetFilter); } /** * Delete target filter query. - * + * * @param targetFilterQueryId * IDs of target filter query to be deleted */ @@ -78,9 +78,9 @@ public class TargetFilterQueryManagement { } /** - * + * * Retrieves all target filter query{@link TargetFilterQuery}. - * + * * @param pageable * pagination parameter * @return the found {@link TargetFilterQuery}s @@ -92,8 +92,8 @@ public class TargetFilterQueryManagement { /** * Retrieves all target filter query which {@link TargetFilterQuery}. - * - * + * + * * @param pageable * pagination parameter * @param name @@ -102,7 +102,7 @@ public class TargetFilterQueryManagement { */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) public Page findTargetFilterQueryByFilters(@NotNull final Pageable pageable, final String name) { - final List> specList = new ArrayList>(); + final List> specList = new ArrayList<>(); if (!Strings.isNullOrEmpty(name)) { specList.add(TargetFilterQuerySpecification.likeName(name)); } @@ -110,7 +110,7 @@ public class TargetFilterQueryManagement { } /** - * + * * @param pageable * pagination parameter * @param specList @@ -119,29 +119,21 @@ public class TargetFilterQueryManagement { */ private Page findTargetFilterQueryByCriteriaAPI(@NotNull final Pageable pageable, final List> specList) { - Specifications specs = null; - if (!specList.isEmpty()) { - specs = Specifications.where(specList.get(0)); - } - if (specList.size() > 1) { - for (final Specification s : specList.subList(1, specList.size())) { - specs = specs.and(s); - } - } - if (specs == null) { + if (specList == null || specList.isEmpty()) { return targetFilterQueryRepository.findAll(pageable); - } else { - return targetFilterQueryRepository.findAll(specs, pageable); } + + final Specifications specs = SpecificationsBuilder.combineWithAnd(specList); + return targetFilterQueryRepository.findAll(specs, pageable); } /** * Find target filter query by name. - * + * * @param targetFilterQueryName * Target filter query name * @return the found {@link TargetFilterQuery} - * + * */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) public TargetFilterQuery findTargetFilterQueryByName(@NotNull final String targetFilterQueryName) { @@ -150,11 +142,11 @@ public class TargetFilterQueryManagement { /** * Find target filter query by id. - * + * * @param targetFilterQueryId * Target filter query id * @return the found {@link TargetFilterQuery} - * + * */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) public TargetFilterQuery findTargetFilterQueryById(@NotNull final Long targetFilterQueryId) { @@ -163,7 +155,7 @@ public class TargetFilterQueryManagement { /** * updates the {@link TargetFilterQuery}. - * + * * @param targetFilterQuery * to be updated * @return the updated {@link TargetFilterQuery} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java index e7483fdd5..e790d0a5e 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java @@ -47,6 +47,7 @@ import org.eclipse.hawkbit.repository.model.TargetTagAssigmentResult; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.repository.model.Target_; import org.eclipse.hawkbit.repository.rsql.RSQLUtility; +import org.eclipse.hawkbit.repository.specifications.SpecificationsBuilder; import org.eclipse.hawkbit.repository.specifications.TargetSpecifications; import org.hibernate.validator.constraints.NotEmpty; import org.springframework.beans.factory.annotation.Autowired; @@ -59,7 +60,6 @@ import org.springframework.data.domain.SliceImpl; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.jpa.domain.Specification; -import org.springframework.data.jpa.domain.Specifications; import org.springframework.data.jpa.repository.Modifying; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Service; @@ -508,39 +508,18 @@ public class TargetManagement { * @return the page with the found {@link Target} */ private Slice findByCriteriaAPI(final Pageable pageable, final List> specList) { - final Specifications specs = creatingTargetSpecifications(specList); - - if (specs == null) { + if (specList == null || specList.isEmpty()) { return criteriaNoCountDao.findAll(pageable, Target.class); - } else { - return criteriaNoCountDao.findAll(specs, pageable, Target.class); } - + return criteriaNoCountDao.findAll(SpecificationsBuilder.combineWithAnd(specList), pageable, Target.class); } private Long countByCriteriaAPI(final List> specList) { - final Specifications specs = creatingTargetSpecifications(specList); - - if (specs == null) { + if (specList == null || specList.isEmpty()) { return targetRepository.count(); - } else { - return targetRepository.count(specs); } - } - - private static Specifications creatingTargetSpecifications(final List> specList) { - Specifications specs = null; - if (!specList.isEmpty()) { - specs = Specifications.where(specList.get(0)); - } - if (specList.size() > 1) { - specList.remove(0); - for (final Specification s : specList) { - specs = specs.and(s); - } - } - return specs; + return targetRepository.count(SpecificationsBuilder.combineWithAnd(specList)); } /** @@ -1010,7 +989,7 @@ public class TargetManagement { @NotNull @PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_TARGET) public List createTargets(@NotNull final List targets) { - if (targetRepository.countByControllerIdIn( + if (!targets.isEmpty() && targetRepository.countByControllerIdIn( targets.stream().map(target -> target.getControllerId()).collect(Collectors.toList())) > 0) { throw new EntityAlreadyExistsException(); } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TargetRepository.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TargetRepository.java index 6cb40cd09..35ba39660 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TargetRepository.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TargetRepository.java @@ -12,10 +12,12 @@ import java.util.Collection; import java.util.List; import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.Tag; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; +import org.eclipse.hawkbit.repository.model.TargetWithActionStatus; import org.springframework.cache.annotation.CacheEvict; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -278,4 +280,43 @@ public interface TargetRepository extends BaseEntityRepository, Jp @Query("UPDATE Target t SET t.assignedDistributionSet = :set, t.lastModifiedAt = :lastModifiedAt, t.lastModifiedBy = :lastModifiedBy WHERE t.id IN :targets") void setAssignedDistributionSet(@Param("set") DistributionSet set, @Param("lastModifiedAt") Long modifiedAt, @Param("lastModifiedBy") String modifiedBy, @Param("targets") Collection targets); + + List findByRolloutTargetGroupRolloutGroup(final RolloutGroup rolloutGroup); + + /** + * + * Finds all targets of a rollout group. + * + * @param rolloutGroupId + * the ID of the rollout group + * @param page + * the page request parameter + * @return a page of all targets related to a rollout group + */ + Page findByRolloutTargetGroupRolloutGroupId(final Long rolloutGroupId, Pageable page); + + /** + * Finds all targets related to a target rollout group stored for a specific + * rollout. + * + * @param rolloutGroup + * the rollout group the targets should belong to + * @param page + * the page request parameter + * @return a page of all targets related to a rollout group + */ + Page findByActionsRolloutGroup(RolloutGroup rolloutGroup, Pageable page); + + /** + * Find all targets with action status for a specific group. + * + * @param pageable + * the page request parameter + * @param rolloutGroupId + * the ID of the rollout group + * @return targets with action status + */ + @Query("select DISTINCT NEW org.eclipse.hawkbit.repository.model.TargetWithActionStatus(a.target,a.status) from Action a inner join fetch a.target t where a.rolloutGroup.id = :rolloutGroupId") + Page findTargetsWithActionStatusByRolloutGroupId(final Pageable pageable, + @Param("rolloutGroupId") Long rolloutGroupId); } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TenantConfigurationManagement.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TenantConfigurationManagement.java new file mode 100644 index 000000000..f6b6afcfc --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/TenantConfigurationManagement.java @@ -0,0 +1,278 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository; + +import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; +import org.eclipse.hawkbit.repository.model.TenantConfiguration; +import org.eclipse.hawkbit.repository.model.TenantConfigurationValue; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; +import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationValidatorException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.ApplicationContext; +import org.springframework.context.EnvironmentAware; +import org.springframework.core.convert.ConversionFailedException; +import org.springframework.core.convert.support.ConfigurableConversionService; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.core.env.Environment; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +/** + * Central tenant configuration management operations of the SP server. + */ +@Transactional(readOnly = true) +@Validated +public class TenantConfigurationManagement implements EnvironmentAware { + + private static final TenantConfigurationManagement INSTANCE = new TenantConfigurationManagement(); + + @Autowired + private TenantConfigurationRepository tenantConfigurationRepository; + + @Autowired + private ApplicationContext applicationContext; + + private final ConfigurableConversionService conversionService = new DefaultConversionService(); + + private Environment environment; + + /** + * Get Singleton instance, needed for classes which are not managed in + * Spring context + * + * @return singleton instance of TenantConfigurationManagement + */ + public static TenantConfigurationManagement getInstance() { + return INSTANCE; + } + + /** + * Retrieves a configuration value from the e.g. tenant overwritten + * configuration values or in case the tenant does not a have a specific + * configuration the global default value hold in the {@link Environment}. + * + * @param + * the type of the configuration value + * @param configurationKey + * the key of the configuration + * @param propertyType + * the type of the configuration value, e.g. {@code String.class} + * , {@code Integer.class}, etc + * @return the converted configuration value either from the tenant specific + * configuration stored or from the fallback default values or + * {@code null} in case key has not been configured and not default + * value exists + * @throws TenantConfigurationValidatorException + * if the {@code propertyType} and the value in general does not + * match the expected type and format defined by the Key + * @throws ConversionFailedException + * if the property cannot be converted to the given + * {@code propertyType} + */ + + @Cacheable(value = "tenantConfiguration", key = "#configurationKey.getKeyName()") + @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION + SpringEvalExpressions.HAS_AUTH_OR + + SpringEvalExpressions.IS_SYSTEM_CODE) + public TenantConfigurationValue getConfigurationValue(final TenantConfigurationKey configurationKey, + final Class propertyType) { + validateTenantConfigurationDataType(configurationKey, propertyType); + + final TenantConfiguration tenantConfiguration = tenantConfigurationRepository + .findByKey(configurationKey.getKeyName()); + + return buildTenantConfigurationValueByKey(configurationKey, propertyType, tenantConfiguration); + } + + /** + * Validates the data type of the tenant configuration. If it is possible to + * cast to the given data type. + * + * @param configurationKey + * the key + * @param propertyType + * the class + */ + protected void validateTenantConfigurationDataType(final TenantConfigurationKey configurationKey, + final Class propertyType) { + if (!configurationKey.getDataType().isAssignableFrom(propertyType)) { + throw new TenantConfigurationValidatorException( + String.format("Cannot parse the database value of type %s into the type %s.", + configurationKey.getDataType(), propertyType)); + } + } + + /** + * Build the tenant configuration by the given key + * + * @param configurationKey + * the key + * @param propertyType + * the property type + * @param tenantConfiguration + * the configuration + * @return if no default value is set and no database value available + * or returns the tenant configuration value + */ + protected TenantConfigurationValue buildTenantConfigurationValueByKey( + final TenantConfigurationKey configurationKey, final Class propertyType, + final TenantConfiguration tenantConfiguration) { + if (tenantConfiguration != null) { + return TenantConfigurationValue. builder().isGlobal(false).createdBy(tenantConfiguration.getCreatedBy()) + .createdAt(tenantConfiguration.getCreatedAt()) + .lastModifiedAt(tenantConfiguration.getLastModifiedAt()) + .lastModifiedBy(tenantConfiguration.getLastModifiedBy()) + .value(conversionService.convert(tenantConfiguration.getValue(), propertyType)).build(); + + } else if (configurationKey.getDefaultKeyName() != null) { + + return TenantConfigurationValue. builder().isGlobal(true).createdBy(null).createdAt(null) + .lastModifiedAt(null).lastModifiedBy(null) + .value(getGlobalConfigurationValue(configurationKey, propertyType)).build(); + } + return null; + } + + /** + * Retrieves a configuration value from the e.g. tenant overwritten + * configuration values or in case the tenant does not a have a specific + * configuration the global default value hold in the {@link Environment}. + * + * @param configurationKey + * the key of the configuration + * @return the converted configuration value either from the tenant specific + * configuration stored or from the fallback default values or + * {@code null} in case key has not been configured and not default + * value exists + * @throws TenantConfigurationValidatorException + * if the {@code propertyType} and the value in general does not + * match the expected type and format defined by the Key + * @throws ConversionFailedException + * if the property cannot be converted to the given + * {@code propertyType} + */ + @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION) + public TenantConfigurationValue getConfigurationValue(final TenantConfigurationKey configurationKey) { + return getConfigurationValue(configurationKey, configurationKey.getDataType()); + } + + /** + * returns the global configuration property either defined in the property + * file or an default value otherwise. + * + * @param + * the type of the configuration value + * @param configurationKey + * the key of the configuration + * @param propertyType + * the type of the configuration value, e.g. {@code String.class} + * , {@code Integer.class}, etc + * @return the global configured value + * @throws TenantConfigurationValidatorException + * if the {@code propertyType} and the value in the property + * file or the default value does not match the expected type + * and format defined by the Key + * @throws ConversionFailedException + * if the property cannot be converted to the given + * {@code propertyType} + */ + @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION) + public T getGlobalConfigurationValue(final TenantConfigurationKey configurationKey, + final Class propertyType) { + + if (!configurationKey.getDataType().isAssignableFrom(propertyType)) { + throw new TenantConfigurationValidatorException( + String.format("Cannot parse the database value of type %s into the type %s.", + configurationKey.getDataType(), propertyType)); + } + + final T valueInProperties = environment.getProperty(configurationKey.getDefaultKeyName(), propertyType); + + if (valueInProperties == null) { + return conversionService.convert(configurationKey.getDefaultValue(), propertyType); + } + + return valueInProperties; + } + + /** + * Adds or updates a specific configuration for a specific tenant. + * + * + * @param configurationKey + * the key of the configuration + * @param value + * the configuration value which will be written into the + * database. + * @return the configuration value which was just written into the database. + * @throws TenantConfigurationValidatorException + * if the {@code propertyType} and the value in general does not + * match the expected type and format defined by the Key + * @throws ConversionFailedException + * if the property cannot be converted to the given + */ + @CacheEvict(value = "tenantConfiguration", key = "#configurationKey.getKeyName()") + @Transactional + @Modifying + @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION) + public TenantConfigurationValue addOrUpdateConfiguration(final TenantConfigurationKey configurationKey, + final T value) { + + if (!configurationKey.getDataType().isAssignableFrom(value.getClass())) { + throw new TenantConfigurationValidatorException(String.format( + "Cannot parse the value %s of type %s into the type %s defined by the configuration key.", value, + value.getClass(), configurationKey.getDataType())); + } + + configurationKey.validate(applicationContext, value); + + TenantConfiguration tenantConfiguration = tenantConfigurationRepository + .findByKey(configurationKey.getKeyName()); + + if (tenantConfiguration == null) { + tenantConfiguration = new TenantConfiguration(configurationKey.getKeyName(), value.toString()); + } else { + tenantConfiguration.setValue(value.toString()); + } + + final TenantConfiguration updatedTenantConfiguration = tenantConfigurationRepository.save(tenantConfiguration); + + final Class clazzT = (Class) value.getClass(); + + return TenantConfigurationValue. builder().isGlobal(false) + .createdBy(updatedTenantConfiguration.getCreatedBy()) + .createdAt(updatedTenantConfiguration.getCreatedAt()) + .lastModifiedAt(updatedTenantConfiguration.getLastModifiedAt()) + .lastModifiedBy(updatedTenantConfiguration.getLastModifiedBy()) + .value(conversionService.convert(updatedTenantConfiguration.getValue(), clazzT)).build(); + } + + /** + * Deletes a specific configuration for the current tenant. Does nothing in + * case there is no tenant specific configuration value. + * + * @param configurationKey + * the configuration key to be deleted + */ + @CacheEvict(value = "tenantConfiguration", key = "#configurationKey.getKeyName()") + @Transactional + @Modifying + @PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION) + public void deleteConfiguration(final TenantConfigurationKey configurationKey) { + tenantConfigurationRepository.deleteByKey(configurationKey.getKeyName()); + } + + @Override + public void setEnvironment(final Environment environment) { + this.environment = environment; + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/exception/RolloutIllegalStateException.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/exception/RolloutIllegalStateException.java new file mode 100644 index 000000000..f2920c18d --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/exception/RolloutIllegalStateException.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.exception; + +import org.eclipse.hawkbit.exception.SpServerError; +import org.eclipse.hawkbit.exception.SpServerRtException; + +/** + * the {@link RolloutIllegalStateException} is thrown when a rollout is changing + * it's state which is not valid. E.g. trying to start a already running + * rollout, or trying to resume a already finished rollout. + * + */ +public class RolloutIllegalStateException extends SpServerRtException { + + private static final long serialVersionUID = 1L; + private static final SpServerError THIS_ERROR = SpServerError.SP_ROLLOUT_ILLEGAL_STATE; + + /** + * Default constructor. + */ + public RolloutIllegalStateException() { + super(THIS_ERROR); + } + + /** + * Parameterized constructor. + * + * @param cause + * of the exception + */ + public RolloutIllegalStateException(final Throwable cause) { + super(THIS_ERROR, cause); + } + + /** + * Parameterized constructor. + * + * @param message + * of the exception + * @param cause + * of the exception + */ + public RolloutIllegalStateException(final String message, final Throwable cause) { + super(message, THIS_ERROR, cause); + } + + /** + * Parameterized constructor. + * + * @param message + * of the exception + */ + public RolloutIllegalStateException(final String message) { + super(message, THIS_ERROR); + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Action.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Action.java index 80624054a..c19b947de 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Action.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Action.java @@ -24,6 +24,7 @@ import javax.persistence.ManyToOne; import javax.persistence.NamedAttributeNode; import javax.persistence.NamedEntityGraph; import javax.persistence.NamedEntityGraphs; +import javax.persistence.NamedSubgraph; import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.Transient; @@ -34,28 +35,22 @@ import org.eclipse.persistence.annotations.CascadeOnDelete; /** *

- * Applicable transition changes of the software {@link SoftwareModule} state of - * a {@link Target}, e.g. install, uninstall, update, start, stop, and - * preparations for the transition change, i.e. download. + * Applicable transition changes of the {@link SoftwareModule}s state of a + * {@link Target}, e.g. install, uninstall, update and preparations for the + * transition change, i.e. download. *

* *

- * Actions are managed by the SP server (SPS) and applied to the edge controller - * by the SP controller (SPC). Actions may also be value added commands that are - * nor directly related to SP, e.g. factory reset. + * Actions are managed by the SP server and applied to the targets by the + * client. *

- * - * - * - * - * */ @Table(name = "sp_action", indexes = { @Index(name = "sp_idx_action_01", columnList = "tenant,distribution_set"), @Index(name = "sp_idx_action_02", columnList = "tenant,target,active"), @Index(name = "sp_idx_action_prim", columnList = "tenant,id") }) @NamedEntityGraphs({ @NamedEntityGraph(name = "Action.ds", attributeNodes = { @NamedAttributeNode("distributionSet") }), @NamedEntityGraph(name = "Action.all", attributeNodes = { @NamedAttributeNode("distributionSet"), - @NamedAttributeNode("target") }) }) + @NamedAttributeNode(value = "target", subgraph = "target.ds") }, subgraphs = @NamedSubgraph(name = "target.ds", attributeNodes = @NamedAttributeNode("assignedDistributionSet") ) ) }) @Entity public class Action extends BaseEntity implements Comparable { private static final long serialVersionUID = 1L; @@ -94,6 +89,14 @@ public class Action extends BaseEntity implements Comparable { CascadeType.REMOVE }) private List actionStatus; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "rolloutgroup", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rolloutgroup") ) + private RolloutGroup rolloutGroup; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "rollout", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rollout") ) + private Rollout rollout; + /** * Note: filled only in {@link Status#DOWNLOAD}. */ @@ -221,6 +224,36 @@ public class Action extends BaseEntity implements Comparable { this.forcedTime = forcedTime; } + /** + * @return the rolloutGroup + */ + public RolloutGroup getRolloutGroup() { + return rolloutGroup; + } + + /** + * @param rolloutGroup + * the rolloutGroup to set + */ + public void setRolloutGroup(final RolloutGroup rolloutGroup) { + this.rolloutGroup = rolloutGroup; + } + + /** + * @return the rollout + */ + public Rollout getRollout() { + return rollout; + } + + /** + * @param rollout + * the rollout to set + */ + public void setRollout(final Rollout rollout) { + this.rollout = rollout; + } + @Override public int compareTo(final Action o) { if (super.getId() == null || o == null || o.getId() == null) { @@ -379,7 +412,13 @@ public class Action extends BaseEntity implements Comparable { /** * Action needs download by this target which has now started. */ - DOWNLOAD; + DOWNLOAD, + + /** + * Action is in waiting state, e.g. the action is scheduled in a rollout + * but not yet activated. + */ + SCHEDULED; } /** diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/ActionStatus.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/ActionStatus.java index 28389ab01..2b0effae9 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/ActionStatus.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/ActionStatus.java @@ -63,7 +63,7 @@ public class ActionStatus extends BaseEntity { @CollectionTable(name = "sp_action_status_messages", joinColumns = @JoinColumn(name = "action_status_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_stat_msg_act_stat") ) , indexes = { @Index(name = "sp_idx_action_status_msgs_01", columnList = "action_status_id") }) @Column(name = "detail_message", length = 512) - private final List messages = new ArrayList(); + private final List messages = new ArrayList<>(); /** * Creates a new {@link ActionStatus} object. diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/ActionWithStatusCount.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/ActionWithStatusCount.java index abe2ee961..7d01b9dd2 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/ActionWithStatusCount.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/ActionWithStatusCount.java @@ -32,6 +32,7 @@ public class ActionWithStatusCount { private final String dsName; private final String dsVersion; private final Action action; + private final String rolloutName; /** * JPA constructor, the parameter are the result set columns of the custom @@ -59,10 +60,14 @@ public class ActionWithStatusCount { * the version of the distributionset * @param actionStatusCount * the count of the action status for this action + * @param rolloutName + * the rollout name */ + public ActionWithStatusCount(final Long actionId, final ActionType actionType, final boolean active, final long forcedTime, final Status status, final Long actionCreatedAt, final Long actionLastModifiedAt, - final Long dsId, final String dsName, final String dsVersion, final Long actionStatusCount) { + final Long dsId, final String dsName, final String dsVersion, final Long actionStatusCount, + final String rolloutName) { this.actionId = actionId; this.actionType = actionType; this.actionActive = active; @@ -74,6 +79,7 @@ public class ActionWithStatusCount { this.dsName = dsName; this.dsVersion = dsVersion; this.actionStatusCount = actionStatusCount; + this.rolloutName = rolloutName; this.action = new Action(); this.action.setActionType(actionType); @@ -166,4 +172,12 @@ public class ActionWithStatusCount { public Long getActionStatusCount() { return actionStatusCount; } + + /** + * @return the rolloutName + */ + public String getRolloutName() { + return rolloutName; + } + } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/BaseEntity.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/BaseEntity.java index cd9873fe8..72d25be6c 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/BaseEntity.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/BaseEntity.java @@ -22,6 +22,7 @@ import javax.persistence.PrePersist; import javax.persistence.Version; import org.eclipse.hawkbit.eventbus.CacheFieldEntityListener; +import org.eclipse.hawkbit.eventbus.EntityPropertyChangeListener; import org.eclipse.hawkbit.repository.exception.TenantNotExistException; import org.eclipse.hawkbit.repository.model.helper.SystemManagementHolder; import org.eclipse.hawkbit.repository.model.helper.TenantAwareHolder; @@ -45,7 +46,7 @@ import org.springframework.hateoas.Identifiable; */ @MappedSuperclass @Access(AccessType.FIELD) -@EntityListeners({ AuditingEntityListener.class, CacheFieldEntityListener.class }) +@EntityListeners({ AuditingEntityListener.class, CacheFieldEntityListener.class, EntityPropertyChangeListener.class }) @TenantDiscriminatorColumn(name = "tenant", length = 40) @Multitenant(MultitenantType.SINGLE_TABLE) public abstract class BaseEntity implements Serializable, Identifiable { @@ -98,9 +99,9 @@ public abstract class BaseEntity implements Serializable, Identifiable { // service final String currentTenant = SystemManagementHolder.getInstance().currentTenant(); if (currentTenant == null) { - throw new TenantNotExistException( - "Tenant " + TenantAwareHolder.getInstance().getTenantAware().getCurrentTenant() - + " does not exists, cannot create entity " + this.getClass() + " with id " + id); + throw new TenantNotExistException("Tenant " + + TenantAwareHolder.getInstance().getTenantAware().getCurrentTenant() + + " does not exists, cannot create entity " + this.getClass() + " with id " + id); } setTenant(currentTenant.toUpperCase()); } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Rollout.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Rollout.java new file mode 100644 index 000000000..83e541ed5 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Rollout.java @@ -0,0 +1,311 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.model; + +import java.util.List; + +import javax.persistence.Column; +import javax.persistence.ConstraintMode; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.ForeignKey; +import javax.persistence.Index; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.Transient; +import javax.persistence.UniqueConstraint; + +import org.eclipse.hawkbit.cache.CacheField; +import org.eclipse.hawkbit.cache.CacheKeys; +import org.eclipse.hawkbit.repository.model.Action.ActionType; + +/** + * @author Michael Hirsch + * + */ +@Entity +@Table(name = "sp_rollout", indexes = { + @Index(name = "sp_idx_rollout_01", columnList = "tenant,name") }, uniqueConstraints = @UniqueConstraint(columnNames = { + "name", "tenant" }, name = "uk_rollout") ) +public class Rollout extends NamedEntity { + + private static final long serialVersionUID = 1L; + + @OneToMany(targetEntity = RolloutGroup.class) + @JoinColumn(name = "rollout", insertable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollout_rolloutgroup") ) + private List rolloutGroups; + + @Column(name = "target_filter", length = 1024, nullable = false) + private String targetFilterQuery; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "distribution_set", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rolltout_ds") ) + private DistributionSet distributionSet; + + @Column(name = "status") + private RolloutStatus status = RolloutStatus.CREATING; + + @Column(name = "last_check") + private long lastCheck = 0L; + + @Column(name = "action_type", nullable = false) + @Enumerated(EnumType.STRING) + private ActionType actionType = ActionType.FORCED; + + @Column(name = "forced_time") + private long forcedTime; + + @Column(name = "total_targets") + private long totalTargets; + + @Transient + @CacheField(key = CacheKeys.ROLLOUT_GROUP_TOTAL) + private int rolloutGroupsTotal = 0; + + @Transient + @CacheField(key = CacheKeys.ROLLOUT_GROUP_CREATED) + private int rolloutGroupsCreated = 0; + + @Transient + private transient TotalTargetCountStatus totalTargetCountStatus; + + /** + * @return the distributionSet + */ + public DistributionSet getDistributionSet() { + return distributionSet; + } + + /** + * @param distributionSet + * the distributionSet to set + */ + public void setDistributionSet(final DistributionSet distributionSet) { + this.distributionSet = distributionSet; + } + + /** + * @return the rolloutGroups + */ + public List getRolloutGroups() { + return rolloutGroups; + } + + /** + * @param rolloutGroups + * the rolloutGroups to set + */ + public void setRolloutGroups(final List rolloutGroups) { + this.rolloutGroups = rolloutGroups; + } + + /** + * @return the targetFilterQuery + */ + public String getTargetFilterQuery() { + return targetFilterQuery; + } + + /** + * @param targetFilterQuery + * the targetFilterQuery to set + */ + public void setTargetFilterQuery(final String targetFilterQuery) { + this.targetFilterQuery = targetFilterQuery; + } + + /** + * @return the status + */ + public RolloutStatus getStatus() { + return status; + } + + /** + * @param status + * the status to set + */ + public void setStatus(final RolloutStatus status) { + this.status = status; + } + + /** + * @return the lastCheck + */ + public long getLastCheck() { + return lastCheck; + } + + /** + * @param lastCheck + * the lastCheck to set + */ + public void setLastCheck(final long lastCheck) { + this.lastCheck = lastCheck; + } + + /** + * @return the actionType + */ + public ActionType getActionType() { + return actionType; + } + + /** + * @param actionType + * the actionType to set + */ + public void setActionType(final ActionType actionType) { + this.actionType = actionType; + } + + /** + * @return the forcedTime + */ + public long getForcedTime() { + return forcedTime; + } + + /** + * @param forcedTime + * the forcedTime to set + */ + public void setForcedTime(final long forcedTime) { + this.forcedTime = forcedTime; + } + + /** + * @return the totalTargets + */ + public long getTotalTargets() { + return totalTargets; + } + + /** + * @param totalTargets + * the totalTargets to set + */ + public void setTotalTargets(final long totalTargets) { + this.totalTargets = totalTargets; + } + + /** + * @return the rolloutGroupsTotal + */ + public int getRolloutGroupsTotal() { + return rolloutGroupsTotal; + } + + /** + * @param rolloutGroupsTotal + * the rolloutGroupsTotal to set + */ + public void setRolloutGroupsTotal(final int rolloutGroupsTotal) { + this.rolloutGroupsTotal = rolloutGroupsTotal; + } + + /** + * @return the rolloutGroupsCreated + */ + public int getRolloutGroupsCreated() { + return rolloutGroupsCreated; + } + + /** + * @param rolloutGroupsCreated + * the rolloutGroupsCreated to set + */ + public void setRolloutGroupsCreated(final int rolloutGroupsCreated) { + this.rolloutGroupsCreated = rolloutGroupsCreated; + } + + /** + * @return the totalTargetCountStatus + */ + public TotalTargetCountStatus getTotalTargetCountStatus() { + if (totalTargetCountStatus == null) { + totalTargetCountStatus = new TotalTargetCountStatus(totalTargets); + } + return totalTargetCountStatus; + } + + /** + * @param totalTargetCountStatus + * the totalTargetCountStatus to set + */ + public void setTotalTargetCountStatus(final TotalTargetCountStatus totalTargetCountStatus) { + this.totalTargetCountStatus = totalTargetCountStatus; + } + + @Override + public String toString() { + return "Rollout [rolloutGroups=" + rolloutGroups + ", targetFilterQuery=" + targetFilterQuery + + ", distributionSet=" + distributionSet + ", status=" + status + ", lastCheck=" + lastCheck + + ", getName()=" + getName() + ", getId()=" + getId() + "]"; + } + + /** + * + * @author Michael Hirsch + * + */ + public enum RolloutStatus { + + /** + * Rollouts is beeing created. + */ + CREATING, + + /** + * Rollout is ready to start. + */ + READY, + + /** + * Rollout is paused. + */ + PAUSED, + + /** + * Rollout is starting. + */ + STARTING, + + /** + * Rollout is stopped. + */ + STOPPED, + + /** + * Rollout is running. + */ + RUNNING, + + /** + * Rollout is finished. + */ + FINISHED, + + /** + * Rollout could not created due errors, might be database problem due + * asynchronous creating. + */ + ERROR_CREATING, + + /** + * Rollout could not started due errors, might be database problem due + * asynchronous starting. + */ + ERROR_STARTING; + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutGroup.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutGroup.java new file mode 100644 index 000000000..dcb95b254 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutGroup.java @@ -0,0 +1,481 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.model; + +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.ConstraintMode; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.ForeignKey; +import javax.persistence.Index; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.Transient; +import javax.persistence.UniqueConstraint; + +/** + * JPA entity definition of persisting a group of an rollout. + * + * @author Michael Hirsch + * + */ +@Entity +@Table(name = "sp_rolloutgroup", indexes = { + @Index(name = "sp_idx_rolloutgroup_01", columnList = "tenant,name") }, uniqueConstraints = @UniqueConstraint(columnNames = { + "name", "rollout", "tenant" }, name = "uk_rolloutgroup") ) +public class RolloutGroup extends NamedEntity { + + private static final long serialVersionUID = 1L; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "rollout", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rolloutgroup_rollout") ) + private Rollout rollout; + + @Column(name = "status") + private RolloutGroupStatus status = RolloutGroupStatus.READY; + + @OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST }) + @JoinColumn(name = "rolloutGroup_Id", insertable = false, updatable = false) + private final List rolloutTargetGroup = new ArrayList<>(); + + @ManyToOne(fetch = FetchType.LAZY) + private RolloutGroup parent; + + @Column(name = "success_condition", nullable = false) + private RolloutGroupSuccessCondition successCondition = RolloutGroupSuccessCondition.THRESHOLD; + + @Column(name = "success_condition_exp", length = 512, nullable = false) + private String successConditionExp = null; + + @Column(name = "success_action", nullable = false) + private RolloutGroupSuccessAction successAction = RolloutGroupSuccessAction.NEXTGROUP; + + @Column(name = "success_action_exp", length = 512, nullable = false) + private String successActionExp = null; + + @Column(name = "error_condition") + private RolloutGroupErrorCondition errorCondition = null; + + @Column(name = "error_condition_exp", length = 512) + private String errorConditionExp = null; + + @Column(name = "error_action") + private RolloutGroupErrorAction errorAction = null; + + @Column(name = "error_action_exp", length = 512) + private String errorActionExp = null; + + @Column(name = "total_targets") + private long totalTargets; + + @Transient + private transient TotalTargetCountStatus totalTargetCountStatus; + + public Rollout getRollout() { + return rollout; + } + + public void setRollout(final Rollout rollout) { + this.rollout = rollout; + } + + public RolloutGroupStatus getStatus() { + return status; + } + + public void setStatus(final RolloutGroupStatus status) { + this.status = status; + } + + public List getRolloutTargetGroup() { + return rolloutTargetGroup; + } + + public RolloutGroup getParent() { + return parent; + } + + public void setParent(final RolloutGroup parent) { + this.parent = parent; + } + + public RolloutGroupSuccessCondition getSuccessCondition() { + return successCondition; + } + + public void setSuccessCondition(final RolloutGroupSuccessCondition finishCondition) { + successCondition = finishCondition; + } + + public String getSuccessConditionExp() { + return successConditionExp; + } + + public void setSuccessConditionExp(final String finishExp) { + successConditionExp = finishExp; + } + + public RolloutGroupErrorCondition getErrorCondition() { + return errorCondition; + } + + public void setErrorCondition(final RolloutGroupErrorCondition errorCondition) { + this.errorCondition = errorCondition; + } + + public String getErrorConditionExp() { + return errorConditionExp; + } + + public void setErrorConditionExp(final String errorExp) { + errorConditionExp = errorExp; + } + + public RolloutGroupErrorAction getErrorAction() { + return errorAction; + } + + public void setErrorAction(final RolloutGroupErrorAction errorAction) { + this.errorAction = errorAction; + } + + public String getErrorActionExp() { + return errorActionExp; + } + + public void setErrorActionExp(final String errorActionExp) { + this.errorActionExp = errorActionExp; + } + + public RolloutGroupSuccessAction getSuccessAction() { + return successAction; + } + + public String getSuccessActionExp() { + return successActionExp; + } + + public long getTotalTargets() { + return totalTargets; + } + + public void setTotalTargets(final long totalTargets) { + this.totalTargets = totalTargets; + } + + public void setSuccessAction(final RolloutGroupSuccessAction successAction) { + this.successAction = successAction; + } + + public void setSuccessActionExp(final String successActionExp) { + this.successActionExp = successActionExp; + } + + /** + * @return the totalTargetCountStatus + */ + public TotalTargetCountStatus getTotalTargetCountStatus() { + if (totalTargetCountStatus == null) { + totalTargetCountStatus = new TotalTargetCountStatus(totalTargets); + } + return totalTargetCountStatus; + } + + /** + * @param totalTargetCountStatus + * the totalTargetCountStatus to set + */ + public void setTotalTargetCountStatus(final TotalTargetCountStatus totalTargetCountStatus) { + this.totalTargetCountStatus = totalTargetCountStatus; + } + + @Override + public String toString() { + return "RolloutGroup [rollout=" + rollout + ", status=" + status + ", rolloutTargetGroup=" + rolloutTargetGroup + + ", parent=" + parent + ", finishCondition=" + successCondition + ", finishExp=" + successConditionExp + + ", errorCondition=" + errorCondition + ", errorExp=" + errorConditionExp + ", getName()=" + getName() + + ", getId()=" + getId() + "]"; + } + + /** + * + * @author Michael Hirsch + * + */ + public enum RolloutGroupStatus { + + /** + * Ready to start the group. + */ + READY, + + /** + * Group is scheduled and started sometime, e.g. trigger of group + * before. + */ + SCHEDULED, + + /** + * Group is finished. + */ + FINISHED, + + /** + * Group is finished and has errors. + */ + ERROR, + + /** + * Group is running. + */ + RUNNING; + } + + /** + * The condition to evaluate if an group is success state. + */ + public enum RolloutGroupSuccessCondition { + THRESHOLD("thresholdRolloutGroupSuccessCondition"); + + private final String beanName; + + private RolloutGroupSuccessCondition(final String beanName) { + this.beanName = beanName; + } + + /** + * @return the beanName + */ + public String getBeanName() { + return beanName; + } + } + + /** + * The condition to evaluate if an group is in error state. + */ + public enum RolloutGroupErrorCondition { + THRESHOLD("thresholdRolloutGroupErrorCondition"); + + private final String beanName; + + private RolloutGroupErrorCondition(final String beanName) { + this.beanName = beanName; + } + + /** + * @return the beanName + */ + public String getBeanName() { + return beanName; + } + } + + /** + * The actions executed when the {@link RolloutGroup#errorCondition} is hit. + */ + public enum RolloutGroupErrorAction { + PAUSE("pauseRolloutGroupAction"); + + private final String beanName; + + private RolloutGroupErrorAction(final String beanName) { + this.beanName = beanName; + } + + /** + * @return the beanName + */ + public String getBeanName() { + return beanName; + } + } + + /** + * The actions executed when the {@link RolloutGroup#successCondition} is + * hit. + */ + public enum RolloutGroupSuccessAction { + NEXTGROUP("startNextRolloutGroupAction"); + + private final String beanName; + + private RolloutGroupSuccessAction(final String beanName) { + this.beanName = beanName; + } + + /** + * @return the beanName + */ + public String getBeanName() { + return beanName; + } + } + + /** + * Object which holds all {@link RolloutGroup} conditions together which can + * easily built. + */ + public static class RolloutGroupConditions { + private RolloutGroupSuccessCondition successCondition = null; + private String successConditionExp = null; + private RolloutGroupSuccessAction successAction = null; + private String successActionExp = null; + private RolloutGroupErrorCondition errorCondition = null; + private String errorConditionExp = null; + private RolloutGroupErrorAction errorAction = null; + private String errorActionExp = null; + + public RolloutGroupSuccessCondition getSuccessCondition() { + return successCondition; + } + + public void setSuccessCondition(final RolloutGroupSuccessCondition finishCondition) { + successCondition = finishCondition; + } + + public String getSuccessConditionExp() { + return successConditionExp; + } + + public void setSuccessConditionExp(final String finishConditionExp) { + successConditionExp = finishConditionExp; + } + + public RolloutGroupSuccessAction getSuccessAction() { + return successAction; + } + + public void setSuccessAction(final RolloutGroupSuccessAction successAction) { + this.successAction = successAction; + } + + public String getSuccessActionExp() { + return successActionExp; + } + + public void setSuccessActionExp(final String successActionExp) { + this.successActionExp = successActionExp; + } + + public RolloutGroupErrorCondition getErrorCondition() { + return errorCondition; + } + + public void setErrorCondition(final RolloutGroupErrorCondition errorCondition) { + this.errorCondition = errorCondition; + } + + public String getErrorConditionExp() { + return errorConditionExp; + } + + public void setErrorConditionExp(final String errorConditionExp) { + this.errorConditionExp = errorConditionExp; + } + + public RolloutGroupErrorAction getErrorAction() { + return errorAction; + } + + public void setErrorAction(final RolloutGroupErrorAction errorAction) { + this.errorAction = errorAction; + } + + public String getErrorActionExp() { + return errorActionExp; + } + + public void setErrorActionExp(final String errorActionExp) { + this.errorActionExp = errorActionExp; + } + } + + /** + * Builder to build easily the {@link RolloutGroupConditions}. + * + */ + public static class RolloutGroupConditionBuilder { + private final RolloutGroupConditions conditions = new RolloutGroupConditions(); + + public RolloutGroupConditions build() { + return conditions; + } + + /** + * Sets the finish condition and expression on the builder. + * + * @param condition + * the finish condition + * @param expression + * the finish expression + * @return the builder itself + */ + public RolloutGroupConditionBuilder successCondition(final RolloutGroupSuccessCondition condition, + final String expression) { + conditions.setSuccessCondition(condition); + conditions.setSuccessConditionExp(expression); + return this; + } + + /** + * Sets the success action and expression on the builder. + * + * @param action + * the success action + * @param expression + * the error expression + * @return the builder itself + */ + public RolloutGroupConditionBuilder successAction(final RolloutGroupSuccessAction action, + final String expression) { + conditions.setSuccessAction(action); + conditions.setSuccessActionExp(expression); + return this; + } + + /** + * Sets the error condition and expression on the builder. + * + * @param condition + * the error condition + * @param expression + * the error expression + * @return the builder itself + */ + public RolloutGroupConditionBuilder errorCondition(final RolloutGroupErrorCondition condition, + final String expression) { + conditions.setErrorCondition(condition); + conditions.setErrorConditionExp(expression); + return this; + } + + /** + * Sets the error action and expression on the builder. + * + * @param action + * the error action + * @param expression + * the error expression + * @return the builder itself + */ + public RolloutGroupConditionBuilder errorAction(final RolloutGroupErrorAction action, final String expression) { + conditions.setErrorAction(action); + conditions.setErrorActionExp(expression); + return this; + } + } + +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutTargetGroup.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutTargetGroup.java new file mode 100644 index 000000000..f1d4fe718 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutTargetGroup.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.model; + +import java.io.Serializable; +import java.util.List; + +import javax.persistence.CascadeType; +import javax.persistence.ConstraintMode; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.ForeignKey; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.JoinColumn; +import javax.persistence.JoinColumns; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +/** + * @author Michael Hirsch + * + */ +@IdClass(RolloutTargetGroupId.class) +@Entity +@Table(name = "sp_rollouttargetgroup") +public class RolloutTargetGroup implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @ManyToOne(targetEntity = RolloutGroup.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST }) + @JoinColumn(name = "rolloutGroup_Id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollouttargetgroup_group") ) + private RolloutGroup rolloutGroup; + + @Id + @ManyToOne(targetEntity = Target.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST }) + @JoinColumn(name = "target_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_rollouttargetgroup_target") ) + private Target target; + + @OneToMany(targetEntity = Action.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST }) + @JoinColumns(value = { @JoinColumn(name = "rolloutgroup", referencedColumnName = "rolloutGroup_Id"), + @JoinColumn(name = "target", referencedColumnName = "target_id") }) + private List actions; + + /** + * default constructor for JPA. + */ + public RolloutTargetGroup() { + // JPA constructor + } + + public RolloutTargetGroup(final RolloutGroup rolloutGroup, final Target target) { + this.rolloutGroup = rolloutGroup; + this.target = target; + } + + public RolloutTargetGroupId getId() { + return new RolloutTargetGroupId(rolloutGroup, target); + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutTargetGroupId.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutTargetGroupId.java new file mode 100644 index 000000000..41850424b --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/RolloutTargetGroupId.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.model; + +import java.io.Serializable; + +/** + * Combined unique key of the table {@link RolloutTargetGroup}. + * + * @author Michael Hirsch + * + */ +public class RolloutTargetGroupId implements Serializable { + /** + * + */ + private static final long serialVersionUID = 1L; + + private Long rolloutGroup; + private Long target; + + /** + * default constructor necessary for JPA. + */ + public RolloutTargetGroupId() { + // default constructor necessary for JPA, empty. + } + + /** + * Constructor. + * + * @param rolloutGroup + * the rollout group for this key + * @param target + * the target for this key + */ + public RolloutTargetGroupId(final RolloutGroup rolloutGroup, final Target target) { + this.rolloutGroup = rolloutGroup.getId(); + this.target = target.getId(); + } + + public Long getRolloutGroup() { + return rolloutGroup; + } + + public Long getTarget() { + return target; + } +} \ No newline at end of file diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/SoftwareModuleMetadata.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/SoftwareModuleMetadata.java index d4ddba3f3..dfc1ecfa7 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/SoftwareModuleMetadata.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/SoftwareModuleMetadata.java @@ -47,7 +47,7 @@ public class SoftwareModuleMetadata implements Serializable { @Id @ManyToOne(targetEntity = SoftwareModule.class, fetch = FetchType.LAZY) - @JoinColumn(name = "sw_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_metadata_sw") ) + @JoinColumn(name = "sw_id", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_metadata_sw")) private SoftwareModule softwareModule; public SoftwareModuleMetadata() { diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Target.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Target.java index 6f37b2d8b..07a97ebcf 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Target.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/Target.java @@ -32,6 +32,8 @@ import javax.persistence.PrimaryKeyJoinColumn; import javax.persistence.Table; import javax.persistence.Transient; import javax.persistence.UniqueConstraint; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.repository.model.helper.SecurityChecker; @@ -67,31 +69,33 @@ import org.springframework.data.domain.Persistable; @Index(name = "sp_idx_target_03", columnList = "tenant,controller_id,assigned_distribution_set"), @Index(name = "sp_idx_target_04", columnList = "tenant,created_at"), @Index(name = "sp_idx_target_prim", columnList = "tenant,id") }, uniqueConstraints = @UniqueConstraint(columnNames = { - "controller_id", "tenant" }, name = "uk_tenant_controller_id") ) + "controller_id", "tenant" }, name = "uk_tenant_controller_id")) @NamedEntityGraph(name = "Target.detail", attributeNodes = { @NamedAttributeNode("tags"), @NamedAttributeNode(value = "assignedDistributionSet"), @NamedAttributeNode(value = "targetInfo") }) public class Target extends NamedEntity implements Persistable { private static final long serialVersionUID = 1L; @Column(name = "controller_id", length = 64) + @Size(min = 1) + @NotNull private String controllerId; @Transient - private boolean isNew = false; + private boolean entityNew = false; @ManyToMany(targetEntity = TargetTag.class) @JoinTable(name = "sp_target_target_tag", joinColumns = { - @JoinColumn(name = "target", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_targtag_target") ) }, inverseJoinColumns = { - @JoinColumn(name = "tag", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_targtag_tag") ) }) - private Set tags = new HashSet(); + @JoinColumn(name = "target", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_targtag_target")) }, inverseJoinColumns = { + @JoinColumn(name = "tag", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_targtag_tag")) }) + private Set tags = new HashSet<>(); @CascadeOnDelete @OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, cascade = { CascadeType.REMOVE }) - @JoinColumn(name = "target", insertable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_act_hist_targ") ) + @JoinColumn(name = "target", insertable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_act_hist_targ")) private final List actions = new ArrayList<>(); @ManyToOne(optional = true, fetch = FetchType.LAZY, targetEntity = DistributionSet.class) - @JoinColumn(name = "assigned_distribution_set", nullable = true, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_target_assign_ds") ) + @JoinColumn(name = "assigned_distribution_set", nullable = true, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_target_assign_ds")) private DistributionSet assignedDistributionSet; @CascadeOnDelete @@ -106,6 +110,11 @@ public class Target extends NamedEntity implements Persistable { @Column(name = "sec_token", insertable = true, updatable = true, nullable = false, length = 128) private String securityToken = null; + @CascadeOnDelete + @OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE, CascadeType.PERSIST }) + @JoinColumn(name = "target_Id", insertable = false, updatable = false) + private final List rolloutTargetGroup = new ArrayList<>(); + /** * Constructor. * @@ -208,15 +217,15 @@ public class Target extends NamedEntity implements Persistable { @Override @Transient public boolean isNew() { - return isNew; + return entityNew; } /** * @param isNew * the isNew to set */ - public void setNew(final boolean isNew) { - this.isNew = isNew; + public void setNew(final boolean entityNew) { + this.entityNew = entityNew; } /** diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TargetInfo.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TargetInfo.java index d491319fa..09d9fed28 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TargetInfo.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TargetInfo.java @@ -38,7 +38,9 @@ import javax.persistence.OneToOne; import javax.persistence.Table; import javax.persistence.Transient; -import org.eclipse.hawkbit.repository.model.helper.PollConfigurationHelper; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.tenancy.configuration.DurationHelper; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.eclipse.persistence.annotations.CascadeOnDelete; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -73,11 +75,11 @@ public class TargetInfo implements Persistable, Serializable { private Long targetId; @Transient - private boolean isNew = false; + private boolean entityNew = false; @CascadeOnDelete @OneToOne(cascade = { CascadeType.MERGE, CascadeType.REMOVE }, fetch = FetchType.LAZY, targetEntity = Target.class) - @JoinColumn(name = "target_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_stat_targ") ) + @JoinColumn(name = "target_id", nullable = false, updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_stat_targ")) @MapsId // use deprecated annotation until HHH-8862 is fixed // @SuppressWarnings( "deprecation" ) @@ -98,7 +100,7 @@ public class TargetInfo implements Persistable, Serializable { private TargetUpdateStatus updateStatus = TargetUpdateStatus.UNKNOWN; @ManyToOne(optional = true, fetch = FetchType.LAZY) - @JoinColumn(name = "installed_distribution_set", nullable = true, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_target_inst_ds") ) + @JoinColumn(name = "installed_distribution_set", nullable = true, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_target_inst_ds")) private DistributionSet installedDistributionSet; /** @@ -108,9 +110,9 @@ public class TargetInfo implements Persistable, Serializable { @Column(name = "attribute_value", length = 128) @MapKeyColumn(name = "attribute_key", nullable = false, length = 32) @CollectionTable(name = "sp_target_attributes", joinColumns = { - @JoinColumn(name = "target_id") }, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_attrib_target") ) + @JoinColumn(name = "target_id") }, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_targ_attrib_target")) // use deprecated annotation until HHH-8862 is fixed - @SuppressWarnings("deprecation") + // @org.hibernate.annotations.ForeignKey( name = "fk_targ_attrib_target" ) private final Map controllerAttributes = Collections.synchronizedMap(new HashMap()); @@ -154,15 +156,15 @@ public class TargetInfo implements Persistable, Serializable { @Override @Transient public boolean isNew() { - return isNew; + return entityNew; } /** * @param isNew * the isNew to set */ - public void setNew(final boolean isNew) { - this.isNew = isNew; + public void setNew(final boolean entityNew) { + this.entityNew = entityNew; } /** @@ -181,7 +183,7 @@ public class TargetInfo implements Persistable, Serializable { } /** - * @param ipAddress + * @param address * the ipAddress to set * * @throws IllegalArgumentException @@ -315,17 +317,21 @@ public class TargetInfo implements Persistable, Serializable { * before this method returns {@code null} */ public PollStatus getPollStatus() { - if (lastTargetQuery != null) { - final Duration pollTime = PollConfigurationHelper.getInstance().getPollTimeInterval(); - final Duration overdueTime = PollConfigurationHelper.getInstance().getOverduePollTimeInterval(); - final LocalDateTime currentDate = LocalDateTime.now(); - final LocalDateTime lastPollDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(lastTargetQuery), - ZoneId.systemDefault()); - final LocalDateTime nextPollDate = lastPollDate.plus(pollTime); - final LocalDateTime overdueDate = nextPollDate.plus(overdueTime); - return new PollStatus(lastPollDate, nextPollDate, overdueDate, currentDate); + if (lastTargetQuery == null) { + return null; } - return null; + + final Duration pollTime = DurationHelper.formattedStringToDuration(TenantConfigurationManagement.getInstance() + .getConfigurationValue(TenantConfigurationKey.POLLING_TIME_INTERVAL, String.class).getValue()); + final Duration overdueTime = DurationHelper.formattedStringToDuration(TenantConfigurationManagement + .getInstance().getConfigurationValue(TenantConfigurationKey.POLLING_OVERDUE_TIME_INTERVAL, String.class) + .getValue()); + final LocalDateTime currentDate = LocalDateTime.now(); + final LocalDateTime lastPollDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(lastTargetQuery), + ZoneId.systemDefault()); + final LocalDateTime nextPollDate = lastPollDate.plus(pollTime); + final LocalDateTime overdueDate = nextPollDate.plus(overdueTime); + return new PollStatus(lastPollDate, nextPollDate, overdueDate, currentDate); } /** @@ -368,16 +374,10 @@ public class TargetInfo implements Persistable, Serializable { return lastPollDate; } - /** - * @return the nextPollDate - */ public LocalDateTime getNextPollDate() { return nextPollDate; } - /** - * @return the overdueDate - */ public LocalDateTime getOverdueDate() { return overdueDate; } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TargetWithActionStatus.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TargetWithActionStatus.java new file mode 100644 index 000000000..a75141658 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TargetWithActionStatus.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.model; + +import org.eclipse.hawkbit.repository.model.Action.Status; + +/** + * + * Rollout - Target with action status. + * + */ +public class TargetWithActionStatus { + + private Target target; + + private Status status = null;; + + public TargetWithActionStatus(final Target target) { + this.target = target; + } + + public TargetWithActionStatus(final Target target, final Status status) { + this.status = status; + this.target = target; + } + + /** + * @return the target + */ + public Target getTarget() { + return target; + } + + /** + * @return the status + */ + public Status getStatus() { + return status; + } + + /** + * @param target + * the target to set + */ + public void setTarget(final Target target) { + this.target = target; + } + + /** + * @param status + * the status to set + */ + public void setStatus(final Status status) { + this.status = status; + } + +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TenantConfigurationValue.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TenantConfigurationValue.java new file mode 100644 index 000000000..d8483e12f --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TenantConfigurationValue.java @@ -0,0 +1,187 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.model; + +/** + * represents a tenant configuration value including some meta data + * + * @param + * type of the configuration value + */ +public class TenantConfigurationValue { + + private T value; + private Long lastModifiedAt; + private String lastModifiedBy; + private Long createdAt; + private String createdBy; + private boolean isGlobal = true; + + private TenantConfigurationValue() { + } + + /** + * Gets the value. + * + * @return the value + */ + public T getValue() { + return value; + } + + /** + * Checks if is global. + * + * @return true, if is global + */ + public boolean isGlobal() { + return isGlobal; + } + + /** + * Gets the last modified at. + * + * @return the last modified at + */ + public Long getLastModifiedAt() { + return lastModifiedAt; + } + + /** + * Gets the last modified by. + * + * @return the last modified by + */ + public String getLastModifiedBy() { + return lastModifiedBy; + } + + /** + * Gets the created at. + * + * @return the created at + */ + public Long getCreatedAt() { + return createdAt; + } + + /** + * Gets the created by. + * + * @return the created by + */ + public String getCreatedBy() { + return createdBy; + } + + /** + * Builder. + * + * @param + * the key type + * @return the tenant configuration value builder + */ + public static TenantConfigurationValueBuilder builder() { + + return new TenantConfigurationValueBuilder(); + } + + /** + * builds the tenant configuration value including some meta data + * + * @param + * type of the configuration value + */ + public static class TenantConfigurationValueBuilder { + + private final TenantConfigurationValue configuration = new TenantConfigurationValue<>(); + + /** + * Builds the. + * + * @return the tenant configuration value + */ + public TenantConfigurationValue build() { + return configuration; + } + + /** + * sets the configuration value itself + * + * @param value + * the value + * @return the tenant configuration value builder + */ + public TenantConfigurationValueBuilder value(final T value) { + this.configuration.value = value; + return this; + } + + /** + * set the is global attribute. + * + * @param isGlobal + * true when there is no tenant specific value, false + * otherwise + * @return the tenant configuration value builder + */ + public TenantConfigurationValueBuilder isGlobal(final boolean isGlobal) { + this.configuration.isGlobal = isGlobal; + return this; + } + + /** + * Sets the last modified at attribute + * + * @param lastModifiedAt + * timestamp of last modification + * @return the tenant configuration value builder + */ + public TenantConfigurationValueBuilder lastModifiedAt(final Long lastModifiedAt) { + this.configuration.lastModifiedAt = lastModifiedAt; + return this; + } + + /** + * sets the last modified by attribute + * + * @param lastModifiedBy + * the last modified by + * @return the tenant configuration value builder + */ + public TenantConfigurationValueBuilder lastModifiedBy(final String lastModifiedBy) { + this.configuration.lastModifiedBy = lastModifiedBy; + return this; + } + + /** + * sets the created at attribute + * + * @param createdAt + * defined when the configuration has been created + * @return the tenant configuration value builder + */ + public TenantConfigurationValueBuilder createdAt(final Long createdAt) { + this.configuration.createdAt = createdAt; + return this; + } + + /** + * sets the created by attribute + * + * @param createdBy + * defines by whom the configuration has been created + * @return the tenant configuration value builder + */ + public TenantConfigurationValueBuilder createdBy(final String createdBy) { + this.configuration.createdBy = createdBy; + return this; + } + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TotalTargetCountActionStatus.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TotalTargetCountActionStatus.java new file mode 100644 index 000000000..9fabef663 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TotalTargetCountActionStatus.java @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.model; + +/** + * Represents rollout or rollout group statuses and count of targets in each + * status. + * + */ +public class TotalTargetCountActionStatus { + + private final Action.Status status; + private final Long count; + private Long id; + + public TotalTargetCountActionStatus(final Long id, final Action.Status status, final Long count) { + this.status = status; + this.count = count; + this.id = id; + } + + public TotalTargetCountActionStatus(final Action.Status status, final Long count) { + this.status = status; + this.count = count; + } + + /** + * @return the id + */ + public Long getId() { + return id; + } + + /** + * @return the status + */ + public Action.Status getStatus() { + return status; + } + + /** + * @return the count + */ + public Long getCount() { + return count; + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TotalTargetCountStatus.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TotalTargetCountStatus.java new file mode 100644 index 000000000..13c41baf3 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/TotalTargetCountStatus.java @@ -0,0 +1,125 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.model; + +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +/** + * + * Store all states with the target count of a rollout or rolloutgroup. + * + */ +public class TotalTargetCountStatus { + + /** + * Status of the total target counts. + */ + public enum Status { + SCHEDULED, RUNNING, ERROR, FINISHED, CANCELLED, NOTSTARTED + } + + private final Map statusTotalCountMap = new EnumMap<>(Status.class); + private final Long totalTargetCount; + + /** + * Create a new states map with the target count for each state. + * + * @param targetCountActionStatus + * the action state map + * @param totalTargets + * the total target count + */ + public TotalTargetCountStatus(final List targetCountActionStatus, + final Long totalTargetCount) { + this.totalTargetCount = totalTargetCount; + mapActionStatusToTotalTargetCountStatus(targetCountActionStatus); + } + + /** + * Create a new states map with the target count for each state. + * + * @param totalTargetCount + * the total target count + */ + public TotalTargetCountStatus(final Long totalTargetCount) { + this(Collections.emptyList(), totalTargetCount); + } + + /** + * The current state mape which the total target count + * + * @return the statusTotalCountMap the state map + */ + public Map getStatusTotalCountMap() { + return statusTotalCountMap; + } + + /** + * Gets the total target count from a state. + * + * @param status + * the state key + * @return the current target count cannot be + */ + public Long getTotalTargetCountByStatus(final Status status) { + final Long count = statusTotalCountMap.get(status); + return count == null ? 0L : count; + } + + /** + * Populate all target status to a the given map + * + * @param statusTotalCountMap + * the map + * @param rolloutStatusCountItems + * all target statut with total count + * @return some state is populated nothing is happend + */ + private final void mapActionStatusToTotalTargetCountStatus( + final List targetCountActionStatus) { + if (targetCountActionStatus == null) { + statusTotalCountMap.put(TotalTargetCountStatus.Status.NOTSTARTED, totalTargetCount); + return; + } + statusTotalCountMap.put(Status.RUNNING, 0L); + Long notStartedTargetCount = totalTargetCount; + for (final TotalTargetCountActionStatus item : targetCountActionStatus) { + switch (item.getStatus()) { + case SCHEDULED: + statusTotalCountMap.put(Status.SCHEDULED, item.getCount()); + break; + case ERROR: + statusTotalCountMap.put(Status.ERROR, item.getCount()); + break; + case FINISHED: + statusTotalCountMap.put(Status.FINISHED, item.getCount()); + break; + case RETRIEVED: + case RUNNING: + case WARNING: + case DOWNLOAD: + case CANCELING: + final Long runningItemsCount = statusTotalCountMap.get(Status.RUNNING) + item.getCount(); + statusTotalCountMap.put(Status.RUNNING, runningItemsCount); + break; + case CANCELED: + statusTotalCountMap.put(Status.CANCELLED, item.getCount()); + break; + default: + throw new IllegalArgumentException("State " + item.getStatus() + "is not valid"); + } + notStartedTargetCount -= item.getCount(); + } + statusTotalCountMap.put(TotalTargetCountStatus.Status.NOTSTARTED, notStartedTargetCount); + } + +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/AfterTransactionCommitExecutorHolder.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/AfterTransactionCommitExecutorHolder.java new file mode 100644 index 000000000..1282bc99d --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/AfterTransactionCommitExecutorHolder.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.model.helper; + +import org.eclipse.hawkbit.eventbus.EntityPropertyChangeListener; +import org.eclipse.hawkbit.executor.AfterTransactionCommitExecutor; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * A singleton bean which holds the {@link AfterTransactionCommitExecutor} to + * have to the cache manager in beans not instantiated by spring e.g. JPA + * entities or {@link EntityPropertyChangeListener} which cannot be autowired. + * + */ +public final class AfterTransactionCommitExecutorHolder { + + private static final AfterTransactionCommitExecutorHolder SINGLETON = new AfterTransactionCommitExecutorHolder(); + + @Autowired + private AfterTransactionCommitExecutor afterCommit; + + private AfterTransactionCommitExecutorHolder() { + + } + + /** + * @return the cache manager holder singleton instance + */ + public static AfterTransactionCommitExecutorHolder getInstance() { + return SINGLETON; + } + + /** + * @return the afterCommit + */ + public AfterTransactionCommitExecutor getAfterCommit() { + return afterCommit; + } + + /** + * @param afterCommit + * the afterCommit to set + */ + public void setAfterCommit(final AfterTransactionCommitExecutor afterCommit) { + this.afterCommit = afterCommit; + } + +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/EventBusHolder.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/EventBusHolder.java new file mode 100644 index 000000000..8111a1c28 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/EventBusHolder.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.model.helper; + +import org.eclipse.hawkbit.eventbus.CacheFieldEntityListener; +import org.springframework.beans.factory.annotation.Autowired; + +import com.google.common.eventbus.EventBus; + +/** + * A singleton bean which holds the {@link EventBus} to have to the cache + * manager in beans not instantiated by spring e.g. JPA entities or + * {@link CacheFieldEntityListener} which cannot be autowired. + * + */ +public final class EventBusHolder { + + private static final EventBusHolder SINGLETON = new EventBusHolder(); + + @Autowired + private EventBus eventBus; + + private EventBusHolder() { + + } + + /** + * @return the cache manager holder singleton instance + */ + public static EventBusHolder getInstance() { + return SINGLETON; + } + + /** + * @return the eventBus + */ + public EventBus getEventBus() { + return eventBus; + } + + /** + * @param eventBus + * the eventBus to set + */ + public void setEventBus(final EventBus eventBus) { + this.eventBus = eventBus; + } + +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/PollConfigurationHelper.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/PollConfigurationHelper.java deleted file mode 100644 index 7f5978a2d..000000000 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/model/helper/PollConfigurationHelper.java +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.eclipse.hawkbit.repository.model.helper; - -import java.time.Duration; - -import javax.annotation.PostConstruct; - -import org.eclipse.hawkbit.ControllerPollProperties; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.EnvironmentAware; - -/** - * A singleton bean which holds configuration of the poll time - * {@code hawkbit.server.controller.polling} and - * {@code hawkbit.server.controller.polling.overdue} to have access to the - * configuration in beans not instatinated by spring e.g. JPA entities which - * cannot implement the {@link EnvironmentAware} interface to retrieve - * environment variables. - * - * - * - * - */ -public final class PollConfigurationHelper { - - private static final Logger LOG = LoggerFactory.getLogger(PollConfigurationHelper.class); - private static final PollConfigurationHelper INSTANCE = new PollConfigurationHelper(); - - private static final int DEFAULT_OVERDUE_HOUR = 0; - private static final int DEFAULT_OVERDUE_MINUTE = 5; - private static final int DEFAULT_OVERDUE_SECOND = 0; - - private static final int DEFAULT_POLL_HOUR = 0; - private static final int DEFAULT_POLL_MINUTE = 5; - private static final int DEFAULT_POLL_SECOND = 0; - - @Autowired - private ControllerPollProperties controllerPollProperties; - - private Duration controllerPollTimeDuration; - private Duration controllerOverduePollTimeDuration; - - private PollConfigurationHelper() { - } - - /** - * Bean post construct to calcualte the poll time and poll overdue time only - * once. - */ - @PostConstruct - public void postConstruct() { - final long[] controllerPollTimeSplit = splitInterval(controllerPollProperties.getPollingTime(), - DEFAULT_POLL_HOUR, DEFAULT_POLL_MINUTE, DEFAULT_POLL_SECOND); - this.controllerPollTimeDuration = Duration.ZERO.plusHours(controllerPollTimeSplit[0]) - .plusMinutes(controllerPollTimeSplit[1]).plusSeconds(controllerPollTimeSplit[2]); - - final long[] controllerOverduePollTimeSplit = splitInterval(controllerPollProperties.getPollingOverdueTime(), - DEFAULT_OVERDUE_HOUR, DEFAULT_OVERDUE_MINUTE, DEFAULT_OVERDUE_SECOND); - this.controllerOverduePollTimeDuration = Duration.ZERO.plusHours(controllerOverduePollTimeSplit[0]) - .plusMinutes(controllerOverduePollTimeSplit[1]).plusSeconds(controllerOverduePollTimeSplit[2]); - - } - - /** - * @return the poll time interval configured in the configuration - * {@code hawkbit.server.controller.polling} or the default value - * which is {@code 00:05:00} never {@code null}. - */ - public Duration getPollTimeInterval() { - return controllerPollTimeDuration; - } - - /** - * @return the overdue poll threshold configured in the configuration - * {@code hawkbit.server.controller.polling.overdue} or the default - * value which is {@code 00:05:00} never {@code null}. - */ - public Duration getOverduePollTimeInterval() { - return controllerOverduePollTimeDuration; - } - - /** - * @return a singleton instance of the environment helper. - */ - public static PollConfigurationHelper getInstance() { - return INSTANCE; - } - - private long[] splitInterval(final String interval, final long defaultHour, final long defaultMinute, - final long defaultSecond) { - if (interval != null) { - final String[] split = interval.split(":"); - if (split.length == 3) { - try { - return new long[] { Long.parseLong(split[0]), Long.parseLong(split[1]), Long.parseLong(split[2]) }; - } catch (final NumberFormatException e) { - LOG.warn("Cannot parse the given poll configuration {}", interval); - } - } - } - LOG.warn("Using default configuration hour:{} min:{}, second:{}", defaultHour, defaultMinute, defaultSecond); - return new long[] { defaultHour, defaultMinute, defaultSecond }; - } -} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/rsql/RSQLParameterSyntaxException.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/rsql/RSQLParameterSyntaxException.java index 324de8eba..aa43f1bbb 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/rsql/RSQLParameterSyntaxException.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/rsql/RSQLParameterSyntaxException.java @@ -43,4 +43,19 @@ public class RSQLParameterSyntaxException extends SpServerRtException { public RSQLParameterSyntaxException(final Throwable cause) { super(SpServerError.SP_REST_RSQL_SEARCH_PARAM_SYNTAX, cause); } + + /** + * Creates a new RSQLParameterSyntaxException with + * {@link SpServerError#SP_REST_RSQL_SEARCH_PARAM_SYNTAX} error. + * + * @param message + * the message of the exception + * @param cause + * the cause (which is saved for later retrieval by the + * getCause() method). (A null value is permitted, and indicates + * that the cause is nonexistent or unknown.) + */ + public RSQLParameterSyntaxException(final String message, final Throwable cause) { + super(message, SpServerError.SP_REST_RSQL_SEARCH_PARAM_SYNTAX, cause); + } } diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/rsql/RSQLUtility.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/rsql/RSQLUtility.java index 44bd5d352..03f213fef 100644 --- a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/rsql/RSQLUtility.java +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/rsql/RSQLUtility.java @@ -18,6 +18,7 @@ import java.util.stream.Collectors; import javax.persistence.EntityManager; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Expression; import javax.persistence.criteria.MapJoin; import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; @@ -27,6 +28,8 @@ import org.eclipse.hawkbit.repository.FieldNameProvider; import org.eclipse.hawkbit.repository.FieldValueConverter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.SimpleTypeConverter; +import org.springframework.beans.TypeMismatchException; import org.springframework.data.jpa.domain.Specification; import cz.jirutka.rsql.parser.RSQLParser; @@ -99,7 +102,31 @@ public final class RSQLUtility { */ public static & FieldNameProvider, T> Specification parse(final String rsql, final Class fieldNameProvider) { - return new RSQLSpecification<>(rsql, fieldNameProvider); + return new RSQLSpecification<>(rsql.toLowerCase(), fieldNameProvider); + } + + /** + * Validate the given rsql string regarding existence and correct syntax. + * + * @param rsql + * the rsql string to get validated + * + */ + public static void isValid(final String rsql) { + parseRsql(rsql); + } + + private static Node parseRsql(final String rsql) { + try { + LOGGER.debug("parsing rsql string {}", rsql); + final Set operators = RSQLOperators.defaultOperators(); + operators.add(new ComparisonOperator("=li=", false)); + return new RSQLParser(operators).parse(rsql); + } catch (final IllegalArgumentException e) { + throw new RSQLParameterSyntaxException("rsql filter must not be null", e); + } catch (final RSQLParserException e) { + throw new RSQLParameterSyntaxException(e); + } } private static final class RSQLSpecification & FieldNameProvider, T> implements Specification { @@ -123,15 +150,7 @@ public final class RSQLUtility { @Override public Predicate toPredicate(final Root root, final CriteriaQuery query, final CriteriaBuilder cb) { - final Node rootNode; - try { - LOGGER.debug("parsing rsql string {}", rsql); - final Set operators = RSQLOperators.defaultOperators(); - operators.add(new ComparisonOperator("=li=", false)); - rootNode = new RSQLParser(operators).parse(rsql); - } catch (final RSQLParserException e) { - throw new RSQLParameterSyntaxException(e); - } + final Node rootNode = parseRsql(rsql); final JpqQueryRSQLVisitor jpqQueryRSQLVisitor = new JpqQueryRSQLVisitor<>(root, cb, enumType); final List accept = rootNode., String> accept(jpqQueryRSQLVisitor); @@ -164,10 +183,13 @@ public final class RSQLUtility { private final CriteriaBuilder cb; private final Class enumType; + private final SimpleTypeConverter simpleTypeConverter; + private JpqQueryRSQLVisitor(final Root root, final CriteriaBuilder cb, final Class enumType) { this.root = root; this.cb = cb; this.enumType = enumType; + simpleTypeConverter = new SimpleTypeConverter(); } @Override @@ -199,10 +221,8 @@ public final class RSQLUtility { validateMapParamter(propertyEnum, node, graph); // sub entity need minium 1 dot - if (!propertyEnum.getSubEntityAttributes().isEmpty()) { - if (graph.length < 2) { - throw createRSQLParameterUnsupportedException(node); - } + if (!propertyEnum.getSubEntityAttributes().isEmpty() && graph.length < 2) { + throw createRSQLParameterUnsupportedException(node); } for (int i = 1; i < graph.length; i++) { @@ -327,7 +347,6 @@ public final class RSQLUtility { return Enum.valueOf(enumType, enumName.toUpperCase()); } - @SuppressWarnings({ "rawtypes", "unchecked" }) private Object convertValueIfNecessary(final ComparisonNode node, final A fieldName, final String value, final Path fieldPath) { // in case the value of an rsql query e.g. type==application is an @@ -344,18 +363,41 @@ public final class RSQLUtility { return transformEnumValue(node, value, javaType); } if (fieldName instanceof FieldValueConverter) { - final Object convertedValue = ((FieldValueConverter) fieldName).convertValue(fieldName, value); - if (convertedValue == null) { - throw new RSQLParameterUnsupportedFieldException("field {" + node.getSelector() - + "} must be one of the following values {" - + Arrays.toString(((FieldValueConverter) fieldName).possibleValues(fieldName)) + "}", null); - } else { - return convertedValue; - } + return convertFieldConverterValue(node, fieldName, value); } + + if (Boolean.TYPE.equals(javaType)) { + return convertBooleanValue(node, value, javaType); + } + return value; } + private Object convertBooleanValue(final ComparisonNode node, final String value, + final Class javaType) { + try { + return simpleTypeConverter.convertIfNecessary(value, javaType); + } catch (final TypeMismatchException e) { + throw new RSQLParameterSyntaxException( + "The value of the given search parameter field {" + node.getSelector() + + "} is not well formed. Only a boolean (true or false) value will be expected {", + e); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private Object convertFieldConverterValue(final ComparisonNode node, final A fieldName, final String value) { + final Object convertedValue = ((FieldValueConverter) fieldName).convertValue(fieldName, value); + if (convertedValue == null) { + throw new RSQLParameterUnsupportedFieldException( + "field {" + node.getSelector() + "} must be one of the following values {" + + Arrays.toString(((FieldValueConverter) fieldName).possibleValues(fieldName)) + "}", + null); + } else { + return convertedValue; + } + } + @SuppressWarnings({ "rawtypes", "unchecked" }) private Object transformEnumValue(final ComparisonNode node, final String value, final Class javaType) { @@ -377,7 +419,7 @@ public final class RSQLUtility { } } - private List mapToPredicate(final ComparisonNode node, Path fieldPath, + private List mapToPredicate(final ComparisonNode node, final Path fieldPath, final List values, final List transformedValues, final A enumField) { // only 'equal' and 'notEqual' can handle transformed value like // enums. The JPA API @@ -391,17 +433,20 @@ public final class RSQLUtility { singleList.add(mapPredicate); } - fieldPath = getMapValueFieldPath(enumField, fieldPath); + addOperatorPredicate(node, getMapValueFieldPath(enumField, fieldPath), transformedValues, transformedValue, + value, singleList); + return Collections.unmodifiableList(singleList); + } + private void addOperatorPredicate(final ComparisonNode node, final Path fieldPath, + final List transformedValues, final Object transformedValue, final String value, + final List singleList) { switch (node.getOperator().getSymbol()) { - case "=li=": - singleList.add(cb.like(cb.upper(pathOfString(fieldPath)), transformedValue.toString().toUpperCase())); - break; case "==": singleList.add(getEqualToPredicate(transformedValue, fieldPath)); break; case "!=": - singleList.add(cb.notEqual(fieldPath, transformedValue)); + singleList.add(getNotEqualToPredicate(transformedValue, fieldPath)); break; case "=gt=": singleList.add(cb.greaterThan(pathOfString(fieldPath), value)); @@ -416,21 +461,46 @@ public final class RSQLUtility { singleList.add(cb.lessThanOrEqualTo(pathOfString(fieldPath), value)); break; case "=in=": - singleList.add(fieldPath.in(transformedValues)); + singleList.add(getInPredicate(transformedValues, fieldPath)); break; case "=out=": - singleList.add(cb.not(fieldPath.in(transformedValues))); + singleList.add(getOutPredicate(transformedValues, fieldPath)); break; default: LOGGER.info("operator symbol {} is either not supported or not implemented"); } - return Collections.unmodifiableList(singleList); } - /** - * @param enumField - * @return - */ + private Predicate getInPredicate(final List transformedValues, final Path fieldPath) { + final List inParams = new ArrayList<>(); + for (final Object param : transformedValues) { + if (param instanceof String) { + inParams.add(((String) param).toUpperCase()); + } + } + if (!inParams.isEmpty()) { + return cb.upper(pathOfString(fieldPath)).in(inParams); + } else { + return fieldPath.in(transformedValues); + + } + } + + private Predicate getOutPredicate(final List transformedValues, final Path fieldPath) { + final List outParams = new ArrayList<>(); + for (final Object param : transformedValues) { + if (param instanceof String) { + outParams.add(((String) param).toUpperCase()); + } + } + if (!outParams.isEmpty()) { + return cb.not(cb.upper(pathOfString(fieldPath)).in(outParams)); + } else { + return cb.not(fieldPath.in(transformedValues)); + + } + } + private Path getMapValueFieldPath(final A enumField, final Path fieldPath) { if (!enumField.isMap() || enumField.getValueFieldName() == null) { return fieldPath; @@ -438,6 +508,7 @@ public final class RSQLUtility { return fieldPath.get(enumField.getValueFieldName()); } + @SuppressWarnings("unchecked") private Predicate mapToMapPredicate(final ComparisonNode node, final Path fieldPath, final A enumField) { if (!enumField.isMap()) { @@ -446,20 +517,34 @@ public final class RSQLUtility { final String[] graph = node.getSelector().split("\\" + FieldNameProvider.SUB_ATTRIBUTE_SEPERATOR); final String keyValue = graph[graph.length - 1]; if (fieldPath instanceof MapJoin) { - return cb.equal(((MapJoin) fieldPath).key(), keyValue); + // Currently we support only string key .So below cast is safe. + return cb.equal(cb.upper((Expression) (((MapJoin) fieldPath).key())), + keyValue.toUpperCase()); } - return cb.equal(fieldPath.get(enumField.getKeyFieldName()), keyValue); + return cb.equal(cb.upper(fieldPath.get(enumField.getKeyFieldName())), keyValue.toUpperCase()); } private Predicate getEqualToPredicate(final Object transformedValue, final Path fieldPath) { if (transformedValue instanceof String) { - final String preFormattedValue = ((String) transformedValue).replace(LIKE_WILDCARD, '%'); - return cb.like(cb.upper(pathOfString(fieldPath)), preFormattedValue.toString().toUpperCase()); + final String preFormattedValue = escapeValueToSQL((String) transformedValue); + return cb.like(cb.upper(pathOfString(fieldPath)), preFormattedValue.toUpperCase()); } return cb.equal(fieldPath, transformedValue); } + private Predicate getNotEqualToPredicate(final Object transformedValue, final Path fieldPath) { + if (transformedValue instanceof String) { + final String preFormattedValue = escapeValueToSQL((String) transformedValue); + return cb.notLike(cb.upper(pathOfString(fieldPath)), preFormattedValue.toUpperCase()); + } + return cb.notEqual(fieldPath, transformedValue); + } + + private String escapeValueToSQL(final String transformedValue) { + return transformedValue.replace("%", "\\%").replace(LIKE_WILDCARD, '%'); + } + @SuppressWarnings("unchecked") private Path pathOfString(final Path path) { return (Path) path; diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/specifications/SpecificationsBuilder.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/specifications/SpecificationsBuilder.java new file mode 100644 index 000000000..83a4c6f8b --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/repository/specifications/SpecificationsBuilder.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.specifications; + +import java.util.List; + +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.domain.Specifications; + +/** + * Helper class to easily combine {@link Specification} instances. + * + */ +public final class SpecificationsBuilder { + + private SpecificationsBuilder() { + + } + + /** + * Combine all given specification with and. The first specification is the + * where clause. + * + * @param specList + * all specification wich will combine + * @return if the given specification list is empty + */ + public static Specifications combineWithAnd(final List> specList) { + if (specList.isEmpty()) { + return null; + } + Specifications specs = Specifications.where(specList.get(0)); + specList.remove(0); + for (final Specification specification : specList) { + specs = specs.and(specification); + } + return specs; + } + +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/PauseRolloutGroupAction.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/PauseRolloutGroupAction.java new file mode 100644 index 000000000..af335cd0d --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/PauseRolloutGroupAction.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rollout.condition; + +import java.util.concurrent.Callable; + +import org.eclipse.hawkbit.repository.RolloutGroupRepository; +import org.eclipse.hawkbit.repository.RolloutManagement; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus; +import org.eclipse.hawkbit.security.SystemSecurityContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Error action evaluator which pauses the whole {@link Rollout} and sets the + * current {@link RolloutGroup} to error. + */ +@Component("pauseRolloutGroupAction") +public class PauseRolloutGroupAction implements RolloutGroupActionEvaluator { + + @Autowired + private RolloutManagement rolloutManagement; + + @Autowired + private RolloutGroupRepository rolloutGroupRepository; + + @Autowired + private SystemSecurityContext systemSecurityContext; + + @Override + public boolean verifyExpression(final String expression) { + return true; + } + + @Override + public void eval(final Rollout rollout, final RolloutGroup rolloutGroup, final String expression) { + systemSecurityContext.runAsSystem(new Callable() { + @Override + public Void call() throws Exception { + rolloutGroup.setStatus(RolloutGroupStatus.ERROR); + rolloutGroupRepository.save(rolloutGroup); + rolloutManagement.pauseRollout(rollout); + return null; + } + }); + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/RolloutGroupActionEvaluator.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/RolloutGroupActionEvaluator.java new file mode 100644 index 000000000..1e2c98707 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/RolloutGroupActionEvaluator.java @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rollout.condition; + +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; + +/** + * + */ +public interface RolloutGroupActionEvaluator { + + boolean verifyExpression(final String expression); + + void eval(Rollout rollout, RolloutGroup rolloutGroup, final String expression); +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/RolloutGroupConditionEvaluator.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/RolloutGroupConditionEvaluator.java new file mode 100644 index 000000000..62c9050b1 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/RolloutGroupConditionEvaluator.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rollout.condition; + +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; + +/** + * + */ +public interface RolloutGroupConditionEvaluator { + + default boolean verifyExpression(final String expression) { + // percentage value between 0 and 100 + try { + final Integer value = Integer.valueOf(expression); + if (value >= 0 || value <= 100) { + return true; + } + return true; + } catch (final NumberFormatException e) { + + return false; + } + } + + boolean eval(Rollout rollout, RolloutGroup rolloutGroup, final String expression); +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/StartNextGroupRolloutGroupSuccessAction.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/StartNextGroupRolloutGroupSuccessAction.java new file mode 100644 index 000000000..21c499d31 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/StartNextGroupRolloutGroupSuccessAction.java @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rollout.condition; + +import java.util.List; + +import org.eclipse.hawkbit.repository.DeploymentManagement; +import org.eclipse.hawkbit.repository.RolloutGroupRepository; +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus; +import org.eclipse.hawkbit.security.SystemSecurityContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Success action which starts the next following {@link RolloutGroup}. + */ +@Component("startNextRolloutGroupAction") +public class StartNextGroupRolloutGroupSuccessAction implements RolloutGroupActionEvaluator { + + private static final Logger logger = LoggerFactory.getLogger(StartNextGroupRolloutGroupSuccessAction.class); + + @Autowired + private RolloutGroupRepository rolloutGroupRepository; + + @Autowired + private DeploymentManagement deploymentManagement; + + @Autowired + private SystemSecurityContext systemSecurityContext; + + @Override + public boolean verifyExpression(final String expression) { + return true; + } + + @Override + public void eval(final Rollout rollout, final RolloutGroup rolloutGroup, final String expression) { + systemSecurityContext.runAsSystem(() -> { + startNextGroup(rollout, rolloutGroup); + return null; + }); + } + + private void startNextGroup(final Rollout rollout, final RolloutGroup rolloutGroup) { + // retrieve all actions accroding to the parent group of the finished + // rolloutGroup, so retrieve all child-group actions which need to be + // started. + final List rolloutGroupActions = deploymentManagement.findActionsByRolloutGroupParentAndStatus(rollout, + rolloutGroup, Action.Status.SCHEDULED); + logger.debug("{} Next actions to start for rollout {} and parent group {}", rolloutGroupActions.size(), rollout, + rolloutGroup); + rolloutGroupActions.forEach(action -> deploymentManagement.startScheduledAction(action)); + if (!rolloutGroupActions.isEmpty()) { + // get all next scheduled groups based on the found actions and set + // them in state running + rolloutGroupActions.forEach(action -> { + final RolloutGroup nextGroup = action.getRolloutGroup(); + logger.debug("Rolloutgroup {} is now running", nextGroup); + nextGroup.setStatus(RolloutGroupStatus.RUNNING); + rolloutGroupRepository.save(nextGroup); + }); + } else { + logger.info("No actions to start for next rolloutgroup of parent {}", rolloutGroup); + // nothing for next group, just finish the group, this can happen + // e.g. if targets has been deleted after the group has been + // scheduled. If the group is empty now, we just finish the group if + // there are not actions available for this group. + final List findByRolloutGroupParent = rolloutGroupRepository + .findByParentAndStatus(rolloutGroup, RolloutGroupStatus.SCHEDULED); + findByRolloutGroupParent.forEach(nextGroup -> { + logger.debug("Rolloutgroup {} is finished, starting next group", nextGroup); + nextGroup.setStatus(RolloutGroupStatus.FINISHED); + rolloutGroupRepository.save(nextGroup); + // find the next group to set in running state + startNextGroup(rollout, nextGroup); + }); + } + } +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/ThresholdRolloutGroupErrorCondition.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/ThresholdRolloutGroupErrorCondition.java new file mode 100644 index 000000000..6e63efa8e --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/ThresholdRolloutGroupErrorCondition.java @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rollout.condition; + +import org.eclipse.hawkbit.repository.ActionRepository; +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * + */ +@Component("thresholdRolloutGroupErrorCondition") +public class ThresholdRolloutGroupErrorCondition implements RolloutGroupConditionEvaluator { + + private static final Logger LOGGER = LoggerFactory.getLogger(ThresholdRolloutGroupErrorCondition.class); + + @Autowired + private ActionRepository actionRepository; + + @Override + public boolean eval(final Rollout rollout, final RolloutGroup rolloutGroup, final String expression) { + final Long totalGroup = actionRepository.countByRolloutAndRolloutGroup(rollout, rolloutGroup); + final Long error = actionRepository.countByRolloutIdAndRolloutGroupIdAndStatus(rollout.getId(), + rolloutGroup.getId(), Action.Status.ERROR); + try { + final Integer threshold = Integer.valueOf(expression); + + if (totalGroup == 0) { + // in case e.g. targets has been deleted we don't have any + // actions left for this group, so the group is finished + return false; + } + + // calculate threshold + return ((float) error / (float) totalGroup) > ((float) threshold / 100F); + } catch (final NumberFormatException e) { + LOGGER.error("Cannot evaluate condition expression " + expression, e); + return false; + } + } + +} diff --git a/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/ThresholdRolloutGroupSuccessCondition.java b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/ThresholdRolloutGroupSuccessCondition.java new file mode 100644 index 000000000..fcf9762c6 --- /dev/null +++ b/hawkbit-repository/src/main/java/org/eclipse/hawkbit/rollout/condition/ThresholdRolloutGroupSuccessCondition.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rollout.condition; + +import org.eclipse.hawkbit.repository.ActionRepository; +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * + */ +@Component("thresholdRolloutGroupSuccessCondition") +public class ThresholdRolloutGroupSuccessCondition implements RolloutGroupConditionEvaluator { + private static final Logger LOGGER = LoggerFactory.getLogger(ThresholdRolloutGroupSuccessCondition.class); + + @Autowired + private ActionRepository actionRepository; + + @Override + public boolean eval(final Rollout rollout, final RolloutGroup rolloutGroup, final String expression) { + final Long totalGroup = rolloutGroup.getTotalTargets(); + final Long finished = this.actionRepository.countByRolloutIdAndRolloutGroupIdAndStatus(rollout.getId(), + rolloutGroup.getId(), Action.Status.FINISHED); + try { + final Integer threshold = Integer.valueOf(expression); + + if (totalGroup == 0) { + // in case e.g. targets has been deleted we don't have any + // actions + // left for this group, so the group is finished + return true; + } + // calculate threshold + return ((float) finished / (float) totalGroup) >= ((float) threshold / 100F); + } catch (final NumberFormatException e) { + LOGGER.error("Cannot evaluate condition expression " + expression, e); + return false; + } + } + +} diff --git a/hawkbit-repository/src/main/resources/db/migration/H2/V1_6_0__rollout_management___H2.sql b/hawkbit-repository/src/main/resources/db/migration/H2/V1_6_0__rollout_management___H2.sql new file mode 100644 index 000000000..490400161 --- /dev/null +++ b/hawkbit-repository/src/main/resources/db/migration/H2/V1_6_0__rollout_management___H2.sql @@ -0,0 +1,103 @@ + create table sp_rolloutgroup ( + id bigint generated by default as identity, + created_at bigint, + created_by varchar(40), + last_modified_at bigint, + last_modified_by varchar(40), + optlock_revision bigint, + tenant varchar(40) not null, + description varchar(512), + name varchar(64) not null, + error_condition integer, + error_condition_exp varchar(512), + error_action integer, + error_action_exp varchar(512), + success_condition integer not null, + success_condition_exp varchar(512) not null, + success_action integer not null, + success_action_exp varchar(512), + status integer, + parent_id bigint, + rollout bigint, + total_targets bigint, + primary key (id) + ); + + create table sp_rollout ( + id bigint generated by default as identity, + created_at bigint, + created_by varchar(40), + last_modified_at bigint, + last_modified_by varchar(40), + optlock_revision bigint, + tenant varchar(40) not null, + description varchar(512), + name varchar(64) not null, + last_check bigint, + group_theshold float, + status integer, + distribution_set bigint, + target_filter varchar(1024), + action_type varchar(255) not null, + forced_time bigint, + total_targets bigint, + primary key (id) + ); + + create table sp_rollouttargetgroup ( + target_Id bigint not null, + rolloutGroup_Id bigint not null, + primary key (rolloutGroup_Id, target_Id) + ); + + create index sp_idx_rollout_01 on sp_rollout (tenant, name); + + create index sp_idx_rolloutgroup_01 on sp_rolloutgroup (tenant, name); + + ALTER TABLE sp_action ADD COLUMN rollout bigint; + ALTER TABLE sp_action ADD COLUMN rolloutgroup bigint; + + alter table sp_rollout + add constraint uk_rollout unique (name, tenant); + + alter table sp_rolloutgroup + add constraint uk_rolloutgroup unique (name, rollout, tenant); + + alter table sp_action + add constraint fk_action_rollout + foreign key (rollout) + references sp_rollout; + + alter table sp_action + add constraint fk_action_rolloutgroup + foreign key (rolloutgroup) + references sp_rolloutgroup; + + alter table sp_rollout + add constraint fk_rollout_ds + foreign key (distribution_set) + references sp_distribution_set; + + alter table sp_rolloutgroup + add constraint fk_rolloutgroup_rollout + foreign key (rollout) + references sp_rollout + on delete cascade; + + alter table sp_rolloutgroup + add constraint fk_rolloutgroup_rolloutgroup + foreign key (parent_id) + references sp_rolloutgroup + on delete cascade; + + alter table sp_rollouttargetgroup + add constraint fk_rollouttargetgroup_target + foreign key (target_id) + references sp_target + on delete cascade; + + alter table sp_rollouttargetgroup + add constraint fk_rollouttargetgroup_rolloutgroup + foreign key (rolloutgroup_id) + references sp_rolloutgroup + on delete cascade; \ No newline at end of file diff --git a/hawkbit-repository/src/main/resources/db/migration/MYSQL/V1_6_0__rollout_management___MYSQL.sql b/hawkbit-repository/src/main/resources/db/migration/MYSQL/V1_6_0__rollout_management___MYSQL.sql new file mode 100644 index 000000000..4cd4c7ea4 --- /dev/null +++ b/hawkbit-repository/src/main/resources/db/migration/MYSQL/V1_6_0__rollout_management___MYSQL.sql @@ -0,0 +1,103 @@ + create table sp_rolloutgroup ( + id bigint not null auto_increment, + created_at bigint, + created_by varchar(40), + last_modified_at bigint, + last_modified_by varchar(40), + optlock_revision bigint, + tenant varchar(40) not null, + description varchar(512), + name varchar(64) not null, + error_condition integer, + error_condition_exp varchar(512), + error_action integer, + error_action_exp varchar(512), + success_condition integer not null, + success_condition_exp varchar(512) not null, + success_action integer not null, + success_action_exp varchar(512), + status integer, + parent_id bigint, + rollout bigint, + total_targets bigint, + primary key (id) + ); + + create table sp_rollout ( + id bigint not null auto_increment, + created_at bigint, + created_by varchar(40), + last_modified_at bigint, + last_modified_by varchar(40), + optlock_revision bigint, + tenant varchar(40) not null, + description varchar(512), + name varchar(64) not null, + last_check bigint, + group_theshold float, + status integer, + distribution_set bigint, + target_filter varchar(1024), + action_type varchar(255) not null, + forced_time bigint, + total_targets bigint, + primary key (id) + ); + + create table sp_rollouttargetgroup ( + target_Id bigint not null, + rolloutGroup_Id bigint not null, + primary key (rolloutGroup_Id, target_Id) + ); + + create index sp_idx_rollout_01 on sp_rollout (tenant, name); + + create index sp_idx_rolloutgroup_01 on sp_rolloutgroup (tenant, name); + + ALTER TABLE sp_action ADD COLUMN rollout bigint; + ALTER TABLE sp_action ADD COLUMN rolloutgroup bigint; + + alter table sp_rollout + add constraint uk_rollout unique (name, tenant); + + alter table sp_rolloutgroup + add constraint uk_rolloutgroup unique (name, rollout, tenant); + + alter table sp_action + add constraint fk_action_rollout + foreign key (rollout) + references sp_rollout (id); + + alter table sp_action + add constraint fk_action_rolloutgroup + foreign key (rolloutgroup) + references sp_rolloutgroup (id); + + alter table sp_rollout + add constraint fk_rollout_ds + foreign key (distribution_set) + references sp_distribution_set (id); + + alter table sp_rolloutgroup + add constraint fk_rolloutgroup_rollout + foreign key (rollout) + references sp_rollout (id) + on delete cascade; + + alter table sp_rolloutgroup + add constraint fk_rolloutgroup_rolloutgroup + foreign key (parent_id) + references sp_rolloutgroup (id) + on delete cascade; + + alter table sp_rollouttargetgroup + add constraint fk_rollouttargetgroup_target + foreign key (target_id) + references sp_target (id) + on delete cascade; + + alter table sp_rollouttargetgroup + add constraint fk_rollouttargetgroup_rolloutgroup + foreign key (rolloutgroup_id) + references sp_rolloutgroup (id) + on delete cascade; \ No newline at end of file diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/AbstractIntegrationTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/AbstractIntegrationTest.java index 94fa8beca..b7091edf8 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/AbstractIntegrationTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/AbstractIntegrationTest.java @@ -11,7 +11,6 @@ package org.eclipse.hawkbit; import static org.fest.assertions.api.Assertions.assertThat; import java.util.List; -import java.util.Properties; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; @@ -28,6 +27,10 @@ import org.eclipse.hawkbit.repository.DistributionSetTagRepository; import org.eclipse.hawkbit.repository.DistributionSetTypeRepository; import org.eclipse.hawkbit.repository.ExternalArtifactRepository; import org.eclipse.hawkbit.repository.LocalArtifactRepository; +import org.eclipse.hawkbit.repository.RolloutGroupManagement; +import org.eclipse.hawkbit.repository.RolloutGroupRepository; +import org.eclipse.hawkbit.repository.RolloutManagement; +import org.eclipse.hawkbit.repository.RolloutRepository; import org.eclipse.hawkbit.repository.SoftwareManagement; import org.eclipse.hawkbit.repository.SoftwareModuleMetadataRepository; import org.eclipse.hawkbit.repository.SoftwareModuleRepository; @@ -39,6 +42,7 @@ import org.eclipse.hawkbit.repository.TargetInfoRepository; import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TargetRepository; import org.eclipse.hawkbit.repository.TargetTagRepository; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.TenantMetaDataRepository; import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; @@ -177,6 +181,22 @@ public abstract class AbstractIntegrationTest implements EnvironmentAware { @Autowired protected TenantAwareCacheManager cacheManager; + + @Autowired + protected TenantConfigurationManagement tenantConfigurationManagement; + + + @Autowired + protected RolloutManagement rolloutManagement; + + @Autowired + protected RolloutGroupManagement rolloutGroupManagement; + + @Autowired + protected RolloutGroupRepository rolloutGroupRepository; + + @Autowired + protected RolloutRepository rolloutRepository; protected MockMvc mvc; @@ -196,23 +216,6 @@ public abstract class AbstractIntegrationTest implements EnvironmentAware { private static CIMySqlTestDatabase tesdatabase; - static { - final Properties props = System.getProperties(); - - // if( props.getProperty( SuiteEmbeddedConfiguration.IM_HOME ) == null ) - // { - // props.setProperty( SuiteEmbeddedConfiguration.IM_HOME, - // "./src/test/resources/im" ); - // } - - } - - /* - * (non-Javadoc) - * - * @see org.springframework.context.EnvironmentAware#setEnvironment(org. - * springframework.core.env. Environment) - */ @Override public void setEnvironment(final Environment environment) { this.environment = environment; @@ -275,6 +278,8 @@ public abstract class AbstractIntegrationTest implements EnvironmentAware { assertThat(softwareModuleTypeRepository.findAll()).isEmpty(); assertThat(distributionSetTypeRepository.findAll()).isEmpty(); assertThat(tenantMetaDataRepository.findAll()).isEmpty(); + assertThat(rolloutGroupRepository.findAll()).isEmpty(); + assertThat(rolloutRepository.findAll()).isEmpty(); } @Transactional diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/AbstractIntegrationTestWithMongoDB.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/AbstractIntegrationTestWithMongoDB.java index eb2174a57..6d116c5f0 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/AbstractIntegrationTestWithMongoDB.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/AbstractIntegrationTestWithMongoDB.java @@ -55,7 +55,7 @@ public abstract class AbstractIntegrationTestWithMongoDB extends AbstractIntegra System.setProperty("spring.data.mongodb.port", String.valueOf(port)); } - Version version = Version.V3_0_5; + Version version = Version.V3_0_8; if (System.getProperty("inf.mongodb.version") != null) { version = Version .valueOf("V" + System.getProperty("inf.mongodb.version").trim().replaceAll("\\.", "_")); diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/MethodSecurityUtil.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/MethodSecurityUtil.java index 2fb68ec31..6633a27e5 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/MethodSecurityUtil.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/MethodSecurityUtil.java @@ -54,7 +54,7 @@ public class MethodSecurityUtil { if (!METHOD_SECURITY_EXCLUSION.contains(method.getName()) && !method.isSynthetic() && Modifier.isPublic(method.getModifiers())) { final PreAuthorize annotation = method.getAnnotation(PreAuthorize.class); - assertThat(annotation).describedAs( + assertThat(annotation).as( "The public method " + method.getName() + " is not annoated with @PreAuthorize, security leak?") .isNotNull(); } diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/TestConfiguration.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/TestConfiguration.java index 7cdca8efd..70372fac2 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/TestConfiguration.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/TestConfiguration.java @@ -9,14 +9,16 @@ package org.eclipse.hawkbit; import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import org.eclipse.hawkbit.cache.CacheConstants; import org.eclipse.hawkbit.cache.TenancyCacheManager; import org.eclipse.hawkbit.cache.TenantAwareCacheManager; +import org.eclipse.hawkbit.repository.model.helper.EventBusHolder; import org.eclipse.hawkbit.repository.utils.RepositoryDataGenerator; import org.eclipse.hawkbit.repository.utils.RepositoryDataGenerator.DatabaseCleanupUtil; +import org.eclipse.hawkbit.security.DdiSecurityProperties; import org.eclipse.hawkbit.security.SecurityContextTenantAware; -import org.eclipse.hawkbit.security.SecurityProperties; import org.eclipse.hawkbit.security.SpringSecurityAuditorAware; import org.eclipse.hawkbit.tenancy.TenantAware; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; @@ -30,7 +32,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.data.domain.AuditorAware; import org.springframework.scheduling.annotation.AsyncConfigurer; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.security.concurrent.DelegatingSecurityContextExecutor; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import com.google.common.eventbus.AsyncEventBus; @@ -45,7 +47,7 @@ import com.mongodb.MongoClientOptions; */ @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true, mode = AdviceMode.ASPECTJ, proxyTargetClass = true, securedEnabled = true) -@EnableConfigurationProperties({ SecurityProperties.class, ControllerPollProperties.class }) +@EnableConfigurationProperties({ HawkbitServerProperties.class, DdiSecurityProperties.class }) @Profile("test") public class TestConfiguration implements AsyncConfigurer { @@ -91,9 +93,14 @@ public class TestConfiguration implements AsyncConfigurer { return new AsyncEventBus(asyncExecutor()); } + @Bean + public EventBusHolder eventBusHolder() { + return EventBusHolder.getInstance(); + } + @Bean public Executor asyncExecutor() { - return new ThreadPoolTaskExecutor(); + return new DelegatingSecurityContextExecutor(Executors.newSingleThreadExecutor()); } @Bean diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/cache/CacheKeysTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/cache/CacheKeysTest.java index b086875c3..b0da78abd 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/cache/CacheKeysTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/cache/CacheKeysTest.java @@ -12,6 +12,11 @@ import static org.fest.assertions.api.Assertions.assertThat; import org.junit.Test; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Unit Tests - Repository") +@Stories("CacheKeys") public class CacheKeysTest { @Test @@ -21,5 +26,4 @@ public class CacheKeysTest { final String entitySpecificCacheKey = CacheKeys.entitySpecificCacheKey(knownEntityId, knownCacheKey); assertThat(entitySpecificCacheKey).isEqualTo(knownEntityId + "." + knownCacheKey); } - } diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/cache/CacheWriteNotifyTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/cache/CacheWriteNotifyTest.java index 3b69b4fed..c88a3e717 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/cache/CacheWriteNotifyTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/cache/CacheWriteNotifyTest.java @@ -26,6 +26,11 @@ import org.springframework.cache.CacheManager; import com.google.common.eventbus.EventBus; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Unit Tests - Repository") +@Stories("CacheWriteNotify") @RunWith(MockitoJUnitRunner.class) public class CacheWriteNotifyTest { diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/eventbus/CacheFieldEntityListenerTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/eventbus/CacheFieldEntityListenerTest.java index e80c49b2d..4943ea88a 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/eventbus/CacheFieldEntityListenerTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/eventbus/CacheFieldEntityListenerTest.java @@ -27,6 +27,11 @@ import org.springframework.cache.CacheManager; import org.springframework.cache.support.SimpleValueWrapper; import org.springframework.hateoas.Identifiable; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Unit Tests - Repository") +@Stories("EventBus") @RunWith(MockitoJUnitRunner.class) public class CacheFieldEntityListenerTest { diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/ActionTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/ActionTest.java index 0ce7dd677..9bd532b5e 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/ActionTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/ActionTest.java @@ -14,10 +14,16 @@ import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.junit.Test; +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Unit Tests - Repository") +@Stories("Deployment Management") public class ActionTest { - // issue MECS-670 timeforced update and eTAG calculation @Test + @Description("Ensures that timeforced moded switch from soft to forces after defined timeframe.") public void timeforcedHitNewHasCodeIsGenerated() throws InterruptedException { final boolean active = true; diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/ArtifactManagementNoMongoDbTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/ArtifactManagementNoMongoDbTest.java index e509a3b0c..dd1e171ca 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/ArtifactManagementNoMongoDbTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/ArtifactManagementNoMongoDbTest.java @@ -25,9 +25,6 @@ import ru.yandex.qatools.allure.annotations.Stories; /** * Addition tests next to {@link ArtifactManagementTest} with no running MongoDB * - * - * - * */ @Features("Component Tests - Repository") @Stories("Artifact Management") diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/ArtifactManagementTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/ArtifactManagementTest.java index 5585aab95..2929110dd 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/ArtifactManagementTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/ArtifactManagementTest.java @@ -150,14 +150,14 @@ public class ArtifactManagementTest extends AbstractIntegrationTestWithMongoDB { ExternalArtifact result = artifactManagement.createExternalArtifact(provider, null, sm.getId()); - assertNotNull(result); + assertNotNull("The result of an external artifact should not be null", result); assertThat(externalArtifactRepository.findAll()).contains(result).hasSize(1); assertThat(result.getSoftwareModule().getId()).isEqualTo(sm.getId()); assertThat(result.getUrl()).isEqualTo("https://fhghdfjgh/{version}/"); assertThat(result.getExternalArtifactProvider()).isEqualTo(provider); result = artifactManagement.createExternalArtifact(provider, "/test", sm2.getId()); - assertNotNull(result); + assertNotNull("The newly created external artifact should not be null", result); assertThat(externalArtifactRepository.findAll()).contains(result).hasSize(2); assertThat(result.getUrl()).isEqualTo("https://fhghdfjgh/test"); assertThat(result.getExternalArtifactProvider()).isEqualTo(provider); @@ -176,7 +176,7 @@ public class ArtifactManagementTest extends AbstractIntegrationTestWithMongoDB { "https://fhghdfjgh", "/{version}/"); final ExternalArtifact result = artifactManagement.createExternalArtifact(provider, null, sm.getId()); - assertNotNull(result); + assertNotNull("The newly created external artifact should not be null", result); assertThat(externalArtifactRepository.findAll()).contains(result).hasSize(1); artifactManagement.deleteExternalArtifact(result.getId()); @@ -348,7 +348,7 @@ public class ArtifactManagementTest extends AbstractIntegrationTestWithMongoDB { final LocalArtifact result = artifactManagement.createLocalArtifact(new ByteArrayInputStream(random), sm.getId(), "file1", false); - assertTrue(IOUtils.contentEquals(new ByteArrayInputStream(random), + assertTrue("The stored binary matches the given binary", IOUtils.contentEquals(new ByteArrayInputStream(random), artifactManagement.loadLocalArtifactBinary(result).getFileInputStream())); } diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/ControllerManagementTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/ControllerManagementTest.java index 8289b8173..a3913da2b 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/ControllerManagementTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/ControllerManagementTest.java @@ -9,10 +9,13 @@ package org.eclipse.hawkbit.repository; import static org.fest.assertions.api.Assertions.assertThat; +import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.List; +import javax.validation.ConstraintViolationException; + import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.hawkbit.AbstractIntegrationTest; import org.eclipse.hawkbit.TestDataUtil; @@ -67,8 +70,26 @@ public class ControllerManagementTest extends AbstractIntegrationTest { .isEqualTo(TargetUpdateStatus.IN_SYNC); assertThat(actionStatusRepository.findAll(pageReq).getNumberOfElements()).isEqualTo(3); - assertThat(deploymentManagement.findActionStatusMessagesByActionInDescOrder(pageReq, savedAction, false) - .getNumberOfElements()).isEqualTo(3); + assertThat(deploymentManagement.findActionStatusByAction(pageReq, savedAction, false).getNumberOfElements()) + .isEqualTo(3); + } + + @Test + @Description("Register a controller which not exist") + public void testfindOrRegisterTargetIfItDoesNotexist() { + final Target target = controllerManagament.findOrRegisterTargetIfItDoesNotexist("AA", null); + assertThat(target).as("target should not be null").isNotNull(); + + final Target sameTarget = controllerManagament.findOrRegisterTargetIfItDoesNotexist("AA", null); + assertThat(target).as("Target should be the equals").isEqualTo(sameTarget); + assertThat(targetRepository.count()).as("Only 1 target should be registred").isEqualTo(1L); + + try { + controllerManagament.findOrRegisterTargetIfItDoesNotexist("", null); + fail("target with empty controller id should not be registred"); + } catch (final ConstraintViolationException e) { + // ok + } } @Test diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/DeploymentManagementTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/DeploymentManagementTest.java index 46747cd9c..ecf079c29 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/DeploymentManagementTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/DeploymentManagementTest.java @@ -10,7 +10,6 @@ package org.eclipse.hawkbit.repository; import static org.fest.assertions.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; import java.util.ArrayList; @@ -19,7 +18,6 @@ import java.util.LinkedList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; import org.eclipse.hawkbit.AbstractIntegrationTest; import org.eclipse.hawkbit.Constants; @@ -51,7 +49,6 @@ import com.google.common.eventbus.Subscribe; import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Features; -import ru.yandex.qatools.allure.annotations.Issue; import ru.yandex.qatools.allure.annotations.Stories; /** @@ -66,6 +63,22 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { @Autowired private EventBus eventBus; + @Test + @Description("Test verifies that the repistory retrieves the action including all defined (lazy) details.") + public void findActionWithLazyDetails() { + final DistributionSet testDs = TestDataUtil.generateDistributionSet("TestDs", "1.0", softwareManagement, + distributionSetManagement, new ArrayList()); + final List testTarget = targetManagement.createTargets(TestDataUtil.generateTargets(1)); + // one action with one action status is generated + final Long actionId = deploymentManagement.assignDistributionSet(testDs, testTarget).getActions().get(0); + final Action action = deploymentManagement.findActionWithDetails(actionId); + + assertThat(action.getDistributionSet()).as("DistributionSet in action").isNotNull(); + assertThat(action.getTarget()).as("Target in action").isNotNull(); + assertThat(action.getTarget().getAssignedDistributionSet()).as("AssignedDistributionSet of target in action") + .isNotNull(); + } + @Test @Description("Test verifies that the custom query to find all actions include the count of action status is working correctly") public void findActionsWithStatusCountByTarget() { @@ -73,7 +86,8 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { distributionSetManagement, new ArrayList()); final List testTarget = targetManagement.createTargets(TestDataUtil.generateTargets(1)); // one action with one action status is generated - final Action action = deploymentManagement.assignDistributionSet(testDs, testTarget).getActions().get(0); + final Action action = deploymentManagement.findActionWithDetails( + deploymentManagement.assignDistributionSet(testDs, testTarget).getActions().get(0)); // save 2 action status actionStatusRepository.save(new ActionStatus(action, Status.RETRIEVED, System.currentTimeMillis())); actionStatusRepository.save(new ActionStatus(action, Status.RUNNING, System.currentTimeMillis())); @@ -81,8 +95,9 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { final List findActionsWithStatusCountByTarget = deploymentManagement .findActionsWithStatusCountByTargetOrderByIdDesc(testTarget.get(0)); - assertThat(findActionsWithStatusCountByTarget).hasSize(1); - assertThat(findActionsWithStatusCountByTarget.get(0).getActionStatusCount()).isEqualTo(3L); + assertThat(findActionsWithStatusCountByTarget).as("wrong action size").hasSize(1); + assertThat(findActionsWithStatusCountByTarget.get(0).getActionStatusCount()).as("wrong action status size") + .isEqualTo(3L); } @Test @@ -98,32 +113,36 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { final DistributionSetTag tag = tagManagement.createDistributionSetTag(new DistributionSetTag("Tag1")); final List assignedDS = distributionSetManagement.assignTag(assignDS, tag); - assertThat(assignedDS.size()).isEqualTo(4); - assignedDS.forEach(ds -> assertThat(ds.getTags().size()).isEqualTo(1)); + assertThat(assignedDS.size()).as("assigned ds has wrong size").isEqualTo(4); + assignedDS.forEach(ds -> assertThat(ds.getTags().size()).as("ds has wrong tag size").isEqualTo(1)); DistributionSetTag findDistributionSetTag = tagManagement.findDistributionSetTag("Tag1"); - assertThat(assignedDS.size()).isEqualTo(findDistributionSetTag.getAssignedToDistributionSet().size()); + assertThat(assignedDS.size()).as("assigned ds has wrong size") + .isEqualTo(findDistributionSetTag.getAssignedToDistributionSet().size()); - assertThat(distributionSetManagement.unAssignTag(Long.valueOf(100), findDistributionSetTag)).isNull(); + assertThat(distributionSetManagement.unAssignTag(Long.valueOf(100), findDistributionSetTag)) + .as("unassign tag result should be null").isNull(); final DistributionSet unAssignDS = distributionSetManagement.unAssignTag(assignDS.get(0), findDistributionSetTag); - assertThat(unAssignDS.getId()).isEqualTo(assignDS.get(0)); - assertThat(unAssignDS.getTags().size()).isEqualTo(0); + assertThat(unAssignDS.getId()).as("unassigned ds is wrong").isEqualTo(assignDS.get(0)); + assertThat(unAssignDS.getTags().size()).as("unassigned ds has wrong tag size").isEqualTo(0); findDistributionSetTag = tagManagement.findDistributionSetTag("Tag1"); - assertThat(findDistributionSetTag.getAssignedToDistributionSet().size()).isEqualTo(3); + assertThat(findDistributionSetTag.getAssignedToDistributionSet().size()).as("ds tag ds has wrong ds size") + .isEqualTo(3); final List unAssignTargets = distributionSetManagement .unAssignAllDistributionSetsByTag(findDistributionSetTag); findDistributionSetTag = tagManagement.findDistributionSetTag("Tag1"); - assertThat(findDistributionSetTag.getAssignedToDistributionSet().size()).isEqualTo(0); - assertThat(unAssignTargets.size()).isEqualTo(3); - unAssignTargets.forEach(target -> assertThat(target.getTags().size()).isEqualTo(0)); + assertThat(findDistributionSetTag.getAssignedToDistributionSet().size()).as("ds tag has wrong ds size") + .isEqualTo(0); + assertThat(unAssignTargets.size()).as("unassigned target has wrong size").isEqualTo(3); + unAssignTargets + .forEach(target -> assertThat(target.getTags().size()).as("target has wrong tag size").isEqualTo(0)); } @Test @Description("Test verifies that an assignment with automatic cancelation works correctly even if the update is split into multiple partitions on the database.") - @Issue("MECS-674") public void multiAssigmentHistoryOverMultiplePagesResultsInTwoActiveAction() { final DistributionSet cancelDs = TestDataUtil.generateDistributionSet("Canceled DS", "1.0", softwareManagement, @@ -140,7 +159,8 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { targetManagement.findAllTargetIds().forEach(targetIdName -> { assertThat(deploymentManagement.findActiveActionsByTarget( - targetManagement.findTargetByControllerID(targetIdName.getControllerId()))).hasSize(2); + targetManagement.findTargetByControllerID(targetIdName.getControllerId()))) + .as("active action has wrong size").hasSize(2); }); } @@ -163,14 +183,14 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { // check initial status assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getUpdateStatus()) - .isEqualTo(TargetUpdateStatus.UNKNOWN); + .as("target has update status").isEqualTo(TargetUpdateStatus.UNKNOWN); // assign the two sets in a row Action firstAction = assignSet(target, dsFirst); Action secondAction = assignSet(target, dsSecond); - assertThat(actionRepository.findAll()).hasSize(2); - assertThat(actionStatusRepository.findAll()).hasSize(2); + assertThat(actionRepository.findAll()).as("wrong size of actions").hasSize(2); + assertThat(actionStatusRepository.findAll()).as("wrong size of action status").hasSize(2); // we cancel second -> back to first deploymentManagement.cancelAction(secondAction, @@ -180,10 +200,11 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { secondAction.setStatus(Status.CANCELED); controllerManagement.addCancelActionStatus( new ActionStatus(secondAction, Status.CANCELED, System.currentTimeMillis()), secondAction); - assertThat(actionStatusRepository.findAll()).hasSize(4); - assertThat(targetManagement.findTargetByControllerID("4712").getAssignedDistributionSet()).isEqualTo(dsFirst); + assertThat(actionStatusRepository.findAll()).as("wrong size of actions status").hasSize(4); + assertThat(targetManagement.findTargetByControllerID("4712").getAssignedDistributionSet()).as("wrong ds") + .isEqualTo(dsFirst); assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getUpdateStatus()) - .isEqualTo(TargetUpdateStatus.PENDING); + .as("wrong update status").isEqualTo(TargetUpdateStatus.PENDING); // we cancel first -> back to installed deploymentManagement.cancelAction(firstAction, @@ -193,11 +214,11 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { firstAction.setStatus(Status.CANCELED); controllerManagement.addCancelActionStatus( new ActionStatus(firstAction, Status.CANCELED, System.currentTimeMillis()), firstAction); - assertThat(actionStatusRepository.findAll()).hasSize(6); + assertThat(actionStatusRepository.findAll()).as("wrong size of action status").hasSize(6); assertThat(targetManagement.findTargetByControllerID("4712").getAssignedDistributionSet()) - .isEqualTo(dsInstalled); + .as("wrong assigned ds").isEqualTo(dsInstalled); assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getUpdateStatus()) - .isEqualTo(TargetUpdateStatus.IN_SYNC); + .as("wrong update status").isEqualTo(TargetUpdateStatus.IN_SYNC); } @Test @@ -219,14 +240,14 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { // check initial status assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getUpdateStatus()) - .isEqualTo(TargetUpdateStatus.UNKNOWN); + .as("wrong update status").isEqualTo(TargetUpdateStatus.UNKNOWN); // assign the two sets in a row Action firstAction = assignSet(target, dsFirst); Action secondAction = assignSet(target, dsSecond); - assertThat(actionRepository.findAll()).hasSize(2); - assertThat(actionStatusRepository.findAll()).hasSize(2); + assertThat(actionRepository.findAll()).as("wrong size of actions").hasSize(2); + assertThat(actionStatusRepository.findAll()).as("wrong size of action status").hasSize(2); // we cancel first -> second is left deploymentManagement.cancelAction(firstAction, @@ -236,26 +257,28 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { firstAction.setStatus(Status.CANCELED); controllerManagement.addCancelActionStatus( new ActionStatus(firstAction, Status.CANCELED, System.currentTimeMillis()), firstAction); - assertThat(actionStatusRepository.findAll()).hasSize(4); - assertThat(targetManagement.findTargetByControllerID("4712").getAssignedDistributionSet()).isEqualTo(dsSecond); + assertThat(actionStatusRepository.findAll()).as("wrong size of action status").hasSize(4); + assertThat(targetManagement.findTargetByControllerID("4712").getAssignedDistributionSet()) + .as("wrong assigned ds").isEqualTo(dsSecond); assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getUpdateStatus()) - .isEqualTo(TargetUpdateStatus.PENDING); + .as("wrong target update status").isEqualTo(TargetUpdateStatus.PENDING); // we cancel second -> remain assigned until finished cancellation deploymentManagement.cancelAction(secondAction, targetManagement.findTargetByControllerID(target.getControllerId())); secondAction = deploymentManagement.findActionWithDetails(secondAction.getId()); - assertThat(actionStatusRepository.findAll()).hasSize(5); - assertThat(targetManagement.findTargetByControllerID("4712").getAssignedDistributionSet()).isEqualTo(dsSecond); + assertThat(actionStatusRepository.findAll()).as("wrong size of action status").hasSize(5); + assertThat(targetManagement.findTargetByControllerID("4712").getAssignedDistributionSet()) + .as("wrong assigned ds").isEqualTo(dsSecond); // confirm cancellation secondAction.setStatus(Status.CANCELED); controllerManagement.addCancelActionStatus( new ActionStatus(secondAction, Status.CANCELED, System.currentTimeMillis()), secondAction); // cancelled success -> back to dsInstalled assertThat(targetManagement.findTargetByControllerID("4712").getAssignedDistributionSet()) - .isEqualTo(dsInstalled); + .as("wrong installed ds").isEqualTo(dsInstalled); assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getUpdateStatus()) - .isEqualTo(TargetUpdateStatus.IN_SYNC); + .as("wrong target info update status").isEqualTo(TargetUpdateStatus.IN_SYNC); } @Test @@ -274,13 +297,13 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { // verify initial status assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getUpdateStatus()) - .isEqualTo(TargetUpdateStatus.UNKNOWN); + .as("wrong target info update status").isEqualTo(TargetUpdateStatus.UNKNOWN); Action assigningAction = assignSet(target, ds); // verify assignment - assertThat(actionRepository.findAll()).hasSize(1); - assertThat(actionStatusRepository.findAll()).hasSize(1); + assertThat(actionRepository.findAll()).as("wrong size of action").hasSize(1); + assertThat(actionStatusRepository.findAll()).as("wrong size of action status").hasSize(1); target = targetManagement.findTargetByControllerID(target.getControllerId()); @@ -288,16 +311,16 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { deploymentManagement.cancelAction(assigningAction, target); assigningAction = deploymentManagement.findActionWithDetails(assigningAction.getId()); - deploymentManagement.forceQuitAction(assigningAction, target); + deploymentManagement.forceQuitAction(assigningAction); assigningAction = deploymentManagement.findActionWithDetails(assigningAction.getId()); // verify - assertThat(assigningAction.getStatus()).isEqualTo(Status.CANCELED); + assertThat(assigningAction.getStatus()).as("wrong size of status").isEqualTo(Status.CANCELED); assertThat(targetManagement.findTargetByControllerID("4712").getAssignedDistributionSet()) - .isEqualTo(dsInstalled); + .as("wrong assigned ds").isEqualTo(dsInstalled); assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getUpdateStatus()) - .isEqualTo(TargetUpdateStatus.IN_SYNC); + .as("wrong target update status").isEqualTo(TargetUpdateStatus.IN_SYNC); } @Test @@ -316,18 +339,17 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { // verify initial status assertThat(targetManagement.findTargetByControllerID("4712").getTargetInfo().getUpdateStatus()) - .isEqualTo(TargetUpdateStatus.UNKNOWN); + .as("wrong update status").isEqualTo(TargetUpdateStatus.UNKNOWN); final Action assigningAction = assignSet(target, ds); // verify assignment - assertThat(actionRepository.findAll()).hasSize(1); - assertThat(actionStatusRepository.findAll()).hasSize(1); + assertThat(actionRepository.findAll()).as("wrong size of action").hasSize(1); + assertThat(actionStatusRepository.findAll()).as("wrong size of action status").hasSize(1); // force quit assignment try { - deploymentManagement.forceQuitAction(assigningAction, - targetManagement.findTargetByControllerID(target.getControllerId())); + deploymentManagement.forceQuitAction(assigningAction); fail("expected ForceQuitActionNotAllowedException"); } catch (final ForceQuitActionNotAllowedException ex) { } @@ -337,11 +359,11 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { deploymentManagement.assignDistributionSet(ds.getId(), new String[] { target.getControllerId() }); assertThat( targetManagement.findTargetByControllerID(target.getControllerId()).getTargetInfo().getUpdateStatus()) - .isEqualTo(TargetUpdateStatus.PENDING); + .as("wrong update status").isEqualTo(TargetUpdateStatus.PENDING); assertThat(targetManagement.findTargetByControllerID(target.getControllerId()).getAssignedDistributionSet()) - .isEqualTo(ds); + .as("wrong assigned ds").isEqualTo(ds); final Action action = actionRepository.findByTargetAndDistributionSet(pageReq, target, ds).getContent().get(0); - assertThat(action).isNotNull(); + assertThat(action).as("action should not be null").isNotNull(); return action; } @@ -349,7 +371,7 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { * test a simple deployment by calling the * {@link TargetRepository#assignDistributionSet(DistributionSet, Iterable)} * and checking the active action and the action history of the targets. - * + * * @throws InterruptedException */ @Test @@ -373,26 +395,30 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { deploymentManagement.assignDistributionSet(ds, savedDeployedTargets); // verify that one Action for each assignDistributionSet - assertThat(actionRepository.findAll(pageReq).getNumberOfElements()).isEqualTo(20); + assertThat(actionRepository.findAll(pageReq).getNumberOfElements()).as("wrong size of actions").isEqualTo(20); final Iterable allFoundTargets = targetManagement.findTargetsAll(pageReq).getContent(); - assertThat(allFoundTargets).containsAll(savedDeployedTargets).containsAll(savedNakedTargets); - assertThat(savedDeployedTargets).doesNotContain(Iterables.toArray(savedNakedTargets, Target.class)); - assertThat(savedNakedTargets).doesNotContain(Iterables.toArray(savedDeployedTargets, Target.class)); + assertThat(allFoundTargets).as("founded targets are wrong").containsAll(savedDeployedTargets) + .containsAll(savedNakedTargets); + assertThat(savedDeployedTargets).as("saved target are wrong") + .doesNotContain(Iterables.toArray(savedNakedTargets, Target.class)); + assertThat(savedNakedTargets).as("saved target are wrong") + .doesNotContain(Iterables.toArray(savedDeployedTargets, Target.class)); for (final Target myt : savedNakedTargets) { final Target t = targetManagement.findTargetByControllerID(myt.getControllerId()); - assertThat(deploymentManagement.findActionsByTarget(t)).isEmpty(); + assertThat(deploymentManagement.findActionsByTarget(t)).as("action should be empty").isEmpty(); } for (final Target myt : savedDeployedTargets) { final Target t = targetManagement.findTargetByControllerID(myt.getControllerId()); final List activeActionsByTarget = deploymentManagement.findActiveActionsByTarget(t); - assertThat(activeActionsByTarget).isNotEmpty(); - assertThat(t.getTargetInfo().getUpdateStatus()).isEqualTo(TargetUpdateStatus.PENDING); + assertThat(activeActionsByTarget).as("action should not be empty").isNotEmpty(); + assertThat(t.getTargetInfo().getUpdateStatus()).as("wrong target update status") + .isEqualTo(TargetUpdateStatus.PENDING); for (final Action ua : activeActionsByTarget) { - assertThat(ua.getDistributionSet()).isEqualTo(ds); + assertThat(ua.getDistributionSet()).as("action has wrong ds").isEqualTo(ds); } } @@ -431,12 +457,13 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { // give some chance to receive events asynchronously Thread.sleep(300); final List events = eventHandlerMock.getEvents(1, TimeUnit.MILLISECONDS); - assertThat(events).isEmpty(); + assertThat(events).as("events should be empty").isEmpty(); final EventHandlerMock eventHandlerMockAfterCompletionOfDs = new EventHandlerMock(10); eventBus.register(eventHandlerMockAfterCompletionOfDs); - assertThat(deploymentManagement.assignDistributionSet(nowComplete, targets).getAssigned()).isEqualTo(10); + assertThat(deploymentManagement.assignDistributionSet(nowComplete, targets).getAssigned()) + .as("assign ds doesn't work").isEqualTo(10); assertTargetAssignDistributionSetEvents(targets, nowComplete, eventHandlerMockAfterCompletionOfDs.getEvents(10, TimeUnit.SECONDS)); } @@ -476,7 +503,8 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { // retrieving all Actions created by the assignDistributionSet call final Page page = actionRepository.findAll(pageReq); // and verify the number - assertThat(page.getTotalElements()).isEqualTo(noOfDeployedTargets * noOfDistributionSets); + assertThat(page.getTotalElements()).as("wrong size of actions") + .isEqualTo(noOfDeployedTargets * noOfDistributionSets); // only records retrieved from the DB can be evaluated to be sure that // all fields are @@ -487,17 +515,20 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { final Iterable undeployedTargetsFromDB = targetRepository.findAll(undeployedTargetIDs); // test that number of Targets - assertThat(allFoundTargets.spliterator().getExactSizeIfKnown()) + assertThat(allFoundTargets.spliterator().getExactSizeIfKnown()).as("number of target is wrong") .isEqualTo(deployedTargetsFromDB.spliterator().getExactSizeIfKnown() + undeployedTargetsFromDB.spliterator().getExactSizeIfKnown()); - assertThat(deployedTargetsFromDB.spliterator().getExactSizeIfKnown()).isEqualTo(noOfDeployedTargets); - assertThat(undeployedTargetsFromDB.spliterator().getExactSizeIfKnown()).isEqualTo(noOfUndeployedTargets); + assertThat(deployedTargetsFromDB.spliterator().getExactSizeIfKnown()).as("number of target is wrong") + .isEqualTo(noOfDeployedTargets); + assertThat(undeployedTargetsFromDB.spliterator().getExactSizeIfKnown()).as("number of target is wrong") + .isEqualTo(noOfUndeployedTargets); // test the content of different lists - assertThat(allFoundTargets).containsAll(deployedTargetsFromDB).containsAll(undeployedTargetsFromDB); - assertThat(deployedTargetsFromDB).containsAll(savedDeployedTargets) + assertThat(allFoundTargets).as("content of founded target is wrong").containsAll(deployedTargetsFromDB) + .containsAll(undeployedTargetsFromDB); + assertThat(deployedTargetsFromDB).as("content of deployed target is wrong").containsAll(savedDeployedTargets) .doesNotContain(Iterables.toArray(undeployedTargetsFromDB, Target.class)); - assertThat(undeployedTargetsFromDB).containsAll(savedNakedTargets) + assertThat(undeployedTargetsFromDB).as("content of undeployed target is wrong").containsAll(savedNakedTargets) .doesNotContain(Iterables.toArray(deployedTargetsFromDB, Target.class)); // For each of the 4 targets 1 distribution sets gets assigned @@ -525,42 +556,42 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { final DistributionSet dsC = deployResWithDsC.getDistributionSets().get(0); // retrieving the UpdateActions created by the assignments - final Action updActA = actionRepository.findByDistributionSet(pageRequest, dsA).getContent().get(0); - final Action updActB = actionRepository.findByDistributionSet(pageRequest, dsB).getContent().get(0); - final Action updActC = actionRepository.findByDistributionSet(pageRequest, dsC).getContent().get(0); + actionRepository.findByDistributionSet(pageRequest, dsA).getContent().get(0); + actionRepository.findByDistributionSet(pageRequest, dsB).getContent().get(0); + actionRepository.findByDistributionSet(pageRequest, dsC).getContent().get(0); // verifying the correctness of the assignments for (final Target t : deployResWithDsA.getDeployedTargets()) { - assertThat(t.getAssignedDistributionSet().getId()).isEqualTo(dsA.getId()); + assertThat(t.getAssignedDistributionSet().getId()).as("assignment is not correct").isEqualTo(dsA.getId()); assertThat(targetManagement.findTargetByControllerID(t.getControllerId()).getTargetInfo() - .getInstalledDistributionSet()).isNull(); + .getInstalledDistributionSet()).as("installed ds should be null").isNull(); } for (final Target t : deployResWithDsB.getDeployedTargets()) { - assertThat(t.getAssignedDistributionSet().getId()).isEqualTo(dsB.getId()); + assertThat(t.getAssignedDistributionSet().getId()).as("assigned ds is wrong").isEqualTo(dsB.getId()); assertThat(targetManagement.findTargetByControllerID(t.getControllerId()).getTargetInfo() - .getInstalledDistributionSet()).isNull(); + .getInstalledDistributionSet()).as("installed ds should be null").isNull(); } for (final Target t : deployResWithDsC.getDeployedTargets()) { assertThat(t.getAssignedDistributionSet().getId()).isEqualTo(dsC.getId()); assertThat(targetManagement.findTargetByControllerID(t.getControllerId()).getTargetInfo() - .getInstalledDistributionSet()).isNull(); + .getInstalledDistributionSet()).as("installed ds should not be null").isNull(); assertThat(targetManagement.findTargetByControllerID(t.getControllerId()).getTargetInfo().getUpdateStatus()) - .isEqualTo(TargetUpdateStatus.PENDING); + .as("wrong target info update status").isEqualTo(TargetUpdateStatus.PENDING); } final List updatedTsDsA = sendUpdateActionStatusToTargets(dsA, deployResWithDsA.getDeployedTargets(), Status.FINISHED, new String[] { "alles gut" }); // verify, that dsA is deployed correctly - assertThat(updatedTsDsA).isEqualTo(deployResWithDsA.getDeployedTargets()); + assertThat(updatedTsDsA).as("ds is not deployed correctly").isEqualTo(deployResWithDsA.getDeployedTargets()); for (final Target t_ : updatedTsDsA) { final Target t = targetManagement.findTargetByControllerID(t_.getControllerId()); - assertThat(t.getAssignedDistributionSet()).isEqualTo(dsA); + assertThat(t.getAssignedDistributionSet()).as("assigned ds is wrong").isEqualTo(dsA); assertThat(targetManagement.findTargetByControllerID(t.getControllerId()).getTargetInfo() - .getInstalledDistributionSet()).isEqualTo(dsA); + .getInstalledDistributionSet()).as("installed ds is wrong").isEqualTo(dsA); assertThat(targetManagement.findTargetByControllerID(t.getControllerId()).getTargetInfo().getUpdateStatus()) - .isEqualTo(TargetUpdateStatus.IN_SYNC); - assertThat(deploymentManagement.findActiveActionsByTarget(t)).hasSize(0); + .as("wrong target info update status").isEqualTo(TargetUpdateStatus.IN_SYNC); + assertThat(deploymentManagement.findActiveActionsByTarget(t)).as("no actions should be active").hasSize(0); } // deploy dsA to the target which already have dsB deployed -> must @@ -569,18 +600,18 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { // UpdateAction for dsA final Iterable deployed2DS = deploymentManagement .assignDistributionSet(dsA, deployResWithDsB.getDeployedTargets()).getAssignedTargets(); - final Action updActA2 = actionRepository.findByDistributionSet(pageRequest, dsA).getContent().get(1); + actionRepository.findByDistributionSet(pageRequest, dsA).getContent().get(1); - assertThat(deployed2DS).containsAll(deployResWithDsB.getDeployedTargets()); - assertThat(deployed2DS).hasSameSizeAs(deployResWithDsB.getDeployedTargets()); + assertThat(deployed2DS).as("deployed ds is wrong").containsAll(deployResWithDsB.getDeployedTargets()); + assertThat(deployed2DS).as("deployed ds is wrong").hasSameSizeAs(deployResWithDsB.getDeployedTargets()); for (final Target t_ : deployed2DS) { final Target t = targetManagement.findTargetByControllerID(t_.getControllerId()); - assertThat(t.getAssignedDistributionSet()).isEqualTo(dsA); + assertThat(t.getAssignedDistributionSet()).as("assigned ds is wrong").isEqualTo(dsA); assertThat(targetManagement.findTargetByControllerID(t.getControllerId()).getTargetInfo() - .getInstalledDistributionSet()).isNull(); + .getInstalledDistributionSet()).as("installed ds should be null").isNull(); assertThat(targetManagement.findTargetByControllerID(t.getControllerId()).getTargetInfo().getUpdateStatus()) - .isEqualTo(TargetUpdateStatus.PENDING); + .as("wrong target info update status").isEqualTo(TargetUpdateStatus.PENDING); } } @@ -613,22 +644,22 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { distributionSetManagement.deleteDistributionSet(dsA.getId()); dsA = distributionSetManagement.findDistributionSetById(dsA.getId()); - assertThat(dsA).isNull(); + assertThat(dsA).as("ds should be null").isNull(); // // verify that the ds is not physically deleted for (final DistributionSet ds : deploymentResult.getDistributionSets()) { distributionSetManagement.deleteDistributionSet(ds.getId()); final DistributionSet foundDS = distributionSetManagement.findDistributionSetById(ds.getId()); - assertThat(foundDS).isNotNull(); - assertThat(foundDS.isDeleted()).isTrue(); + assertThat(foundDS).as("founded should not be null").isNotNull(); + assertThat(foundDS.isDeleted()).as("founded ds should be deleted").isTrue(); } // verify that deleted attribute is used correctly List allFoundDS = distributionSetManagement.findDistributionSetsAll(pageReq, false, true) .getContent(); - assertThat(allFoundDS.size()).isEqualTo(0); + assertThat(allFoundDS.size()).as("no ds should be founded").isEqualTo(0); allFoundDS = distributionSetManagement.findDistributionSetsAll(pageRequest, true, true).getContent(); - assertThat(allFoundDS).hasSize(noOfDistributionSets); + assertThat(allFoundDS).as("wrong size of founded ds").hasSize(noOfDistributionSets); for (final DistributionSet ds : deploymentResult.getDistributionSets()) { sendUpdateActionStatusToTargets(ds, deploymentResult.getDeployedTargets(), Status.FINISHED, @@ -642,9 +673,9 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { // successfully and no activeAction is referring to created distribution // sets allFoundDS = distributionSetManagement.findDistributionSetsAll(pageRequest, false, true).getContent(); - assertThat(allFoundDS.size()).isEqualTo(0); + assertThat(allFoundDS.size()).as("no ds should be founded").isEqualTo(0); allFoundDS = distributionSetManagement.findDistributionSetsAll(pageRequest, true, true).getContent(); - assertThat(allFoundDS).hasSize(noOfDistributionSets); + assertThat(allFoundDS).as("size of founded ds is wrong").hasSize(noOfDistributionSets); } @@ -668,15 +699,15 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { "blabla alles gut"); } - assertThat(targetManagement.countTargetsAll()).isNotZero(); - assertThat(actionStatusRepository.count()).isNotZero(); + assertThat(targetManagement.countTargetsAll()).as("size of targets is wrong").isNotZero(); + assertThat(actionStatusRepository.count()).as("size of action status is wrong").isNotZero(); targetManagement .deleteTargets(deploymentResult.getUndeployedTargetIDs().toArray(new Long[noOfUndeployedTargets])); targetManagement.deleteTargets(deploymentResult.getDeployedTargetIDs().toArray(new Long[noOfDeployedTargets])); - assertThat(targetManagement.countTargetsAll()).isZero(); - assertThat(actionStatusRepository.count()).isZero(); + assertThat(targetManagement.countTargetsAll()).as("size of targets should be zero").isZero(); + assertThat(actionStatusRepository.count()).as("size of action status is wrong").isZero(); } private List sendUpdateActionStatusToTargets(final DistributionSet dsA, final Iterable targs, @@ -727,16 +758,20 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { // checking the revisions of the created entities // verifying that the revision of the object and the revision within the // DB has not changed - assertThat(dsA.getOptLockRevision()).isEqualTo( + assertThat(dsA.getOptLockRevision()).as("lock revision is wrong").isEqualTo( distributionSetManagement.findDistributionSetByIdWithDetails(dsA.getId()).getOptLockRevision()); // verifying that the assignment is correct - assertEquals(1, deploymentManagement.findActiveActionsByTarget(targ).size()); - assertEquals(1, deploymentManagement.findActionsByTarget(targ).size()); - assertEquals(TargetUpdateStatus.PENDING, targ.getTargetInfo().getUpdateStatus()); - assertEquals(dsA, targ.getAssignedDistributionSet()); - assertEquals(dsA, deploymentManagement.findActiveActionsByTarget(targ).get(0).getDistributionSet()); - assertNull(targ.getTargetInfo().getInstalledDistributionSet()); + assertThat(deploymentManagement.findActiveActionsByTarget(targ).size()).as("Active target actions are wrong") + .isEqualTo(1); + assertThat(deploymentManagement.findActionsByTarget(targ).size()).as("Target actions are wrong").isEqualTo(1); + assertThat(targ.getTargetInfo().getUpdateStatus()).as("UpdateStatus of target is wrong") + .isEqualTo(TargetUpdateStatus.PENDING); + assertThat(targ.getAssignedDistributionSet()).as("Assigned distribution set of target is wrong").isEqualTo(dsA); + assertThat(deploymentManagement.findActiveActionsByTarget(targ).get(0).getDistributionSet()) + .as("Distribution set of actionn is wrong").isEqualTo(dsA); + assertThat(deploymentManagement.findActiveActionsByTarget(targ).get(0).getDistributionSet()) + .as("Installed distribution set of action should be null").isNotNull(); final Page updAct = actionRepository.findByDistributionSet(pageReq, dsA); final Action action = updAct.getContent().get(0); @@ -746,29 +781,28 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { targ = targetManagement.findTargetByControllerID(targ.getControllerId()); - assertEquals(0, deploymentManagement.findActiveActionsByTarget(targ).size()); - // try { - assertEquals(1, deploymentManagement.findInActiveActionsByTarget(targ).size()); - // } - // catch( final LazyInitializationException ex ) { - // - // } - assertEquals(TargetUpdateStatus.IN_SYNC, targ.getTargetInfo().getUpdateStatus()); - assertEquals(dsA, targ.getAssignedDistributionSet()); - assertEquals(dsA, targ.getTargetInfo().getInstalledDistributionSet()); + assertEquals("active target actions are wrong", 0, deploymentManagement.findActiveActionsByTarget(targ).size()); + assertEquals("active actions are wrong", 1, deploymentManagement.findInActiveActionsByTarget(targ).size()); + + assertEquals("tagret update status is not correct", TargetUpdateStatus.IN_SYNC, + targ.getTargetInfo().getUpdateStatus()); + assertEquals("wrong assigned ds", dsA, targ.getAssignedDistributionSet()); + assertEquals("wrong installed ds", dsA, targ.getTargetInfo().getInstalledDistributionSet()); targs = deploymentManagement.assignDistributionSet(dsB.getId(), new String[] { "target-id-A" }) .getAssignedTargets(); targ = targs.iterator().next(); - assertEquals(1, deploymentManagement.findActiveActionsByTarget(targ).size()); - assertEquals(TargetUpdateStatus.PENDING, + assertEquals("active actions are wrong", 1, deploymentManagement.findActiveActionsByTarget(targ).size()); + assertEquals("target status is wrong", TargetUpdateStatus.PENDING, targetManagement.findTargetByControllerID(targ.getControllerId()).getTargetInfo().getUpdateStatus()); - assertEquals(dsB, targ.getAssignedDistributionSet()); - assertEquals(dsA.getId(), targetManagement.findTargetByControllerIDWithDetails(targ.getControllerId()) - .getTargetInfo().getInstalledDistributionSet().getId()); - assertEquals(dsB, deploymentManagement.findActiveActionsByTarget(targ).get(0).getDistributionSet()); + assertEquals("wrong assigned ds", dsB, targ.getAssignedDistributionSet()); + assertEquals("Installed ds is wrong", dsA.getId(), + targetManagement.findTargetByControllerIDWithDetails(targ.getControllerId()).getTargetInfo() + .getInstalledDistributionSet().getId()); + assertEquals("Active ds is wrong", dsB, + deploymentManagement.findActiveActionsByTarget(targ).get(0).getDistributionSet()); } @@ -778,12 +812,11 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { public void checkThatDsRevisionsIsNotChangedWithTargetAssignment() { final DistributionSet dsA = TestDataUtil.generateDistributionSet("a", softwareManagement, distributionSetManagement); - final DistributionSet dsB = TestDataUtil.generateDistributionSet("b", softwareManagement, - distributionSetManagement); + TestDataUtil.generateDistributionSet("b", softwareManagement, distributionSetManagement); Target targ = targetManagement .createTarget(TestDataUtil.buildTargetFixture("target-id-A", "first description")); - assertThat(dsA.getOptLockRevision()).isEqualTo( + assertThat(dsA.getOptLockRevision()).as("lock revision is wrong").isEqualTo( distributionSetManagement.findDistributionSetByIdWithDetails(dsA.getId()).getOptLockRevision()); final List targs = new ArrayList(); @@ -791,7 +824,7 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { final Iterable savedTargs = deploymentManagement.assignDistributionSet(dsA, targs).getAssignedTargets(); targ = savedTargs.iterator().next(); - assertThat(dsA.getOptLockRevision()).isEqualTo( + assertThat(dsA.getOptLockRevision()).as("lock revision is wrong").isEqualTo( distributionSetManagement.findDistributionSetByIdWithDetails(dsA.getId()).getOptLockRevision()); } @@ -805,17 +838,17 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { // assign ds to create an action final DistributionSetAssignmentResult assignDistributionSet = deploymentManagement .assignDistributionSet(ds.getId(), ActionType.SOFT, Action.NO_FORCE_TIME, target.getControllerId()); - final Action action = assignDistributionSet.getActions().get(0); + final Action action = deploymentManagement.findActionWithDetails(assignDistributionSet.getActions().get(0)); // verify preparation Action findAction = deploymentManagement.findAction(action.getId()); - assertThat(findAction.getActionType()).isEqualTo(ActionType.SOFT); + assertThat(findAction.getActionType()).as("action type is wrong").isEqualTo(ActionType.SOFT); // test deploymentManagement.forceTargetAction(action.getId()); // verify test findAction = deploymentManagement.findAction(action.getId()); - assertThat(findAction.getActionType()).isEqualTo(ActionType.FORCED); + assertThat(findAction.getActionType()).as("action type is wrong").isEqualTo(ActionType.FORCED); } @Test @@ -828,18 +861,18 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { // assign ds to create an action final DistributionSetAssignmentResult assignDistributionSet = deploymentManagement .assignDistributionSet(ds.getId(), ActionType.FORCED, Action.NO_FORCE_TIME, target.getControllerId()); - final Action action = assignDistributionSet.getActions().get(0); + final Action action = deploymentManagement.findActionWithDetails(assignDistributionSet.getActions().get(0)); // verify perparation Action findAction = deploymentManagement.findAction(action.getId()); - assertThat(findAction.getActionType()).isEqualTo(ActionType.FORCED); + assertThat(findAction.getActionType()).as("action type is wrong").isEqualTo(ActionType.FORCED); // test final Action forceTargetAction = deploymentManagement.forceTargetAction(action.getId()); // verify test - assertThat(forceTargetAction.getActionType()).isEqualTo(ActionType.FORCED); + assertThat(forceTargetAction.getActionType()).as("action type is wrong").isEqualTo(ActionType.FORCED); findAction = deploymentManagement.findAction(action.getId()); - assertThat(findAction.getActionType()).isEqualTo(ActionType.FORCED); + assertThat(findAction.getActionType()).as("action type is wrong").isEqualTo(ActionType.FORCED); } /** @@ -848,7 +881,7 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { *

* All created distribution sets are assigned to all targets of the * target list deployedTargets. - * + * * @param undeployedTargetPrefix * prefix to be used as target controller prefix * @param noOfUndeployedTargets @@ -901,14 +934,14 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { if (event.getControllerId().equals(myt.getControllerId())) { found = true; final List activeActionsByTarget = deploymentManagement.findActiveActionsByTarget(myt); - assertThat(activeActionsByTarget).isNotEmpty(); - assertThat(event.getActionId()).isEqualTo(activeActionsByTarget.get(0).getId()) - .as("Action id in database and event do not match"); - assertThat(event.getSoftwareModules()) + assertThat(activeActionsByTarget).as("size of active actions for target is wrong").isNotEmpty(); + assertThat(event.getActionId()).as("Action id in database and event do not match") + .isEqualTo(activeActionsByTarget.get(0).getId()); + assertThat(event.getSoftwareModules()).as("softwaremodule size is not correct") .containsOnly(ds.getModules().toArray(new SoftwareModule[ds.getModules().size()])); } } - assertThat(found).isTrue().as("No event found for controller " + myt.getControllerId()); + assertThat(found).as("No event found for controller " + myt.getControllerId()).isTrue(); } } @@ -927,42 +960,19 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { private final List deployedTargets = new ArrayList(); private final List distributionSets = new ArrayList(); - private final String undeployedTargetPrefix; - private final String deployedTargetPrefix; - private final String distributionSetPrefix; - public DeploymentResult(final Iterable deployedTs, final Iterable undeployedTs, final Iterable dss, final String deployedTargetPrefix, final String undeployedTargetPrefix, final String distributionSetPrefix) { - this.undeployedTargetPrefix = undeployedTargetPrefix; - this.deployedTargetPrefix = deployedTargetPrefix; - this.distributionSetPrefix = distributionSetPrefix; - Iterables.addAll(deployedTargets, deployedTs); Iterables.addAll(undeployedTargets, undeployedTs); Iterables.addAll(distributionSets, dss); - deployedTargets.forEach(new Consumer() { - @Override - public void accept(final Target t) { - deployedTargetIDs.add(t.getId()); - } - }); + deployedTargets.forEach(t -> deployedTargetIDs.add(t.getId())); - undeployedTargets.forEach(new Consumer() { - @Override - public void accept(final Target t) { - undeployedTargetIDs.add(t.getId()); - } - }); + undeployedTargets.forEach(t -> undeployedTargetIDs.add(t.getId())); - distributionSets.forEach(new Consumer() { - @Override - public void accept(final DistributionSet ds) { - distributionSetIDs.add(ds.getId()); - } - }); + distributionSets.forEach(ds -> distributionSetIDs.add(ds.getId())); } @@ -1001,27 +1011,6 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { return deployedTargets; } - /** - * @return the noOfUndeployedTargets - */ - public int getNoOfUndeployedTargets() { - return undeployedTargetIDs.size(); - } - - /** - * @return the noOfDeployedTargets - */ - public int getNoOfDeployedTargets() { - return deployedTargetIDs.size(); - } - - /** - * @return the noOfDistributionSets - */ - public int getNoOfDistributionSets() { - return distributionSets.size(); - } - /** * @return the undeployedTargetIDs */ @@ -1029,26 +1018,6 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { return undeployedTargetIDs; } - /** - * @return the undeployedTargetPrefix - */ - public String getUndeployedTargetPrefix() { - return undeployedTargetPrefix; - } - - /** - * @return the deployedTargetPrefix - */ - public String getDeployedTargetPrefix() { - return deployedTargetPrefix; - } - - /** - * @return the distributionSetPrefix - */ - public String getDistributionSetPrefix() { - return distributionSetPrefix; - } } private static class EventHandlerMock { @@ -1073,9 +1042,9 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { latch.await(timeout, unit); final List handledEvents = new LinkedList( events); - assertThat(handledEvents).hasSize(expectedNumberOfEvents) - .as("Did not receive the expected amount of events (" + expectedNumberOfEvents - + ") within timeout. Received events are " + handledEvents); + assertThat(handledEvents).as("Did not receive the expected amount of events (" + expectedNumberOfEvents + + ") within timeout. Received events are " + handledEvents).hasSize(expectedNumberOfEvents); + return handledEvents; } } @@ -1101,9 +1070,8 @@ public class DeploymentManagementTest extends AbstractIntegrationTest { throws InterruptedException { latch.await(timeout, unit); final List handledEvents = new LinkedList(events); - assertThat(handledEvents).hasSize(expectedNumberOfEvents) - .as("Did not receive the expected amount of events (" + expectedNumberOfEvents - + ") within timeout. Received events are " + handledEvents); + assertThat(handledEvents).as("Did not receive the expected amount of events (" + expectedNumberOfEvents + + ") within timeout. Received events are " + handledEvents).hasSize(expectedNumberOfEvents); return handledEvents; } } diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/ReportManagementTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/ReportManagementTest.java index 5c9c86aa7..0184a9d26 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/ReportManagementTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/ReportManagementTest.java @@ -18,7 +18,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.List; -import java.util.concurrent.Callable; import java.util.stream.Collectors; import org.eclipse.hawkbit.AbstractIntegrationTest; @@ -96,10 +95,11 @@ public class ReportManagementTest extends AbstractIntegrationTest { // +1 because we go back #maxMonthBackAmountReportTargets but in the // report the current month // is included for sure, so from this month we go back - assertThat(targetsCreatedOverPeriod.getData()).hasSize(maxMonthBackAmountReportTargets + 1); + assertThat(targetsCreatedOverPeriod.getData()).as("created over period has wrong size") + .hasSize(maxMonthBackAmountReportTargets + 1); for (final DataReportSeriesItem reportItem : targetsCreatedOverPeriod.getData()) { // only one target is created for each month - assertThat(reportItem.getData().intValue()).isEqualTo(1); + assertThat(reportItem.getData().intValue()).as("Target for each month").isEqualTo(1); } // check cache evict @@ -109,7 +109,7 @@ public class ReportManagementTest extends AbstractIntegrationTest { } targetsCreatedOverPeriod = reportManagement.targetsCreatedOverPeriod(DateTypes.perMonth(), from, to); for (final DataReportSeriesItem reportItem : targetsCreatedOverPeriod.getData()) { - assertThat(reportItem.getData().intValue()).isEqualTo(2); + assertThat(reportItem.getData().intValue()).as("Target for each month").isEqualTo(2); } } @@ -138,7 +138,8 @@ public class ReportManagementTest extends AbstractIntegrationTest { final Target createTarget = targetManagement.createTarget(new Target("t" + month)); final DistributionSetAssignmentResult result = deploymentManagement.assignDistributionSet(distributionSet, Lists.newArrayList(createTarget)); - controllerManagament.registerRetrieved(result.getActions().get(0), + controllerManagament.registerRetrieved( + deploymentManagement.findActionWithDetails(result.getActions().get(0)), "Controller retrieved update action and should start now the download."); } DataReportSeries feedbackReceivedOverTime = reportManagement @@ -146,10 +147,11 @@ public class ReportManagementTest extends AbstractIntegrationTest { // +1 because we go back #maxMonthBackAmountReportTargets but in the // report the current month // is included for sure, so from this month we go back - assertThat(feedbackReceivedOverTime.getData()).hasSize(maxMonthBackAmountReportTargets + 1); + assertThat(feedbackReceivedOverTime.getData()).as("feedback receiver has wrong data size") + .hasSize(maxMonthBackAmountReportTargets + 1); for (final DataReportSeriesItem reportItem : feedbackReceivedOverTime.getData()) { // only one target feedback is created for each month - assertThat(reportItem.getData().intValue()).isEqualTo(1); + assertThat(reportItem.getData().intValue()).as("data size is wrong").isEqualTo(1); } // check cache evict @@ -158,12 +160,13 @@ public class ReportManagementTest extends AbstractIntegrationTest { final Target createTarget = targetManagement.createTarget(new Target("t2" + month)); final DistributionSetAssignmentResult result = deploymentManagement.assignDistributionSet(distributionSet, Lists.newArrayList(createTarget)); - controllerManagament.registerRetrieved(result.getActions().get(0), + controllerManagament.registerRetrieved( + deploymentManagement.findActionWithDetails(result.getActions().get(0)), "Controller retrieved update action and should start now the download."); } feedbackReceivedOverTime = reportManagement.feedbackReceivedOverTime(DateTypes.perMonth(), from, to); for (final DataReportSeriesItem reportItem : feedbackReceivedOverTime.getData()) { - assertThat(reportItem.getData().intValue()).isEqualTo(2); + assertThat(reportItem.getData().intValue()).as("report item has wrong data size").isEqualTo(2); } } @@ -221,21 +224,26 @@ public class ReportManagementTest extends AbstractIntegrationTest { .getData()[0]; if (dataReportSeriesItem.getType().equals("ds1")) { // total count of three because ds1 has two different versions - assertThat(dataReportSeriesItem.getData()).isEqualTo(3L); + assertThat(dataReportSeriesItem.getData()).as("Version/Item type of DistributionSet 1 in statistics") + .isEqualTo(3L); final DataReportSeriesItem[] outerData = innerOuterDataReportSeries.getOuterSeries().getData(); assertThat(Arrays.stream(outerData).map(DataReportSeriesItem::getType).collect(Collectors.toList())) - .contains("0.0.0", "0.0.1"); + .as("versio item contains wrong version").contains("0.0.0", "0.0.1"); } else if (dataReportSeriesItem.getType().equals("ds2")) { - assertThat(dataReportSeriesItem.getData()).isEqualTo(1L); + assertThat(dataReportSeriesItem.getData()).as("Version/Item type of DistributionSet 2 in statistics") + .isEqualTo(1L); final DataReportSeriesItem[] outerData = innerOuterDataReportSeries.getOuterSeries().getData(); - assertThat(outerData).hasSize(1); - assertThat(outerData[0].getType()).isEqualTo("0.0.2"); - + assertThat(outerData).as("Version/Item type has wrong size").hasSize(1); + assertThat(outerData[0].getType()).as("Version/Item type of DistributionSet 2 in statistics") + .isEqualTo("0.0.2"); } else if (dataReportSeriesItem.getType().equals("ds3")) { - assertThat(dataReportSeriesItem.getData()).isEqualTo(0L); + + assertThat(dataReportSeriesItem.getData()).as("Version/Item type of DistributionSet 3 in statistics") + .isEqualTo(0L); final DataReportSeriesItem[] outerData = innerOuterDataReportSeries.getOuterSeries().getData(); - assertThat(outerData).hasSize(1); - assertThat(outerData[0].getType()).isEqualTo("0.0.3"); + assertThat(outerData).as("Version/Item type has wrong size").hasSize(1); + assertThat(outerData[0].getType()).as("Version/Item type of DistributionSet 3 in statistics") + .isEqualTo("0.0.3"); } else { fail("no assertion count for distribution set " + dataReportSeriesItem.getType()); } @@ -251,8 +259,7 @@ public class ReportManagementTest extends AbstractIntegrationTest { final DataReportSeriesItem dataReportSeriesItem = innerOuterDataReportSeries.getInnerSeries() .getData()[0]; if (dataReportSeriesItem.getType().equals("ds1")) { - assertThat(dataReportSeriesItem.getData()).isEqualTo(4L); - + assertThat(dataReportSeriesItem.getData()).as("Data report item number").isEqualTo(4L); } } } @@ -278,19 +285,23 @@ public class ReportManagementTest extends AbstractIntegrationTest { switch (reportItem.getType()) { case ERROR: - assertThat(reportItem.getData()).isEqualTo(knownErrorCount); + assertThat(reportItem.getData()).as("ERROR count for targets in statistics").isEqualTo(knownErrorCount); break; case IN_SYNC: - assertThat(reportItem.getData()).isEqualTo(knownSyncCount); + assertThat(reportItem.getData()).as("IN_SYNC count for targets in statistics") + .isEqualTo(knownSyncCount); break; case PENDING: - assertThat(reportItem.getData()).isEqualTo(knownPendingCount); + assertThat(reportItem.getData()).as("PENDING count for targets in statistics") + .isEqualTo(knownPendingCount); break; case REGISTERED: - assertThat(reportItem.getData()).isEqualTo(knownRegCount); + assertThat(reportItem.getData()).as("REGISTERED count for targets in statistics") + .isEqualTo(knownRegCount); break; case UNKNOWN: - assertThat(reportItem.getData()).isEqualTo(knownUnknownCount); + assertThat(reportItem.getData()).as("UNKNOWN count for targets in statistics") + .isEqualTo(knownUnknownCount); break; default: fail("missing case for unknown target update status " + reportItem.getType()); @@ -309,19 +320,24 @@ public class ReportManagementTest extends AbstractIntegrationTest { switch (reportItem.getType()) { case ERROR: - assertThat(reportItem.getData()).isEqualTo(knownErrorCount * 2); + assertThat(reportItem.getData()).as("ERROR count for targets in statistics") + .isEqualTo(knownErrorCount * 2); break; case IN_SYNC: - assertThat(reportItem.getData()).isEqualTo(knownSyncCount * 2); + assertThat(reportItem.getData()).as("IN_SYNC count for targets in statistics") + .isEqualTo(knownSyncCount * 2); break; case PENDING: - assertThat(reportItem.getData()).isEqualTo(knownPendingCount * 2); + assertThat(reportItem.getData()).as("PENDING count for targets in statistics") + .isEqualTo(knownPendingCount * 2); break; case REGISTERED: - assertThat(reportItem.getData()).isEqualTo(knownRegCount * 2); + assertThat(reportItem.getData()).as("REGISTERED count for targets in statistics") + .isEqualTo(knownRegCount * 2); break; case UNKNOWN: - assertThat(reportItem.getData()).isEqualTo(knownUnknownCount * 2); + assertThat(reportItem.getData()).as("UNKNOWN count for targets in statistics") + .isEqualTo(knownUnknownCount * 2); break; default: fail("missing case for unknown target update status " + reportItem.getType()); @@ -373,22 +389,30 @@ public class ReportManagementTest extends AbstractIntegrationTest { final DataReportSeriesItem dataReportSeriesItem = innerOuterDataReportSeries.getInnerSeries() .getData()[0]; if (dataReportSeriesItem.getType().equals("ds1")) { + // total count of three because ds1 has two different versions - assertThat(dataReportSeriesItem.getData()).isEqualTo(3L); + assertThat(dataReportSeriesItem.getData()).as("Total count of DistributionSet 1 in statistics") + .isEqualTo(3L); + final DataReportSeriesItem[] outerData = innerOuterDataReportSeries.getOuterSeries().getData(); assertThat(Arrays.stream(outerData).map(DataReportSeriesItem::getType).collect(Collectors.toList())) - .contains("0.0.0", "0.0.1"); + .as("Out series contains wrong version").contains("0.0.0", "0.0.1"); + } else if (dataReportSeriesItem.getType().equals("ds2")) { - assertThat(dataReportSeriesItem.getData()).isEqualTo(1L); + assertThat(dataReportSeriesItem.getData()).as("Total count of DistributionSet 2 in statistics") + .isEqualTo(1L); final DataReportSeriesItem[] outerData = innerOuterDataReportSeries.getOuterSeries().getData(); - assertThat(outerData).hasSize(1); - assertThat(outerData[0].getType()).isEqualTo("0.0.2"); + assertThat(outerData).as("out series has wrong size").hasSize(1); + assertThat(outerData[0].getType()).as("Version/Item type of DistributionSet 2 in statistics") + .isEqualTo("0.0.2"); } else if (dataReportSeriesItem.getType().equals("ds3")) { - assertThat(dataReportSeriesItem.getData()).isEqualTo(0L); + assertThat(dataReportSeriesItem.getData()).as("Total count of DistributionSet 3 in statistics") + .isEqualTo(0L); final DataReportSeriesItem[] outerData = innerOuterDataReportSeries.getOuterSeries().getData(); - assertThat(outerData).hasSize(1); - assertThat(outerData[0].getType()).isEqualTo("0.0.3"); + assertThat(outerData).as("out series has wrong size").hasSize(1); + assertThat(outerData[0].getType()).as("Version/Item type of DistributionSet 3 in statistics") + .isEqualTo("0.0.3"); } else { fail("no assertion count for distribution set " + dataReportSeriesItem.getType()); } @@ -402,7 +426,8 @@ public class ReportManagementTest extends AbstractIntegrationTest { final DataReportSeriesItem dataReportSeriesItem = innerOuterDataReportSeries.getInnerSeries() .getData()[0]; if (dataReportSeriesItem.getType().equals("ds1")) { - assertThat(dataReportSeriesItem.getData()).isEqualTo(4L); + assertThat(dataReportSeriesItem.getData()).as("Total count of DistributionSet 1 in statistics") + .isEqualTo(4L); } } } @@ -435,29 +460,24 @@ public class ReportManagementTest extends AbstractIntegrationTest { DataReportSeries targetsNotLastPoll = reportManagement.targetsLastPoll(); DataReportSeriesItem[] data = targetsNotLastPoll.getData(); - // for( final DataReportSeriesItem dataReportSeriesItem : - // data ) { - // System.out.println( dataReportSeriesItem.getData() ); - // } - // --- Verfiy --- // verify hour - assertThat(data[0].getType()).isEqualTo(SeriesTime.HOUR); - assertThat(data[0].getData()).isEqualTo((long) knownTargetsPollLastHour); + assertThat(data[0].getType()).as("Series time").isEqualTo(SeriesTime.HOUR); + assertThat(data[0].getData()).as("Targets poll last hour").isEqualTo((long) knownTargetsPollLastHour); // verify day - assertThat(data[1].getType()).isEqualTo(SeriesTime.DAY); - assertThat(data[1].getData()).isEqualTo((long) knownTargetsPollLastDay); + assertThat(data[1].getType()).as("Series time").isEqualTo(SeriesTime.DAY); + assertThat(data[1].getData()).as("Targets poll last day").isEqualTo((long) knownTargetsPollLastDay); // verify week - assertThat(data[2].getType()).isEqualTo(SeriesTime.WEEK); - assertThat(data[2].getData()).isEqualTo((long) knownTargetsPollLastWeek); + assertThat(data[2].getType()).as("Series time").isEqualTo(SeriesTime.WEEK); + assertThat(data[2].getData()).as("Targets poll last week").isEqualTo((long) knownTargetsPollLastWeek); // test cache evict createTargets("hourPoll2", knownTargetsPollLastHour, now.minusMinutes(59)); targetsNotLastPoll = reportManagement.targetsLastPoll(); data = targetsNotLastPoll.getData(); - assertThat(data[0].getType()).isEqualTo(SeriesTime.HOUR); - assertThat(data[0].getData()).isEqualTo((long) knownTargetsPollLastHour * 2); + assertThat(data[0].getType()).as("Series time").isEqualTo(SeriesTime.HOUR); + assertThat(data[0].getData()).as("Targets poll last hour").isEqualTo((long) knownTargetsPollLastHour * 2); } @@ -468,33 +488,26 @@ public class ReportManagementTest extends AbstractIntegrationTest { final int targetCreateAmount = 10; // create targets for another tenant - securityRule.runAs(WithSpringAuthorityRule.withUserAndTenant("user", "anotherTenant"), new Callable() { - @Override - public Void call() throws Exception { - for (int index = 0; index < targetCreateAmount; index++) { - targetManagement.createTarget(new Target("t" + index)); - } - return null; + securityRule.runAs(WithSpringAuthorityRule.withUserAndTenant("user", "anotherTenant"), () -> { + for (int index = 0; index < targetCreateAmount; index++) { + targetManagement.createTarget(new Target("t" + index)); } + return null; }); // ensure targets has been created for 'anotherTenant' final Slice targetsForAnotherTenant = securityRule.runAs( - WithSpringAuthorityRule.withUserAndTenant("user", "anotherTenant"), new Callable>() { - @Override - public Slice call() throws Exception { - return targetManagement.findTargetsAll(new PageRequest(0, 1000)); - } - }); - assertThat(targetsForAnotherTenant).hasSize(targetCreateAmount); + WithSpringAuthorityRule.withUserAndTenant("user", "anotherTenant"), + () -> targetManagement.findTargetsAll(new PageRequest(0, 1000))); + assertThat(targetsForAnotherTenant).as("targets has wrong size").hasSize(targetCreateAmount); final LocalDateTime to = LocalDateTime.now(); final LocalDateTime from = to.minusMonths(targetCreateAmount); // now retrieve the report for the 'mytenant' final DataReportSeries targetsCreatedOverPeriod = reportManagement .targetsCreatedOverPeriod(DateTypes.perMonth(), from, to); - // final no targets should final be created for this tenant - assertThat(targetsCreatedOverPeriod.getData()).hasSize(0); + assertThat(targetsCreatedOverPeriod.getData()).as("final no targets should final be created for this tenant") + .hasSize(0); } @@ -555,7 +568,7 @@ public class ReportManagementTest extends AbstractIntegrationTest { /* * (non-Javadoc) - * + * * @see org.springframework.data.auditing.DateTimeProvider#getNow() */ @Override diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/RolloutManagementTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/RolloutManagementTest.java new file mode 100644 index 000000000..22f9ee8b4 --- /dev/null +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/RolloutManagementTest.java @@ -0,0 +1,1037 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository; + +import static org.fest.assertions.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; + +import org.eclipse.hawkbit.AbstractIntegrationTest; +import org.eclipse.hawkbit.TestDataUtil; +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.Action.Status; +import org.eclipse.hawkbit.repository.model.ActionStatus; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupConditions; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorAction; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition; +import org.eclipse.hawkbit.repository.model.SoftwareModule; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; +import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus; +import org.eclipse.hawkbit.repository.rsql.RSQLUtility; +import org.eclipse.hawkbit.repository.utils.MultipleInvokeHelper; +import org.eclipse.hawkbit.repository.utils.SuccessCondition; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Description; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.jpa.domain.Specification; + +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +/** + * Junit tests for RolloutManagment. + * + * @author Michael Hirsch + * + */ +@Features("Component Tests - Repository") +@Stories("Rollout Management") +public class RolloutManagementTest extends AbstractIntegrationTest { + + @Autowired + private RolloutManagement rolloutManagement; + + @Autowired + private RolloutGroupManagement rolloutGroupManagement; + + @Test + @Description("Verfiying that the rollout is created correctly, executing the filter and split up the targets in the correct group size.") + public void creatingRolloutIsCorrectPersisted() { + final int amountTargetsForRollout = 10; + final int amountOtherTargets = 15; + final int amountGroups = 5; + final String successCondition = "50"; + final String errorCondition = "80"; + final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + + // verify the split of the target and targetGroup + final Page rolloutGroups = rolloutGroupManagement + .findRolloutGroupsByRolloutId(createdRollout.getId(), pageReq); + // we have total of #amountTargetsForRollout in rollouts splitted in + // group size #groupSize + assertThat(rolloutGroups).hasSize(amountGroups); + } + + @Test + @Description("Verfiying that when the rollout is started the actions for all targets in the rollout is created and the state of the first group is running as well as the corresponding actions") + public void startRolloutSetFirstGroupAndActionsInRunningStateAndOthersInScheduleState() { + final int amountTargetsForRollout = 10; + final int amountOtherTargets = 15; + final int amountGroups = 5; + final String successCondition = "50"; + final String errorCondition = "80"; + final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + + // start the rollout + rolloutManagement.startRollout(createdRollout); + + // verify first group is running + final RolloutGroup firstGroup = rolloutGroupManagement.findRolloutGroupsByRolloutId(createdRollout.getId(), + new OffsetBasedPageRequest(0, 1, new Sort(Direction.ASC, "id"))).getContent().get(0); + assertThat(firstGroup.getStatus()).isEqualTo(RolloutGroupStatus.RUNNING); + + // verify other groups are scheduled + final List scheduledGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId( + createdRollout.getId(), new OffsetBasedPageRequest(1, 100, new Sort(Direction.ASC, "id"))).getContent(); + scheduledGroups.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.SCHEDULED) + .as("group which should be in scheduled state is in " + group.getStatus() + " state")); + // verify that the first group actions has been started and are in state + // running + final List runningActions = deploymentManagement.findActionsByRolloutAndStatus(createdRollout, + Status.RUNNING); + assertThat(runningActions).hasSize(amountTargetsForRollout / amountGroups); + // the rest targets are only scheduled + assertThat(deploymentManagement.findActionsByRolloutAndStatus(createdRollout, Status.SCHEDULED)) + .hasSize(amountTargetsForRollout - (amountTargetsForRollout / amountGroups)); + } + + @Test + @Description("Verfiying that a finish condition of a group is hit the next group of the rollout is also started") + public void checkRunningRolloutsDoesNotStartNextGroupIfFinishConditionIsNotHit() { + final int amountTargetsForRollout = 10; + final int amountOtherTargets = 15; + final int amountGroups = 5; + final String successCondition = "50"; + final String errorCondition = "80"; + final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + + rolloutManagement.startRollout(createdRollout); + final List runningActions = deploymentManagement.findActionsByRolloutAndStatus(createdRollout, + Status.RUNNING); + // finish one action should be sufficient due the finish condition is at + // 50% + final Action action = runningActions.get(0); + action.setStatus(Status.FINISHED); + controllerManagament.addUpdateActionStatus( + new ActionStatus(action, Status.FINISHED, System.currentTimeMillis(), ""), action); + + // check running rollouts again, now the finish condition should be hit + // and should start the next group + rolloutManagement.checkRunningRollouts(0); + + // verify that now the first and the second group are in running state + final List runningRolloutGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId( + createdRollout.getId(), new OffsetBasedPageRequest(0, 2, new Sort(Direction.ASC, "id"))).getContent(); + runningRolloutGroups.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.RUNNING) + .as("group should be in running state because it should be started but it is in " + group.getStatus() + + " state")); + + // verify that the other groups are still in schedule state + final List scheduledRolloutGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId( + createdRollout.getId(), new OffsetBasedPageRequest(2, 10, new Sort(Direction.ASC, "id"))).getContent(); + scheduledRolloutGroups.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.SCHEDULED) + .as("group should be in scheduled state because it should not be started but it is in " + + group.getStatus() + " state")); + } + + @Test + @Description("Verfiying that the error handling action of a group is executed to pause the current rollout") + public void checkErrorHitOfGroupCallsErrorActionToPauseTheRollout() { + final int amountTargetsForRollout = 10; + final int amountOtherTargets = 15; + final int amountGroups = 5; + final String successCondition = "50"; + final String errorCondition = "80"; + final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + + rolloutManagement.startRollout(createdRollout); + // set both actions in error state so error condition is hit and error + // action is executed + final List runningActions = deploymentManagement.findActionsByRolloutAndStatus(createdRollout, + Status.RUNNING); + + // finish actions with error + for (final Action action : runningActions) { + action.setStatus(Status.ERROR); + controllerManagament.addUpdateActionStatus( + new ActionStatus(action, Status.ERROR, System.currentTimeMillis(), ""), action); + } + + // check running rollouts again, now the error condition should be hit + // and should execute the error action + rolloutManagement.checkRunningRollouts(0); + + final Rollout rollout = rolloutManagement.findRolloutById(createdRollout.getId()); + // the rollout itself should be in paused based on the error action + assertThat(rollout.getStatus()).isEqualTo(RolloutStatus.PAUSED); + + // the first rollout group should be in error state + final List errorGroup = rolloutGroupManagement.findRolloutGroupsByRolloutId( + createdRollout.getId(), new OffsetBasedPageRequest(0, 1, new Sort(Direction.ASC, "id"))).getContent(); + assertThat(errorGroup).hasSize(1); + assertThat(errorGroup.get(0).getStatus()).isEqualTo(RolloutGroupStatus.ERROR); + + // all other groups should still be in scheduled state + final List scheduleGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId( + createdRollout.getId(), new OffsetBasedPageRequest(1, 100, new Sort(Direction.ASC, "id"))).getContent(); + scheduleGroups.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.SCHEDULED)); + } + + @Test + @Description("Verfiying a paused rollout in case of error action hit can be resumed again") + public void errorActionPausesRolloutAndRolloutGetsResumedStartsNextScheduledGroup() { + final int amountTargetsForRollout = 10; + final int amountOtherTargets = 15; + final int amountGroups = 5; + final String successCondition = "50"; + final String errorCondition = "80"; + final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + rolloutManagement.startRollout(createdRollout); + // set both actions in error state so error condition is hit and error + // action is executed + final List runningActions = deploymentManagement.findActionsByRolloutAndStatus(createdRollout, + Status.RUNNING); + // finish actions with error + for (final Action action : runningActions) { + action.setStatus(Status.ERROR); + controllerManagament.addUpdateActionStatus( + new ActionStatus(action, Status.ERROR, System.currentTimeMillis(), ""), action); + } + + // check running rollouts again, now the error condition should be hit + // and should execute the error action + rolloutManagement.checkRunningRollouts(0); + + final Rollout rollout = rolloutManagement.findRolloutById(createdRollout.getId()); + // the rollout itself should be in paused based on the error action + assertThat(rollout.getStatus()).isEqualTo(RolloutStatus.PAUSED); + + // all other groups should still be in scheduled state + final List scheduleGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId( + createdRollout.getId(), new OffsetBasedPageRequest(1, 100, new Sort(Direction.ASC, "id"))).getContent(); + scheduleGroups.forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.SCHEDULED)); + + // resume the rollout again after it gets paused by error action + rolloutManagement.resumeRollout(rolloutManagement.findRolloutById(createdRollout.getId())); + + // the rollout should be running again + assertThat(rolloutManagement.findRolloutById(createdRollout.getId()).getStatus()) + .isEqualTo(RolloutStatus.RUNNING); + + // checking rollouts again + rolloutManagement.checkRunningRollouts(0); + + // next group should be running again after resuming the rollout + final List resumedGroups = rolloutGroupManagement.findRolloutGroupsByRolloutId( + createdRollout.getId(), new OffsetBasedPageRequest(1, 1, new Sort(Direction.ASC, "id"))).getContent(); + assertThat(resumedGroups).hasSize(1); + assertThat(resumedGroups.get(0).getStatus()).isEqualTo(RolloutGroupStatus.RUNNING); + } + + @Test + @Description("Verfiying that the rollout is starting group after group and gets finished at the end") + public void rolloutStartsGroupAfterGroupAndGetsFinished() { + final int amountTargetsForRollout = 10; + final int amountOtherTargets = 15; + final int amountGroups = 5; + final String successCondition = "50"; + final String errorCondition = "80"; + final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + rolloutManagement.startRollout(createdRollout); + // finish running actions, 2 actions should be finished + assertThat(changeStatusForAllRunningActions(createdRollout, Status.FINISHED)).isEqualTo(2); + + // calculate the rest of the groups and finish them + for (int groupsLeft = amountGroups - 1; groupsLeft >= 1; groupsLeft--) { + // next check and start next group + rolloutManagement.checkRunningRollouts(0); + // finish running actions, 2 actions should be finished + assertThat(changeStatusForAllRunningActions(createdRollout, Status.FINISHED)).isEqualTo(2); + assertThat(rolloutManagement.findRolloutById(createdRollout.getId()).getStatus()) + .isEqualTo(RolloutStatus.RUNNING); + + } + // check rollout to see that all actions and all groups are finished and + // so can go to FINISHED state of the rollout + rolloutManagement.checkRunningRollouts(0); + + // verify all groups are in finished state + rolloutGroupManagement + .findRolloutGroupsByRolloutId(createdRollout.getId(), + new OffsetBasedPageRequest(0, 100, new Sort(Direction.ASC, "id"))) + .forEach(group -> assertThat(group.getStatus()).isEqualTo(RolloutGroupStatus.FINISHED)); + + // verify that rollout itself is in finished state + final Rollout findRolloutById = rolloutManagement.findRolloutById(createdRollout.getId()); + assertThat(findRolloutById.getStatus()).isEqualTo(RolloutStatus.FINISHED); + } + + @Test + @Description("Verify that the targets have the right status during the rollout.") + public void countCorrectStatusForEachTargetDuringRollout() { + + final int amountTargetsForRollout = 8; + final int amountOtherTargets = 15; + final int amountGroups = 4; + final String successCondition = "50"; + final String errorCondition = "80"; + final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + + // targets have not started + Map validationMap = createInitStatusMap(); + validationMap.put(TotalTargetCountStatus.Status.NOTSTARTED, 8L); + validateRolloutActionStatus(createdRollout.getId(), validationMap); + + rolloutManagement.startRollout(createdRollout); + // 6 targets are ready and 2 are running + validationMap = createInitStatusMap(); + validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 6L); + validationMap.put(TotalTargetCountStatus.Status.RUNNING, 2L); + validateRolloutActionStatus(createdRollout.getId(), validationMap); + + changeStatusForAllRunningActions(createdRollout, Status.FINISHED); + rolloutManagement.checkRunningRollouts(0); + // 4 targets are ready, 2 are finished and 2 are running + validationMap = createInitStatusMap(); + validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 4L); + validationMap.put(TotalTargetCountStatus.Status.FINISHED, 2L); + validationMap.put(TotalTargetCountStatus.Status.RUNNING, 2L); + validateRolloutActionStatus(createdRollout.getId(), validationMap); + + changeStatusForAllRunningActions(createdRollout, Status.FINISHED); + rolloutManagement.checkRunningRollouts(0); + // 2 targets are ready, 4 are finished and 2 are running + validationMap = createInitStatusMap(); + validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 2L); + validationMap.put(TotalTargetCountStatus.Status.FINISHED, 4L); + validationMap.put(TotalTargetCountStatus.Status.RUNNING, 2L); + validateRolloutActionStatus(createdRollout.getId(), validationMap); + + changeStatusForAllRunningActions(createdRollout, Status.FINISHED); + rolloutManagement.checkRunningRollouts(0); + // 0 targets are ready, 6 are finished and 2 are running + validationMap = createInitStatusMap(); + validationMap.put(TotalTargetCountStatus.Status.FINISHED, 6L); + validationMap.put(TotalTargetCountStatus.Status.RUNNING, 2L); + validateRolloutActionStatus(createdRollout.getId(), validationMap); + + changeStatusForAllRunningActions(createdRollout, Status.FINISHED); + rolloutManagement.checkRunningRollouts(0); + // 0 targets are ready, 8 are finished and 0 are running + validationMap = createInitStatusMap(); + validationMap.put(TotalTargetCountStatus.Status.FINISHED, 8L); + validateRolloutActionStatus(createdRollout.getId(), validationMap); + + } + + @Test + @Description("Verify that the targets have the right status during the rollout when an error emerges.") + public void countCorrectStatusForEachTargetDuringRolloutWithError() { + + final int amountTargetsForRollout = 8; + final int amountOtherTargets = 15; + final int amountGroups = 4; + final String successCondition = "50"; + final String errorCondition = "80"; + final Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + + // 8 targets have not started + Map validationMap = createInitStatusMap(); + validationMap.put(TotalTargetCountStatus.Status.NOTSTARTED, 8L); + validateRolloutActionStatus(createdRollout.getId(), validationMap); + + rolloutManagement.startRollout(createdRollout); + // 6 targets are ready and 2 are running + validationMap = createInitStatusMap(); + validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 6L); + validationMap.put(TotalTargetCountStatus.Status.RUNNING, 2L); + validateRolloutActionStatus(createdRollout.getId(), validationMap); + + changeStatusForAllRunningActions(createdRollout, Status.ERROR); + rolloutManagement.checkRunningRollouts(0); + // 6 targets are ready and 2 are error + validationMap = createInitStatusMap(); + validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 6L); + validationMap.put(TotalTargetCountStatus.Status.ERROR, 2L); + validateRolloutActionStatus(createdRollout.getId(), validationMap); + } + + @Test + @Description("Verify that the targets have the right status during the rollout when receiving the status of rollout groups.") + public void countCorrectStatusForEachTargetGroupDuringRollout() { + + final int amountTargetsForRollout = 9; + final int amountOtherTargets = 15; + final int amountGroups = 4; + final String successCondition = "50"; + final String errorCondition = "80"; + Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + + rolloutManagement.startRollout(createdRollout); + changeStatusForAllRunningActions(createdRollout, Status.FINISHED); + rolloutManagement.checkRunningRollouts(0); + + // 3 targets finished (Group 1), 3 targets running (Group 3) and 3 + // targets SCHEDULED (Group 3) + createdRollout = rolloutManagement.findRolloutById(createdRollout.getId()); + final List rolloutGruops = createdRollout.getRolloutGroups(); + Map expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.FINISHED, 3L); + validateRolloutGroupActionStatus(rolloutGruops.get(0), expectedTargetCountStatus); + expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 3L); + validateRolloutGroupActionStatus(rolloutGruops.get(1), expectedTargetCountStatus); + expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 3L); + validateRolloutGroupActionStatus(rolloutGruops.get(2), expectedTargetCountStatus); + + } + + @Test + @Description("Verify that target actions of rollout get canceled when a manuel distribution sets assignment is done.") + public void targetsOfRolloutGetsManuelDsAssignment() { + + final int amountTargetsForRollout = 10; + final int amountOtherTargets = 0; + final int amountGroups = 2; + final String successCondition = "50"; + final String errorCondition = "80"; + Rollout createdRollout = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + final DistributionSet ds = createdRollout.getDistributionSet(); + + rolloutManagement.startRollout(createdRollout); + createdRollout = rolloutManagement.findRolloutById(createdRollout.getId()); + // 5 targets are running + final List runningActions = deploymentManagement.findActionsByRolloutAndStatus(createdRollout, + Status.RUNNING); + assertThat(runningActions.size()).isEqualTo(5); + + // 5 targets are in the group and the DS has been assigned + final List rolloutGroups = createdRollout.getRolloutGroups(); + final Page targets = rolloutGroupManagement.findRolloutGroupTargets(rolloutGroups.get(0), + new OffsetBasedPageRequest(0, 20, new Sort(Direction.ASC, "id"))); + final List targetList = targets.getContent(); + assertThat(targetList.size()).isEqualTo(5); + for (final Target t : targetList) { + final DistributionSet assignedDs = t.getAssignedDistributionSet(); + assertThat(assignedDs.getId()).isEqualTo(ds.getId()); + } + + final List targetToCancel = new ArrayList(); + targetToCancel.add(targetList.get(0)); + targetToCancel.add(targetList.get(1)); + targetToCancel.add(targetList.get(2)); + final DistributionSet dsForCancelTest = TestDataUtil.generateDistributionSet("dsForTest", softwareManagement, + distributionSetManagement); + deploymentManagement.assignDistributionSet(dsForCancelTest, targetToCancel); + // 5 targets are canceling but still have the status running and 5 are + // still in SCHEDULED + final Map validationMap = createInitStatusMap(); + validationMap.put(TotalTargetCountStatus.Status.RUNNING, 5L); + validationMap.put(TotalTargetCountStatus.Status.SCHEDULED, 5L); + validateRolloutActionStatus(createdRollout.getId(), validationMap); + } + + @Test + @Description("Verify that target actions of a rollout get cancelled when another rollout with same targets gets started.") + public void targetsOfRolloutGetDistributionSetAssignmentByOtherRollout() { + + final int amountTargetsForRollout = 15; + final int amountOtherTargets = 5; + final int amountGroups = 3; + final String successCondition = "50"; + final String errorCondition = "80"; + Rollout rolloutOne = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + rolloutManagement.startRollout(rolloutOne); + rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()); + + final DistributionSet dsForRolloutTwo = TestDataUtil.generateDistributionSet("dsForRolloutTwo", + softwareManagement, distributionSetManagement); + + final Rollout rolloutTwo = createRolloutByVariables("rolloutTwo", "This is the description for rollout two", 1, + "controllerId==rollout-*", dsForRolloutTwo, "50", "80"); + changeStatusForAllRunningActions(rolloutOne, Status.FINISHED); + rolloutManagement.checkRunningRollouts(0); + // Verify that 5 targets are finished, 5 are running and 5 are ready. + Map expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 5L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.FINISHED, 5L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 5L); + validateRolloutActionStatus(rolloutOne.getId(), expectedTargetCountStatus); + + rolloutManagement.startRollout(rolloutTwo); + // Verify that 5 targets are finished, 5 are still running and 5 are + // cancelled. + expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.FINISHED, 5L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 5L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.CANCELLED, 5L); + validateRolloutActionStatus(rolloutOne.getId(), expectedTargetCountStatus); + } + + @Test + @Description("Verify that error status of DistributionSet installation during rollout can get rerun with second rollout so that all targets have some DistributionSet installed at the end.") + public void startSecondRolloutAfterFristRolloutEndenWithErrors() { + + final int amountTargetsForRollout = 15; + final int amountOtherTargets = 0; + final int amountGroups = 3; + final String successCondition = "50"; + final String errorCondition = "80"; + Rollout rolloutOne = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + final DistributionSet distributionSet = rolloutOne.getDistributionSet(); + rolloutManagement.startRollout(rolloutOne); + rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()); + changeStatusForRunningActions(rolloutOne, Status.ERROR, 2); + changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3); + rolloutManagement.checkRunningRollouts(0); + changeStatusForRunningActions(rolloutOne, Status.ERROR, 2); + changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3); + rolloutManagement.checkRunningRollouts(0); + changeStatusForRunningActions(rolloutOne, Status.ERROR, 2); + changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3); + rolloutManagement.checkRunningRollouts(0); + + // 9 targets are finished and 6 have error + Map expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.FINISHED, 9L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.ERROR, 6L); + validateRolloutActionStatus(rolloutOne.getId(), expectedTargetCountStatus); + // rollout is finished + rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()); + assertThat(rolloutOne.getStatus()).isEqualTo(RolloutStatus.FINISHED); + + final int amountGroupsForRolloutTwo = 1; + Rollout rolloutTwo = createRolloutByVariables("rolloutTwo", "This is the description for rollout two", + amountGroupsForRolloutTwo, "controllerId==rollout-*", distributionSet, "50", "80"); + + rolloutManagement.startRollout(rolloutTwo); + rolloutTwo = rolloutManagement.findRolloutById(rolloutTwo.getId()); + // 6 error targets are know running + expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 6L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.NOTSTARTED, 9L); + validateRolloutActionStatus(rolloutTwo.getId(), expectedTargetCountStatus); + changeStatusForAllRunningActions(rolloutTwo, Status.FINISHED); + final Page targetPage = targetManagement.findTargetByUpdateStatus(pageReq, TargetUpdateStatus.IN_SYNC); + final List targetList = targetPage.getContent(); + // 15 targets in finished/IN_SYNC status and same DS assigned + assertThat(targetList.size()).isEqualTo(amountTargetsForRollout); + for (final Target t : targetList) { + final DistributionSet ds = t.getAssignedDistributionSet(); + assertThat(ds).isEqualTo(distributionSet); + } + } + + @Test + @Description("Verify that the rollout moves to the next group when the success condition was achieved and the error condition was not exceeded.") + public void successConditionAchievedAndErrorConditionNotExceeded() { + + final int amountTargetsForRollout = 10; + final int amountOtherTargets = 0; + final int amountGroups = 2; + final String successCondition = "50"; + final String errorCondition = "80"; + Rollout rolloutOne = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + + rolloutManagement.startRollout(rolloutOne); + rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()); + changeStatusForRunningActions(rolloutOne, Status.ERROR, 2); + changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3); + rolloutManagement.checkRunningRollouts(0); + // verify: 40% error but 60% finished -> should move to next group + final List rolloutGruops = rolloutOne.getRolloutGroups(); + final Map expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 5L); + validateRolloutGroupActionStatus(rolloutGruops.get(1), expectedTargetCountStatus); + + } + + @Test + @Description("Verify that the rollout does not move to the next group when the sucess condition was not achieved.") + public void successConditionNotAchieved() { + + final int amountTargetsForRollout = 10; + final int amountOtherTargets = 0; + final int amountGroups = 2; + final String successCondition = "80"; + final String errorCondition = "90"; + Rollout rolloutOne = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + + rolloutManagement.startRollout(rolloutOne); + rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()); + changeStatusForRunningActions(rolloutOne, Status.ERROR, 2); + changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3); + rolloutManagement.checkRunningRollouts(0); + // verify: 40% error and 60% finished -> should not move to next group + // because successCondition 80% + rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()); + final List rolloutGruops = rolloutOne.getRolloutGroups(); + final Map expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 5L); + validateRolloutGroupActionStatus(rolloutGruops.get(1), expectedTargetCountStatus); + } + + @Test + @Description("Verify that the rollout pauses when the error condition was exceeded.") + public void errorConditionExceeded() { + + final int amountTargetsForRollout = 10; + final int amountOtherTargets = 0; + final int amountGroups = 2; + final String successCondition = "50"; + final String errorCondition = "20"; + Rollout rolloutOne = createSimpleTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountOtherTargets, amountGroups, successCondition, errorCondition); + + rolloutManagement.startRollout(rolloutOne); + rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()); + changeStatusForRunningActions(rolloutOne, Status.ERROR, 2); + changeStatusForRunningActions(rolloutOne, Status.FINISHED, 3); + rolloutManagement.checkRunningRollouts(0); + // verify: 40% error -> should pause because errorCondition is 20% + rolloutOne = rolloutManagement.findRolloutById(rolloutOne.getId()); + assertThat(RolloutStatus.PAUSED).isEqualTo(rolloutOne.getStatus()); + } + + @Test + @Description("Verify that all rollouts are return with expected target statuses.") + public void findAllRolloutsWithDetailedStatus() { + + final int amountTargetsForRollout = 12; + final int amountGroups = 2; + final String successCondition = "50"; + final String errorCondition = "20"; + final Rollout rolloutA = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups, + successCondition, errorCondition, "RolloutA", "RolloutA"); + rolloutManagement.startRollout(rolloutA); + + final int amountTargetsForRollout2 = 10; + final int amountGroups2 = 2; + final Rollout rolloutB = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout2, amountGroups2, + successCondition, errorCondition, "RolloutB", "RolloutB"); + rolloutManagement.startRollout(rolloutB); + changeStatusForAllRunningActions(rolloutB, Status.FINISHED); + rolloutManagement.checkRunningRollouts(0); + + final int amountTargetsForRollout3 = 10; + final int amountGroups3 = 2; + final Rollout rolloutC = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout3, amountGroups3, + successCondition, errorCondition, "RolloutC", "RolloutC"); + rolloutManagement.startRollout(rolloutC); + changeStatusForAllRunningActions(rolloutC, Status.ERROR); + rolloutManagement.checkRunningRollouts(0); + + final int amountTargetsForRollout4 = 15; + final int amountGroups4 = 3; + final Rollout rolloutD = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout4, amountGroups4, + successCondition, errorCondition, "RolloutD", "RolloutD"); + rolloutManagement.startRollout(rolloutD); + changeStatusForRunningActions(rolloutD, Status.ERROR, 1); + rolloutManagement.checkRunningRollouts(0); + changeStatusForAllRunningActions(rolloutD, Status.FINISHED); + rolloutManagement.checkRunningRollouts(0); + + final Page rolloutPage = rolloutManagement + .findAllRolloutsWithDetailedStatus(new OffsetBasedPageRequest(0, 100, new Sort(Direction.ASC, "name"))); + final List rolloutList = rolloutPage.getContent(); + + // validate rolloutA -> 6 running and 6 ready + Map expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 6L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 6L); + validateRolloutActionStatus(rolloutList.get(0).getId(), expectedTargetCountStatus); + + // validate rolloutB -> 5 running and 5 finished + expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.FINISHED, 5L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 5L); + validateRolloutActionStatus(rolloutList.get(1).getId(), expectedTargetCountStatus); + + // validate rolloutC -> 5 running and 5 error + expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.ERROR, 5L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 5L); + validateRolloutActionStatus(rolloutList.get(2).getId(), expectedTargetCountStatus); + + // validate rolloutD -> 1, error, 4 finished, 5 running and 5 ready + expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.ERROR, 1L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.FINISHED, 4L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 5L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 5L); + validateRolloutActionStatus(rolloutList.get(3).getId(), expectedTargetCountStatus); + } + + @Test + @Description("Verify the count of existing rollouts.") + public void rightCountForAllRollouts() { + + final int amountTargetsForRollout = 6; + ; + final int amountGroups = 2; + final String successCondition = "50"; + final String errorCondition = "80"; + for (int i = 1; i <= 10; i++) { + createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups, successCondition, + errorCondition, "Rollout" + i, "Rollout" + i); + } + final Long count = rolloutManagement.countRolloutsAll(); + assertThat(count).isEqualTo(10L); + } + + @Test + @Description("Verify the count of filtered existing rollouts.") + public void countRolloutsAllByFilters() { + + final int amountTargetsForRollout = 6; + final int amountGroups = 2; + final String successCondition = "50"; + final String errorCondition = "80"; + for (int i = 1; i <= 5; i++) { + createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups, successCondition, + errorCondition, "Rollout" + i, "Rollout" + i); + } + for (int i = 1; i <= 5; i++) { + createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups, successCondition, + errorCondition, "SomethingElse" + i, "SomethingElse" + i); + } + + final Long count = rolloutManagement.countRolloutsAllByFilters("Rollout%"); + assertThat(count).isEqualTo(5L); + + } + + @Test + @Description("Verify that the filtering and sorting ascending for rollout is working correctly.") + public void findRolloutByFilters() { + + final int amountTargetsForRollout = 6; + final int amountGroups = 2; + final String successCondition = "50"; + final String errorCondition = "80"; + for (int i = 1; i <= 5; i++) { + createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups, successCondition, + errorCondition, "Rollout" + i, "Rollout" + i); + } + for (int i = 1; i <= 8; i++) { + createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups, successCondition, + errorCondition, "SomethingElse" + i, "SomethingElse" + i); + } + + final Slice rollout = rolloutManagement + .findRolloutByFilters(new OffsetBasedPageRequest(0, 100, new Sort(Direction.ASC, "name")), "Rollout%"); + final List rolloutList = rollout.getContent(); + assertThat(rolloutList.size()).isEqualTo(5); + int i = 1; + for (final Rollout r : rolloutList) { + assertThat(r.getName()).isEqualTo("Rollout" + i); + i++; + } + } + + @Test + @Description("Verify that the expected rollout is found by name.") + public void findRolloutByName() { + + final int amountTargetsForRollout = 12; + final int amountGroups = 2; + final String successCondition = "50"; + final String errorCondition = "80"; + final String rolloutName = "Rollout137"; + final Rollout rolloutCreated = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, + amountGroups, successCondition, errorCondition, rolloutName, "RolloutA"); + + final Rollout rolloutFound = rolloutManagement.findRolloutByName(rolloutName); + assertThat(rolloutCreated).isEqualTo(rolloutFound); + + } + + @Test + @Description("Verify that the percent count is acting like aspected when targets move to the status finished or error.") + public void getFinishedPercentForRunningGroup() { + + final int amountTargetsForRollout = 10; + final int amountGroups = 2; + final String successCondition = "50"; + final String errorCondition = "80"; + final String rolloutName = "MyRollout"; + Rollout myRollout = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups, + successCondition, errorCondition, rolloutName, rolloutName); + rolloutManagement.startRollout(myRollout); + changeStatusForRunningActions(myRollout, Status.FINISHED, 2); + rolloutManagement.checkRunningRollouts(0); + myRollout = rolloutManagement.findRolloutById(myRollout.getId()); + + float percent = rolloutManagement.getFinishedPercentForRunningGroup(myRollout.getId(), + myRollout.getRolloutGroups().get(0)); + assertThat(percent).isEqualTo(40); + + changeStatusForRunningActions(myRollout, Status.FINISHED, 3); + rolloutManagement.checkRunningRollouts(0); + + percent = rolloutManagement.getFinishedPercentForRunningGroup(myRollout.getId(), + myRollout.getRolloutGroups().get(0)); + assertThat(percent).isEqualTo(100); + + changeStatusForRunningActions(myRollout, Status.FINISHED, 4); + changeStatusForAllRunningActions(myRollout, Status.ERROR); + rolloutManagement.checkRunningRollouts(0); + + percent = rolloutManagement.getFinishedPercentForRunningGroup(myRollout.getId(), + myRollout.getRolloutGroups().get(1)); + assertThat(percent).isEqualTo(80); + } + + @Test + @Description("Verify that the expected targets in the expected order are returned for the rollout groups.") + public void findRolloutGroupTargetsWithRsqlParam() { + + final int amountTargetsForRollout = 15; + final int amountGroups = 3; + final int amountOtherTargets = 15; + final String successCondition = "50"; + final String errorCondition = "80"; + final String rolloutName = "MyRollout"; + Rollout myRollout = createTestRolloutWithTargetsAndDistributionSet(amountTargetsForRollout, amountGroups, + successCondition, errorCondition, rolloutName, rolloutName); + + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountOtherTargets, "others-", "rollout")); + + final String rsqlParam = "controllerId==*MyRoll*"; + final Specification rsqlSpecification = RSQLUtility.parse(rsqlParam, TargetFields.class); + + rolloutManagement.startRollout(myRollout); + myRollout = rolloutManagement.findRolloutById(myRollout.getId()); + final List rolloutGroups = myRollout.getRolloutGroups(); + + Page targetPage = rolloutGroupManagement.findRolloutGroupTargets(rolloutGroups.get(0), + rsqlSpecification, new OffsetBasedPageRequest(0, 100, new Sort(Direction.ASC, "controllerId"))); + final List targetlistGroup1 = targetPage.getContent(); + assertThat(targetlistGroup1.size()).isEqualTo(5); + assertThat(targetlistGroup1.get(0).getControllerId()).isEqualTo("MyRollout--00000"); + + targetPage = rolloutGroupManagement.findRolloutGroupTargets(rolloutGroups.get(1), rsqlSpecification, + new OffsetBasedPageRequest(0, 100, new Sort(Direction.DESC, "controllerId"))); + final List targetlistGroup2 = targetPage.getContent(); + assertThat(targetlistGroup2.size()).isEqualTo(5); + assertThat(targetlistGroup2.get(0).getControllerId()).isEqualTo("MyRollout--00009"); + + targetPage = rolloutGroupManagement.findRolloutGroupTargets(rolloutGroups.get(2), rsqlSpecification, + new OffsetBasedPageRequest(0, 100, new Sort(Direction.ASC, "controllerId"))); + final List targetlistGroup3 = targetPage.getContent(); + assertThat(targetlistGroup3.size()).isEqualTo(5); + assertThat(targetlistGroup3.get(0).getControllerId()).isEqualTo("MyRollout--00010"); + + } + + @Test + @Description("Verify the creation and the start of a rollout in asynchronous mode.") + public void createAndStartRolloutInAsync() throws Exception { + + final int amountTargetsForRollout = 500; + final int amountGroups = 5; + final String successCondition = "50"; + final String errorCondition = "80"; + final String rolloutName = "rolloutTest"; + final String targetPrefixName = rolloutName; + final DistributionSet distributionSet = TestDataUtil.generateDistributionSet("dsFor" + rolloutName, + softwareManagement, distributionSetManagement); + targetManagement.createTargets( + TestDataUtil.buildTargetFixtures(amountTargetsForRollout, targetPrefixName + "-", targetPrefixName)); + final RolloutGroupConditions conditions = new RolloutGroup.RolloutGroupConditionBuilder() + .successCondition(RolloutGroupSuccessCondition.THRESHOLD, successCondition) + .errorCondition(RolloutGroupErrorCondition.THRESHOLD, errorCondition) + .errorAction(RolloutGroupErrorAction.PAUSE, null).build(); + Rollout myRollout = new Rollout(); + myRollout.setName(rolloutName); + myRollout.setDescription("This is a test description for the rollout"); + myRollout.setTargetFilterQuery("controllerId==" + targetPrefixName + "-*"); + myRollout.setDistributionSet(distributionSet); + + myRollout = rolloutManagement.createRolloutAsync(myRollout, amountGroups, conditions); + + SuccessConditionRolloutStatus conditionRolloutTargetCount = new SuccessConditionRolloutStatus( + RolloutStatus.READY); + assertThat(MultipleInvokeHelper.doWithTimeout(new RolloutStatusCallable(myRollout.getId()), + conditionRolloutTargetCount, 15000, 500)).as("Rollout status").isNotNull(); + + myRollout = rolloutManagement.findRolloutById(myRollout.getId()); + assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.READY); + rolloutManagement.startRolloutAsync(myRollout); + + conditionRolloutTargetCount = new SuccessConditionRolloutStatus(RolloutStatus.RUNNING); + assertThat(MultipleInvokeHelper.doWithTimeout(new RolloutStatusCallable(myRollout.getId()), + conditionRolloutTargetCount, 15000, 500)).as("Rollout status").isNotNull(); + + myRollout = rolloutManagement.findRolloutById(myRollout.getId()); + assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.RUNNING); + final Map expectedTargetCountStatus = createInitStatusMap(); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 100L); + expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 400L); + validateRolloutActionStatus(myRollout.getId(), expectedTargetCountStatus); + } + + private void validateRolloutGroupActionStatus(final RolloutGroup rolloutGroup, + final Map expectedTargetCountStatus) { + final RolloutGroup rolloutGroupWithDetail = rolloutGroupManagement + .findRolloutGroupWithDetailedStatus(rolloutGroup.getId()); + validateStatus(rolloutGroupWithDetail.getTotalTargetCountStatus(), expectedTargetCountStatus); + } + + private void validateRolloutActionStatus(final Long rolloutId, + final Map expectedTargetCountStatus) { + final Rollout rolloutWithDetail = rolloutManagement.findRolloutWithDetailedStatus(rolloutId); + validateStatus(rolloutWithDetail.getTotalTargetCountStatus(), expectedTargetCountStatus); + } + + private void validateStatus(final TotalTargetCountStatus totalTargetCountStatus, + final Map expectedTotalCountStates) { + for (final Map.Entry entry : expectedTotalCountStates.entrySet()) { + final Long countReady = totalTargetCountStatus.getTotalTargetCountByStatus(entry.getKey()); + assertThat(countReady).isEqualTo(entry.getValue()); + } + } + + private Rollout createSimpleTestRolloutWithTargetsAndDistributionSet(final int amountTargetsForRollout, + final int amountOtherTargets, final int groupSize, final String successCondition, + final String errorCondition) { + final SoftwareModule ah = softwareManagement + .createSoftwareModule(new SoftwareModule(appType, "agent-hub", "1.0.1", null, "")); + final SoftwareModule jvm = softwareManagement + .createSoftwareModule(new SoftwareModule(runtimeType, "oracle-jre", "1.7.2", null, "")); + final SoftwareModule os = softwareManagement + .createSoftwareModule(new SoftwareModule(osType, "poky", "3.0.2", null, "")); + final DistributionSet rolloutDS = distributionSetManagement.createDistributionSet( + TestDataUtil.buildDistributionSet("rolloutDS", "0.0.0", standardDsType, os, jvm, ah)); + targetManagement + .createTargets(TestDataUtil.buildTargetFixtures(amountTargetsForRollout, "rollout-", "rollout")); + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountOtherTargets, "others-", "rollout")); + final String filterQuery = "controllerId==rollout-*"; + return createRolloutByVariables("test-rollout-name-1", "test-rollout-description-1", groupSize, filterQuery, + rolloutDS, successCondition, errorCondition); + } + + private Rollout createTestRolloutWithTargetsAndDistributionSet(final int amountTargetsForRollout, + final int groupSize, final String successCondition, final String errorCondition, final String rolloutName, + final String targetPrefixName) { + final DistributionSet dsForRolloutTwo = TestDataUtil.generateDistributionSet("dsFor" + rolloutName, + softwareManagement, distributionSetManagement); + targetManagement.createTargets( + TestDataUtil.buildTargetFixtures(amountTargetsForRollout, targetPrefixName + "-", targetPrefixName)); + return createRolloutByVariables(rolloutName, rolloutName + "description", groupSize, + "controllerId==" + targetPrefixName + "-*", dsForRolloutTwo, successCondition, errorCondition); + } + + private Rollout createRolloutByVariables(final String rolloutName, final String rolloutDescription, + final int groupSize, final String filterQuery, final DistributionSet distributionSet, + final String successCondition, final String errorCondition) { + final RolloutGroupConditions conditions = new RolloutGroup.RolloutGroupConditionBuilder() + .successCondition(RolloutGroupSuccessCondition.THRESHOLD, successCondition) + .errorCondition(RolloutGroupErrorCondition.THRESHOLD, errorCondition) + .errorAction(RolloutGroupErrorAction.PAUSE, null).build(); + final Rollout rolloutToCreate = new Rollout(); + rolloutToCreate.setName(rolloutName); + rolloutToCreate.setDescription(rolloutDescription); + rolloutToCreate.setTargetFilterQuery(filterQuery); + rolloutToCreate.setDistributionSet(distributionSet); + return rolloutManagement.createRollout(rolloutToCreate, groupSize, conditions); + } + + private int changeStatusForAllRunningActions(final Rollout rollout, final Status status) { + final List runningActions = deploymentManagement.findActionsByRolloutAndStatus(rollout, Status.RUNNING); + for (final Action action : runningActions) { + action.setStatus(status); + controllerManagament.addUpdateActionStatus(new ActionStatus(action, status, System.currentTimeMillis(), ""), + action); + } + return runningActions.size(); + } + + private int changeStatusForRunningActions(final Rollout rollout, final Status status, + final int amountOfTargetsToGetChanged) { + final List runningActions = deploymentManagement.findActionsByRolloutAndStatus(rollout, Status.RUNNING); + assertThat(runningActions.size()).isGreaterThanOrEqualTo(amountOfTargetsToGetChanged); + for (int i = 0; i < amountOfTargetsToGetChanged; i++) { + controllerManagament.addUpdateActionStatus( + new ActionStatus(runningActions.get(i), status, System.currentTimeMillis(), ""), + runningActions.get(i)); + } + return runningActions.size(); + } + + private Map createInitStatusMap() { + final Map map = new HashMap(); + for (final TotalTargetCountStatus.Status status : TotalTargetCountStatus.Status.values()) { + map.put(status, 0L); + } + return map; + } + + private static class SuccessConditionRolloutStatus implements SuccessCondition { + + private final RolloutStatus rolloutStatus; + + public SuccessConditionRolloutStatus(final RolloutStatus rolloutStatus) { + this.rolloutStatus = rolloutStatus; + } + + @Override + public boolean success(final RolloutStatus result) { + return result.equals(rolloutStatus); + } + } + + private class RolloutStatusCallable implements Callable { + + final Long rolloutId; + + RolloutStatusCallable(final Long rolloutId) { + this.rolloutId = rolloutId; + } + + @Override + public RolloutStatus call() throws Exception { + + final Rollout myRollout = rolloutManagement.findRolloutById(rolloutId); + return myRollout.getStatus(); + + } + } + +} diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/SoftwareManagementTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/SoftwareManagementTest.java index dc9a654c7..49776e392 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/SoftwareManagementTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/SoftwareManagementTest.java @@ -159,10 +159,10 @@ public class SoftwareManagementTest extends AbstractIntegrationTestWithMongoDB { public void hardDeleteOfNotAssignedArtifact() { // [STEP1]: Create SoftwareModuleX with Artifacts - SoftwareModule unassignedModule = createSoftwareModuleWithArtifacts(osType, "moduleX", "3.0.2", 2); - Iterator artifactsIt = unassignedModule.getArtifacts().iterator(); - Artifact artifact1 = artifactsIt.next(); - Artifact artifact2 = artifactsIt.next(); + final SoftwareModule unassignedModule = createSoftwareModuleWithArtifacts(osType, "moduleX", "3.0.2", 2); + final Iterator artifactsIt = unassignedModule.getArtifacts().iterator(); + final Artifact artifact1 = artifactsIt.next(); + final Artifact artifact2 = artifactsIt.next(); // [STEP2]: Delete unassigned SoftwareModule softwareManagement.deleteSoftwareModule(unassignedModule); @@ -185,7 +185,7 @@ public class SoftwareManagementTest extends AbstractIntegrationTestWithMongoDB { public void softDeleteOfAssignedArtifact() { // Init DistributionSet - DistributionSet disSet = distributionSetManagement + final DistributionSet disSet = distributionSetManagement .createDistributionSet(new DistributionSet("ds1", "v1.0", "test ds", standardDsType, null)); // [STEP1]: Create SoftwareModuleX with ArtifactX @@ -200,14 +200,14 @@ public class SoftwareManagementTest extends AbstractIntegrationTestWithMongoDB { // [VERIFY EXPECTED RESULT]: // verify: assignedModule is marked as deleted assignedModule = softwareManagement.findSoftwareModuleById(assignedModule.getId()); - assertTrue(assignedModule.isDeleted()); + assertTrue("The module should be flagged as deleted", assignedModule.isDeleted()); assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0); assertThat(softwareModuleRepository.findAll()).hasSize(1); // verify: binary data is deleted - Iterator artifactsIt = assignedModule.getArtifacts().iterator(); - Artifact artifact1 = artifactsIt.next(); - Artifact artifact2 = artifactsIt.next(); + final Iterator artifactsIt = assignedModule.getArtifacts().iterator(); + final Artifact artifact1 = artifactsIt.next(); + final Artifact artifact2 = artifactsIt.next(); assertArtfiactNull(artifact1, artifact2); // verify: artifact meta data is still available @@ -221,7 +221,7 @@ public class SoftwareManagementTest extends AbstractIntegrationTestWithMongoDB { // Init target and DistributionSet final Target target = targetManagement.createTarget(new Target("test123")); - DistributionSet disSet = distributionSetManagement + final DistributionSet disSet = distributionSetManagement .createDistributionSet(new DistributionSet("ds1", "v1.0", "test ds", standardDsType, null)); // [STEP1]: Create SoftwareModuleX and include the new ArtifactX @@ -242,14 +242,14 @@ public class SoftwareManagementTest extends AbstractIntegrationTestWithMongoDB { // [VERIFY EXPECTED RESULT]: // verify: assignedModule is marked as deleted assignedModule = softwareManagement.findSoftwareModuleById(assignedModule.getId()); - assertTrue(assignedModule.isDeleted()); + assertTrue("The found module should be flagged deleted", assignedModule.isDeleted()); assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0); assertThat(softwareModuleRepository.findAll()).hasSize(1); // verify: binary data is deleted - Iterator artifactsIt = assignedModule.getArtifacts().iterator(); - Artifact artifact1 = artifactsIt.next(); - Artifact artifact2 = artifactsIt.next(); + final Iterator artifactsIt = assignedModule.getArtifacts().iterator(); + final Artifact artifact1 = artifactsIt.next(); + final Artifact artifact2 = artifactsIt.next(); assertArtfiactNull(artifact1, artifact2); // verify: artifact meta data is still available @@ -265,7 +265,7 @@ public class SoftwareManagementTest extends AbstractIntegrationTestWithMongoDB { assertThat(operations.find(new Query())).hasSize(0); // Init artifact binary data, target and DistributionSets - byte[] source = RandomUtils.nextBytes(1024); + final byte[] source = RandomUtils.nextBytes(1024); // [STEP1]: Create SoftwareModuleX and add a new ArtifactX SoftwareModule moduleX = createSoftwareModuleWithArtifacts(osType, "modulex", "v1.0", 0); @@ -273,7 +273,7 @@ public class SoftwareManagementTest extends AbstractIntegrationTestWithMongoDB { // [STEP2]: Create newArtifactX and add it to SoftwareModuleX artifactManagement.createLocalArtifact(new ByteArrayInputStream(source), moduleX.getId(), "artifactx", false); moduleX = softwareManagement.findSoftwareModuleWithDetails(moduleX.getId()); - Artifact artifactX = moduleX.getArtifacts().iterator().next(); + final Artifact artifactX = moduleX.getArtifacts().iterator().next(); // [STEP3]: Create SoftwareModuleY and add the same ArtifactX SoftwareModule moduleY = createSoftwareModuleWithArtifacts(osType, "moduley", "v1.0", 0); @@ -281,7 +281,7 @@ public class SoftwareManagementTest extends AbstractIntegrationTestWithMongoDB { // [STEP4]: Assign the same ArtifactX to SoftwareModuleY artifactManagement.createLocalArtifact(new ByteArrayInputStream(source), moduleY.getId(), "artifactx", false); moduleY = softwareManagement.findSoftwareModuleWithDetails(moduleY.getId()); - Artifact artifactY = moduleY.getArtifacts().iterator().next(); + final Artifact artifactY = moduleY.getArtifacts().iterator().next(); // verify: that only one entry was created in mongoDB assertThat(operations.find(new Query())).hasSize(1); @@ -325,14 +325,14 @@ public class SoftwareManagementTest extends AbstractIntegrationTestWithMongoDB { artifactManagement.createLocalArtifact(new ByteArrayInputStream(source), moduleX.getId(), "artifactx", false); moduleX = softwareManagement.findSoftwareModuleWithDetails(moduleX.getId()); - Artifact artifactX = moduleX.getArtifacts().iterator().next(); + final Artifact artifactX = moduleX.getArtifacts().iterator().next(); // [STEP2]: Create SoftwareModuleY and add the same ArtifactX SoftwareModule moduleY = createSoftwareModuleWithArtifacts(osType, "moduley", "v1.0", 0); artifactManagement.createLocalArtifact(new ByteArrayInputStream(source), moduleY.getId(), "artifactx", false); moduleY = softwareManagement.findSoftwareModuleWithDetails(moduleY.getId()); - Artifact artifactY = moduleY.getArtifacts().iterator().next(); + final Artifact artifactY = moduleY.getArtifacts().iterator().next(); // verify: that only one entry was created in mongoDB assertThat(operations.find(new Query())).hasSize(1); @@ -358,8 +358,8 @@ public class SoftwareManagementTest extends AbstractIntegrationTestWithMongoDB { // verify: SoftwareModuleX and SofwtareModule are marked as deleted assertThat(moduleX).isNotNull(); assertThat(moduleY).isNotNull(); - assertTrue(moduleX.isDeleted()); - assertTrue(moduleY.isDeleted()); + assertTrue("The module should be flagged deleted", moduleX.isDeleted()); + assertTrue("The module should be flagged deleted", moduleY.isDeleted()); assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0); assertThat(softwareModuleRepository.findAll()).hasSize(2); @@ -370,10 +370,10 @@ public class SoftwareManagementTest extends AbstractIntegrationTestWithMongoDB { assertThat(artifactRepository.findOne(artifactY.getId())).isNotNull(); } - private SoftwareModule createSoftwareModuleWithArtifacts(SoftwareModuleType type, String name, String version, - int numberArtifacts) { + private SoftwareModule createSoftwareModuleWithArtifacts(final SoftwareModuleType type, final String name, + final String version, final int numberArtifacts) { - long countSoftwareModule = softwareModuleRepository.count(); + final long countSoftwareModule = softwareModuleRepository.count(); // create SoftwareModule SoftwareModule softwareModule = softwareManagement @@ -388,7 +388,7 @@ public class SoftwareManagementTest extends AbstractIntegrationTestWithMongoDB { softwareModule = softwareManagement.findSoftwareModuleWithDetails(softwareModule.getId()); assertThat(softwareModuleRepository.findAll()).hasSize((int) countSoftwareModule + 1); - List artifacts = softwareModule.getArtifacts(); + final List artifacts = softwareModule.getArtifacts(); assertThat(artifacts).hasSize(numberArtifacts); if (numberArtifacts != 0) { diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/SystemManagementTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/SystemManagementTest.java index cdb76fd8d..1a1632d32 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/SystemManagementTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/SystemManagementTest.java @@ -21,10 +21,7 @@ import org.eclipse.hawkbit.report.model.TenantUsage; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.Target; -import org.eclipse.hawkbit.repository.model.TenantConfiguration; -import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.junit.Test; -import org.springframework.core.convert.ConversionFailedException; import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Features; @@ -151,91 +148,4 @@ public class SystemManagementTest extends AbstractIntegrationTestWithMongoDB { }); } - @Test - @Description("Tests that tenant specific configuration can be persisted and in case the tenant does not have specific configuration the default from environment is used instead.") - public void storeTenantSpecificConfiguration() { - final TenantConfigurationKey configKey = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED; - final String envPropertyDefault = environment.getProperty(configKey.getDefaultKeyName()); - assertThat(envPropertyDefault).isNotNull(); - - // get the configuration from the system management - final String defaultConfigValue = systemManagement.getConfigurationValue(configKey, String.class); - assertThat(envPropertyDefault).isEqualTo(defaultConfigValue); - - // update the tenant specific configuration - final String newConfigurationValue = "thisIsAnotherValueForPolling"; - assertThat(newConfigurationValue).isNotEqualTo(defaultConfigValue); - systemManagement - .addOrUpdateConfiguration(new TenantConfiguration(configKey.getKeyName(), newConfigurationValue)); - - // verify that new configuration value is used - final String updatedConfigurationValue = systemManagement.getConfigurationValue(configKey, String.class); - assertThat(updatedConfigurationValue).isEqualTo(newConfigurationValue); - assertThat(systemManagement.getTenantConfigurations()).hasSize(1); - } - - @Test - @Description("Tests that the tenant specific configuration can be updated") - public void updateTenantSpecifcConfiguration() { - final TenantConfigurationKey configKey = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED; - final String value1 = "firstValue"; - final String value2 = "secondValue"; - - // add value first - systemManagement.addOrUpdateConfiguration(new TenantConfiguration(configKey.getKeyName(), value1)); - assertThat(systemManagement.getConfigurationValue(configKey, String.class)).isEqualTo(value1); - - // update to value second - systemManagement.addOrUpdateConfiguration(new TenantConfiguration(configKey.getKeyName(), value2)); - assertThat(systemManagement.getConfigurationValue(configKey, String.class)).isEqualTo(value2); - } - - @Test - @Description("Tests that the configuration value can be converted from String to Integer automatically") - public void tenantConfigurationValueConversion() { - final TenantConfigurationKey configKey = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED; - final Integer value1 = 123; - systemManagement - .addOrUpdateConfiguration(new TenantConfiguration(configKey.getKeyName(), String.valueOf(value1))); - assertThat(systemManagement.getConfigurationValue(configKey, Integer.class)).isEqualTo(value1); - } - - @Test(expected = ConversionFailedException.class) - @Description("Tests that the get configuration throws exception in case the value cannot be automatically converted from String to Integer") - public void wrongTenantConfigurationValueConversionThrowsException() { - final TenantConfigurationKey configKey = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED; - final String value1 = "thisIsNotANumber"; - // add value as String - systemManagement - .addOrUpdateConfiguration(new TenantConfiguration(configKey.getKeyName(), String.valueOf(value1))); - // try to get it as Integer - systemManagement.getConfigurationValue(configKey, Integer.class); - } - - @Test - @Description("Tests that a deletion of a tenant specific configuration deletes it from the database.") - public void deleteConfigurationReturnNullConfiguration() { - final TenantConfigurationKey configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY; - - // gateway token does not have default value so no configuration value - // is should be available - final String defaultConfigValue = systemManagement.getConfigurationValue(configKey, String.class); - assertThat(defaultConfigValue).isNull(); - - // update the tenant specific configuration - final String newConfigurationValue = "thisIsAnotherValueForPolling"; - assertThat(newConfigurationValue).isNotEqualTo(defaultConfigValue); - systemManagement - .addOrUpdateConfiguration(new TenantConfiguration(configKey.getKeyName(), newConfigurationValue)); - - // verify that new configuration value is used - final String updatedConfigurationValue = systemManagement.getConfigurationValue(configKey, String.class); - assertThat(updatedConfigurationValue).isEqualTo(newConfigurationValue); - - // delete the tenant specific configuration - systemManagement.deleteConfiguration(configKey); - // ensure that now gateway token is set again, because is deleted and - // must be null now - assertThat(systemManagement.getConfigurationValue(configKey, String.class)).isNull(); - } } diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/TagManagementTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/TagManagementTest.java index 9c1a9b66e..eb263b242 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/TagManagementTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/TagManagementTest.java @@ -10,6 +10,7 @@ package org.eclipse.hawkbit.repository; import static org.fest.assertions.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; import java.util.List; @@ -22,6 +23,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSetTag; import org.eclipse.hawkbit.repository.model.Tag; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetTag; +import org.junit.Before; import org.junit.Test; import org.slf4j.LoggerFactory; @@ -34,18 +36,19 @@ import ru.yandex.qatools.allure.annotations.Stories; /** * Test class for {@link TagManagement}. * - * - * */ @Features("Component Tests - Repository") @Stories("Tag Management") -// TODO: fully document tests -> @Description for long text and reasonable -// method name as short text public class TagManagementTest extends AbstractIntegrationTest { public TagManagementTest() { LOG = LoggerFactory.getLogger(TagManagementTest.class); } + @Before + public void setup() { + assertThat(targetTagRepository.findAll()).as("Not tags should be available").isEmpty(); + } + @Test @Description("Full DS tag lifecycle tested. Create tags, assign them to sets and delete the tags.") public void createAndAssignAndDeleteDistributionSetTags() { @@ -92,7 +95,7 @@ public class TagManagementTest extends AbstractIntegrationTest { // search for not deleted distributionSetFilterBuilder = getDistributionSetFilterBuilder().setIsComplete(true) .setTagNames(Lists.newArrayList(tagA.getName())); - assertEquals( + assertEquals("filter works not correct", dsAs.spliterator().getExactSizeIfKnown() + dsABs.spliterator().getExactSizeIfKnown() + dsACs.spliterator().getExactSizeIfKnown() + dsABCs.spliterator().getExactSizeIfKnown(), distributionSetManagement.findDistributionSetsByFilters(pageReq, distributionSetFilterBuilder.build()) @@ -100,7 +103,7 @@ public class TagManagementTest extends AbstractIntegrationTest { distributionSetFilterBuilder = getDistributionSetFilterBuilder().setIsComplete(true) .setTagNames(Lists.newArrayList(tagB.getName())); - assertEquals( + assertEquals("filter works not correct", dsBs.spliterator().getExactSizeIfKnown() + dsABs.spliterator().getExactSizeIfKnown() + dsBCs.spliterator().getExactSizeIfKnown() + dsABCs.spliterator().getExactSizeIfKnown(), distributionSetManagement.findDistributionSetsByFilters(pageReq, distributionSetFilterBuilder.build()) @@ -108,7 +111,7 @@ public class TagManagementTest extends AbstractIntegrationTest { distributionSetFilterBuilder = getDistributionSetFilterBuilder().setIsComplete(true) .setTagNames(Lists.newArrayList(tagC.getName())); - assertEquals( + assertEquals("filter works not correct", dsCs.spliterator().getExactSizeIfKnown() + dsACs.spliterator().getExactSizeIfKnown() + dsBCs.spliterator().getExactSizeIfKnown() + dsABCs.spliterator().getExactSizeIfKnown(), distributionSetManagement.findDistributionSetsByFilters(pageReq, distributionSetFilterBuilder.build()) @@ -116,22 +119,22 @@ public class TagManagementTest extends AbstractIntegrationTest { distributionSetFilterBuilder = getDistributionSetFilterBuilder().setIsComplete(true) .setTagNames(Lists.newArrayList(tagX.getName())); - assertEquals(0, distributionSetManagement + assertEquals("filter works not correct", 0, distributionSetManagement .findDistributionSetsByFilters(pageReq, distributionSetFilterBuilder.build()).getTotalElements()); - assertEquals(5, distributionSetTagRepository.findAll().spliterator().getExactSizeIfKnown()); + assertEquals("wrong tag size", 5, distributionSetTagRepository.findAll().spliterator().getExactSizeIfKnown()); tagManagement.deleteDistributionSetTag(tagY.getName()); - assertEquals(4, distributionSetTagRepository.findAll().spliterator().getExactSizeIfKnown()); + assertEquals("wrong tag size", 4, distributionSetTagRepository.findAll().spliterator().getExactSizeIfKnown()); tagManagement.deleteDistributionSetTag(tagX.getName()); - assertEquals(3, distributionSetTagRepository.findAll().spliterator().getExactSizeIfKnown()); + assertEquals("wrong tag size", 3, distributionSetTagRepository.findAll().spliterator().getExactSizeIfKnown()); tagManagement.deleteDistributionSetTag(tagB.getName()); - assertEquals(2, distributionSetTagRepository.findAll().spliterator().getExactSizeIfKnown()); + assertEquals("wrong tag size", 2, distributionSetTagRepository.findAll().spliterator().getExactSizeIfKnown()); distributionSetFilterBuilder = getDistributionSetFilterBuilder().setIsComplete(Boolean.TRUE) .setTagNames(Lists.newArrayList(tagA.getName())); - assertEquals( + assertEquals("filter works not correct", dsAs.spliterator().getExactSizeIfKnown() + dsABs.spliterator().getExactSizeIfKnown() + dsACs.spliterator().getExactSizeIfKnown() + dsABCs.spliterator().getExactSizeIfKnown(), distributionSetManagement.findDistributionSetsByFilters(pageReq, distributionSetFilterBuilder.build()) @@ -139,12 +142,12 @@ public class TagManagementTest extends AbstractIntegrationTest { distributionSetFilterBuilder = getDistributionSetFilterBuilder().setIsComplete(Boolean.TRUE) .setTagNames(Lists.newArrayList(tagB.getName())); - assertEquals(0, distributionSetManagement + assertEquals("filter works not correct", 0, distributionSetManagement .findDistributionSetsByFilters(pageReq, distributionSetFilterBuilder.build()).getTotalElements()); distributionSetFilterBuilder = getDistributionSetFilterBuilder().setIsComplete(Boolean.TRUE) .setTagNames(Lists.newArrayList(tagC.getName())); - assertEquals( + assertEquals("filter works not correct", dsCs.spliterator().getExactSizeIfKnown() + dsACs.spliterator().getExactSizeIfKnown() + dsBCs.spliterator().getExactSizeIfKnown() + dsABCs.spliterator().getExactSizeIfKnown(), distributionSetManagement.findDistributionSetsByFilters(pageReq, distributionSetFilterBuilder.build()) @@ -155,45 +158,28 @@ public class TagManagementTest extends AbstractIntegrationTest { return new DistributionSetFilterBuilder(); } - /** - * Test method for - * {@link org.eclipse.hawkbit.repository.TagManagement#findTargetTag(java.lang.String)} - * . - */ @Test - public void testFindTargetTag() { - assertThat(targetTagRepository.findAll()).isEmpty(); - + @Description("Ensures that all tags are retrieved through repository.") + public void findAllTargetTags() { final List tags = createTargetsWithTags(); assertThat(targetTagRepository.findAll()).isEqualTo(tagManagement.findAllTargetTags()).isEqualTo(tags) - .hasSize(20); + .as("Wrong tag size").hasSize(20); } - /** - * Test method for - * {@link org.eclipse.hawkbit.repository.TagManagement#createTargetTag(org.eclipse.hawkbit.repository.model.TargetTag)} - * . - */ @Test - public void testCreateTargetTag() { - assertThat(targetTagRepository.findAll()).isEmpty(); - + @Description("Ensures that a created tag is persisted in the repository as defined.") + public void createTargetTag() { final Tag tag = tagManagement.createTargetTag(new TargetTag("kai1", "kai2", "colour")); - assertThat(targetTagRepository.findByNameEquals("kai1").getDescription()).isEqualTo("kai2"); - assertThat(tagManagement.findTargetTag("kai1").getColour()).isEqualTo("colour"); - assertThat(tagManagement.findTargetTagById(tag.getId()).getColour()).isEqualTo("colour"); + assertThat(targetTagRepository.findByNameEquals("kai1").getDescription()).as("wrong tag ed").isEqualTo("kai2"); + assertThat(tagManagement.findTargetTag("kai1").getColour()).as("wrong tag found").isEqualTo("colour"); + assertThat(tagManagement.findTargetTagById(tag.getId()).getColour()).as("wrong tag found").isEqualTo("colour"); } - /** - * Test method for - * {@link org.eclipse.hawkbit.repository.TagManagement#deleteTargetTag(java.lang.String[])} - * . - */ @Test - public void testDeleteTargetTagsStringArray() { - assertThat(targetTagRepository.findAll()).isEmpty(); + @Description("Ensures that a deleted tag is removed from the repository as defined.") + public void deleteTargetTas() { // create test data final Iterable tags = createTargetsWithTags(); @@ -212,16 +198,13 @@ public class TagManagementTest extends AbstractIntegrationTest { assertThat(targetManagement.findTargetByControllerID(target.getControllerId()).getTags()) .doesNotContain(toDelete); } - assertThat(targetTagRepository.findOne(toDelete.getId())).isNull(); - assertThat(tagManagement.findAllTargetTags()).hasSize(19); + assertThat(targetTagRepository.findOne(toDelete.getId())).as("No tag should be found").isNull(); + assertThat(tagManagement.findAllTargetTags()).as("Wrong target tag size").hasSize(19); } @Test - @Description("Tests the creation of a target tag.") + @Description("Tests the name update of a target tag.") public void updateTargetTag() { - assertThat(targetTagRepository.findAll()).isEmpty(); - - // create test data final List tags = createTargetsWithTags(); // change data @@ -232,91 +215,104 @@ public class TagManagementTest extends AbstractIntegrationTest { tagManagement.updateTargetTag(savedAssigned); // check data - assertThat(tagManagement.findAllTargetTags()).hasSize(tags.size()); - assertThat(targetTagRepository.findOne(savedAssigned.getId()).getName()).isEqualTo("test123"); - assertThat(targetTagRepository.findOne(savedAssigned.getId()).getOptLockRevision()).isEqualTo(2); + assertThat(tagManagement.findAllTargetTags()).as("Wrong target tag size").hasSize(tags.size()); + assertThat(targetTagRepository.findOne(savedAssigned.getId()).getName()).as("wrong target tag is saved") + .isEqualTo("test123"); + assertThat(targetTagRepository.findOne(savedAssigned.getId()).getOptLockRevision()) + .as("wrong target tag is saved").isEqualTo(2); } - /** - * Test method for - * {@link org.eclipse.hawkbit.repository.TagManagement#createDistributionSetTag(org.eclipse.hawkbit.repository.model.DistributionSetTag)} - * . - */ @Test - public void testCreateDistributionSetTag() { - assertThat(distributionSetTagRepository.findAll()).isEmpty(); - + @Description("Ensures that a created tag is persisted in the repository as defined.") + public void createDistributionSetTag() { final Tag tag = tagManagement.createDistributionSetTag(new DistributionSetTag("kai1", "kai2", "colour")); - assertThat(distributionSetTagRepository.findByNameEquals("kai1").getDescription()).isEqualTo("kai2"); - assertThat(tagManagement.findDistributionSetTag("kai1").getColour()).isEqualTo("colour"); - assertThat(tagManagement.findDistributionSetTagById(tag.getId()).getColour()).isEqualTo("colour"); + assertThat(distributionSetTagRepository.findByNameEquals("kai1").getDescription()).as("wrong tag found") + .isEqualTo("kai2"); + assertThat(tagManagement.findDistributionSetTag("kai1").getColour()).as("wrong tag found").isEqualTo("colour"); + assertThat(tagManagement.findDistributionSetTagById(tag.getId()).getColour()).as("wrong tag found") + .isEqualTo("colour"); } - /** - * Test method for - * {@link org.eclipse.hawkbit.repository.TagManagement#createDistributionSetTags(java.lang.Iterable)} - * . - */ @Test - public void testCreateDistributionSetTags() { - assertThat(distributionSetTagRepository.findAll()).isEmpty(); - + @Description("Ensures that a created tags are persisted in the repository as defined.") + public void createDistributionSetTags() { final List tags = createDsSetsWithTags(); - assertThat(distributionSetTagRepository.findAll()).hasSize(tags.size()); + assertThat(distributionSetTagRepository.findAll()).as("Wrong size of tags created").hasSize(tags.size()); } - /** - * Test method for - * {@link org.eclipse.hawkbit.repository.TagManagement#deleteDistributionSetTag(java.lang.String[])} - * . - */ @Test - public void testDeleteDistributionSetTag() { - assertThat(distributionSetTagRepository.findAll()).isEmpty(); - + @Description("Ensures that a deleted tag is removed from the repository as defined.") + public void deleteDistributionSetTag() { // create test data final Iterable tags = createDsSetsWithTags(); final DistributionSetTag toDelete = tags.iterator().next(); for (final DistributionSet set : distributionSetRepository.findAll()) { assertThat(distributionSetManagement.findDistributionSetByIdWithDetails(set.getId()).getTags()) - .contains(toDelete); + .as("Wrong tag found").contains(toDelete); } // delete tagManagement.deleteDistributionSetTag(tags.iterator().next().getName()); // check - assertThat(distributionSetTagRepository.findOne(toDelete.getId())).isNull(); - assertThat(tagManagement.findAllDistributionSetTags()).hasSize(19); + assertThat(distributionSetTagRepository.findOne(toDelete.getId())).as("Deleted tag should be null").isNull(); + assertThat(tagManagement.findAllDistributionSetTags()).as("Wrong size of tags after deletion").hasSize(19); for (final DistributionSet set : distributionSetRepository.findAll()) { assertThat(distributionSetManagement.findDistributionSetByIdWithDetails(set.getId()).getTags()) - .doesNotContain(toDelete); + .as("Wrong found tags").doesNotContain(toDelete); } } - @Test(expected = EntityAlreadyExistsException.class) - public void testFailedDuplicateTargetTagNameException() { - tagManagement.createTargetTag(new TargetTag("A")); + @Description("Ensures that a tag cannot be created if one exists already with that name (ecpects EntityAlreadyExistsException).") + public void failedDuplicateTargetTagNameException() { tagManagement.createTargetTag(new TargetTag("A")); + try { + tagManagement.createTargetTag(new TargetTag("A")); + fail("Expected EntityAlreadyExistsException"); + } catch (final EntityAlreadyExistsException e) { + } } - @Test(expected = EntityAlreadyExistsException.class) - public void testFailedDuplicateDsTagNameException() { - tagManagement.createDistributionSetTag(new DistributionSetTag("A")); - tagManagement.createDistributionSetTag(new DistributionSetTag("A")); + @Description("Ensures that a tag cannot be updated to a name that already exists on another tag (ecpects EntityAlreadyExistsException).") + public void failedDuplicateTargetTagNameExceptionAfterUpdate() { + tagManagement.createTargetTag(new TargetTag("A")); + final TargetTag tag = tagManagement.createTargetTag(new TargetTag("B")); + tag.setName("A"); + try { + tagManagement.updateTargetTag(tag); + fail("Expected EntityAlreadyExistsException"); + } catch (final EntityAlreadyExistsException e) { + } + } + + @Description("Ensures that a tag cannot be created if one exists already with that name (ecpects EntityAlreadyExistsException).") + public void failedDuplicateDsTagNameException() { + tagManagement.createDistributionSetTag(new DistributionSetTag("A")); + try { + tagManagement.createDistributionSetTag(new DistributionSetTag("A")); + fail("Expected EntityAlreadyExistsException"); + } catch (final EntityAlreadyExistsException e) { + } + } + + @Description("Ensures that a tag cannot be updated to a name that already exists on another tag (ecpects EntityAlreadyExistsException).") + public void failedDuplicateDsTagNameExceptionAfterUpdate() { + tagManagement.createDistributionSetTag(new DistributionSetTag("A")); + final DistributionSetTag tag = tagManagement.createDistributionSetTag(new DistributionSetTag("B")); + tag.setName("A"); + try { + tagManagement.updateDistributionSetTag(tag); + fail("Expected EntityAlreadyExistsException"); + } catch (final EntityAlreadyExistsException e) { + } } - /** - * Test method for - * {@link org.eclipse.hawkbit.repository.TagManagement#updateDistributionSetTag(org.eclipse.hawkbit.repository.model.DistributionSetTag)} - * . - */ @Test - public void testUpdateDistributionSetTag() { - assertThat(distributionSetTagRepository.findAll()).isEmpty(); + @Description("Tests the name update of a target tag.") + public void updateDistributionSetTag() { // create test data final List tags = createDsSetsWithTags(); @@ -329,24 +325,19 @@ public class TagManagementTest extends AbstractIntegrationTest { tagManagement.updateDistributionSetTag(savedAssigned); // check data - assertThat(tagManagement.findAllDistributionSetTags()).hasSize(tags.size()); - assertThat(distributionSetTagRepository.findOne(savedAssigned.getId()).getName()).isEqualTo("test123"); + assertThat(tagManagement.findAllDistributionSetTags()).as("Wrong size of ds tags").hasSize(tags.size()); + assertThat(distributionSetTagRepository.findOne(savedAssigned.getId()).getName()).as("Wrong ds tag found") + .isEqualTo("test123"); } - /** - * Test method for - * {@link org.eclipse.hawkbit.repository.TagManagement#findAllDistributionSetTags()} - * . - */ @Test - public void testFindDistributionSetTagsAll() { - assertThat(distributionSetTagRepository.findAll()).isEmpty(); - + @Description("Ensures that all tags are retrieved through repository.") + public void findDistributionSetTagsAll() { final List tags = createDsSetsWithTags(); // test - assertThat(tagManagement.findAllDistributionSetTags()).hasSize(tags.size()); - assertThat(distributionSetTagRepository.findAll()).hasSize(20); + assertThat(tagManagement.findAllDistributionSetTags()).as("Wrong size of tags").hasSize(tags.size()); + assertThat(distributionSetTagRepository.findAll()).as("Wrong size of tags").hasSize(20); } private List createTargetsWithTags() { diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/TargetManagementSearchTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/TargetManagementSearchTest.java index bd9884e89..34aa223c3 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/TargetManagementSearchTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/TargetManagementSearchTest.java @@ -47,7 +47,7 @@ public class TargetManagementSearchTest extends AbstractIntegrationTest { final TargetTag targTagC = tagManagement.createTargetTag(new TargetTag("TargTag-C")); final TargetTag targTagD = tagManagement.createTargetTag(new TargetTag("TargTag-D")); - // TODO kzimmerm: test also installedDS (not only assignedDS) + // TODO kaizimmerm: test also installedDS (not only assignedDS) final DistributionSet setA = TestDataUtil.generateDistributionSet("", softwareManagement, distributionSetManagement); @@ -90,7 +90,7 @@ public class TargetManagementSearchTest extends AbstractIntegrationTest { final PageRequest pageReq = new PageRequest(0, 500); // try to find several targets with different filter settings - // TODO kzimmerm: comment and check also the content itself, not only + // TODO kaizimmerm: comment and check also the content itself, not only // the numbers // (containsOnly) assertThat(targetManagement.countTargetsAll()).isEqualTo(400); @@ -185,7 +185,7 @@ public class TargetManagementSearchTest extends AbstractIntegrationTest { } - // TODO kzimmerm: add filter tests + // TODO kaizimmerm: add filter tests @Test @Description("Tests the correct order of targets based on selected distribution set. The system expects to have an order based on installed, assigned DS.") public void targetSearchWithVariousFilterCombinationsAndOrderByDistributionSet() { diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/TargetManagementTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/TargetManagementTest.java index a7b8e673f..20dffde29 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/TargetManagementTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/TargetManagementTest.java @@ -10,7 +10,6 @@ package org.eclipse.hawkbit.repository; import static org.fest.assertions.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -26,6 +25,7 @@ import java.util.Set; import java.util.stream.Collectors; import javax.persistence.Query; +import javax.validation.ConstraintViolationException; import org.eclipse.hawkbit.AbstractIntegrationTest; import org.eclipse.hawkbit.TestDataUtil; @@ -65,6 +65,24 @@ public class TargetManagementTest extends AbstractIntegrationTest { } } + @Test + @Description("Verify that a target with empty controller id cannot be created") + public void createTargetWithNoControllerId() { + try { + targetManagement.createTarget(new Target("")); + fail("target with empty controller id should not be created"); + } catch (final ConstraintViolationException e) { + // ok + } + + try { + targetManagement.createTarget(new Target(null)); + fail("target with empty controller id should not be created"); + } catch (final ConstraintViolationException e) { + // ok + } + } + @Test @Description("Ensures that targets can assigned and unassigned to a target tag. Not exists target will be ignored for the assignment.") public void assignAndUnassignTargetsToTag() { @@ -78,24 +96,26 @@ public class TargetManagementTest extends AbstractIntegrationTest { final TargetTag targetTag = tagManagement.createTargetTag(new TargetTag("Tag1")); final List assignedTargets = targetManagement.assignTag(assignTarget, targetTag); - assertThat(assignedTargets.size()).isEqualTo(4); + assertThat(assignedTargets.size()).as("Assigned targets are wrong").isEqualTo(4); assignedTargets.forEach(target -> assertThat(target.getTags().size()).isEqualTo(1)); TargetTag findTargetTag = tagManagement.findTargetTag("Tag1"); - assertThat(assignedTargets.size()).isEqualTo(findTargetTag.getAssignedToTargets().size()); + assertThat(assignedTargets.size()).as("Assigned targets are wrong") + .isEqualTo(findTargetTag.getAssignedToTargets().size()); - assertThat(targetManagement.unAssignTag("NotExist", findTargetTag)).isNull(); + assertThat(targetManagement.unAssignTag("NotExist", findTargetTag)).as("Unassign target does not work") + .isNull(); final Target unAssignTarget = targetManagement.unAssignTag("targetId123", findTargetTag); - assertThat(unAssignTarget.getControllerId()).isEqualTo("targetId123"); - assertThat(unAssignTarget.getTags().size()).isEqualTo(0); + assertThat(unAssignTarget.getControllerId()).as("Controller id is wrong").isEqualTo("targetId123"); + assertThat(unAssignTarget.getTags()).as("Tag size is wrong").isEmpty(); findTargetTag = tagManagement.findTargetTag("Tag1"); - assertThat(findTargetTag.getAssignedToTargets().size()).isEqualTo(3); + assertThat(findTargetTag.getAssignedToTargets()).as("Assigned targets are wrong").hasSize(3); final List unAssignTargets = targetManagement.unAssignAllTargetsByTag(findTargetTag); findTargetTag = tagManagement.findTargetTag("Tag1"); - assertThat(findTargetTag.getAssignedToTargets().size()).isEqualTo(0); - assertThat(unAssignTargets.size()).isEqualTo(3); + assertThat(findTargetTag.getAssignedToTargets()).as("Unassigned targets are wrong").isEmpty(); + assertThat(unAssignTargets).as("Unassigned targets are wrong").hasSize(3); unAssignTargets.forEach(target -> assertThat(target.getTags().size()).isEqualTo(0)); } @@ -103,14 +123,14 @@ public class TargetManagementTest extends AbstractIntegrationTest { @Description("Ensures that targets can deleted e.g. test all cascades") public void deleteAndCreateTargets() { Target target = targetManagement.createTarget(new Target("targetId123")); - assertThat(targetManagement.countTargetsAll()).isEqualTo(1); + assertThat(targetManagement.countTargetsAll()).as("target count is wrong").isEqualTo(1); targetManagement.deleteTargets(target.getId()); - assertThat(targetManagement.countTargetsAll()).isEqualTo(0); + assertThat(targetManagement.countTargetsAll()).as("target count is wrong").isEqualTo(0); target = createTargetWithAttributes("4711"); - assertThat(targetManagement.countTargetsAll()).isEqualTo(1); + assertThat(targetManagement.countTargetsAll()).as("target count is wrong").isEqualTo(1); targetManagement.deleteTargets(target.getId()); - assertThat(targetManagement.countTargetsAll()).isEqualTo(0); + assertThat(targetManagement.countTargetsAll()).as("target count is wrong").isEqualTo(0); final List targets = new ArrayList(); for (int i = 0; i < 5; i++) { @@ -118,9 +138,9 @@ public class TargetManagementTest extends AbstractIntegrationTest { targets.add(target.getId()); targets.add(createTargetWithAttributes("" + (i * i + 1000)).getId()); } - assertThat(targetManagement.countTargetsAll()).isEqualTo(10); + assertThat(targetManagement.countTargetsAll()).as("target count is wrong").isEqualTo(10); targetManagement.deleteTargets(targets.toArray(new Long[targets.size()])); - assertThat(targetManagement.countTargetsAll()).isEqualTo(0); + assertThat(targetManagement.countTargetsAll()).as("target count is wrong").isEqualTo(0); } private Target createTargetWithAttributes(final String controllerId) { @@ -132,7 +152,8 @@ public class TargetManagementTest extends AbstractIntegrationTest { target = controllerManagament.updateControllerAttributes(controllerId, testData); target = targetManagement.findTargetByControllerIDWithDetails(controllerId); - assertThat(target.getTargetInfo().getControllerAttributes()).isEqualTo(testData); + assertThat(target.getTargetInfo().getControllerAttributes()).as("Controller Attributes are wrong") + .isEqualTo(testData); return target; } @@ -144,10 +165,14 @@ public class TargetManagementTest extends AbstractIntegrationTest { final DistributionSet set2 = TestDataUtil.generateDistributionSet("test2", softwareManagement, distributionSetManagement); - assertThat(targetManagement.countTargetByAssignedDistributionSet(set.getId())).isEqualTo(0); - assertThat(targetManagement.countTargetByInstalledDistributionSet(set.getId())).isEqualTo(0); - assertThat(targetManagement.countTargetByAssignedDistributionSet(set2.getId())).isEqualTo(0); - assertThat(targetManagement.countTargetByInstalledDistributionSet(set2.getId())).isEqualTo(0); + assertThat(targetManagement.countTargetByAssignedDistributionSet(set.getId())).as("Target count is wrong") + .isEqualTo(0); + assertThat(targetManagement.countTargetByInstalledDistributionSet(set.getId())).as("Target count is wrong") + .isEqualTo(0); + assertThat(targetManagement.countTargetByAssignedDistributionSet(set2.getId())).as("Target count is wrong") + .isEqualTo(0); + assertThat(targetManagement.countTargetByInstalledDistributionSet(set2.getId())).as("Target count is wrong") + .isEqualTo(0); Target target = createTargetWithAttributes("4711"); @@ -156,7 +181,7 @@ public class TargetManagementTest extends AbstractIntegrationTest { final DistributionSetAssignmentResult result = deploymentManagement.assignDistributionSet(set.getId(), "4711"); - final Action action = result.getActions().get(0); + final Action action = deploymentManagement.findActionWithDetails(result.getActions().get(0)); action.setStatus(Status.FINISHED); controllerManagament.addUpdateActionStatus( new ActionStatus(action, Status.FINISHED, System.currentTimeMillis(), "message"), action); @@ -165,13 +190,19 @@ public class TargetManagementTest extends AbstractIntegrationTest { target = targetManagement.findTargetByControllerIDWithDetails("4711"); // read data - assertThat(targetManagement.countTargetByAssignedDistributionSet(set.getId())).isEqualTo(0); - assertThat(targetManagement.countTargetByInstalledDistributionSet(set.getId())).isEqualTo(1); - assertThat(targetManagement.countTargetByAssignedDistributionSet(set2.getId())).isEqualTo(1); - assertThat(targetManagement.countTargetByInstalledDistributionSet(set2.getId())).isEqualTo(0); - assertThat(target.getTargetInfo().getLastTargetQuery()).isGreaterThanOrEqualTo(current); - assertThat(target.getAssignedDistributionSet()).isEqualTo(set2); - assertThat(target.getTargetInfo().getInstalledDistributionSet().getId()).isEqualTo(set.getId()); + assertThat(targetManagement.countTargetByAssignedDistributionSet(set.getId())).as("Target count is wrong") + .isEqualTo(0); + assertThat(targetManagement.countTargetByInstalledDistributionSet(set.getId())).as("Target count is wrong") + .isEqualTo(1); + assertThat(targetManagement.countTargetByAssignedDistributionSet(set2.getId())).as("Target count is wrong") + .isEqualTo(1); + assertThat(targetManagement.countTargetByInstalledDistributionSet(set2.getId())).as("Target count is wrong") + .isEqualTo(0); + assertThat(target.getTargetInfo().getLastTargetQuery()).as("Target query is not work") + .isGreaterThanOrEqualTo(current); + assertThat(target.getAssignedDistributionSet()).as("Assigned ds size is wrong").isEqualTo(set2); + assertThat(target.getTargetInfo().getInstalledDistributionSet().getId()).as("Installed ds is wrong") + .isEqualTo(set.getId()); } @@ -203,7 +234,7 @@ public class TargetManagementTest extends AbstractIntegrationTest { * verifies, that all {@link TargetTag} of parameter. NOTE: it's accepted * that the target have additional tags assigned to them which are not * contained within parameter tags. - * + * * @param strict * if true, the given targets MUST contain EXACTLY ALL given * tags, AND NO OTHERS. If false, the given targets MUST contain @@ -226,10 +257,10 @@ public class TargetManagementTest extends AbstractIntegrationTest { } } if (strict) { - fail(); + fail("Target does not contain all tags"); } } - fail(); + fail("Target does not contain any tags or the expected tag was not found"); } } @@ -240,7 +271,7 @@ public class TargetManagementTest extends AbstractIntegrationTest { for (final Tag tag : tags) { for (final Tag tt : t.getTags()) { if (tag.getName().equals(tt.getName())) { - fail(); + fail("Target should have no tags"); } } } @@ -256,30 +287,33 @@ public class TargetManagementTest extends AbstractIntegrationTest { final Target target = TestDataUtil.buildTargetFixture(myCtrlID, "the description!"); Target savedTarget = targetManagement.createTarget(target); - assertNotNull(savedTarget); + assertNotNull("The target should not be null", savedTarget); final Long createdAt = savedTarget.getCreatedAt(); Long modifiedAt = savedTarget.getLastModifiedAt(); - assertEquals(createdAt, modifiedAt); - assertNotNull(savedTarget.getCreatedAt()); - assertNotNull(savedTarget.getLastModifiedAt()); - assertEquals(target, savedTarget); + + assertThat(createdAt).as("CreatedAt compared with modifiedAt").isEqualTo(modifiedAt); + assertNotNull("The createdAt attribut of the target should no be null", savedTarget.getCreatedAt()); + assertNotNull("The lastModifiedAt attribut of the target should no be null", savedTarget.getLastModifiedAt()); + assertThat(target).as("Target compared with saved target").isEqualTo(savedTarget); savedTarget.setDescription("changed description"); Thread.sleep(1); savedTarget = targetManagement.updateTarget(savedTarget); - - assertNotNull(savedTarget.getLastModifiedAt()); - assertNotEquals(createdAt, savedTarget.getLastModifiedAt()); - assertNotEquals(modifiedAt, savedTarget.getLastModifiedAt()); + assertNotNull("The lastModifiedAt attribute of the target should not be null", savedTarget.getLastModifiedAt()); + assertThat(createdAt).as("CreatedAt compared with saved modifiedAt") + .isNotEqualTo(savedTarget.getLastModifiedAt()); + assertThat(modifiedAt).as("ModifiedAt compared with saved modifiedAt") + .isNotEqualTo(savedTarget.getLastModifiedAt()); modifiedAt = savedTarget.getLastModifiedAt(); final Target foundTarget = targetManagement.findTargetByControllerID(savedTarget.getControllerId()); - - assertNotNull(foundTarget); - assertEquals(myCtrlID, foundTarget.getControllerId()); - assertEquals(savedTarget, foundTarget); - assertEquals(createdAt, foundTarget.getCreatedAt()); - assertEquals(modifiedAt, foundTarget.getLastModifiedAt()); + assertNotNull("The target should not be null", foundTarget); + assertThat(myCtrlID).as("ControllerId compared with saved controllerId") + .isEqualTo(foundTarget.getControllerId()); + assertThat(savedTarget).as("Target compared with saved target").isEqualTo(foundTarget); + assertThat(createdAt).as("CreatedAt compared with saved createdAt").isEqualTo(foundTarget.getCreatedAt()); + assertThat(modifiedAt).as("LastModifiedAt compared with saved lastModifiedAt") + .isEqualTo(foundTarget.getLastModifiedAt()); } @Test @@ -296,8 +330,11 @@ public class TargetManagementTest extends AbstractIntegrationTest { final Target savedExtra = targetManagement.createTarget(extra); Iterable allFound = targetRepository.findAll(); - assertEquals(firstList.size(), firstSaved.spliterator().getExactSizeIfKnown()); - assertEquals(firstList.size() + 1, allFound.spliterator().getExactSizeIfKnown()); + + assertThat(Long.valueOf(firstList.size())).as("List size of targets") + .isEqualTo(firstSaved.spliterator().getExactSizeIfKnown()); + assertThat(Long.valueOf(firstList.size() + 1)).as("LastModifiedAt compared with saved lastModifiedAt") + .isEqualTo(allFound.spliterator().getExactSizeIfKnown()); // change the objects and save to again to trigger a change on // lastModifiedAt @@ -308,18 +345,23 @@ public class TargetManagementTest extends AbstractIntegrationTest { _founds: for (final Target foundTarget : allFound) { for (final Target changedTarget : firstSaved) { if (changedTarget.getControllerId().equals(foundTarget.getControllerId())) { - assertEquals(changedTarget.getDescription(), foundTarget.getDescription()); - assertTrue(changedTarget.getName().startsWith(foundTarget.getName())); - assertTrue(changedTarget.getName().endsWith("changed")); - assertEquals(changedTarget.getCreatedAt(), foundTarget.getCreatedAt()); - assertThat(changedTarget.getLastModifiedAt()).isNotEqualTo(changedTarget.getCreatedAt()); - + assertThat(changedTarget.getDescription()) + .as("Description of changed target compared with description saved target") + .isEqualTo(foundTarget.getDescription()); + assertThat(changedTarget.getName()).as("Name of changed target starts with name of saved target") + .startsWith(foundTarget.getName()); + assertThat(changedTarget.getName()).as("Name of changed target ends with 'changed'") + .endsWith("changed"); + assertThat(changedTarget.getCreatedAt()).as("CreatedAt compared with saved createdAt") + .isEqualTo(foundTarget.getCreatedAt()); + assertThat(changedTarget.getLastModifiedAt()).as("LastModifiedAt compared with saved createdAt") + .isNotEqualTo(changedTarget.getCreatedAt()); continue _founds; } } if (!foundTarget.getControllerId().equals(savedExtra.getControllerId())) { - fail(); + fail("The controllerId of the found target is not equal to the controllerId of the saved target"); } } @@ -341,11 +383,10 @@ public class TargetManagementTest extends AbstractIntegrationTest { targetManagement.deleteTargets(deletedTargetIDs); allFound = targetManagement.findTargetsAll(new PageRequest(0, 200)).getContent(); - assertEquals(firstSaved.spliterator().getExactSizeIfKnown() - nr2Del, - allFound.spliterator().getExactSizeIfKnown()); + assertThat(firstSaved.spliterator().getExactSizeIfKnown() - nr2Del).as("Size of splited list") + .isEqualTo(allFound.spliterator().getExactSizeIfKnown()); - // verify that all undeleted are still found - assertThat(allFound).doesNotContain(deletedTargets); + assertThat(allFound).as("Not all undeleted found").doesNotContain(deletedTargets); } @Test @@ -375,16 +416,27 @@ public class TargetManagementTest extends AbstractIntegrationTest { targetInfo = targetInfoRepository.save(targetInfo); } final Query qry = entityManager.createNativeQuery("select * from sp_target_attributes ta"); - final List result = qry.getResultList(); - assertEquals(attribs.size() * ts.spliterator().getExactSizeIfKnown(), result.size()); + final List result = qry.getResultList(); + + assertThat(attribs.size() * ts.spliterator().getExactSizeIfKnown()).as("Amount of all target attributes") + .isEqualTo(result.size()); for (final Target myT : ts) { final Target t = targetManagement.findTargetByControllerIDWithDetails(myT.getControllerId()); - assertEquals(attribs.size(), t.getTargetInfo().getControllerAttributes().size()); + assertThat(attribs.size()).as("Amount of target attributes per target") + .isEqualTo(t.getTargetInfo().getControllerAttributes().size()); + for (final Entry ca : t.getTargetInfo().getControllerAttributes().entrySet()) { - assertTrue(attribs.containsKey(ca.getKey())); + assertTrue("Attributes list does not contain target attribute key", attribs.containsKey(ca.getKey())); // has the same value: see string concatenation above - assertEquals(String.format("%s-%s", attribs.get(ca.getKey()), t.getControllerId()), ca.getValue()); + // assertThat(String.format("%s-%s", + // attribs.get(ca.getKey()))).as("Value of string + // concatenation") + // .isEqualTo(ca.getValue()); + + assertEquals("The value of the string concatenation is not equal to the value of the target attributes", + String.format("%s-%s", attribs.get(ca.getKey()), t.getControllerId()), ca.getValue()); + } } @@ -427,7 +479,8 @@ public class TargetManagementTest extends AbstractIntegrationTest { final Target tNoAttrib = targetManagement.findTargetByControllerID(tNoAttribl.getControllerId()); if (tNoAttrib.getControllerId().equals(target.getControllerId())) { - assertThat(target.getTargetInfo().getControllerAttributes()).isEmpty(); + assertThat(target.getTargetInfo().getControllerAttributes()) + .as("Controller attributes should be empty").isEmpty(); continue restTarget_; } } @@ -439,7 +492,7 @@ public class TargetManagementTest extends AbstractIntegrationTest { if (tNoAttrib.getControllerId().equals(target.getControllerId())) { assertThat(target.getTargetInfo().getControllerAttributes().keySet().toArray()) - .doesNotContain(attribs2Del.toArray()); + .as("Controller attributes are wrong").doesNotContain(attribs2Del.toArray()); continue restTarget_; } } @@ -464,12 +517,14 @@ public class TargetManagementTest extends AbstractIntegrationTest { t2 = targetManagement.createTarget(t2); t1 = targetManagement.findTargetByControllerID(t1.getControllerId()); - assertThat(t1.getTags()).hasSize(noT1Tags).containsAll(t1Tags); - assertThat(t1.getTags()).hasSize(noT1Tags).doesNotContain(Iterables.toArray(t2Tags, TargetTag.class)); + assertThat(t1.getTags()).as("Tag size is wrong").hasSize(noT1Tags).containsAll(t1Tags); + assertThat(t1.getTags()).as("Tag size is wrong").hasSize(noT1Tags) + .doesNotContain(Iterables.toArray(t2Tags, TargetTag.class)); t2 = targetManagement.findTargetByControllerID(t2.getControllerId()); - assertThat(t2.getTags()).hasSize(noT2Tags).containsAll(t2Tags); - assertThat(t2.getTags()).hasSize(noT2Tags).doesNotContain(Iterables.toArray(t1Tags, TargetTag.class)); + assertThat(t2.getTags()).as("Tag size is wrong").hasSize(noT2Tags).containsAll(t2Tags); + assertThat(t2.getTags()).as("Tag size is wrong").hasSize(noT2Tags) + .doesNotContain(Iterables.toArray(t1Tags, TargetTag.class)); } @Test @@ -491,7 +546,7 @@ public class TargetManagementTest extends AbstractIntegrationTest { final TargetTag tagA = tagManagement.createTargetTag(new TargetTag("A")); final TargetTag tagB = tagManagement.createTargetTag(new TargetTag("B")); final TargetTag tagC = tagManagement.createTargetTag(new TargetTag("C")); - final TargetTag tagX = tagManagement.createTargetTag(new TargetTag("X")); + tagManagement.createTargetTag(new TargetTag("X")); // doing different assignments targetManagement.toggleTagAssignment(tagATargets, tagA); @@ -505,7 +560,8 @@ public class TargetManagementTest extends AbstractIntegrationTest { targetManagement.toggleTagAssignment(tagABCTargets, tagB); targetManagement.toggleTagAssignment(tagABCTargets, tagC); - assertThat(targetManagement.countTargetByFilters(null, null, null, Boolean.FALSE, "X")).isEqualTo(0); + assertThat(targetManagement.countTargetByFilters(null, null, null, Boolean.FALSE, "X")) + .as("Target count is wrong").isEqualTo(0); // search for targets with tag tagA final List targetWithTagA = new ArrayList(); @@ -535,11 +591,11 @@ public class TargetManagementTest extends AbstractIntegrationTest { // check again target lists refreshed from DB assertThat(targetManagement.countTargetByFilters(null, null, null, Boolean.FALSE, "A")) - .isEqualTo(targetWithTagA.size()); + .as("Target count is wrong").isEqualTo(targetWithTagA.size()); assertThat(targetManagement.countTargetByFilters(null, null, null, Boolean.FALSE, "B")) - .isEqualTo(targetWithTagB.size()); + .as("Target count is wrong").isEqualTo(targetWithTagB.size()); assertThat(targetManagement.countTargetByFilters(null, null, null, Boolean.FALSE, "C")) - .isEqualTo(targetWithTagC.size()); + .as("Target count is wrong").isEqualTo(targetWithTagC.size()); } @Test @@ -616,14 +672,15 @@ public class TargetManagementTest extends AbstractIntegrationTest { targetManagement.toggleTagAssignment(targAs, targTagA); assertThat(targetManagement.findTargetsByControllerIDsWithTags( - targAs.stream().map(target -> target.getControllerId()).collect(Collectors.toList()))).hasSize(25); + targAs.stream().map(target -> target.getControllerId()).collect(Collectors.toList()))) + .as("Target count is wrong").hasSize(25); // no lazy loading exception and tag correctly assigned assertThat(targetManagement .findTargetsByControllerIDsWithTags( targAs.stream().map(target -> target.getControllerId()).collect(Collectors.toList())) .stream().map(target -> target.getTags().contains(targTagA)).collect(Collectors.toList())) - .containsOnly(true); + .as("Tags not correctly assigned").containsOnly(true); } @Test @@ -638,7 +695,7 @@ public class TargetManagementTest extends AbstractIntegrationTest { final List findAllTargetIds = findAllTargetIdNames.stream().map(TargetIdName::getControllerId) .collect(Collectors.toList()); - assertThat(findAllTargetIds).containsOnly(createdTargetIds); + assertThat(findAllTargetIds).as("Target list has wrong content").containsOnly(createdTargetIds); } @Test @@ -656,9 +713,8 @@ public class TargetManagementTest extends AbstractIntegrationTest { final List targetsListWithNoTag = targetManagement .findTargetByFilters(new PageRequest(0, 500), null, null, null, Boolean.TRUE, tagNames).getContent(); - // Total targets - assertEquals(50, targetManagement.findAllTargetIds().size()); - // Targets with no tag - assertEquals(25, targetsListWithNoTag.size()); + assertThat(50).as("Total targets").isEqualTo(targetManagement.findAllTargetIds().size()); + assertThat(25).as("Targets with no tag").isEqualTo(targetsListWithNoTag.size()); + } } diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/TenantConfigurationManagementTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/TenantConfigurationManagementTest.java new file mode 100644 index 000000000..3425c6479 --- /dev/null +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/TenantConfigurationManagementTest.java @@ -0,0 +1,224 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository; + +import static org.fest.assertions.api.Assertions.assertThat; + +import java.time.Duration; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.hawkbit.AbstractIntegrationTestWithMongoDB; +import org.eclipse.hawkbit.repository.model.TenantConfigurationValue; +import org.eclipse.hawkbit.tenancy.configuration.DurationHelper; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; +import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationValidatorException; +import org.junit.Assert; +import org.junit.Test; + +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Component Tests - Repository") +@Stories("Tenant Configuration Management") +public class TenantConfigurationManagementTest extends AbstractIntegrationTestWithMongoDB { + + @Test + @Description("Tests that tenant specific configuration can be persisted and in case the tenant does not have specific configuration the default from environment is used instead.") + public void storeTenantSpecificConfigurationAsString() { + final TenantConfigurationKey configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_NAME; + final String envPropertyDefault = environment.getProperty(configKey.getDefaultKeyName()); + assertThat(envPropertyDefault).isNotNull(); + + // get the configuration from the system management + final TenantConfigurationValue defaultConfigValue = tenantConfigurationManagement + .getConfigurationValue(configKey, String.class); + + assertThat(defaultConfigValue.isGlobal()).isEqualTo(true); + assertThat(defaultConfigValue.getValue()).isEqualTo(envPropertyDefault); + + // update the tenant specific configuration + final String newConfigurationValue = "thisIsAnotherTokenName"; + assertThat(newConfigurationValue).isNotEqualTo(defaultConfigValue.getValue()); + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, newConfigurationValue); + + // verify that new configuration value is used + final TenantConfigurationValue updatedConfigurationValue = tenantConfigurationManagement + .getConfigurationValue(configKey, String.class); + + assertThat(updatedConfigurationValue.isGlobal()).isEqualTo(false); + assertThat(updatedConfigurationValue.getValue()).isEqualTo(newConfigurationValue); + // assertThat(tenantConfigurationManagement.getTenantConfigurations()).hasSize(1); + } + + @Test + @Description("Tests that the tenant specific configuration can be updated") + public void updateTenantSpecifcConfiguration() { + final TenantConfigurationKey configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_NAME; + final String value1 = "firstValue"; + final String value2 = "secondValue"; + + // add value first + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, value1); + assertThat(tenantConfigurationManagement.getConfigurationValue(configKey, String.class).getValue()) + .isEqualTo(value1); + + // update to value second + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, value2); + assertThat(tenantConfigurationManagement.getConfigurationValue(configKey, String.class).getValue()) + .isEqualTo(value2); + } + + @Test + @Description("Tests that the configuration value can be converted from String to Integer automatically") + public void storeAndUpdateTenantSpecificConfigurationAsBoolean() { + final TenantConfigurationKey configKey = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED; + final Boolean value1 = true; + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, value1); + assertThat(tenantConfigurationManagement.getConfigurationValue(configKey, Boolean.class).getValue()) + .isEqualTo(value1); + final Boolean value2 = false; + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, value2); + assertThat(tenantConfigurationManagement.getConfigurationValue(configKey, Boolean.class).getValue()) + .isEqualTo(value2); + } + + @Test(expected = TenantConfigurationValidatorException.class) + @Description("Tests that the get configuration throws exception in case the value cannot be automatically converted from String to Boolean") + public void wrongTenantConfigurationValueTypeThrowsException() { + final TenantConfigurationKey configKey = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_ENABLED; + final String value1 = "thisIsNotABoolean"; + // add value as String + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, value1); + } + + @Test + @Description("Tests that a deletion of a tenant specific configuration deletes it from the database.") + public void deleteConfigurationReturnNullConfiguration() { + final TenantConfigurationKey configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY; + + // gateway token does not have default value so no configuration value + // is should be available + final String defaultConfigValue = tenantConfigurationManagement.getConfigurationValue(configKey, String.class) + .getValue(); + assertThat(defaultConfigValue).isNull(); + + // update the tenant specific configuration + final String newConfigurationValue = "thisIsAnotherValueForPolling"; + assertThat(newConfigurationValue).isNotEqualTo(defaultConfigValue); + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, newConfigurationValue); + + // verify that new configuration value is used + final String updatedConfigurationValue = tenantConfigurationManagement + .getConfigurationValue(configKey, String.class).getValue(); + assertThat(updatedConfigurationValue).isEqualTo(newConfigurationValue); + + // delete the tenant specific configuration + tenantConfigurationManagement.deleteConfiguration(configKey); + // ensure that now gateway token is set again, because is deleted and + // must be null now + assertThat(tenantConfigurationManagement.getConfigurationValue(configKey, String.class).getValue()).isNull(); + } + + @Test(expected = TenantConfigurationValidatorException.class) + @Description("Test that an Exception is thrown, when an integer is stored but a string expected.") + public void storesIntegerWhenStringIsExpected() { + final TenantConfigurationKey configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_NAME; + final Integer wrongDataype = 123; + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongDataype); + } + + @Test(expected = TenantConfigurationValidatorException.class) + @Description("Test that an Exception is thrown, when an integer is stored but a boolean expected.") + public void storesIntegerWhenBooleanIsExpected() { + final TenantConfigurationKey configKey = TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED; + final Integer wrongDataype = 123; + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongDataype); + } + + @Test(expected = TenantConfigurationValidatorException.class) + @Description("Test that an Exception is thrown, when an integer is stored as PollingTime.") + public void storesIntegerWhenPollingIntervalIsExpected() { + final TenantConfigurationKey configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL; + final Integer wrongDataype = 123; + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongDataype); + } + + @Test(expected = TenantConfigurationValidatorException.class) + @Description("Test that an Exception is thrown, when an invalid formatted string is stored as PollingTime.") + public void storesWrongFormattedStringAsPollingInterval() { + final TenantConfigurationKey configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL; + final String wrongFormatted = "wrongFormatted"; + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, wrongFormatted); + } + + @Test(expected = TenantConfigurationValidatorException.class) + @Description("Test that an Exception is thrown, when an invalid formatted string is stored as PollingTime.") + public void storesTooSmallDurationAsPollingInterval() { + final TenantConfigurationKey configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL; + + final String tooSmallDuration = DurationHelper + .durationToFormattedString(DurationHelper.getDurationByTimeValues(0, 0, 1)); + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, tooSmallDuration); + } + + @Test + @Description("Stores a correct formatted PollignTime and reads it again.") + public void storesCorrectDurationAsPollingInterval() { + final TenantConfigurationKey configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL; + + final Duration duration = DurationHelper.getDurationByTimeValues(1, 2, 0); + assertThat(duration).isEqualTo(Duration.ofHours(1).plusMinutes(2)); + + tenantConfigurationManagement.addOrUpdateConfiguration(configKey, + DurationHelper.durationToFormattedString(duration)); + + final String storedDurationString = tenantConfigurationManagement.getConfigurationValue(configKey, String.class) + .getValue(); + assertThat(duration).isEqualTo(DurationHelper.formattedStringToDuration(storedDurationString)); + } + + @Test + @Description("Request a config value in a wrong Value") + public void requestConfigValueWithWrongType() { + try { + tenantConfigurationManagement.getConfigurationValue(TenantConfigurationKey.POLLING_TIME_INTERVAL, + Object.class); + Assert.fail(""); + } catch (final TenantConfigurationValidatorException e) { + + } + } + + @Test + @Description("Verifies that every TenenatConfiguraationKeyName exists only once") + public void verifyThatAllKeysAreDifferent() { + final Map keynames = new HashMap(); + + Arrays.stream(TenantConfigurationKey.values()).forEach(key -> { + + if (keynames.containsKey(key.getKeyName())) { + throw new IllegalStateException("The key names are not unique"); + } + + keynames.put(key.getKeyName(), null); + }); + } + + @Test + @Description("Get TenantConfigurationKeyByName") + public void getTenantConfigurationKeyByName() { + final TenantConfigurationKey configKey = TenantConfigurationKey.POLLING_TIME_INTERVAL; + + assertThat(TenantConfigurationKey.fromKeyName(configKey.getKeyName())).isEqualTo(configKey); + } + +} diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLActionFieldsTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLActionFieldsTest.java index 71cf511ae..8b20af27d 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLActionFieldsTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLActionFieldsTest.java @@ -26,7 +26,7 @@ import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Stories; -@Features("Component Tests - RSQL filtering") +@Features("Component Tests - Repository") @Stories("RSQL filter actions") public class RSQLActionFieldsTest extends AbstractIntegrationTest { @@ -72,7 +72,7 @@ public class RSQLActionFieldsTest extends AbstractIntegrationTest { try { assertRSQLQuery(ActionFields.STATUS.name() + "==true", 5); - fail(); + fail("Missing expected RSQLParameterUnsupportedFieldException because status cannot be compared with 'true'"); } catch (final RSQLParameterUnsupportedFieldException e) { } } diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLDistributionSetFieldTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLDistributionSetFieldTest.java index 014b7a055..ea1db0ed8 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLDistributionSetFieldTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLDistributionSetFieldTest.java @@ -9,6 +9,7 @@ package org.eclipse.hawkbit.repository.rsql; import static org.fest.assertions.api.Assertions.assertThat; +import static org.junit.Assert.fail; import java.util.Arrays; @@ -27,7 +28,7 @@ import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Stories; -@Features("Component Tests - RSQL filtering") +@Features("Component Tests - Repository") @Stories("RSQL filter distribution set") public class RSQLDistributionSetFieldTest extends AbstractIntegrationTest { @@ -43,7 +44,7 @@ public class RSQLDistributionSetFieldTest extends AbstractIntegrationTest { final DistributionSet ds2 = TestDataUtil .generateDistributionSets("NewDS", 3, softwareManagement, distributionSetManagement).get(0); - ds2.setDescription("DS2"); + ds2.setDescription("DS%"); ds2.getMetadata().add(new DistributionSetMetadata("metaKey", ds2, "value")); distributionSetManagement.updateDistributionSet(ds2); @@ -76,6 +77,7 @@ public class RSQLDistributionSetFieldTest extends AbstractIntegrationTest { public void testFilterByParameterDescription() { assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==DS", 1); assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==DS*", 2); + assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==DS%", 1); assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "==noExist*", 0); assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "=in=(DS,notexist)", 1); assertRSQLQuery(DistributionSetFields.DESCRIPTION.name() + "=out=(DS,notexist)", 3); @@ -94,7 +96,11 @@ public class RSQLDistributionSetFieldTest extends AbstractIntegrationTest { @Description("Test filter distribution set by complete property") public void testFilterByAttribute() { assertRSQLQuery(DistributionSetFields.COMPLETE.name() + "==true", 4); - assertRSQLQuery(DistributionSetFields.COMPLETE.name() + "==noExist*", 0); + try { + assertRSQLQuery(DistributionSetFields.COMPLETE.name() + "==noExist*", 0); + fail("Expected RSQLParameterSyntaxException"); + } catch (final RSQLParameterSyntaxException e) { + } assertRSQLQuery(DistributionSetFields.COMPLETE.name() + "=in=(true)", 4); assertRSQLQuery(DistributionSetFields.COMPLETE.name() + "=out=(true)", 0); } @@ -134,7 +140,7 @@ public class RSQLDistributionSetFieldTest extends AbstractIntegrationTest { final Page find = distributionSetManagement.findDistributionSetsAll( RSQLUtility.parse(rsqlParam, DistributionSetFields.class), new PageRequest(0, 100), false); final long countAll = find.getTotalElements(); - assertThat(find).isNotNull(); - assertThat(countAll).isEqualTo(excpectedEntity); + assertThat(find).as("Founded entity is should not be null").isNotNull(); + assertThat(countAll).as("Founded entity size is wrong").isEqualTo(excpectedEntity); } } diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLDistributionSetMetadataFieldsTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLDistributionSetMetadataFieldsTest.java index 2d113a0a3..1d1e8b7f3 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLDistributionSetMetadataFieldsTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLDistributionSetMetadataFieldsTest.java @@ -24,7 +24,7 @@ import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Stories; -@Features("Component Tests - RSQL filtering") +@Features("Component Tests - Repository") @Stories("RSQL filter distribution set metadata") public class RSQLDistributionSetMetadataFieldsTest extends AbstractIntegrationTest { diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLRolloutGroupFields.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLRolloutGroupFields.java new file mode 100644 index 000000000..78d333826 --- /dev/null +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLRolloutGroupFields.java @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.rsql; + +import static org.fest.assertions.api.Assertions.assertThat; + +import org.eclipse.hawkbit.AbstractIntegrationTest; +import org.eclipse.hawkbit.TestDataUtil; +import org.eclipse.hawkbit.repository.RolloutGroupFields; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupConditionBuilder; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition; +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; + +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Component Tests - Repository") +@Stories("RSQL filter rollout group") +public class RSQLRolloutGroupFields extends AbstractIntegrationTest { + + private Long rolloutGroupId; + private Rollout rollout; + + @Before + public void seuptBeforeTest() { + final int amountTargets = 20; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*"); + rollout = rolloutManagement.findRolloutById(rollout.getId()); + this.rolloutGroupId = rollout.getRolloutGroups().get(0).getId(); + } + + @Test + @Description("Test filter rollout group by id") + public void testFilterByParameterId() { + assertRSQLQuery(RolloutGroupFields.ID.name() + "==" + rolloutGroupId, 1); + assertRSQLQuery(RolloutGroupFields.ID.name() + "==noExist*", 0); + assertRSQLQuery(RolloutGroupFields.ID.name() + "=in=(" + rolloutGroupId + ")", 1); + assertRSQLQuery(RolloutGroupFields.ID.name() + "=out=(" + rolloutGroupId + ")", 3); + } + + @Test + @Description("Test filter rollout group by name") + public void testFilterByParameterName() { + assertRSQLQuery(RolloutGroupFields.NAME.name() + "==group-1", 1); + assertRSQLQuery(RolloutGroupFields.NAME.name() + "==*", 4); + assertRSQLQuery(RolloutGroupFields.NAME.name() + "==noExist*", 0); + assertRSQLQuery(RolloutGroupFields.NAME.name() + "=in=(group-1,group-2)", 2); + assertRSQLQuery(RolloutGroupFields.NAME.name() + "=out=(group-1,group-2)", 2); + } + + @Test + @Description("Test filter rollout group by description") + public void testFilterByParameterDescription() { + assertRSQLQuery(RolloutGroupFields.DESCRIPTION.name() + "==group-1", 1); + assertRSQLQuery(RolloutGroupFields.DESCRIPTION.name() + "==group*", 4); + assertRSQLQuery(RolloutGroupFields.DESCRIPTION.name() + "==noExist*", 0); + assertRSQLQuery(RolloutGroupFields.DESCRIPTION.name() + "=in=(group-1,notexist)", 1); + assertRSQLQuery(RolloutGroupFields.DESCRIPTION.name() + "=out=(group-1,notexist)", 3); + } + + private void assertRSQLQuery(final String rsqlParam, final long expcetedTargets) { + final Page findTargetPage = rolloutGroupManagement.findRolloutGroupsByPredicate(rollout, + RSQLUtility.parse(rsqlParam, RolloutGroupFields.class), new PageRequest(0, 100)); + final long countTargetsAll = findTargetPage.getTotalElements(); + assertThat(findTargetPage).isNotNull(); + assertThat(countTargetsAll).isEqualTo(expcetedTargets); + } + + private Rollout createRollout(final String name, final int amountGroups, final long distributionSetId, + final String targetFilterQuery) { + final Rollout rollout = new Rollout(); + rollout.setDistributionSet(distributionSetManagement.findDistributionSetById(distributionSetId)); + rollout.setName(name); + rollout.setTargetFilterQuery(targetFilterQuery); + return rolloutManagement.createRollout(rollout, amountGroups, new RolloutGroupConditionBuilder() + .successCondition(RolloutGroupSuccessCondition.THRESHOLD, "100").build()); + } +} diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLSoftwareModuleFieldTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLSoftwareModuleFieldTest.java index 8f2fba6da..88f0817f7 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLSoftwareModuleFieldTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLSoftwareModuleFieldTest.java @@ -23,7 +23,7 @@ import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Stories; -@Features("Component Tests - RSQL filtering") +@Features("Component Tests - Repository") @Stories("RSQL filter software module") public class RSQLSoftwareModuleFieldTest extends AbstractIntegrationTest { @@ -61,6 +61,7 @@ public class RSQLSoftwareModuleFieldTest extends AbstractIntegrationTest { public void testFilterByParameterName() { assertRSQLQuery(SoftwareModuleFields.NAME.name() + "==agent-hub", 1); assertRSQLQuery(SoftwareModuleFields.NAME.name() + "==agent-hub*", 2); + assertRSQLQuery(SoftwareModuleFields.NAME.name() + "!=agent-hub*", 2); assertRSQLQuery(SoftwareModuleFields.NAME.name() + "==noExist*", 0); assertRSQLQuery(SoftwareModuleFields.NAME.name() + "=in=(agent-hub,notexist)", 1); assertRSQLQuery(SoftwareModuleFields.NAME.name() + "=out=(agent-hub,notexist)", 3); @@ -94,12 +95,12 @@ public class RSQLSoftwareModuleFieldTest extends AbstractIntegrationTest { } @Test - @Description("") + @Description("Test filter software module by metadata") public void testFilterByMetadata() { assertRSQLQuery(SoftwareModuleFields.METADATA.name() + ".metaKey==metaValue", 1); assertRSQLQuery(SoftwareModuleFields.METADATA.name() + ".metaKey==*v*", 2); assertRSQLQuery(SoftwareModuleFields.METADATA.name() + ".metaKey==noExist*", 0); - assertRSQLQuery(SoftwareModuleFields.METADATA.name() + ".metaKey=in=(metaValue,notexist)", 1); + assertRSQLQuery(SoftwareModuleFields.METADATA.name() + ".metaKey=in=(metaValue,value)", 2); assertRSQLQuery(SoftwareModuleFields.METADATA.name() + ".metaKey=out=(metaValue,notexist)", 1); assertRSQLQuery(SoftwareModuleFields.METADATA.name() + ".notExist==metaValue", 0); diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLSoftwareModuleMetadataFieldsTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLSoftwareModuleMetadataFieldsTest.java index f75893b43..c863c1460 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLSoftwareModuleMetadataFieldsTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLSoftwareModuleMetadataFieldsTest.java @@ -24,7 +24,7 @@ import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Stories; -@Features("Component Tests - RSQL filtering") +@Features("Component Tests - Repository") @Stories("RSQL filter software module metadata") public class RSQLSoftwareModuleMetadataFieldsTest extends AbstractIntegrationTest { diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLSoftwareModuleTypeFieldsTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLSoftwareModuleTypeFieldsTest.java index 008de9199..268bddf50 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLSoftwareModuleTypeFieldsTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLSoftwareModuleTypeFieldsTest.java @@ -21,7 +21,7 @@ import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Stories; -@Features("Component Tests - RSQL filtering") +@Features("Component Tests - Repository") @Stories("RSQL filter software module test type") public class RSQLSoftwareModuleTypeFieldsTest extends AbstractIntegrationTest { @@ -55,7 +55,7 @@ public class RSQLSoftwareModuleTypeFieldsTest extends AbstractIntegrationTest { @Test @Description("Test filter software module test type by max") public void testFilterByMaxAssignment() { - assertRSQLQuery(SoftwareModuleTypeFields.MAX.name() + "==1", 3); + assertRSQLQuery(SoftwareModuleTypeFields.MAXASSIGNMENTS.name() + "==1", 3); } private void assertRSQLQuery(final String rsqlParam, final long excpectedEntity) { diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLTagFieldsTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLTagFieldsTest.java index e3ab8fc2c..b35ed13d1 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLTagFieldsTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLTagFieldsTest.java @@ -23,7 +23,7 @@ import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Stories; -@Features("Component Tests - RSQL filtering") +@Features("Component Tests - Repository") @Stories("RSQL filter target and distribution set tags") public class RSQLTagFieldsTest extends AbstractIntegrationTest { diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLTargetFieldTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLTargetFieldTest.java index 50b6600e9..54efab860 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLTargetFieldTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLTargetFieldTest.java @@ -30,7 +30,7 @@ import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Stories; -@Features("Component Tests - RSQL filtering") +@Features("Component Tests - Repository") @Stories("RSQL filter target") public class RSQLTargetFieldTest extends AbstractIntegrationTest { @@ -114,8 +114,9 @@ public class RSQLTargetFieldTest extends AbstractIntegrationTest { assertRSQLQuery(TargetFields.UPDATESTATUS.name() + "!=pending", 3); try { assertRSQLQuery(TargetFields.UPDATESTATUS.name() + "==noExist*", 0); - fail(); + fail("RSQLParameterUnsupportedFieldException was expected since update status unknown"); } catch (final RSQLParameterUnsupportedFieldException e) { + // test ok - exception was excepted } assertRSQLQuery(TargetFields.UPDATESTATUS.name() + "=in=(pending,error)", 1); assertRSQLQuery(TargetFields.UPDATESTATUS.name() + "=out=(pending,error)", 3); diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLUtilityTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLUtilityTest.java index 85092b560..356464bc2 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLUtilityTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/rsql/RSQLUtilityTest.java @@ -40,7 +40,7 @@ import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Stories; @RunWith(MockitoJUnitRunner.class) -@Features("Component Tests - RSQL filtering") +@Features("Component Tests - Repository") @Stories("RSQL search utility") // TODO: fully document tests -> @Description for long text and reasonable // method name as short text @@ -63,7 +63,7 @@ public class RSQLUtilityTest { try { RSQLUtility.parse(wrongRSQL, SoftwareModuleFields.class).toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock); - fail(); + fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax"); } catch (final RSQLParameterSyntaxException e) { } } @@ -75,7 +75,7 @@ public class RSQLUtilityTest { try { RSQLUtility.parse(wrongRSQL, SoftwareModuleFields.class).toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock); - fail(); + fail("Missing an expected RSQLParameterUnsupportedFieldException because of unknown RSQL field"); } catch (final RSQLParameterUnsupportedFieldException e) { } @@ -87,7 +87,7 @@ public class RSQLUtilityTest { try { RSQLUtility.parse(wrongRSQL, TargetFields.class).toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock); - fail(); + fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax"); } catch (final RSQLParameterUnsupportedFieldException e) { } @@ -95,7 +95,7 @@ public class RSQLUtilityTest { try { RSQLUtility.parse(wrongRSQL, TargetFields.class).toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock); - fail(); + fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax"); } catch (final RSQLParameterUnsupportedFieldException e) { } @@ -103,7 +103,7 @@ public class RSQLUtilityTest { try { RSQLUtility.parse(wrongRSQL, DistributionSetFields.class).toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock); - fail(); + fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax"); } catch (final RSQLParameterUnsupportedFieldException e) { } @@ -115,7 +115,7 @@ public class RSQLUtilityTest { try { RSQLUtility.parse(wrongRSQL, TargetFields.class).toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock); - fail(); + fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax"); } catch (final RSQLParameterUnsupportedFieldException e) { } @@ -123,7 +123,7 @@ public class RSQLUtilityTest { try { RSQLUtility.parse(wrongRSQL, TargetFields.class).toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock); - fail(); + fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax"); } catch (final RSQLParameterUnsupportedFieldException e) { } @@ -131,7 +131,7 @@ public class RSQLUtilityTest { try { RSQLUtility.parse(wrongRSQL, TargetFields.class).toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock); - fail(); + fail("Missing expected RSQLParameterSyntaxException because of wrong RSQL syntax"); } catch (final RSQLParameterUnsupportedFieldException e) { } } @@ -156,7 +156,7 @@ public class RSQLUtilityTest { } @Test - public void correctRsqlBuildsNotEqualPredicate() { + public void correctRsqlBuildsNotLikePredicate() { reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock); final String correctRsql = "name!=abc"; when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock); @@ -164,13 +164,37 @@ public class RSQLUtilityTest { when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class)); when(criteriaBuilderMock. greaterThanOrEqualTo(any(Expression.class), any(String.class))) .thenReturn(mock(Predicate.class)); + when(criteriaBuilderMock.upper(eq(pathOfString(baseSoftwareModuleRootMock)))) + .thenReturn(pathOfString(baseSoftwareModuleRootMock)); // test RSQLUtility.parse(correctRsql, SoftwareModuleFields.class).toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock); // verfication verify(criteriaBuilderMock, times(1)).and(any(Predicate.class)); - verify(criteriaBuilderMock, times(1)).notEqual(eq(baseSoftwareModuleRootMock), eq("abc")); + verify(criteriaBuilderMock, times(1)).notLike(eq(pathOfString(baseSoftwareModuleRootMock)), + eq("abc".toUpperCase())); + } + + @Test + public void correctRsqlBuildsLikePredicateWithPercentage() { + reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock); + final String correctRsql = "name==a%"; + when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock); + when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class); + when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class)); + when(criteriaBuilderMock. greaterThanOrEqualTo(any(Expression.class), any(String.class))) + .thenReturn(mock(Predicate.class)); + when(criteriaBuilderMock.upper(eq(pathOfString(baseSoftwareModuleRootMock)))) + .thenReturn(pathOfString(baseSoftwareModuleRootMock)); + // test + RSQLUtility.parse(correctRsql, SoftwareModuleFields.class).toPredicate(baseSoftwareModuleRootMock, + criteriaQueryMock, criteriaBuilderMock); + + // verfication + verify(criteriaBuilderMock, times(1)).and(any(Predicate.class)); + verify(criteriaBuilderMock, times(1)).like(eq(pathOfString(baseSoftwareModuleRootMock)), + eq("a\\%".toUpperCase())); } @Test @@ -191,27 +215,6 @@ public class RSQLUtilityTest { verify(criteriaBuilderMock, times(1)).lessThan(eq(pathOfString(baseSoftwareModuleRootMock)), eq("abc")); } - @Test - public void correctRsqlBuildsLikePredicate() { - reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock); - final String correctRsql = "name=li=abc"; - when(baseSoftwareModuleRootMock.get("name")).thenReturn(baseSoftwareModuleRootMock); - when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class); - when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class)); - when(criteriaBuilderMock. greaterThanOrEqualTo(any(Expression.class), any(String.class))) - .thenReturn(mock(Predicate.class)); - when(criteriaBuilderMock.upper(eq(pathOfString(baseSoftwareModuleRootMock)))) - .thenReturn(pathOfString(baseSoftwareModuleRootMock)); - // test - RSQLUtility.parse(correctRsql, SoftwareModuleFields.class).toPredicate(baseSoftwareModuleRootMock, - criteriaQueryMock, criteriaBuilderMock); - - // verfication - verify(criteriaBuilderMock, times(1)).and(any(Predicate.class)); - verify(criteriaBuilderMock, times(1)).like(eq(pathOfString(baseSoftwareModuleRootMock)), - eq("abc".toUpperCase())); - } - @Test public void correctRsqlWithEnumValue() { reset(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock); diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/utils/MultipleInvokeHelper.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/utils/MultipleInvokeHelper.java new file mode 100644 index 000000000..d40fe1fd0 --- /dev/null +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/utils/MultipleInvokeHelper.java @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.utils; + +import java.util.concurrent.Callable; + +/** + * Helper to call a request multiple times regarding a given condition until + * timeout is reached. + * + */ +public final class MultipleInvokeHelper { + + /** + * Call with timeout until result is not null. + * + * @param callable + * class + * @param timeout + * value + * @param pollInterval + * value + * @return + * @throws Exception + */ + public static T doWithTimeoutUntilResultIsNotNull(final Callable callable, final long timeout, + final long pollInterval) throws Exception // NOPMD + { + return doWithTimeout(callable, new SuccessCondition() { + @Override + public boolean success(final T result) { + return result != null; + }; + }, timeout, pollInterval); + } + + /** + * Call with timeout. + * + * @param callable + * class + * @param successCondition + * class + * @param timeout + * value + * @param pollInterval + * value + * @return + * @throws Exception + */ + public static T doWithTimeout(final Callable callable, final SuccessCondition successCondition, + final long timeout, final long pollInterval) throws Exception // NOPMD + { + + if (pollInterval < 0) { + throw new IllegalArgumentException("pollInterval must non negative"); + } + + long duration = 0; + Exception exception = null; + T returnValue = null; + while (untilTimeoutReached(timeout, duration)) { + try { + returnValue = callable.call(); + // clear exception + exception = null; + } catch (final Exception ex) { + exception = ex; + } + Thread.sleep(pollInterval); + duration += pollInterval > 0 ? pollInterval : 1; + if (exception == null && successCondition.success(returnValue)) { + return returnValue; + } else { + returnValue = null; + } + } + if (exception != null) { + throw exception; + } + return returnValue; + } + + private static boolean untilTimeoutReached(final long timeout, final long duration) { + return duration <= timeout || timeout < 0; + } + +} diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/utils/RepositoryDataGenerator.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/utils/RepositoryDataGenerator.java index dffec4df1..d7efa50d5 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/utils/RepositoryDataGenerator.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/utils/RepositoryDataGenerator.java @@ -199,13 +199,14 @@ public final class RepositoryDataGenerator { } - private void createActionStatusHistory(final List actions, final int sizeMultiplikator) { + private void createActionStatusHistory(final List actions, final int sizeMultiplikator) { final AtomicInteger counter = new AtomicInteger(); int index = 0; - for (final Action actionGiven : actions) { + for (final Long actionGiven : actions) { // retrieved - Action action = controllerManagement.registerRetrieved(actionGiven, + Action action = controllerManagement.registerRetrieved( + deploymentManagement.findActionWithDetails(actionGiven), "Controller retrieved update action and should start now the download."); // download @@ -260,10 +261,11 @@ public final class RepositoryDataGenerator { } } - private void createSimpleActionStatusHistory(final List actions) { - for (final Action actionGiven : actions) { + private void createSimpleActionStatusHistory(final List actions) { + for (final Long actionGiven : actions) { // retrieved - Action action = controllerManagement.registerRetrieved(actionGiven, + Action action = controllerManagement.registerRetrieved( + deploymentManagement.findActionWithDetails(actionGiven), "Controller retrieved update action and should start now the download."); // close diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/utils/SuccessCondition.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/utils/SuccessCondition.java new file mode 100644 index 000000000..6c9553dc8 --- /dev/null +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/repository/utils/SuccessCondition.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.repository.utils; + +/** + * SuccessCondition Interface. + * + * @param + * type of the value to get verified + */ +public interface SuccessCondition { + + /** + * The implementation of the success condition. + * + * @param result + * @return + */ + boolean success(final T result); + +} diff --git a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/tenancy/MultiTenancyEntityTest.java b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/tenancy/MultiTenancyEntityTest.java index 51e49ef04..1dd4c9823 100644 --- a/hawkbit-repository/src/test/java/org/eclipse/hawkbit/tenancy/MultiTenancyEntityTest.java +++ b/hawkbit-repository/src/test/java/org/eclipse/hawkbit/tenancy/MultiTenancyEntityTest.java @@ -10,8 +10,6 @@ package org.eclipse.hawkbit.tenancy; import static org.fest.assertions.api.Assertions.assertThat; -import java.util.concurrent.Callable; - import org.eclipse.hawkbit.AbstractIntegrationTest; import org.eclipse.hawkbit.WithSpringAuthorityRule; import org.eclipse.hawkbit.WithUser; @@ -31,9 +29,6 @@ import ru.yandex.qatools.allure.annotations.Stories; * CRUD-Operations are tenant aware and cannot access or delete entities not * belonging to the current tenant. * - * - * - * */ @Features("Component Tests - Repository") @Stories("Multi Tenancy") @@ -97,13 +92,9 @@ public class MultiTenancyEntityTest extends AbstractIntegrationTest { // check that the cache is not getting in the way, i.e. "bumlux" results // in bumlux and not // mytenant - assertThat( - securityRule.runAs(WithSpringAuthorityRule.withUserAndTenant("user", "bumlux"), new Callable() { - @Override - public String call() throws Exception { - return systemManagement.getTenantMetadata().getTenant().toUpperCase(); - } - })).isEqualTo("bumlux".toUpperCase()); + assertThat(securityRule.runAs(WithSpringAuthorityRule.withUserAndTenant("user", "bumlux"), + () -> systemManagement.getTenantMetadata().getTenant().toUpperCase())) + .isEqualTo("bumlux".toUpperCase()); } @Test @@ -154,59 +145,38 @@ public class MultiTenancyEntityTest extends AbstractIntegrationTest { } private Target createTargetForTenant(final String controllerId, final String tenant) throws Exception { - return securityRule.runAs(WithSpringAuthorityRule.withUserAndTenant("user", tenant), new Callable() { - @Override - public Target call() throws Exception { - return targetManagement.createTarget(new Target(controllerId)); - } - }); + return securityRule.runAs(WithSpringAuthorityRule.withUserAndTenant("user", tenant), + () -> targetManagement.createTarget(new Target(controllerId))); } private Slice findTargetsForTenant(final String tenant) throws Exception { return securityRule.runAs(WithSpringAuthorityRule.withUserAndTenant("user", tenant), - new Callable>() { - @Override - public Slice call() throws Exception { - return targetManagement.findTargetsAll(pageReq); - } - }); + () -> targetManagement.findTargetsAll(pageReq)); } private void deleteTargetsForTenant(final String tenant, final Long... targetIds) throws Exception { - securityRule.runAs(WithSpringAuthorityRule.withUserAndTenant("user", tenant), new Callable() { - @Override - public Void call() throws Exception { - targetManagement.deleteTargets(targetIds); - return null; - } + securityRule.runAs(WithSpringAuthorityRule.withUserAndTenant("user", tenant), () -> { + targetManagement.deleteTargets(targetIds); + return null; }); } private DistributionSet createDistributionSetForTenant(final String name, final String version, final String tenant) throws Exception { - return securityRule.runAs(WithSpringAuthorityRule.withUserAndTenant("user", tenant), - new Callable() { - @Override - public DistributionSet call() throws Exception { - final DistributionSet ds = new DistributionSet(); - ds.setName(name); - ds.setTenant(tenant); - ds.setVersion(version); - ds.setType(distributionSetManagement - .createDistributionSetType(new DistributionSetType("typetest", "test", "foobar"))); - return distributionSetManagement.createDistributionSet(ds); - } - }); + return securityRule.runAs(WithSpringAuthorityRule.withUserAndTenant("user", tenant), () -> { + final DistributionSet ds = new DistributionSet(); + ds.setName(name); + ds.setTenant(tenant); + ds.setVersion(version); + ds.setType(distributionSetManagement + .createDistributionSetType(new DistributionSetType("typetest", "test", "foobar"))); + return distributionSetManagement.createDistributionSet(ds); + }); } private Page findDistributionSetForTenant(final String tenant) throws Exception { return securityRule.runAs(WithSpringAuthorityRule.withUserAndTenant("user", tenant), - new Callable>() { - @Override - public Page call() throws Exception { - return distributionSetManagement.findDistributionSetsAll(pageReq, false, false); - } - }); + () -> distributionSetManagement.findDistributionSetsAll(pageReq, false, false)); } } diff --git a/hawkbit-repository/src/test/resources/application-test.properties b/hawkbit-repository/src/test/resources/application-test.properties index e5fb04a21..b1904f911 100644 --- a/hawkbit-repository/src/test/resources/application-test.properties +++ b/hawkbit-repository/src/test/resources/application-test.properties @@ -10,7 +10,8 @@ spring.data.mongodb.uri=mongodb://localhost/spArtifactRepository${random.value} spring.data.mongodb.port=28017 -hawkbit.server.controller.security.authentication.header.enabled=true +hawkbit.server.ddi.security.authentication.header.enabled=true +hawkbit.server.ddi.security.authentication.gatewaytoken.name=TestToken hawkbit.server.artifact.repo.upload.maxFileSize=5MB @@ -29,11 +30,6 @@ flyway.initOnMigrate=true flyway.sqlMigrationSuffix=${spring.jpa.database}.sql #spring.jpa.show-sql=true -# SP Controller configuration +# DDI configuration hawkbit.controller.pollingTime=00:01:00 -hawkbit.controller.pollingOverdueTime=00:01:00 - -## Configuration for RabbitMQ integration -hawkbit.dmf.rabbitmq.deadLetterQueue=dmf_connector_deadletter -hawkbit.dmf.rabbitmq.deadLetterExchange=dmf.connector.deadletter -hawkbit.dmf.rabbitmq.receiverQueue=dmf_receiver +hawkbit.controller.pollingOverdueTime=00:01:00 \ No newline at end of file diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/RestConstants.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/RestConstants.java index 33b154844..bde96f7a8 100644 --- a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/RestConstants.java +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/RestConstants.java @@ -116,6 +116,8 @@ public final class RestConstants { * The target URL mapping rest resource. */ public static final String TARGET_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/targets"; + + public static final String SYSTEM_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/system"; /** * The software module URL mapping rest resource. */ @@ -154,6 +156,16 @@ public final class RestConstants { */ public static final String DISTRIBUTIONSET_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/distributionsets"; + /** + * The rollout URL mapping rest resource. + */ + public static final String ROLLOUT_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/rollouts"; + + /** + * Request parameter for async + */ + public static final String REQUEST_PARAMETER_ASYNC = "async"; + /** * The target URL mapping, href link for artifact download. */ diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/api/DistributionSetRestApi.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/api/DistributionSetRestApi.java new file mode 100644 index 000000000..05d377950 --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/api/DistributionSetRestApi.java @@ -0,0 +1,365 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.eclipse.hawkbit.rest.resource.api; + +import java.util.List; + +import org.eclipse.hawkbit.rest.resource.RestConstants; +import org.eclipse.hawkbit.rest.resource.model.MetadataRest; +import org.eclipse.hawkbit.rest.resource.model.MetadataRestPageList; +import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetPagedList; +import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetRequestBodyPost; +import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetRequestBodyPut; +import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetRest; +import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetsRest; +import org.eclipse.hawkbit.rest.resource.model.distributionset.TargetAssignmentRequestBody; +import org.eclipse.hawkbit.rest.resource.model.distributionset.TargetAssignmentResponseBody; +import org.eclipse.hawkbit.rest.resource.model.softwaremodule.SoftwareModuleAssigmentRest; +import org.eclipse.hawkbit.rest.resource.model.softwaremodule.SoftwareModulePagedList; +import org.eclipse.hawkbit.rest.resource.model.target.TargetPagedList; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * REST Resource handling for DistributionSet CRUD operations. + */ +@RequestMapping(RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING) +public interface DistributionSetRestApi { + + /** + * Handles the GET request of retrieving all DistributionSets . + * + * @param pagingOffsetParam + * the offset of list of sets for pagination, might not be + * present in the rest request then default value will be applied + * @param pagingLimitParam + * the limit of the paged request, might not be present in the + * rest request then default value will be applied + * @param sortParam + * the sorting parameter in the request URL, syntax + * {@code field:direction, field:direction} + * @param rsqlParam + * the search parameter in the request URL, syntax + * {@code q=name==abc} + * @return a list of all set for a defined or default page request with + * status OK. The response is always paged. In any failure the + * JsonResponseExceptionHandler is handling the response. + */ + @RequestMapping(method = RequestMethod.GET, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getDistributionSets( + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam); + + /** + * Handles the GET request of retrieving a single DistributionSet . + * + * @param distributionSetId + * the ID of the set to retrieve + * + * @return a single DistributionSet with status OK. + * + * @throws EntityNotFoundException + * in case no DistributionSet with the given ID exists. + */ + @RequestMapping(method = RequestMethod.GET, value = "/{distributionSetId}", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getDistributionSet( + @PathVariable("distributionSetId") final Long distributionSetId); + + /** + * Handles the POST request of creating new distribution sets . The request + * body must always be a list of sets. + * + * @param sets + * the DistributionSets to be created. + * @return In case all sets could successful created the ResponseEntity with + * status code 201 - Created but without ResponseBody. In any + * failure the JsonResponseExceptionHandler is handling the + * response. + */ + @RequestMapping(method = RequestMethod.POST, consumes = { MediaType.APPLICATION_JSON_VALUE, + "application/hal+json" }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity createDistributionSets( + @RequestBody final List sets); + + /** + * Handles the DELETE request for a single DistributionSet . + * + * @param distributionSetId + * the ID of the DistributionSet to delete + * @return status OK if delete as successful. + * + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/{distributionSetId}") + public ResponseEntity deleteDistributionSet(@PathVariable("distributionSetId") final Long distributionSetId); + + /** + * Handles the UPDATE request for a single DistributionSet . + * + * @param distributionSetId + * the ID of the DistributionSet to delete + * @param toUpdate + * with the data that needs updating + * + * @return status OK if update as successful with updated content. + * + */ + @RequestMapping(method = RequestMethod.PUT, value = "/{distributionSetId}", consumes = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) + public ResponseEntity updateDistributionSet( + @PathVariable("distributionSetId") final Long distributionSetId, + @RequestBody final DistributionSetRequestBodyPut toUpdate); + + /** + * Handles the GET request of retrieving assigned targets to a specific + * distribution set. + * + * @param distributionSetId + * the ID of the distribution set to retrieve the assigned + * targets + * @param pagingOffsetParam + * the offset of list of targets for pagination, might not be + * present in the rest request then default value will be applied + * @param pagingLimitParam + * the limit of the paged request, might not be present in the + * rest request then default value will be applied + * @param sortParam + * the sorting parameter in the request URL, syntax + * {@code field:direction, field:direction} + * @param rsqlParam + * the search parameter in the request URL, syntax + * {@code q=name==abc} + * @return status OK if get request is successful with the paged list of + * targets + */ + @RequestMapping(method = RequestMethod.GET, value = "/{distributionSetId}/assignedTargets", produces = { + MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) + public ResponseEntity getAssignedTargets( + @PathVariable("distributionSetId") final Long distributionSetId, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam); + + /** + * Handles the GET request of retrieving installed targets to a specific + * distribution set. + * + * @param distributionSetId + * the ID of the distribution set to retrieve the assigned + * targets + * @param pagingOffsetParam + * the offset of list of targets for pagination, might not be + * present in the rest request then default value will be applied + * @param pagingLimitParam + * the limit of the paged request, might not be present in the + * rest request then default value will be applied + * @param sortParam + * the sorting parameter in the request URL, syntax + * {@code field:direction, field:direction} + * @param rsqlParam + * the search parameter in the request URL, syntax + * {@code q=name==abc} + * @return status OK if get request is successful with the paged list of + * targets + */ + @RequestMapping(method = RequestMethod.GET, value = "/{distributionSetId}/installedTargets", produces = { + MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) + public ResponseEntity getInstalledTargets( + @PathVariable("distributionSetId") final Long distributionSetId, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam); + + /** + * Handles the POST request of assigning multiple targets to a single + * distribution set. + * + * @param distributionSetId + * the ID of the distribution set within the URL path parameter + * @param targetIds + * the IDs of the target which should get assigned to the + * distribution set given in the response body + * @return status OK if the assignment of the targets was successful and a + * complex return body which contains information about the assigned + * targets and the already assigned targets counters + */ + @RequestMapping(method = RequestMethod.POST, value = "/{distributionSetId}/assignedTargets", consumes = { + "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) + public ResponseEntity createAssignedTarget( + @PathVariable("distributionSetId") final Long distributionSetId, + @RequestBody final List targetIds); + + /** + * Gets a paged list of meta data for a distribution set. + * + * @param distributionSetId + * the ID of the distribution set for the meta data + * @param pagingOffsetParam + * the offset of list of targets for pagination, might not be + * present in the rest request then default value will be applied + * @param pagingLimitParam + * the limit of the paged request, might not be present in the + * rest request then default value will be applied + * @param sortParam + * the sorting parameter in the request URL, syntax + * {@code field:direction, field:direction} + * @param rsqlParam + * the search parameter in the request URL, syntax + * {@code q=key==abc} + * @return status OK if get request is successful with the paged list of + * meta data + */ + @RequestMapping(method = RequestMethod.GET, value = "/{distributionSetId}/metadata", produces = { + MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) + public ResponseEntity getMetadata( + @PathVariable("distributionSetId") final Long distributionSetId, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam); + + /** + * Gets a single meta data value for a specific key of a distribution set. + * + * @param distributionSetId + * the ID of the distribution set to get the meta data from + * @param metadataKey + * the key of the meta data entry to retrieve the value from + * @return status OK if get request is successful with the value of the meta + * data + */ + @RequestMapping(method = RequestMethod.GET, value = "/{distributionSetId}/metadata/{metadataKey}", produces = { + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getMetadataValue( + @PathVariable("distributionSetId") final Long distributionSetId, + @PathVariable("metadataKey") final String metadataKey); + + /** + * Updates a single meta data value of a distribution set. + * + * @param distributionSetId + * the ID of the distribution set to update the meta data entry + * @param metadataKey + * the key of the meta data to update the value + * @return status OK if the update request is successful and the updated + * meta data result + */ + @RequestMapping(method = RequestMethod.PUT, value = "/{distributionSetId}/metadata/{metadataKey}", produces = { + MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) + public ResponseEntity updateMetadata(@PathVariable("distributionSetId") final Long distributionSetId, + @PathVariable("metadataKey") final String metadataKey, @RequestBody final MetadataRest metadata); + + /** + * Deletes a single meta data entry from the distribution set. + * + * @param distributionSetId + * the ID of the distribution set to delete the meta data entry + * @param metadataKey + * the key of the meta data to delete + * @return status OK if the delete request is successful + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/{distributionSetId}/metadata/{metadataKey}") + public ResponseEntity deleteMetadata(@PathVariable("distributionSetId") final Long distributionSetId, + @PathVariable("metadataKey") final String metadataKey); + + /** + * Creates a list of meta data for a specific distribution set. + * + * @param distributionSetId + * the ID of the distribution set to create meta data for + * @param metadataRest + * the list of meta data entries to create + * @return status created if post request is successful with the value of + * the created meta data + */ + @RequestMapping(method = RequestMethod.POST, value = "/{distributionSetId}/metadata", consumes = { + MediaType.APPLICATION_JSON_VALUE, + "application/hal+json" }, produces = { MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) + public ResponseEntity> createMetadata( + @PathVariable("distributionSetId") final Long distributionSetId, + @RequestBody final List metadataRest); + + /** + * Assigns a list of software modules to a distribution set. + * + * @param distributionSetId + * the ID of the distribution set to assign software modules for + * @param softwareModuleIDs + * the list of software modules ids to assign + * @return http status + * + * @throws EntityNotFoundException + * in case no distribution set with the given + * {@code distributionSetId} exists. + */ + @RequestMapping(method = RequestMethod.POST, value = "/{distributionSetId}/assignedSM", consumes = { + MediaType.APPLICATION_JSON_VALUE, + "application/hal+json" }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity assignSoftwareModules(@PathVariable("distributionSetId") final Long distributionSetId, + @RequestBody final List softwareModuleIDs); + + /** + * Deletes the assignment of the software module form the distribution set. + * + * @param distributionSetId + * the ID of the distribution set to reject the software module + * for + * @param softwareModuleId + * the software module id to get rejected form the distribution + * set + * @return status OK if rejection was successful. + * @throws EntityNotFoundException + * in case no distribution set with the given + * {@code distributionSetId} exists. + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/{distributionSetId}/assignedSM/{softwareModuleId}") + public ResponseEntity deleteAssignSoftwareModules( + @PathVariable("distributionSetId") final Long distributionSetId, + @PathVariable("softwareModuleId") final Long softwareModuleId); + + /** + * Handles the GET request for retrieving the assigned software modules of a + * specific distribution set. + * + * @param distributionSetId + * the ID of the distribution to retrieve + * @param pagingOffsetParam + * the offset of list of sets for pagination, might not be + * present in the rest request then default value will be applied + * @param pagingLimitParam + * the limit of the paged request, might not be present in the + * rest request then default value will be applied + * @param sortParam + * the sorting parameter in the request URL, syntax + * {@code field:direction, field:direction} + * @return a list of the assigned software modules of a distribution set + * with status OK, if none is assigned than {@code null} + * @throws EntityNotFoundException + * in case no distribution set with the given + * {@code distributionSetId} exists. + */ + @RequestMapping(method = RequestMethod.GET, value = "/{distributionSetId}/assignedSM", produces = { + "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getAssignedSoftwareModules( + @PathVariable("distributionSetId") final Long distributionSetId, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam); +} diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/api/DistributionSetTagRestApi.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/api/DistributionSetTagRestApi.java new file mode 100644 index 000000000..d99368dc2 --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/api/DistributionSetTagRestApi.java @@ -0,0 +1,215 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource.api; + +import java.util.List; + +import org.eclipse.hawkbit.rest.resource.RestConstants; +import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetsRest; +import org.eclipse.hawkbit.rest.resource.model.tag.AssignedDistributionSetRequestBody; +import org.eclipse.hawkbit.rest.resource.model.tag.DistributionSetTagAssigmentResultRest; +import org.eclipse.hawkbit.rest.resource.model.tag.TagPagedList; +import org.eclipse.hawkbit.rest.resource.model.tag.TagRequestBodyPut; +import org.eclipse.hawkbit.rest.resource.model.tag.TagRest; +import org.eclipse.hawkbit.rest.resource.model.tag.TagsRest; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * REST Resource handling for DistributionSetTag CRUD operations. + * + */ +@RequestMapping(RestConstants.DISTRIBUTIONSET_TAG_V1_REQUEST_MAPPING) +public interface DistributionSetTagRestApi { + /** + * Handles the GET request of retrieving all DistributionSet tags. + * + * @param pagingOffsetParam + * the offset of list of DistributionSet tags for pagination, + * might not be present in the rest request then default value + * will be applied + * @param pagingLimitParam + * the limit of the paged request, might not be present in the + * rest request then default value will be applied + * @param sortParam + * the sorting parameter in the request URL, syntax + * {@code field:direction, field:direction} + * @param rsqlParam + * the search parameter in the request URL, syntax + * {@code q=name==abc} + * @return a list of all target tags for a defined or default page request + * with status OK. The response is always paged. In any failure the + * JsonResponseExceptionHandler is handling the response. + */ + @RequestMapping(method = RequestMethod.GET, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getDistributionSetTags( + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam); + + /** + * Handles the GET request of retrieving a single distribution set tag. + * + * @param distributionsetTagId + * the ID of the distribution set tag to retrieve + * + * @return a single distribution set tag with status OK. + * @throws EntityNotFoundException + * in case the given {@code distributionsetTagId} doesn't + * exists. + */ + @RequestMapping(method = RequestMethod.GET, value = "/{distributionsetTagId}", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getDistributionSetTag( + @PathVariable("distributionsetTagId") final Long distributionsetTagId); + + /** + * Handles the POST request of creating new distribution set tag. The + * request body must always be a list of tags. + * + * @param tags + * the distribution set tags to be created. + * @return In case all modules could successful created the ResponseEntity + * with status code 201 - Created. The Response Body are the created + * distribution set tags but without ResponseBody. + */ + @RequestMapping(method = RequestMethod.POST, consumes = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity createDistributionSetTags(@RequestBody final List tags); + + /** + * + * Handles the PUT request of updating a single distribution set tag. + * + * @param distributionsetTagId + * the ID of the distribution set tag + * @param restDSTagRest + * the the request body to be updated + * @return status OK if update is successful and the updated distribution + * set tag. + * @throws EntityNotFoundException + * in case the given {@code distributionsetTagId} doesn't + * exists. + */ + @RequestMapping(method = RequestMethod.PUT, value = "/{distributionsetTagId}", consumes = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity updateDistributionSetTag( + @PathVariable("distributionsetTagId") final Long distributionsetTagId, + @RequestBody final TagRequestBodyPut restDSTagRest); + + /** + * Handles the DELETE request for a single distribution set tag. + * + * @param distributionsetTagId + * the ID of the distribution set tag + * @return status OK if delete as successfully. + * @throws EntityNotFoundException + * in case the given {@code distributionsetTagId} doesn't + * exists. + * + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/{distributionsetTagId}") + public ResponseEntity deleteDistributionSetTag( + @PathVariable("distributionsetTagId") final Long distributionsetTagId); + + /** + * Handles the GET request of retrieving all assigned distribution sets by + * the given tag id. + * + * @param distributionsetTagId + * the ID of the distribution set tag + * + * @return the list of assigned distribution sets. + * @throws EntityNotFoundException + * in case the given {@code distributionsetTagId} doesn't + * exists. + */ + @RequestMapping(method = RequestMethod.GET, value = RestConstants.DISTRIBUTIONSET_REQUEST_MAPPING) + public ResponseEntity getAssignedDistributionSets( + @PathVariable("distributionsetTagId") final Long distributionsetTagId); + + /** + * Handles the POST request to toggle the assignment of distribution sets by + * the given tag id. + * + * @param distributionsetTagIds + * the ID of the distribution set tag to retrieve + * @param assignedDSRequestBodies + * list of distribution set ids to be toggled + * + * @return the list of assigned distribution sets and unassigned + * distribution sets. + * @throws EntityNotFoundException + * in case the given {@code distributionsetTagId} doesn't + * exists. + */ + @RequestMapping(method = RequestMethod.POST, value = RestConstants.DISTRIBUTIONSET_REQUEST_MAPPING + + "/toggleTagAssignment") + public ResponseEntity toggleTagAssignment( + @PathVariable("distributionsetTagId") final Long distributionsetTagId, + @RequestBody final List assignedDSRequestBodies); + + /** + * Handles the POST request to assign distribution sets to the given tag id. + * + * @param distributionsetTagId + * the ID of the distribution set tag to retrieve + * @param assignedDSRequestBodies + * list of distribution sets ids to be assigned + * + * @return the list of assigned distribution set. + * @throws EntityNotFoundException + * in case the given {@code distributionsetTagId} doesn't + * exists. + */ + @RequestMapping(method = RequestMethod.POST, value = RestConstants.DISTRIBUTIONSET_REQUEST_MAPPING) + public ResponseEntity assignDistributionSets( + @PathVariable("distributionsetTagId") final Long distributionsetTagId, + @RequestBody final List assignedDSRequestBodies); + + /** + * Handles the DELETE request to unassign all distribution set from the + * given tag id. + * + * @param distributionsetTagId + * the ID of the distribution set tag to retrieve + * @return http status code + * @throws EntityNotFoundException + * in case the given {@code distributionsetTagId} doesn't + * exists. + */ + @RequestMapping(method = RequestMethod.DELETE, value = RestConstants.DISTRIBUTIONSET_REQUEST_MAPPING) + public ResponseEntity unassignDistributionSets( + @PathVariable("distributionsetTagId") final Long distributionsetTagId); + + /** + * Handles the DELETE request to unassign one distribution set from the + * given tag id. + * + * @param distributionsetTagId + * the ID of the distribution set tag + * @param distributionsetId + * the ID of the distribution set to unassign + * @return http status code + * @throws EntityNotFoundException + * in case the given {@code distributionsetTagId} doesn't + * exists. + */ + @RequestMapping(method = RequestMethod.DELETE, value = RestConstants.DISTRIBUTIONSET_REQUEST_MAPPING + + "/{distributionsetId}") + public ResponseEntity unassignDistributionSet( + @PathVariable("distributionsetTagId") final Long distributionsetTagId, + @PathVariable("distributionsetId") final Long distributionsetId); +} diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/api/DistributionSetTypeRestApi.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/api/DistributionSetTypeRestApi.java new file mode 100644 index 000000000..9bed7f4f8 --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/api/DistributionSetTypeRestApi.java @@ -0,0 +1,259 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource.api; + +import java.util.List; + +import org.eclipse.hawkbit.rest.resource.RestConstants; +import org.eclipse.hawkbit.rest.resource.model.IdRest; +import org.eclipse.hawkbit.rest.resource.model.distributionsettype.DistributionSetTypePagedList; +import org.eclipse.hawkbit.rest.resource.model.distributionsettype.DistributionSetTypeRequestBodyPost; +import org.eclipse.hawkbit.rest.resource.model.distributionsettype.DistributionSetTypeRequestBodyPut; +import org.eclipse.hawkbit.rest.resource.model.distributionsettype.DistributionSetTypeRest; +import org.eclipse.hawkbit.rest.resource.model.distributionsettype.DistributionSetTypesRest; +import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypeRest; +import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypesRest; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * REST Resource handling for SoftwareModule and related Artifact CRUD + * operations. + * + */ +@RequestMapping(RestConstants.DISTRIBUTIONSETTYPE_V1_REQUEST_MAPPING) +public interface DistributionSetTypeRestApi { + + /** + * Handles the GET request of retrieving all DistributionSetTypes. + * + * @param pagingOffsetParam + * the offset of list of modules for pagination, might not be + * present in the rest request then default value will be applied + * @param pagingLimitParam + * the limit of the paged request, might not be present in the + * rest request then default value will be applied + * @param sortParam + * the sorting parameter in the request URL, syntax + * {@code field:direction, field:direction} + * @param rsqlParam + * the search parameter in the request URL, syntax + * {@code q=name==abc} + * + * @return a list of all DistributionSetType for a defined or default page + * request with status OK. The response is always paged. In any + * failure the JsonResponseExceptionHandler is handling the + * response. + */ + @RequestMapping(method = RequestMethod.GET, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getDistributionSetTypes( + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam); + + /** + * Handles the GET request of retrieving a single DistributionSetType + * within. + * + * @param distributionSetTypeId + * the ID of the module type to retrieve + * + * @return a single softwareModule with status OK. + * @throws EntityNotFoundException + * in case no with the given {@code softwareModuleId} exists. + */ + @RequestMapping(method = RequestMethod.GET, value = "/{distributionSetTypeId}", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getDistributionSetType( + @PathVariable("distributionSetTypeId") final Long distributionSetTypeId); + + /** + * Handles the DELETE request for a single Distribution Set Type. + * + * @param distributionSetTypeId + * the ID of the module to retrieve + * @return status OK if delete as sucessfull. + * + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/{distributionSetTypeId}") + public ResponseEntity deleteDistributionSetType( + @PathVariable("distributionSetTypeId") final Long distributionSetTypeId); + + /** + * Handles the PUT request of updating a Distribution Set Type. + * + * @param distributionSetTypeId + * the ID of the software module in the URL + * @param restDistributionSetType + * the module type to be updated. + * @return status OK if update is successful + */ + @RequestMapping(method = RequestMethod.PUT, value = "/{distributionSetTypeId}", consumes = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity updateDistributionSetType( + @PathVariable("distributionSetTypeId") final Long distributionSetTypeId, + @RequestBody final DistributionSetTypeRequestBodyPut restDistributionSetType); + + /** + * Handles the POST request of creating new DistributionSetTypes. The + * request body must always be a list of types. + * + * @param distributionSetTypes + * the modules to be created. + * @return In case all modules could successful created the ResponseEntity + * with status code 201 - Created but without ResponseBody. In any + * failure the JsonResponseExceptionHandler is handling the + * response. + */ + @RequestMapping(method = RequestMethod.POST, consumes = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity createDistributionSetTypes( + @RequestBody final List distributionSetTypes); + + /** + * Handles the GET request of retrieving the list of mandatory software + * module types in that distribution set type. + * + * @param distributionSetTypeId + * of the DistributionSetType. + * @return Unpaged list of module types and OK in case of success. + */ + @RequestMapping(method = RequestMethod.GET, value = "/{distributionSetTypeId}/" + + RestConstants.DISTRIBUTIONSETTYPE_V1_MANDATORY_MODULE_TYPES, produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getMandatoryModules( + @PathVariable("distributionSetTypeId") final Long distributionSetTypeId); + + /** + * Handles the GET request of retrieving the single mandatory software + * module type in that distribution set type. + * + * @param distributionSetTypeId + * of the DistributionSetType. + * @param softwareModuleTypeId + * of SoftwareModuleType. + * @return Unpaged list of module types and OK in case of success. + */ + @RequestMapping(method = RequestMethod.GET, value = "/{distributionSetTypeId}/" + + RestConstants.DISTRIBUTIONSETTYPE_V1_MANDATORY_MODULE_TYPES + + "/{softwareModuleTypeId}", produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getMandatoryModule( + @PathVariable("distributionSetTypeId") final Long distributionSetTypeId, + @PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId); + + /** + * Handles the GET request of retrieving the single optional software module + * type in that distribution set type. + * + * @param distributionSetTypeId + * of the DistributionSetType. + * @param softwareModuleTypeId + * of SoftwareModuleType. + * @return Unpaged list of module types and OK in case of success. + */ + @RequestMapping(method = RequestMethod.GET, value = "/{distributionSetTypeId}/" + + RestConstants.DISTRIBUTIONSETTYPE_V1_OPTIONAL_MODULE_TYPES + + "/{softwareModuleTypeId}", produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getOptionalModule( + @PathVariable("distributionSetTypeId") final Long distributionSetTypeId, + @PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId); + + /** + * Handles the GET request of retrieving the list of optional software + * module types in that distribution set type. + * + * @param distributionSetTypeId + * of the DistributionSetType. + * @return Unpaged list of module types and OK in case of success. + */ + @RequestMapping(method = RequestMethod.GET, value = "/{distributionSetTypeId}/" + + RestConstants.DISTRIBUTIONSETTYPE_V1_OPTIONAL_MODULE_TYPES, produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getOptionalModules( + @PathVariable("distributionSetTypeId") final Long distributionSetTypeId); + + /** + * Handles DELETE request for removing a mandatory module from the + * DistributionSetType. + * + * @param distributionSetTypeId + * of the DistributionSetType. + * @param softwareModuleTypeId + * of the SoftwareModuleType to remove + * + * @return OK if the request was successful + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/{distributionSetTypeId}/" + + RestConstants.DISTRIBUTIONSETTYPE_V1_MANDATORY_MODULE_TYPES + + "/{softwareModuleTypeId}", produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity removeMandatoryModule( + @PathVariable("distributionSetTypeId") final Long distributionSetTypeId, + @PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId); + + /** + * Handles DELETE request for removing an optional module from the + * DistributionSetType. + * + * @param distributionSetTypeId + * of the DistributionSetType. + * @param softwareModuleTypeId + * of the SoftwareModuleType to remove + * + * @return OK if the request was successful + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/{distributionSetTypeId}/" + + RestConstants.DISTRIBUTIONSETTYPE_V1_OPTIONAL_MODULE_TYPES + + "/{softwareModuleTypeId}", produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity removeOptionalModule( + @PathVariable("distributionSetTypeId") final Long distributionSetTypeId, + @PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId); + + /** + * Handles the POST request for adding a mandatory software module type to a + * distribution set type. + * + * @param distributionSetTypeId + * of the DistributionSetType. + * @param smtId + * of the SoftwareModuleType to add + * + * @return OK if the request was successful + */ + @RequestMapping(method = RequestMethod.POST, value = "/{distributionSetTypeId}/" + + RestConstants.DISTRIBUTIONSETTYPE_V1_MANDATORY_MODULE_TYPES, consumes = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity addMandatoryModule( + @PathVariable("distributionSetTypeId") final Long distributionSetTypeId, @RequestBody final IdRest smtId); + + /** + * Handles the POST request for adding an optional software module type to a + * distribution set type. + * + * @param distributionSetTypeId + * of the DistributionSetType. + * @param smtId + * of the SoftwareModuleType to add + * + * @return OK if the request was successful + */ + @RequestMapping(method = RequestMethod.POST, value = "/{distributionSetTypeId}/" + + RestConstants.DISTRIBUTIONSETTYPE_V1_OPTIONAL_MODULE_TYPES, consumes = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity addOptionalModule( + @PathVariable("distributionSetTypeId") final Long distributionSetTypeId, @RequestBody final IdRest smtId); + +} diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/api/RolloutRestApi.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/api/RolloutRestApi.java new file mode 100644 index 000000000..fe1c7caa5 --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/api/RolloutRestApi.java @@ -0,0 +1,209 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource.api; + +import org.eclipse.hawkbit.rest.resource.RestConstants; +import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutPagedList; +import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutResponseBody; +import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutRestRequestBody; +import org.eclipse.hawkbit.rest.resource.model.rolloutgroup.RolloutGroupPagedList; +import org.eclipse.hawkbit.rest.resource.model.rolloutgroup.RolloutGroupResponseBody; +import org.eclipse.hawkbit.rest.resource.model.target.TargetPagedList; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * REST Resource handling rollout CRUD operations. + * + */ +@RequestMapping(RestConstants.ROLLOUT_V1_REQUEST_MAPPING) +public interface RolloutRestApi { + + /** + * Handles the GET request of retrieving all rollouts. + * + * @param pagingOffsetParam + * the offset of list of rollouts for pagination, might not be + * present in the rest request then default value will be applied + * @param pagingLimitParam + * the limit of the paged request, might not be present in the + * rest request then default value will be applied + * @param sortParam + * the sorting parameter in the request URL, syntax + * {@code field:direction, field:direction} + * @param rsqlParam + * the search parameter in the request URL, syntax + * {@code q=name==abc} + * @return a list of all rollouts for a defined or default page request with + * status OK. The response is always paged. In any failure the + * JsonResponseExceptionHandler is handling the response. + */ + @RequestMapping(method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) + public ResponseEntity getRollouts( + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam); + + /** + * Handles the GET request of retrieving a single rollout. + * + * @param rolloutId + * the ID of the rollout to retrieve + * @return a single rollout with status OK. + * @throws EntityNotFoundException + * in case no rollout with the given {@code rolloutId} exists. + */ + @RequestMapping(value = "/{rolloutId}", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE, + "application/hal+json" }) + public ResponseEntity getRollout(@PathVariable("rolloutId") final Long rolloutId); + + /** + * Handles the POST request for creating rollout. + * + * @param rollout + * the rollout body to be created. + * @return In case rollout could successful created the ResponseEntity with + * status code 201 with the successfully created rollout. In any + * failure the JsonResponseExceptionHandler is handling the + * response. + * @throws EntityNotFoundException + */ + @RequestMapping(method = RequestMethod.POST, consumes = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity create(@RequestBody final RolloutRestRequestBody rolloutRequestBody); + + /** + * Handles the POST request for starting a rollout. + * + * @param rolloutId + * the ID of the rollout to be started. + * @return OK response (200) if rollout could be started. In case of any + * exception the corresponding errors occur. + * @throws EntityNotFoundException + * @see RolloutManagement#startRollout(Rollout) + * @see ResponseExceptionHandler + */ + @RequestMapping(method = RequestMethod.POST, value = "/{rolloutId}/start", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity start(@PathVariable("rolloutId") final Long rolloutId, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_ASYNC, defaultValue = "false") final boolean startAsync); + + /** + * Handles the POST request for pausing a rollout. + * + * @param rolloutId + * the ID of the rollout to be paused. + * @return OK response (200) if rollout could be paused. In case of any + * exception the corresponding errors occur. + * @throws EntityNotFoundException + * @see RolloutManagement#pauseRollout(Rollout) + * @see ResponseExceptionHandler + */ + @RequestMapping(method = RequestMethod.POST, value = "/{rolloutId}/pause", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity pause(@PathVariable("rolloutId") final Long rolloutId); + + /** + * Handles the POST request for resuming a rollout. + * + * @param rolloutId + * the ID of the rollout to be resumed. + * @return OK response (200) if rollout could be resumed. In case of any + * exception the corresponding errors occur. + * @throws EntityNotFoundException + * @see RolloutManagement#resumeRollout(Rollout) + * @see ResponseExceptionHandler + */ + @RequestMapping(method = RequestMethod.POST, value = "/{rolloutId}/resume", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity resume(@PathVariable("rolloutId") final Long rolloutId); + + /** + * Handles the GET request of retrieving all rollout groups referred to a + * rollout. + * + * @param pagingOffsetParam + * the offset of list of rollout groups for pagination, might not + * be present in the rest request then default value will be + * applied + * @param pagingLimitParam + * the limit of the paged request, might not be present in the + * rest request then default value will be applied + * @param sortParam + * the sorting parameter in the request URL, syntax + * {@code field:direction, field:direction} + * @param rsqlParam + * the search parameter in the request URL, syntax + * {@code q=name==abc} + * @return a list of all rollout groups referred to a rollout for a defined + * or default page request with status OK. The response is always + * paged. In any failure the JsonResponseExceptionHandler is + * handling the response. + */ + @RequestMapping(method = RequestMethod.GET, value = "/{rolloutId}/deploygroups", produces = { + MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) + public ResponseEntity getRolloutGroups(@PathVariable("rolloutId") final Long rolloutId, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam); + + /** + * Handles the GET request for retrieving a single rollout group. + * + * @param rolloutId + * the rolloutId to retrieve the group from + * @param groupId + * the groupId to retrieve the rollout group + * @return the OK response containing the RolloutGroupResponseBody + * @throws EntityNotFoundException + */ + @RequestMapping(method = RequestMethod.GET, value = "/{rolloutId}/deploygroups/{groupId}", produces = { + MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) + public ResponseEntity getRolloutGroup(@PathVariable("rolloutId") final Long rolloutId, + @PathVariable("groupId") final Long groupId); + + /** + * Retrieves all targets related to a specific rollout group. + * + * @param rolloutId + * the ID of the rollout + * @param groupId + * the ID of the rollout group + * @param pagingOffsetParam + * the offset of list of rollout groups for pagination, might not + * be present in the rest request then default value will be + * applied + * @param pagingLimitParam + * the limit of the paged request, might not be present in the + * rest request then default value will be applied + * @param sortParam + * the sorting parameter in the request URL, syntax + * {@code field:direction, field:direction} + * @param rsqlParam + * the search parameter in the request URL, syntax + * {@code q=name==abc} + * @return a paged list of targets related to a specific rollout and rollout + * group. + */ + @RequestMapping(method = RequestMethod.GET, value = "/{rolloutId}/deploygroups/{groupId}/targets", produces = { + MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) + public ResponseEntity getRolloutGroupTargets(@PathVariable("rolloutId") final Long rolloutId, + @PathVariable("groupId") final Long groupId, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam); +} diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/api/SoftwareModuleRestAPI.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/api/SoftwareModuleRestAPI.java new file mode 100644 index 000000000..0662c43ba --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/api/SoftwareModuleRestAPI.java @@ -0,0 +1,289 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource.api; + +import java.util.List; + +import org.eclipse.hawkbit.rest.resource.RestConstants; +import org.eclipse.hawkbit.rest.resource.model.MetadataRest; +import org.eclipse.hawkbit.rest.resource.model.MetadataRestPageList; +import org.eclipse.hawkbit.rest.resource.model.artifact.ArtifactRest; +import org.eclipse.hawkbit.rest.resource.model.artifact.ArtifactsRest; +import org.eclipse.hawkbit.rest.resource.model.softwaremodule.SoftwareModulePagedList; +import org.eclipse.hawkbit.rest.resource.model.softwaremodule.SoftwareModuleRequestBodyPost; +import org.eclipse.hawkbit.rest.resource.model.softwaremodule.SoftwareModuleRequestBodyPut; +import org.eclipse.hawkbit.rest.resource.model.softwaremodule.SoftwareModuleRest; +import org.eclipse.hawkbit.rest.resource.model.softwaremodule.SoftwareModulesRest; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.multipart.MultipartFile; + +/** + * REST Resource handling for SoftwareModule and related Artifact CRUD + * operations. + * + */ +@RequestMapping(RestConstants.SOFTWAREMODULE_V1_REQUEST_MAPPING) +public interface SoftwareModuleRestAPI { + + /** + * Handles POST request for artifact upload. + * + * @param softwareModuleId + * of the parent SoftwareModule + * @param file + * that has to be uploaded + * @param optionalFileName + * to override {@link MultipartFile#getOriginalFilename()} + * @param md5Sum + * checksum for uploaded content check + * @param sha1Sum + * checksum for uploaded content check + * + * @return In case all sets could successful be created the ResponseEntity + * with status code 201 - Created but without ResponseBody. In any + * failure the JsonResponseExceptionHandler is handling the + * response. + */ + @RequestMapping(method = RequestMethod.POST, value = "/{softwareModuleId}/artifacts", produces = { + "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity uploadArtifact(@PathVariable("softwareModuleId") final Long softwareModuleId, + @RequestParam("file") final MultipartFile file, + @RequestParam(value = "filename", required = false) final String optionalFileName, + @RequestParam(value = "md5sum", required = false) final String md5Sum, + @RequestParam(value = "sha1sum", required = false) final String sha1Sum); + + /** + * Handles the GET request of retrieving all meta data of artifacts assigned + * to a software module. + * + * @param softwareModuleId + * of the parent SoftwareModule + * + * @return a list of all artifacts for a defined or default page request + * with status OK. The response is always paged. In any failure the + * JsonResponseExceptionHandler is handling the response. + */ + @RequestMapping(method = RequestMethod.GET, value = "/{softwareModuleId}/artifacts", produces = { + "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + @ResponseBody + public ResponseEntity getArtifacts(@PathVariable("softwareModuleId") final Long softwareModuleId); + + /** + * Handles the GET request of retrieving a single Artifact meta data + * request. + * + * @param softwareModuleId + * of the parent SoftwareModule + * @param artifactId + * of the related LocalArtifact + * + * @return responseEntity with status ok if successful + */ + @RequestMapping(method = RequestMethod.GET, value = "/{softwareModuleId}/artifacts/{artifactId}", produces = { + "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + @ResponseBody + public ResponseEntity getArtifact(@PathVariable("softwareModuleId") final Long softwareModuleId, + @PathVariable("artifactId") final Long artifactId); + + /** + * Handles the DELETE request for a single SoftwareModule. + * + * @param softwareModuleId + * the ID of the module that has the artifact + * @param artifactId + * of the artifact to be deleted + * + * @return status OK if delete as successful. + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/{softwareModuleId}/artifacts/{artifactId}") + @ResponseBody + public ResponseEntity deleteArtifact(@PathVariable("softwareModuleId") final Long softwareModuleId, + @PathVariable("artifactId") final Long artifactId); + + /** + * Handles the GET request of retrieving all softwaremodules. + * + * @param pagingOffsetParam + * the offset of list of modules for pagination, might not be + * present in the rest request then default value will be applied + * @param pagingLimitParam + * the limit of the paged request, might not be present in the + * rest request then default value will be applied + * @param sortParam + * the sorting parameter in the request URL, syntax + * {@code field:direction, field:direction} + * @param rsqlParam + * the search parameter in the request URL, syntax + * {@code q=name==abc} + * + * @return a list of all modules for a defined or default page request with + * status OK. The response is always paged. In any failure the + * JsonResponseExceptionHandler is handling the response. + */ + @RequestMapping(method = RequestMethod.GET, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getSoftwareModules( + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam); + + /** + * Handles the GET request of retrieving a single software module. + * + * @param softwareModuleId + * the ID of the module to retrieve + * + * @return a single softwareModule with status OK. + * @throws EntityNotFoundException + * in case no with the given {@code softwareModuleId} exists. + */ + @RequestMapping(method = RequestMethod.GET, value = "/{softwareModuleId}", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getSoftwareModule( + @PathVariable("softwareModuleId") final Long softwareModuleId); + + /** + * Handles the POST request of creating new softwaremodules. The request + * body must always be a list of modules. + * + * @param softwareModules + * the modules to be created. + * @return In case all modules could successful created the ResponseEntity + * with status code 201 - Created but without ResponseBody. In any + * failure the JsonResponseExceptionHandler is handling the + * response. + */ + @RequestMapping(method = RequestMethod.POST, consumes = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity createSoftwareModules( + @RequestBody final List softwareModules); + + /** + * Handles the PUT request of updating a software module. + * + * @param softwareModuleId + * the ID of the software module in the URL + * @param restSoftwareModule + * the modules to be updated. + * @return status OK if update is successful + */ + @RequestMapping(method = RequestMethod.PUT, value = "/{softwareModuleId}", consumes = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity updateSoftwareModule( + @PathVariable("softwareModuleId") final Long softwareModuleId, + @RequestBody final SoftwareModuleRequestBodyPut restSoftwareModule); + + /** + * Handles the DELETE request for a single softwaremodule. + * + * @param softwareModuleId + * the ID of the module to retrieve + * @return status OK if delete as sucessfull. + * + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/{softwareModuleId}") + public ResponseEntity deleteSoftwareModule(@PathVariable("softwareModuleId") final Long softwareModuleId); + + /** + * Gets a paged list of meta data for a software module. + * + * @param softwareModuleId + * the ID of the software module for the meta data + * @param pagingOffsetParam + * the offset of list of meta data for pagination, might not be + * present in the rest request then default value will be applied + * @param pagingLimitParam + * the limit of the paged request, might not be present in the + * rest request then default value will be applied + * @param sortParam + * the sorting parameter in the request URL, syntax + * {@code field:direction, field:direction} + * @param rsqlParam + * the search parameter in the request URL, syntax + * {@code q=key==abc} + * @return status OK if get request is successful with the paged list of + * meta data + */ + @RequestMapping(method = RequestMethod.GET, value = "/{softwareModuleId}/metadata", produces = { + MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) + public ResponseEntity getMetadata( + @PathVariable("softwareModuleId") final Long softwareModuleId, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam); + + /** + * Gets a single meta data value for a specific key of a software module. + * + * @param softwareModuleId + * the ID of the software module to get the meta data from + * @param metadataKey + * the key of the meta data entry to retrieve the value from + * @return status OK if get request is successful with the value of the meta + * data + */ + @RequestMapping(method = RequestMethod.GET, value = "/{softwareModuleId}/metadata/{metadataKey}", produces = { + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getMetadataValue(@PathVariable("softwareModuleId") final Long softwareModuleId, + @PathVariable("metadataKey") final String metadataKey); + + /** + * Updates a single meta data value of a software module. + * + * @param softwareModuleId + * the ID of the software module to update the meta data entry + * @param metadataKey + * the key of the meta data to update the value + * @return status OK if the update request is successful and the updated + * meta data result + */ + @RequestMapping(method = RequestMethod.PUT, value = "/{softwareModuleId}/metadata/{metadataKey}", produces = { + MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) + public ResponseEntity updateMetadata(@PathVariable("softwareModuleId") final Long softwareModuleId, + @PathVariable("metadataKey") final String metadataKey, @RequestBody final MetadataRest metadata); + + /** + * Deletes a single meta data entry from the software module. + * + * @param softwareModuleId + * the ID of the software module to delete the meta data entry + * @param metadataKey + * the key of the meta data to delete + * @return status OK if the delete request is successful + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/{softwareModuleId}/metadata/{metadataKey}") + public ResponseEntity deleteMetadata(@PathVariable("softwareModuleId") final Long softwareModuleId, + @PathVariable("metadataKey") final String metadataKey); + + /** + * Creates a list of meta data for a specific software module. + * + * @param softwareModuleId + * the ID of the distribution set to create meta data for + * @param metadataRest + * the list of meta data entries to create + * @return status created if post request is successful with the value of + * the created meta data + */ + @RequestMapping(method = RequestMethod.POST, value = "/{softwareModuleId}/metadata", consumes = { + MediaType.APPLICATION_JSON_VALUE, + "application/hal+json" }, produces = { MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) + public ResponseEntity> createMetadata( + @PathVariable("softwareModuleId") final Long softwareModuleId, + @RequestBody final List metadataRest); + +} diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/api/SoftwareModuleTypeRestApi.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/api/SoftwareModuleTypeRestApi.java new file mode 100644 index 000000000..0c4756a04 --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/api/SoftwareModuleTypeRestApi.java @@ -0,0 +1,119 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource.api; + +import java.util.List; + +import org.eclipse.hawkbit.rest.resource.RestConstants; +import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypePagedList; +import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypeRequestBodyPost; +import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypeRequestBodyPut; +import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypeRest; +import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypesRest; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * REST Resource handling for SoftwareModule and related Artifact CRUD + * operations. + * + */ +@RequestMapping(RestConstants.SOFTWAREMODULETYPE_V1_REQUEST_MAPPING) +public interface SoftwareModuleTypeRestApi { + /** + * Handles the GET request of retrieving all SoftwareModuleTypes . + * + * @param pagingOffsetParam + * the offset of list of modules for pagination, might not be + * present in the rest request then default value will be applied + * @param pagingLimitParam + * the limit of the paged request, might not be present in the + * rest request then default value will be applied + * @param sortParam + * the sorting parameter in the request URL, syntax + * {@code field:direction, field:direction} + * @param rsqlParam + * the search parameter in the request URL, syntax + * {@code q=name==abc} + * + * @return a list of all module type for a defined or default page request + * with status OK. The response is always paged. In any failure the + * JsonResponseExceptionHandler is handling the response. + */ + @RequestMapping(method = RequestMethod.GET, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getTypes( + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam); + + /** + * Handles the GET request of retrieving a single software module type . + * + * @param softwareModuleTypeId + * the ID of the module type to retrieve + * + * @return a single softwareModule with status OK. + * @throws EntityNotFoundException + * in case no with the given {@code softwareModuleId} exists. + */ + @RequestMapping(method = RequestMethod.GET, value = "/{softwareModuleTypeId}", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getSoftwareModuleType( + @PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId); + + /** + * Handles the DELETE request for a single software module type . + * + * @param softwareModuleTypeId + * the ID of the module to retrieve + * @return status OK if delete as successfully. + * + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/{softwareModuleTypeId}") + public ResponseEntity deleteSoftwareModuleType( + @PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId); + + /** + * Handles the PUT request of updating a software module type . + * + * @param softwareModuleTypeId + * the ID of the software module in the URL + * @param restSoftwareModuleType + * the module type to be updated. + * @return status OK if update is successful + */ + @RequestMapping(method = RequestMethod.PUT, value = "/{softwareModuleTypeId}", consumes = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity updateSoftwareModuleType( + @PathVariable("softwareModuleTypeId") final Long softwareModuleTypeId, + @RequestBody final SoftwareModuleTypeRequestBodyPut restSoftwareModuleType); + + /** + * Handles the POST request of creating new SoftwareModuleTypes. The request + * body must always be a list of types. + * + * @param softwareModuleTypes + * the modules to be created. + * @return In case all modules could successful created the ResponseEntity + * with status code 201 - Created but without ResponseBody. In any + * failure the JsonResponseExceptionHandler is handling the + * response. + */ + @RequestMapping(method = RequestMethod.POST, consumes = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity createSoftwareModuleTypes( + @RequestBody final List softwareModuleTypes); + +} diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/api/TargetRestApi.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/api/TargetRestApi.java new file mode 100644 index 000000000..9d63a520f --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/api/TargetRestApi.java @@ -0,0 +1,284 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource.api; + +import java.util.List; + +import org.eclipse.hawkbit.rest.resource.RestConstants; +import org.eclipse.hawkbit.rest.resource.model.action.ActionPagedList; +import org.eclipse.hawkbit.rest.resource.model.action.ActionRest; +import org.eclipse.hawkbit.rest.resource.model.action.ActionStatusPagedList; +import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetRest; +import org.eclipse.hawkbit.rest.resource.model.target.DistributionSetAssigmentRest; +import org.eclipse.hawkbit.rest.resource.model.target.TargetAttributes; +import org.eclipse.hawkbit.rest.resource.model.target.TargetPagedList; +import org.eclipse.hawkbit.rest.resource.model.target.TargetRequestBody; +import org.eclipse.hawkbit.rest.resource.model.target.TargetRest; +import org.eclipse.hawkbit.rest.resource.model.target.TargetsRest; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * Api for handling target operations. + */ +@RequestMapping(RestConstants.TARGET_V1_REQUEST_MAPPING) +public interface TargetRestApi { + + /** + * Handles the GET request of retrieving a single target. + * + * @param targetId + * the ID of the target to retrieve + * @return a single target with status OK. + * @throws EntityNotFoundException + * in case no target with the given {@code targetId} exists. + */ + + @RequestMapping(method = RequestMethod.GET, value = "/{targetId}", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getTarget(@PathVariable("targetId") final String targetId); + + /** + * Handles the GET request of retrieving all targets. + * + * @param pagingOffsetParam + * the offset of list of targets for pagination, might not be + * present in the rest request then default value will be applied + * @param pagingLimitParam + * the limit of the paged request, might not be present in the + * rest request then default value will be applied + * @param sortParam + * the sorting parameter in the request URL, syntax + * {@code field:direction, field:direction} + * @param rsqlParam + * the search parameter in the request URL, syntax + * {@code q=name==abc} + * @return a list of all targets for a defined or default page request with + * status OK. The response is always paged. In any failure the + * JsonResponseExceptionHandler is handling the response. + */ + + @RequestMapping(method = RequestMethod.GET, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getTargets( + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam); + + /** + * Handles the POST request of creating new targets. The request body must + * always be a list of targets. + * + * @param targets + * the targets to be created. + * @return In case all targets could successful created the ResponseEntity + * with status code 201 with a list of successfully created + * entities. In any failure the JsonResponseExceptionHandler is + * handling the response. + */ + @RequestMapping(method = RequestMethod.POST, consumes = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity createTargets(@RequestBody final List targets); + + /** + * Handles the PUT request of updating a target. The ID is within the URL + * path of the request. A given ID in the request body is ignored. It's not + * possible to set fields to {@code null} values. + * + * @param targetId + * the path parameter which contains the ID of the target + * @param targetRest + * the request body which contains the fields which should be + * updated, fields which are not given are ignored for the + * udpate. + * @return the updated target response which contains all fields also fields + * which have not updated + */ + @RequestMapping(method = RequestMethod.PUT, value = "/{targetId}", consumes = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity updateTarget(@PathVariable("targetId") final String targetId, + @RequestBody final TargetRequestBody targetRest); + + /** + * Handles the DELETE request of deleting a target. + * + * @param targetId + * the ID of the target to be deleted + * @return If the given targetId could exists and could be deleted Http OK. + * In any failure the JsonResponseExceptionHandler is handling the + * response. + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/{targetId}", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity deleteTarget(@PathVariable("targetId") final String targetId); + + /** + * Handles the GET request of retrieving the attributes of a specific + * target. + * + * @param targetId + * the ID of the target to retrieve the attributes. + * @return the target attributes as map response with status OK + * @throws EntityNotFoundException + * in case no target with the given {@code targetId} exists. + */ + @RequestMapping(method = RequestMethod.GET, value = "/{targetId}/attributes", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getAttributes(@PathVariable("targetId") final String targetId); + + /** + * Handles the GET request of retrieving the Actions of a specific target. + * + * @param targetId + * to load actions for + * @param pagingOffsetParam + * the offset of list of targets for pagination, might not be + * present in the rest request then default value will be applied + * @param pagingLimitParam + * the limit of the paged request, might not be present in the + * rest request then default value will be applied + * @param sortParam + * the sorting parameter in the request URL, syntax + * {@code field:direction, field:direction} + * @param rsqlParam + * the search parameter in the request URL, syntax + * {@code q=status==pending} + * @return a list of all Actions for a defined or default page request with + * status OK. The response is always paged. In any failure the + * JsonResponseExceptionHandler is handling the response. + */ + @RequestMapping(method = RequestMethod.GET, value = "/{targetId}/actions", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getActionHistory(@PathVariable("targetId") final String targetId, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam); + + /** + * Handles the GET request of retrieving a specific Actions of a specific + * Target. + * + * @param targetId + * to load the action for + * @param actionId + * to load + * @return the action + */ + @RequestMapping(method = RequestMethod.GET, value = "/{targetId}/actions/{actionId}", produces = { + "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getAction(@PathVariable("targetId") final String targetId, + @PathVariable("actionId") final Long actionId); + + /** + * Handles the DELETE request of canceling an specific Actions of a specific + * Target. + * + * @param targetId + * the ID of the target in the URL path parameter + * @param actionId + * the ID of the action in the URL path parameter + * @param force + * optional parameter, which indicates a force cancel + * @return status no content in case cancellation was successful + * @throws CancelActionNotAllowedException + * if the given action is not active and cannot be canceled. + * @throws EntityNotFoundException + * if the target or the action is not found + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/{targetId}/actions/{actionId}") + public ResponseEntity cancelAction(@PathVariable("targetId") final String targetId, + @PathVariable("actionId") final Long actionId, + @RequestParam(value = "force", required = false, defaultValue = "false") final boolean force); + + /** + * Handles the GET request of retrieving the ActionStatus of a specific + * target and action. + * + * @param targetId + * of the the action + * @param actionId + * of the status we are intend to load + * @param pagingOffsetParam + * the offset of list of targets for pagination, might not be + * present in the rest request then default value will be applied + * @param pagingLimitParam + * the limit of the paged request, might not be present in the + * rest request then default value will be applied + * @param sortParam + * the sorting parameter in the request URL, syntax + * {@code field:direction, field:direction} + * @return a list of all ActionStatus for a defined or default page request + * with status OK. The response is always paged. In any failure the + * JsonResponseExceptionHandler is handling the response. + */ + @RequestMapping(method = RequestMethod.GET, value = "/{targetId}/actions/{actionId}/status", produces = { + "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getActionStatusList(@PathVariable("targetId") final String targetId, + @PathVariable("actionId") final Long actionId, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam); + + /** + * Handles the GET request of retrieving the assigned distribution set of an + * specific target. + * + * @param targetId + * the ID of the target to retrieve the assigned distribution + * @return the assigned distribution set with status OK, if none is assigned + * than {@code null} content (e.g. "{}") + * @throws EntityNotFoundException + * in case no target with the given {@code targetId} exists. + */ + @RequestMapping(method = RequestMethod.GET, value = "/{targetId}/assignedDS", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getAssignedDistributionSet( + @PathVariable("targetId") final String targetId); + + /** + * Changes the assigned distribution set of a target. + * + * @param targetId + * of the target to change + * @param dsId + * of the distributionset that is to be assigned + * @return http status + * + * @throws EntityNotFoundException + * in case no target with the given {@code targetId} exists. + * + */ + @RequestMapping(method = RequestMethod.POST, value = "/{targetId}/assignedDS", consumes = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity postAssignedDistributionSet(@PathVariable("targetId") final String targetId, + @RequestBody final DistributionSetAssigmentRest dsId); + + /** + * Handles the GET request of retrieving the installed distribution set of + * an specific target. + * + * @param targetId + * the ID of the target to retrieve + * @return the assigned installed set with status OK, if none is installed + * than {@code null} content (e.g. "{}") + * @throws EntityNotFoundException + * in case no target with the given {@code targetId} exists. + */ + @RequestMapping(method = RequestMethod.GET, value = "/{targetId}/installedDS", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getInstalledDistributionSet( + @PathVariable("targetId") final String targetId); + +} \ No newline at end of file diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/api/TargetTagRestApi.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/api/TargetTagRestApi.java new file mode 100644 index 000000000..59770db08 --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/api/TargetTagRestApi.java @@ -0,0 +1,196 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource.api; + +import java.util.List; + +import org.eclipse.hawkbit.rest.resource.RestConstants; +import org.eclipse.hawkbit.rest.resource.model.tag.AssignedTargetRequestBody; +import org.eclipse.hawkbit.rest.resource.model.tag.TagPagedList; +import org.eclipse.hawkbit.rest.resource.model.tag.TagRequestBodyPut; +import org.eclipse.hawkbit.rest.resource.model.tag.TagRest; +import org.eclipse.hawkbit.rest.resource.model.tag.TagsRest; +import org.eclipse.hawkbit.rest.resource.model.tag.TargetTagAssigmentResultRest; +import org.eclipse.hawkbit.rest.resource.model.target.TargetsRest; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * REST Resource handling for TargetTag CRUD operations. + * + */ +@RequestMapping(RestConstants.TARGET_TAG_V1_REQUEST_MAPPING) +public interface TargetTagRestApi { + + /** + * Handles the GET request of retrieving all target tags. + * + * @param pagingOffsetParam + * the offset of list of target tags for pagination, might not be + * present in the rest request then default value will be applied + * @param pagingLimitParam + * the limit of the paged request, might not be present in the + * rest request then default value will be applied + * @param sortParam + * the sorting parameter in the request URL, syntax + * {@code field:direction, field:direction} + * @param rsqlParam + * the search parameter in the request URL, syntax + * {@code q=name==abc} + * @return a list of all target tags for a defined or default page request + * with status OK. The response is always paged. In any failure the + * JsonResponseExceptionHandler is handling the response. + */ + @RequestMapping(method = RequestMethod.GET, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getTargetTags( + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, + @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam); + + /** + * Handles the GET request of retrieving a single target tag. + * + * @param targetTagId + * the ID of the target tag to retrieve + * + * @return a single target tag with status OK. + * @throws EntityNotFoundException + * in case the given {@code targetTagId} doesn't exists. + */ + @RequestMapping(method = RequestMethod.GET, value = "/{targetTagId}", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getTargetTag(@PathVariable("targetTagId") final Long targetTagId); + + /** + * Handles the POST request of creating new target tag. The request body + * must always be a list of tags. + * + * @param tags + * the target tags to be created. + * @return In case all modules could successful created the ResponseEntity + * with status code 201 - Created. The Response Body are the created + * target tags but without ResponseBody. + */ + @RequestMapping(method = RequestMethod.POST, consumes = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity createTargetTags(@RequestBody final List tags); + + /** + * + * Handles the PUT request of updating a single targetr tag. + * + * @param targetTagId + * the ID of the target tag + * @param restTargetTagRest + * the the request body to be updated + * @return status OK if update is successful and the updated target tag. + * @throws EntityNotFoundException + * in case the given {@code targetTagId} doesn't exists. + */ + @RequestMapping(method = RequestMethod.PUT, value = "/{targetTagId}", consumes = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity updateTagretTag(@PathVariable("targetTagId") final Long targetTagId, + @RequestBody final TagRequestBodyPut restTargetTagRest); + + /** + * Handles the DELETE request for a single target tag. + * + * @param targetTagId + * the ID of the target tag + * @return status OK if delete as successfully. + * @throws EntityNotFoundException + * in case the given {@code targetTagId} doesn't exists. + * + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/{targetTagId}") + public ResponseEntity deleteTargetTag(@PathVariable("targetTagId") final Long targetTagId); + + /** + * Handles the GET request of retrieving all assigned targets by the given + * tag id. + * + * @param targetTagId + * the ID of the target tag to retrieve + * + * @return the list of assigned targets. + * @throws EntityNotFoundException + * in case the given {@code targetTagId} doesn't exists. + */ + @RequestMapping(method = RequestMethod.GET, value = RestConstants.TARGET_TAG_TAGERTS_REQUEST_MAPPING) + public ResponseEntity getAssignedTargets(@PathVariable("targetTagId") final Long targetTagId); + + /** + * Handles the POST request to toggle the assignment of targets by the given + * tag id. + * + * @param targetTagId + * the ID of the target tag to retrieve + * @param assignedTargetRequestBodies + * list of target ids to be toggled + * + * @return the list of assigned targets and unassigned targets. + * @throws EntityNotFoundException + * in case the given {@code targetTagId} doesn't exists. + */ + @RequestMapping(method = RequestMethod.POST, value = RestConstants.TARGET_TAG_TAGERTS_REQUEST_MAPPING + + "/toggleTagAssignment") + public ResponseEntity toggleTagAssignment( + @PathVariable("targetTagId") final Long targetTagId, + @RequestBody final List assignedTargetRequestBodies); + + /** + * Handles the POST request to assign targets to the given tag id. + * + * @param targetTagId + * the ID of the target tag to retrieve + * @param assignedTargetRequestBodies + * list of target ids to be assigned + * + * @return the list of assigned targets. + * @throws EntityNotFoundException + * in case the given {@code targetTagId} doesn't exists. + */ + @RequestMapping(method = RequestMethod.POST, value = RestConstants.TARGET_TAG_TAGERTS_REQUEST_MAPPING) + public ResponseEntity assignTargets(@PathVariable("targetTagId") final Long targetTagId, + @RequestBody final List assignedTargetRequestBodies); + + /** + * Handles the DELETE request to unassign all targets from the given tag id. + * + * @param targetTagId + * the ID of the target tag to retrieve + * @return http status code + * @throws EntityNotFoundException + * in case the given {@code targetTagId} doesn't exists. + */ + @RequestMapping(method = RequestMethod.DELETE, value = RestConstants.TARGET_TAG_TAGERTS_REQUEST_MAPPING) + public ResponseEntity unassignTargets(@PathVariable("targetTagId") final Long targetTagId); + + /** + * Handles the DELETE request to unassign one target from the given tag id. + * + * @param targetTagId + * the ID of the target tag + * @param controllerId + * the ID of the target to unassign + * @return http status code + * @throws EntityNotFoundException + * in case the given {@code targetTagId} doesn't exists. + */ + @RequestMapping(method = RequestMethod.DELETE, value = RestConstants.TARGET_TAG_TAGERTS_REQUEST_MAPPING + + "/{controllerId}") + public ResponseEntity unassignTarget(@PathVariable("targetTagId") final Long targetTagId, + @PathVariable("controllerId") final String controllerId); +} diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/action/ActionPagedList.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/action/ActionPagedList.java index f8908277f..27b36779a 100644 --- a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/action/ActionPagedList.java +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/action/ActionPagedList.java @@ -14,7 +14,7 @@ import java.util.List; import org.eclipse.hawkbit.rest.resource.model.PagedList; /** - * Paged list rest model for {@link Action} to RESTful API representation. + * Paged list rest model for {@link ErrorAction} to RESTful API representation. * */ public class ActionPagedList extends PagedList { diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutCondition.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutCondition.java new file mode 100644 index 000000000..914a5d5c2 --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutCondition.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource.model.rollout; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +/** + * + */ +@JsonInclude(Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class RolloutCondition { + + private Condition condition = Condition.THRESHOLD; + private String expression = "100"; + + /** + * + */ + public RolloutCondition() { + } + + public RolloutCondition(final Condition condition, final String expression) { + this.condition = condition; + this.expression = expression; + } + + /** + * @return the condition + */ + public Condition getCondition() { + return condition; + } + + /** + * @param condition + * the condition to set + */ + public void setCondition(final Condition condition) { + this.condition = condition; + } + + /** + * @return the expession + */ + public String getExpression() { + return expression; + } + + /** + * @param expession + * the expession to set + */ + public void setExpression(final String expession) { + this.expression = expession; + } + + public enum Condition { + THRESHOLD; + } +} diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutErrorAction.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutErrorAction.java new file mode 100644 index 000000000..2ed6077e6 --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutErrorAction.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource.model.rollout; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +/** + * + */ +@JsonInclude(Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class RolloutErrorAction { + + private ErrorAction action = ErrorAction.PAUSE; + private String expression = null; + + /** + * @return the action + */ + public ErrorAction getAction() { + return action; + } + + /** + * @param action + * the action to set + */ + public void setAction(final ErrorAction action) { + this.action = action; + } + + /** + * @return the expression + */ + public String getExpression() { + return expression; + } + + /** + * @param expression + * the expression to set + */ + public void setExpression(final String expression) { + this.expression = expression; + } + + public enum ErrorAction { + PAUSE; + } +} diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutPagedList.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutPagedList.java new file mode 100644 index 000000000..ea1544991 --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutPagedList.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource.model.rollout; + +import java.util.List; + +import org.eclipse.hawkbit.rest.resource.model.PagedList; + +/** + * Paged list for Rollout. + * + * + */ +public class RolloutPagedList extends PagedList { + + private final List content; + + public RolloutPagedList(final List content, final long total) { + super(content, total); + this.content = content; + } + + /** + * @return the content of the paged list. Never {@code null}. + */ + public List getContent() { + return content; + } + +} diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutResponseBody.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutResponseBody.java new file mode 100644 index 000000000..dd1296cfd --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutResponseBody.java @@ -0,0 +1,124 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource.model.rollout; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.hawkbit.rest.resource.model.NamedEntityRest; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * + */ +@JsonInclude(Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class RolloutResponseBody extends NamedEntityRest { + + private String targetFilterQuery; + private Long distributionSetId; + + @JsonProperty(value = "id", required = true) + private Long rolloutId; + + @JsonProperty(required = true) + private String status; + + @JsonProperty(required = true) + private Long totalTargets; + + @JsonProperty(required = true) + private final Map totalTargetsPerStatus = new HashMap<>(); + + /** + * @return the status + */ + public String getStatus() { + return status; + } + + /** + * @param status + * the status to set + */ + public void setStatus(final String status) { + this.status = status; + } + + /** + * @return the rolloutId + */ + public Long getRolloutId() { + return rolloutId; + } + + /** + * @param rolloutId + * the rolloutId to set + */ + public void setRolloutId(final Long rolloutId) { + this.rolloutId = rolloutId; + } + + /** + * @return the targetFilterQuery + */ + public String getTargetFilterQuery() { + return targetFilterQuery; + } + + /** + * @param targetFilterQuery + * the targetFilterQuery to set + */ + public void setTargetFilterQuery(final String targetFilterQuery) { + this.targetFilterQuery = targetFilterQuery; + } + + /** + * @return the distributionSetId + */ + public Long getDistributionSetId() { + return distributionSetId; + } + + /** + * @param distributionSetId + * the distributionSetId to set + */ + public void setDistributionSetId(final Long distributionSetId) { + this.distributionSetId = distributionSetId; + } + + /** + * @param totalTargets + * the totalTargets to set + */ + public void setTotalTargets(final Long totalTargets) { + this.totalTargets = totalTargets; + } + + /** + * @return the totalTargets + */ + public Long getTotalTargets() { + return totalTargets; + } + + /** + * @return the totalTargetsPerStatus + */ + public Map getTotalTargetsPerStatus() { + return totalTargetsPerStatus; + } +} diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutRestRequestBody.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutRestRequestBody.java new file mode 100644 index 000000000..0b83948ff --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutRestRequestBody.java @@ -0,0 +1,175 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource.model.rollout; + +import org.eclipse.hawkbit.rest.resource.model.NamedEntityRest; +import org.eclipse.hawkbit.rest.resource.model.distributionset.ActionTypeRest; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +/** + * Model for request containing a rollout body e.g. in a POST request of + * creating a rollout via REST API. + */ +@JsonInclude(Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class RolloutRestRequestBody extends NamedEntityRest { + + private String targetFilterQuery; + private long distributionSetId; + + private int amountGroups = 1; + + private RolloutCondition successCondition = new RolloutCondition(); + private RolloutSuccessAction successAction = new RolloutSuccessAction(); + private RolloutCondition errorCondition = null; + private RolloutErrorAction errorAction = null; + + private Long forcetime; + + private ActionTypeRest type; + + /** + * @return the finishCondition + */ + public RolloutCondition getSuccessCondition() { + return successCondition; + } + + /** + * @param successCondition + * the finishCondition to set + */ + public void setSuccessCondition(final RolloutCondition successCondition) { + this.successCondition = successCondition; + } + + /** + * @return the successAction + */ + public RolloutSuccessAction getSuccessAction() { + return successAction; + } + + /** + * @param successAction + * the successAction to set + */ + public void setSuccessAction(final RolloutSuccessAction successAction) { + this.successAction = successAction; + } + + /** + * @return the errorCondition + */ + public RolloutCondition getErrorCondition() { + return errorCondition; + } + + /** + * @param errorCondition + * the errorCondition to set + */ + public void setErrorCondition(final RolloutCondition errorCondition) { + this.errorCondition = errorCondition; + } + + /** + * @return the targetFilterQuery + */ + public String getTargetFilterQuery() { + return targetFilterQuery; + } + + /** + * @param targetFilterQuery + * the targetFilterQuery to set + */ + public void setTargetFilterQuery(final String targetFilterQuery) { + this.targetFilterQuery = targetFilterQuery; + } + + /** + * @return the distributionSetId + */ + public long getDistributionSetId() { + return distributionSetId; + } + + /** + * @param distributionSetId + * the distributionSetId to set + */ + public void setDistributionSetId(final long distributionSetId) { + this.distributionSetId = distributionSetId; + } + + /** + * @return the groupSize + */ + public int getAmountGroups() { + return amountGroups; + } + + /** + * @param groupSize + * the groupSize to set + */ + public void setAmountGroups(final int groupSize) { + this.amountGroups = groupSize; + } + + /** + * @return the forcetime + */ + public Long getForcetime() { + return forcetime; + } + + /** + * @param forcetime + * the forcetime to set + */ + public void setForcetime(final Long forcetime) { + this.forcetime = forcetime; + } + + /** + * @return the type + */ + public ActionTypeRest getType() { + return type; + } + + /** + * @param type + * the type to set + */ + public void setType(final ActionTypeRest type) { + this.type = type; + } + + /** + * @return the errorAction + */ + public RolloutErrorAction getErrorAction() { + return errorAction; + } + + /** + * @param errorAction + * the errorAction to set + */ + public void setErrorAction(final RolloutErrorAction errorAction) { + this.errorAction = errorAction; + } + +} diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutSuccessAction.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutSuccessAction.java new file mode 100644 index 000000000..f14e9a8bf --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rollout/RolloutSuccessAction.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource.model.rollout; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +/** + * + */ +@JsonInclude(Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class RolloutSuccessAction { + + private SuccessAction action = SuccessAction.NEXTGROUP; + private String expression = null; + + /** + * + */ + public RolloutSuccessAction() { + } + + public RolloutSuccessAction(final SuccessAction action, final String expression) { + this.action = action; + this.expression = expression; + } + + /** + * @return the action + */ + public SuccessAction getAction() { + return action; + } + + /** + * @param action + * the action to set + */ + public void setAction(final SuccessAction action) { + this.action = action; + } + + /** + * @return the expession + */ + public String getExpression() { + return expression; + } + + /** + * @param expession + * the expession to set + */ + public void setExpression(final String expession) { + this.expression = expession; + } + + public enum SuccessAction { + NEXTGROUP; + } +} diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rolloutgroup/RolloutGroupPagedList.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rolloutgroup/RolloutGroupPagedList.java new file mode 100644 index 000000000..a341ac6ba --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rolloutgroup/RolloutGroupPagedList.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource.model.rolloutgroup; + +import java.util.List; + +import org.eclipse.hawkbit.rest.resource.model.PagedList; + +/** + * Paged list for Rollout. + * + */ +public class RolloutGroupPagedList extends PagedList { + + private final List content; + + public RolloutGroupPagedList(final List content, final long total) { + super(content, total); + this.content = content; + } + + /** + * @return the content of the paged list. Never {@code null}. + */ + public List getContent() { + return content; + } + +} diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rolloutgroup/RolloutGroupResponseBody.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rolloutgroup/RolloutGroupResponseBody.java new file mode 100644 index 000000000..bb1b0116e --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/rolloutgroup/RolloutGroupResponseBody.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource.model.rolloutgroup; + +import org.eclipse.hawkbit.rest.resource.model.NamedEntityRest; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Model for the rollout group annotated with json-annotations for easier + * serialization and de-serialization. + */ +@JsonInclude(Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class RolloutGroupResponseBody extends NamedEntityRest { + + @JsonProperty(value = "id", required = true) + private Long rolloutGroupId; + + @JsonProperty(required = true) + private String status; + + /** + * @return the rolloutGroupId + */ + public Long getRolloutGroupId() { + return rolloutGroupId; + } + + /** + * @param rolloutGroupId + * the rolloutGroupId to set + */ + public void setRolloutGroupId(final Long rolloutGroupId) { + this.rolloutGroupId = rolloutGroupId; + } + + /** + * @return the status + */ + public String getStatus() { + return status; + } + + /** + * @param status + * the status to set + */ + public void setStatus(final String status) { + this.status = status; + } +} diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/TenantConfigurationRest.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/TenantConfigurationValueRequest.java similarity index 59% rename from hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/TenantConfigurationRest.java rename to hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/TenantConfigurationValueRequest.java index 2d31d9f61..b0ae6e546 100644 --- a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/TenantConfigurationRest.java +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/TenantConfigurationValueRequest.java @@ -11,45 +11,33 @@ package org.eclipse.hawkbit.rest.resource.model.system; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; /** - * Response body for tenant configuration requests. - * + * A json annotated rest model for System Configuration for PUT. */ @JsonInclude(Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) -public class TenantConfigurationRest { +public class TenantConfigurationValueRequest { - private String key; - private String value; + @JsonProperty(required = true) + private Object value; /** - * @return the key + * + * @return the value of the TenantConfigurationValueRequest */ - public String getKey() { - return key; - } - - /** - * @param key - * the key to set - */ - public void setKey(final String key) { - this.key = key; - } - - /** - * @return the value - */ - public String getValue() { + public Object getValue() { return value; } /** + * Sets the TenantConfigurationValueRequest + * * @param value - * the value to set */ - public void setValue(final String value) { + public void setValue(final Object value) { this.value = value; } + } diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/TenantConfigurationValueRest.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/TenantConfigurationValueRest.java new file mode 100644 index 000000000..47dcfebe0 --- /dev/null +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/TenantConfigurationValueRest.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ + +package org.eclipse.hawkbit.rest.resource.model.system; + +import org.springframework.hateoas.ResourceSupport; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +/** + * A json annotated rest model for a tenant configuration value to RESTful API + * representation. + * + */ +@JsonInclude(Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class TenantConfigurationValueRest extends ResourceSupport { + + @JsonInclude(Include.ALWAYS) + private Object value; + + @JsonInclude(Include.ALWAYS) + private boolean isGlobal = true; + + private Long lastModifiedAt; + private String lastModifiedBy; + private Long createdAt; + private String createdBy; + + public Object getValue() { + return value; + } + + public void setValue(final Object value) { + this.value = value; + } + + public boolean isGlobal() { + return isGlobal; + } + + public void setGlobal(final boolean isGlobal) { + this.isGlobal = isGlobal; + } + + public Long getLastModifiedAt() { + return lastModifiedAt; + } + + public void setLastModifiedAt(final Long lastModifiedAt) { + this.lastModifiedAt = lastModifiedAt; + } + + public String getLastModifiedBy() { + return lastModifiedBy; + } + + public void setLastModifiedBy(final String lastModifiedBy) { + this.lastModifiedBy = lastModifiedBy; + } + + public Long getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(final Long createdAt) { + this.createdAt = createdAt; + } + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(final String createdBy) { + this.createdBy = createdBy; + } + +} diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/CacheRest.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/systemmanagement/CacheRest.java similarity index 95% rename from hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/CacheRest.java rename to hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/systemmanagement/CacheRest.java index e430cba1c..35d35c0b1 100644 --- a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/CacheRest.java +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/systemmanagement/CacheRest.java @@ -6,7 +6,7 @@ * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ -package org.eclipse.hawkbit.rest.resource.model.system; +package org.eclipse.hawkbit.rest.resource.model.systemmanagement; import java.util.Collection; diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/SystemStatisticsRest.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/systemmanagement/SystemStatisticsRest.java similarity index 97% rename from hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/SystemStatisticsRest.java rename to hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/systemmanagement/SystemStatisticsRest.java index 84ce69948..c69201d69 100644 --- a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/SystemStatisticsRest.java +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/systemmanagement/SystemStatisticsRest.java @@ -6,7 +6,7 @@ * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ -package org.eclipse.hawkbit.rest.resource.model.system; +package org.eclipse.hawkbit.rest.resource.model.systemmanagement; import java.util.List; diff --git a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/TenantSystemUsageRest.java b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/systemmanagement/TenantSystemUsageRest.java similarity index 96% rename from hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/TenantSystemUsageRest.java rename to hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/systemmanagement/TenantSystemUsageRest.java index b300d94de..bc8653b36 100644 --- a/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/system/TenantSystemUsageRest.java +++ b/hawkbit-rest-api/src/main/java/org/eclipse/hawkbit/rest/resource/model/systemmanagement/TenantSystemUsageRest.java @@ -6,7 +6,7 @@ * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ -package org.eclipse.hawkbit.rest.resource.model.system; +package org.eclipse.hawkbit.rest.resource.model.systemmanagement; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/cache/CacheWriteNotify.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/cache/CacheWriteNotify.java deleted file mode 100644 index 1bf5704e1..000000000 --- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/cache/CacheWriteNotify.java +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.eclipse.hawkbit.cache; - -import org.eclipse.hawkbit.eventbus.event.DownloadProgressEvent; -import org.eclipse.hawkbit.repository.model.Action; -import org.eclipse.hawkbit.repository.model.ActionStatus; -import org.eclipse.hawkbit.tenancy.TenantAware; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.Cache; -import org.springframework.cache.CacheManager; -import org.springframework.stereotype.Service; - -import com.google.common.eventbus.EventBus; - -/** - * An service which combines the functionality for functional use cases to write - * into the cache an notify the writing to the cache to the {@link EventBus}. - * - * - * - * - */ -@Service -public class CacheWriteNotify { - - /** - * - */ - private static final int DOWNLOAD_PROGRESS_MAX = 100; - - @Autowired - private CacheManager cacheManager; - - @Autowired - private EventBus eventBus; - - @Autowired - private TenantAware tenantAware; - - /** - * writes the download progress in percentage into the cache - * {@link CacheKeys#DOWNLOAD_PROGRESS_PERCENT} and notifies the - * {@link EventBus} with a {@link DownloadProgressEvent}. - * - * @param statusId - * the ID of the {@link ActionStatus} - * @param progressPercent - * the progress in percentage which must be between 0-100 - */ - public void downloadProgressPercent(final long statusId, final int progressPercent) { - - final Cache cache = cacheManager.getCache(Action.class.getName()); - final String cacheKey = CacheKeys.entitySpecificCacheKey(String.valueOf(statusId), - CacheKeys.DOWNLOAD_PROGRESS_PERCENT); - if (progressPercent < DOWNLOAD_PROGRESS_MAX) { - cache.put(cacheKey, progressPercent); - } else { - // in case we reached progress 100 delete the cache value again - // because otherwise he will - // keep there forever - cache.evict(cacheKey); - } - - eventBus.post(new DownloadProgressEvent(tenantAware.getCurrentTenant(), statusId, progressPercent)); - } - - /** - * @param cacheManager - * the cacheManager to set - */ - void setCacheManager(final CacheManager cacheManager) { - this.cacheManager = cacheManager; - } - - /** - * @param eventBus - * the eventBus to set - */ - void setEventBus(final EventBus eventBus) { - this.eventBus = eventBus; - } - - /** - * @param tenantAware - * the tenantAware to set - */ - void setTenantAware(final TenantAware tenantAware) { - this.tenantAware = tenantAware; - } -} diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/controller/ArtifactStoreController.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/controller/ArtifactStoreController.java index 05fd6c492..c2dbd3ba5 100644 --- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/controller/ArtifactStoreController.java +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/controller/ArtifactStoreController.java @@ -25,13 +25,11 @@ import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.LocalArtifact; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.rest.resource.helper.RestResourceConversionHelper; +import org.eclipse.hawkbit.security.HawkbitSecurityProperties; import org.eclipse.hawkbit.util.IpUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.bind.RelaxedPropertyResolver; -import org.springframework.context.EnvironmentAware; -import org.springframework.core.env.Environment; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.web.bind.annotation.AuthenticationPrincipal; @@ -55,7 +53,7 @@ import org.springframework.web.bind.annotation.RestController; */ @RestController @RequestMapping(ControllerConstants.ARTIFACTS_V1_REQUEST_MAPPING) -public class ArtifactStoreController implements EnvironmentAware { +public class ArtifactStoreController { private static final Logger LOG = LoggerFactory.getLogger(ArtifactStoreController.class); @Autowired @@ -67,14 +65,8 @@ public class ArtifactStoreController implements EnvironmentAware { @Autowired private CacheWriteNotify cacheWriteNotify; - private static final String SP_SERVER_CONFIG_PREFIX = "hawkbit.server."; - private RelaxedPropertyResolver environment; - - @Override - public void setEnvironment(final Environment environment) { - this.environment = new RelaxedPropertyResolver(environment, SP_SERVER_CONFIG_PREFIX); - - } + @Autowired + private HawkbitSecurityProperties securityProperties; /** * Handles GET {@link Artifact} download request. This could be full or @@ -138,8 +130,8 @@ public class ArtifactStoreController implements EnvironmentAware { private Action checkAndReportDownloadByTarget(final HttpServletRequest request, final String targetid, final LocalArtifact artifact) { - final Target target = controllerManagement.updateLastTargetQuery(targetid, IpUtil.getClientIpFromRequest( - request, environment.getProperty("security.rp.remote_ip_header", String.class, "X-Forwarded-For"))); + final Target target = controllerManagement.updateLastTargetQuery(targetid, + IpUtil.getClientIpFromRequest(request, securityProperties.getClients().getRemoteIpHeader())); final Action action = controllerManagement .getActionForDownloadByTargetAndSoftwareModule(target.getControllerId(), artifact.getSoftwareModule()); diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/controller/RootController.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/controller/RootController.java index 8246dd430..ff1e26f8b 100644 --- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/controller/RootController.java +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/controller/RootController.java @@ -15,7 +15,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; -import org.eclipse.hawkbit.ControllerPollProperties; import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; import org.eclipse.hawkbit.cache.CacheWriteNotify; import org.eclipse.hawkbit.controller.model.ActionFeedback; @@ -41,15 +40,13 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.rest.resource.helper.RestResourceConversionHelper; +import org.eclipse.hawkbit.security.HawkbitSecurityProperties; import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.util.IpUtil; import org.hibernate.validator.constraints.NotEmpty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.bind.RelaxedPropertyResolver; -import org.springframework.context.EnvironmentAware; -import org.springframework.core.env.Environment; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -74,13 +71,11 @@ import org.springframework.web.bind.annotation.RestController; */ @RestController @RequestMapping(ControllerConstants.BASE_V1_REQUEST_MAPPING) -public class RootController implements EnvironmentAware { +public class RootController { private static final Logger LOG = LoggerFactory.getLogger(RootController.class); private static final String GIVEN_ACTION_IS_NOT_ASSIGNED_TO_GIVEN_TARGET = "given action ({}) is not assigned to given target ({})."; - private static final String SP_SERVER_CONFIG_PREFIX = "hawkbit.server."; - @Autowired private ControllerManagement controllerManagement; @@ -90,25 +85,14 @@ public class RootController implements EnvironmentAware { @Autowired private ArtifactManagement artifactManagement; - @Autowired - private ControllerPollProperties controllerPollProperties; - @Autowired private CacheWriteNotify cacheWriteNotify; @Autowired private TenantAware tenantAware; - private String requestHeader; - - @Override - public void setEnvironment(final Environment environment) { - final RelaxedPropertyResolver relaxedPropertyResolver = new RelaxedPropertyResolver(environment, - SP_SERVER_CONFIG_PREFIX); - - requestHeader = relaxedPropertyResolver.getProperty("security.rp.remote_ip_header", String.class, - "X-Forwarded-For"); - } + @Autowired + private HawkbitSecurityProperties securityProperties; /** * Returns all artifacts of a given software module and target. @@ -155,17 +139,18 @@ public class RootController implements EnvironmentAware { LOG.debug("getControllerBase({})", targetid); final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotexist(targetid, - IpUtil.getClientIpFromRequest(request, requestHeader)); + IpUtil.getClientIpFromRequest(request, securityProperties.getClients().getRemoteIpHeader())); if (target.getTargetInfo().getUpdateStatus() == TargetUpdateStatus.UNKNOWN) { LOG.debug("target with {} extsisted but was in status UNKNOWN -> REGISTERED)", targetid); controllerManagement.updateTargetStatus(target.getTargetInfo(), TargetUpdateStatus.REGISTERED, - System.currentTimeMillis(), IpUtil.getClientIpFromRequest(request, requestHeader)); + System.currentTimeMillis(), + IpUtil.getClientIpFromRequest(request, securityProperties.getClients().getRemoteIpHeader())); } return new ResponseEntity<>( DataConversionHelper.fromTarget(target, controllerManagement.findActionByTargetAndActive(target), - controllerPollProperties.getPollingTime(), tenantAware), + controllerManagement.findPollingTime(), tenantAware), HttpStatus.OK); } @@ -195,7 +180,7 @@ public class RootController implements EnvironmentAware { ResponseEntity result; final Target target = controllerManagement.updateLastTargetQuery(targetid, - IpUtil.getClientIpFromRequest(request, requestHeader)); + IpUtil.getClientIpFromRequest(request, securityProperties.getClients().getRemoteIpHeader())); final SoftwareModule module = softwareManagement.findSoftwareModuleById(softwareModuleId); if (checkModule(fileName, module)) { @@ -265,7 +250,8 @@ public class RootController implements EnvironmentAware { public ResponseEntity downloadArtifactMd5(@PathVariable final String targetid, @PathVariable final Long softwareModuleId, @PathVariable final String fileName, final HttpServletResponse response, final HttpServletRequest request) { - controllerManagement.updateLastTargetQuery(targetid, IpUtil.getClientIpFromRequest(request, requestHeader)); + controllerManagement.updateLastTargetQuery(targetid, + IpUtil.getClientIpFromRequest(request, securityProperties.getClients().getRemoteIpHeader())); final SoftwareModule module = softwareManagement.findSoftwareModuleById(softwareModuleId); @@ -311,7 +297,7 @@ public class RootController implements EnvironmentAware { LOG.debug("getControllerBasedeploymentAction({},{})", targetid, resource); final Target target = controllerManagement.updateLastTargetQuery(targetid, - IpUtil.getClientIpFromRequest(request, requestHeader)); + IpUtil.getClientIpFromRequest(request, securityProperties.getClients().getRemoteIpHeader())); final Action action = findActionWithExceptionIfNotFound(actionId); if (!action.getTarget().getId().equals(target.getId())) { @@ -362,7 +348,7 @@ public class RootController implements EnvironmentAware { LOG.debug("provideBasedeploymentActionFeedback for target [{},{}]: {}", targetid, actionId, feedback); final Target target = controllerManagement.updateLastTargetQuery(targetid, - IpUtil.getClientIpFromRequest(request, requestHeader)); + IpUtil.getClientIpFromRequest(request, securityProperties.getClients().getRemoteIpHeader())); if (!actionId.equals(feedback.getId())) { LOG.warn( @@ -435,8 +421,6 @@ public class RootController implements EnvironmentAware { LOG.debug("Controller reported intermediate status (actionid: {}, targetid: {}) as we got {} report.", actionid, targetid, feedback.getStatus().getExecution()); actionStatus.setStatus(Status.RUNNING); - // MECS-400: we should not use the unstructed message list for - // the server comment on the status. actionStatus.addMessage("Controller reported: " + feedback.getStatus().getExecution()); } @@ -469,7 +453,8 @@ public class RootController implements EnvironmentAware { + ControllerConstants.CONFIG_DATA_ACTION, method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity putConfigData(@Valid @RequestBody final ConfigData configData, @PathVariable final String targetid, final HttpServletRequest request) { - controllerManagement.updateLastTargetQuery(targetid, IpUtil.getClientIpFromRequest(request, requestHeader)); + controllerManagement.updateLastTargetQuery(targetid, + IpUtil.getClientIpFromRequest(request, securityProperties.getClients().getRemoteIpHeader())); controllerManagement.updateControllerAttributes(targetid, configData.getData()); @@ -495,7 +480,7 @@ public class RootController implements EnvironmentAware { LOG.debug("getControllerCancelAction({})", targetid); final Target target = controllerManagement.updateLastTargetQuery(targetid, - IpUtil.getClientIpFromRequest(request, requestHeader)); + IpUtil.getClientIpFromRequest(request, securityProperties.getClients().getRemoteIpHeader())); final Action action = findActionWithExceptionIfNotFound(actionId); if (!action.getTarget().getId().equals(target.getId())) { @@ -542,7 +527,7 @@ public class RootController implements EnvironmentAware { LOG.debug("provideCancelActionFeedback for target [{}]: {}", targetid, feedback); final Target target = controllerManagement.updateLastTargetQuery(targetid, - IpUtil.getClientIpFromRequest(request, requestHeader)); + IpUtil.getClientIpFromRequest(request, securityProperties.getClients().getRemoteIpHeader())); if (!actionId.equals(feedback.getId())) { LOG.warn( diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/DistributionSetMapper.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/DistributionSetMapper.java index 830b47798..9ae133dd5 100644 --- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/DistributionSetMapper.java +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/DistributionSetMapper.java @@ -22,6 +22,8 @@ import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.SoftwareModule; +import org.eclipse.hawkbit.rest.resource.api.DistributionSetRestApi; +import org.eclipse.hawkbit.rest.resource.api.DistributionSetTypeRestApi; import org.eclipse.hawkbit.rest.resource.model.MetadataRest; import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetRequestBodyPost; import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetRest; @@ -169,13 +171,13 @@ public final class DistributionSetMapper { response.setRequiredMigrationStep(distributionSet.isRequiredMigrationStep()); response.add( - linkTo(methodOn(DistributionSetResource.class).getDistributionSet(response.getDsId())).withRel("self")); + linkTo(methodOn(DistributionSetRestApi.class).getDistributionSet(response.getDsId())).withRel("self")); response.add(linkTo( - methodOn(DistributionSetTypeResource.class).getDistributionSetType(distributionSet.getType().getId())) + methodOn(DistributionSetTypeRestApi.class).getDistributionSetType(distributionSet.getType().getId())) .withRel("type")); - response.add(linkTo(methodOn(DistributionSetResource.class).getMetadata(response.getDsId(), + response.add(linkTo(methodOn(DistributionSetRestApi.class).getMetadata(response.getDsId(), Integer.parseInt(RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET), Integer.parseInt(RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT), null, null)) .withRel("metadata")); diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/DistributionSetResource.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/DistributionSetResource.java index b0a245915..a4c71378d 100644 --- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/DistributionSetResource.java +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/DistributionSetResource.java @@ -30,6 +30,7 @@ import org.eclipse.hawkbit.repository.model.DsMetadataCompositeKey; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.rsql.RSQLUtility; +import org.eclipse.hawkbit.rest.resource.api.DistributionSetRestApi; import org.eclipse.hawkbit.rest.resource.helper.RestResourceConversionHelper; import org.eclipse.hawkbit.rest.resource.model.MetadataRest; import org.eclipse.hawkbit.rest.resource.model.MetadataRestPageList; @@ -51,25 +52,14 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * REST Resource handling for {@link DistributionSet} CRUD operations. - * - * - * - * */ @RestController -@RequestMapping(RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING) -public class DistributionSetResource { +public class DistributionSetResource implements DistributionSetRestApi { private static final Logger LOG = LoggerFactory.getLogger(DistributionSetResource.class); @Autowired @@ -90,32 +80,9 @@ public class DistributionSetResource { @Autowired private DistributionSetManagement distributionSetManagement; - /** - * Handles the GET request of retrieving all {@link DistributionSet}s within - * SP. - * - * @param pagingOffsetParam - * the offset of list of sets for pagination, might not be - * present in the rest request then default value will be applied - * @param pagingLimitParam - * the limit of the paged request, might not be present in the - * rest request then default value will be applied - * @param sortParam - * the sorting parameter in the request URL, syntax - * {@code field:direction, field:direction} - * @param rsqlParam - * the search parameter in the request URL, syntax - * {@code q=name==abc} - * @return a list of all set for a defined or default page request with - * status OK. The response is always paged. In any failure the - * JsonResponseExceptionHandler is handling the response. - */ - @RequestMapping(method = RequestMethod.GET, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity getDistributionSets( - @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) { + @Override + public ResponseEntity getDistributionSets(final int pagingOffsetParam, + final int pagingLimitParam, final String sortParam, final String rsqlParam) { final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam); final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam); @@ -124,99 +91,51 @@ public class DistributionSetResource { final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting); final Page findDsPage; if (rsqlParam != null) { - findDsPage = distributionSetManagement.findDistributionSetsAll( + findDsPage = this.distributionSetManagement.findDistributionSetsAll( RSQLUtility.parse(rsqlParam, DistributionSetFields.class), pageable, false); } else { - findDsPage = distributionSetManagement.findDistributionSetsAll(pageable, false, null); + findDsPage = this.distributionSetManagement.findDistributionSetsAll(pageable, false, null); } final List rest = DistributionSetMapper.toResponseFromDsList(findDsPage.getContent()); return new ResponseEntity<>(new DistributionSetPagedList(rest, findDsPage.getTotalElements()), HttpStatus.OK); } - /** - * Handles the GET request of retrieving a single {@link DistributionSet} - * within SP. - * - * @param distributionSetId - * the ID of the set to retrieve - * - * @return a single {@link DistributionSet} with status OK. - * - * @throws EntityNotFoundException - * in case no {@link DistributionSet} with the given ID exists. - */ - @RequestMapping(method = RequestMethod.GET, value = "/{distributionSetId}", produces = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity getDistributionSet(@PathVariable final Long distributionSetId) { + @Override + public ResponseEntity getDistributionSet(final Long distributionSetId) { final DistributionSet foundDs = findDistributionSetWithExceptionIfNotFound(distributionSetId); return new ResponseEntity<>(DistributionSetMapper.toResponse(foundDs), HttpStatus.OK); } - /** - * Handles the POST request of creating new distribution sets within SP. The - * request body must always be a list of sets. The requests is delegating to - * the {@link SoftwareManagement#createDistributionSet(DistributionSet))}. - * - * @param sets - * the {@link DistributionSet}s to be created. - * @return In case all sets could successful created the ResponseEntity with - * status code 201 - Created but without ResponseBody. In any - * failure the JsonResponseExceptionHandler is handling the - * response. - */ - @RequestMapping(method = RequestMethod.POST, consumes = { MediaType.APPLICATION_JSON_VALUE, - "application/hal+json" }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + @Override public ResponseEntity createDistributionSets( - @RequestBody final List sets) { + final List sets) { LOG.debug("creating {} distribution sets", sets.size()); // set default Ds type if ds type is null - sets.stream().filter(ds -> ds.getType() == null).forEach(ds -> ds.setType( - systemManagement.getTenantMetadata(currentTenant.getCurrentTenant()).getDefaultDsType().getKey())); + sets.stream().filter(ds -> ds.getType() == null).forEach(ds -> ds.setType(this.systemManagement + .getTenantMetadata(this.currentTenant.getCurrentTenant()).getDefaultDsType().getKey())); - final Iterable createdDSets = distributionSetManagement.createDistributionSets( - DistributionSetMapper.dsFromRequest(sets, softwareManagement, distributionSetManagement)); + final Iterable createdDSets = this.distributionSetManagement.createDistributionSets( + DistributionSetMapper.dsFromRequest(sets, this.softwareManagement, this.distributionSetManagement)); LOG.debug("{} distribution sets created, return status {}", sets.size(), HttpStatus.CREATED); return new ResponseEntity<>(DistributionSetMapper.toResponseDistributionSets(createdDSets), HttpStatus.CREATED); } - /** - * Handles the DELETE request for a single {@link DistributionSet} within - * SP. - * - * @param distributionSetId - * the ID of the {@link DistributionSet} to delete - * @return status OK if delete as successful. - * - */ - @RequestMapping(method = RequestMethod.DELETE, value = "/{distributionSetId}") - public ResponseEntity deleteDistributionSet(@PathVariable final Long distributionSetId) { + @Override + public ResponseEntity deleteDistributionSet(final Long distributionSetId) { final DistributionSet set = findDistributionSetWithExceptionIfNotFound(distributionSetId); - distributionSetManagement.deleteDistributionSet(set); + this.distributionSetManagement.deleteDistributionSet(set); return new ResponseEntity<>(HttpStatus.OK); } - /** - * Handles the UPDATE request for a single {@link DistributionSet} within - * SP. - * - * @param distributionSetId - * the ID of the {@link DistributionSet} to delete - * @param toUpdate - * with the data that needs updating - * - * @return status OK if update as successful with updated content. - * - */ - @RequestMapping(method = RequestMethod.PUT, value = "/{distributionSetId}", consumes = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }, produces = { MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) - public ResponseEntity updateDistributionSet(@PathVariable final Long distributionSetId, - @RequestBody final DistributionSetRequestBodyPut toUpdate) { + @Override + public ResponseEntity updateDistributionSet(final Long distributionSetId, + final DistributionSetRequestBodyPut toUpdate) { final DistributionSet set = findDistributionSetWithExceptionIfNotFound(distributionSetId); if (toUpdate.getDescription() != null) { @@ -231,38 +150,13 @@ public class DistributionSetResource { set.setVersion(toUpdate.getVersion()); } return new ResponseEntity<>( - DistributionSetMapper.toResponse(distributionSetManagement.updateDistributionSet(set)), HttpStatus.OK); + DistributionSetMapper.toResponse(this.distributionSetManagement.updateDistributionSet(set)), + HttpStatus.OK); } - /** - * Handles the GET request of retrieving assigned targets to a specific - * distribution set. - * - * @param distributionSetId - * the ID of the distribution set to retrieve the assigned - * targets - * @param pagingOffsetParam - * the offset of list of targets for pagination, might not be - * present in the rest request then default value will be applied - * @param pagingLimitParam - * the limit of the paged request, might not be present in the - * rest request then default value will be applied - * @param sortParam - * the sorting parameter in the request URL, syntax - * {@code field:direction, field:direction} - * @param rsqlParam - * the search parameter in the request URL, syntax - * {@code q=name==abc} - * @return status OK if get request is successful with the paged list of - * targets - */ - @RequestMapping(method = RequestMethod.GET, value = "/{distributionSetId}/assignedTargets", produces = { - MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) - public ResponseEntity getAssignedTargets(@PathVariable final Long distributionSetId, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) { + @Override + public ResponseEntity getAssignedTargets(final Long distributionSetId, final int pagingOffsetParam, + final int pagingLimitParam, final String sortParam, final String rsqlParam) { // check if distribution set exists otherwise throw exception // immediately @@ -275,45 +169,19 @@ public class DistributionSetResource { final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting); final Page targetsAssignedDS; if (rsqlParam != null) { - targetsAssignedDS = targetManagement.findTargetByAssignedDistributionSet(distributionSetId, + targetsAssignedDS = this.targetManagement.findTargetByAssignedDistributionSet(distributionSetId, RSQLUtility.parse(rsqlParam, TargetFields.class), pageable); } else { - targetsAssignedDS = targetManagement.findTargetByAssignedDistributionSet(distributionSetId, pageable); + targetsAssignedDS = this.targetManagement.findTargetByAssignedDistributionSet(distributionSetId, pageable); } return new ResponseEntity<>(new TargetPagedList(TargetMapper.toResponse(targetsAssignedDS.getContent()), targetsAssignedDS.getTotalElements()), HttpStatus.OK); } - /** - * Handles the GET request of retrieving installed targets to a specific - * distribution set. - * - * @param distributionSetId - * the ID of the distribution set to retrieve the assigned - * targets - * @param pagingOffsetParam - * the offset of list of targets for pagination, might not be - * present in the rest request then default value will be applied - * @param pagingLimitParam - * the limit of the paged request, might not be present in the - * rest request then default value will be applied - * @param sortParam - * the sorting parameter in the request URL, syntax - * {@code field:direction, field:direction} - * @param rsqlParam - * the search parameter in the request URL, syntax - * {@code q=name==abc} - * @return status OK if get request is successful with the paged list of - * targets - */ - @RequestMapping(method = RequestMethod.GET, value = "/{distributionSetId}/installedTargets", produces = { - MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) - public ResponseEntity getInstalledTargets(@PathVariable final Long distributionSetId, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) { + @Override + public ResponseEntity getInstalledTargets(final Long distributionSetId, + final int pagingOffsetParam, final int pagingLimitParam, final String sortParam, final String rsqlParam) { // check if distribution set exists otherwise throw exception // immediately findDistributionSetWithExceptionIfNotFound(distributionSetId); @@ -325,36 +193,22 @@ public class DistributionSetResource { final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting); final Page targetsInstalledDS; if (rsqlParam != null) { - targetsInstalledDS = targetManagement.findTargetByInstalledDistributionSet(distributionSetId, + targetsInstalledDS = this.targetManagement.findTargetByInstalledDistributionSet(distributionSetId, RSQLUtility.parse(rsqlParam, TargetFields.class), pageable); } else { - targetsInstalledDS = targetManagement.findTargetByInstalledDistributionSet(distributionSetId, pageable); + targetsInstalledDS = this.targetManagement.findTargetByInstalledDistributionSet(distributionSetId, + pageable); } return new ResponseEntity<>(new TargetPagedList(TargetMapper.toResponse(targetsInstalledDS.getContent()), targetsInstalledDS.getTotalElements()), HttpStatus.OK); } - /** - * Handles the POST request of assigning multiple targets to a single - * distribution set. - * - * @param distributionSetId - * the ID of the distribution set within the URL path parameter - * @param targetIds - * the IDs of the target which should get assigned to the - * distribution set given in the response body - * @return status OK if the assignment of the targets was successful and a - * complex return body which contains information about the assigned - * targets and the already assigned targets counters - */ - @RequestMapping(method = RequestMethod.POST, value = "/{distributionSetId}/assignedTargets", consumes = { - "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }, produces = { MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) - public ResponseEntity createAssignedTarget(@PathVariable final Long distributionSetId, - @RequestBody final List targetIds) { + @Override + public ResponseEntity createAssignedTarget(final Long distributionSetId, + final List targetIds) { - final DistributionSetAssignmentResult assignDistributionSet = deployManagament.assignDistributionSet( + final DistributionSetAssignmentResult assignDistributionSet = this.deployManagament.assignDistributionSet( distributionSetId, targetIds.stream() .map(t -> new TargetWithActionType(t.getId(), @@ -364,33 +218,9 @@ public class DistributionSetResource { return new ResponseEntity<>(DistributionSetMapper.toResponse(assignDistributionSet), HttpStatus.OK); } - /** - * Gets a paged list of meta data for a distribution set. - * - * @param distributionSetId - * the ID of the distribution set for the meta data - * @param pagingOffsetParam - * the offset of list of targets for pagination, might not be - * present in the rest request then default value will be applied - * @param pagingLimitParam - * the limit of the paged request, might not be present in the - * rest request then default value will be applied - * @param sortParam - * the sorting parameter in the request URL, syntax - * {@code field:direction, field:direction} - * @param rsqlParam - * the search parameter in the request URL, syntax - * {@code q=key==abc} - * @return status OK if get request is successful with the paged list of - * meta data - */ - @RequestMapping(method = RequestMethod.GET, value = "/{distributionSetId}/metadata", produces = { - MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) - public ResponseEntity getMetadata(@PathVariable final Long distributionSetId, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) { + @Override + public ResponseEntity getMetadata(final Long distributionSetId, final int pagingOffsetParam, + final int pagingLimitParam, final String sortParam, final String rsqlParam) { // check if distribution set exists otherwise throw exception // immediately @@ -404,11 +234,11 @@ public class DistributionSetResource { final Page metaDataPage; if (rsqlParam != null) { - metaDataPage = distributionSetManagement.findDistributionSetMetadataByDistributionSetId(distributionSetId, - RSQLUtility.parse(rsqlParam, DistributionSetMetadataFields.class), pageable); + metaDataPage = this.distributionSetManagement.findDistributionSetMetadataByDistributionSetId( + distributionSetId, RSQLUtility.parse(rsqlParam, DistributionSetMetadataFields.class), pageable); } else { - metaDataPage = distributionSetManagement.findDistributionSetMetadataByDistributionSetId(distributionSetId, - pageable); + metaDataPage = this.distributionSetManagement + .findDistributionSetMetadataByDistributionSetId(distributionSetId, pageable); } return new ResponseEntity<>( @@ -418,119 +248,59 @@ public class DistributionSetResource { } - /** - * Gets a single meta data value for a specific key of a distribution set. - * - * @param distributionSetId - * the ID of the distribution set to get the meta data from - * @param metadataKey - * the key of the meta data entry to retrieve the value from - * @return status OK if get request is successful with the value of the meta - * data - */ - @RequestMapping(method = RequestMethod.GET, value = "/{distributionSetId}/metadata/{metadataKey}", produces = { - MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity getMetadataValue(@PathVariable final Long distributionSetId, - @PathVariable final String metadataKey) { + @Override + public ResponseEntity getMetadataValue(final Long distributionSetId, final String metadataKey) { // check if distribution set exists otherwise throw exception // immediately final DistributionSet ds = findDistributionSetWithExceptionIfNotFound(distributionSetId); - final DistributionSetMetadata findOne = distributionSetManagement + final DistributionSetMetadata findOne = this.distributionSetManagement .findOne(new DsMetadataCompositeKey(ds, metadataKey)); return ResponseEntity. ok(DistributionSetMapper.toResponseDsMetadata(findOne)); } - /** - * Updates a single meta data value of a distribution set. - * - * @param distributionSetId - * the ID of the distribution set to update the meta data entry - * @param metadataKey - * the key of the meta data to update the value - * @return status OK if the update request is successful and the updated - * meta data result - */ - @RequestMapping(method = RequestMethod.PUT, value = "/{distributionSetId}/metadata/{metadataKey}", produces = { - MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) - public ResponseEntity updateMetadata(@PathVariable final Long distributionSetId, - @PathVariable final String metadataKey, @RequestBody final MetadataRest metadata) { + @Override + public ResponseEntity updateMetadata(final Long distributionSetId, final String metadataKey, + final MetadataRest metadata) { // check if distribution set exists otherwise throw exception // immediately final DistributionSet ds = findDistributionSetWithExceptionIfNotFound(distributionSetId); - final DistributionSetMetadata updated = distributionSetManagement + final DistributionSetMetadata updated = this.distributionSetManagement .updateDistributionSetMetadata(new DistributionSetMetadata(metadataKey, ds, metadata.getValue())); return ResponseEntity.ok(DistributionSetMapper.toResponseDsMetadata(updated)); } - /** - * Deletes a single meta data entry from the distribution set. - * - * @param distributionSetId - * the ID of the distribution set to delete the meta data entry - * @param metadataKey - * the key of the meta data to delete - * @return status OK if the delete request is successful - */ - @RequestMapping(method = RequestMethod.DELETE, value = "/{distributionSetId}/metadata/{metadataKey}") - public ResponseEntity deleteMetadata(@PathVariable final Long distributionSetId, - @PathVariable final String metadataKey) { + @Override + public ResponseEntity deleteMetadata(final Long distributionSetId, final String metadataKey) { // check if distribution set exists otherwise throw exception // immediately final DistributionSet ds = findDistributionSetWithExceptionIfNotFound(distributionSetId); - distributionSetManagement.deleteDistributionSetMetadata(new DsMetadataCompositeKey(ds, metadataKey)); + this.distributionSetManagement.deleteDistributionSetMetadata(new DsMetadataCompositeKey(ds, metadataKey)); return ResponseEntity.ok().build(); } - /** - * Creates a list of meta data for a specific distribution set. - * - * @param distributionSetId - * the ID of the distribution set to create meta data for - * @param metadataRest - * the list of meta data entries to create - * @return status created if post request is successful with the value of - * the created meta data - */ - @RequestMapping(method = RequestMethod.POST, value = "/{distributionSetId}/metadata", consumes = { - MediaType.APPLICATION_JSON_VALUE, - "application/hal+json" }, produces = { MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) - public ResponseEntity> createMetadata(@PathVariable final Long distributionSetId, - @RequestBody final List metadataRest) { + @Override + public ResponseEntity> createMetadata(final Long distributionSetId, + final List metadataRest) { // check if distribution set exists otherwise throw exception // immediately final DistributionSet ds = findDistributionSetWithExceptionIfNotFound(distributionSetId); - final List created = distributionSetManagement + final List created = this.distributionSetManagement .createDistributionSetMetadata(DistributionSetMapper.fromRequestDsMetadata(ds, metadataRest)); return new ResponseEntity<>(DistributionSetMapper.toResponseDsMetadata(created), HttpStatus.CREATED); } - /** - * Assigns a list of software modules to a distribution set. - * - * @param distributionSetId - * the ID of the distribution set to assign software modules for - * @param softwareModuleIDs - * the list of software modules ids to assign - * @return {@link HttpStatus#OK} - * - * @throws EntityNotFoundException - * in case no distribution set with the given - * {@code distributionSetId} exists. - */ - @RequestMapping(method = RequestMethod.POST, value = "/{distributionSetId}/assignedSM", consumes = { - MediaType.APPLICATION_JSON_VALUE, - "application/hal+json" }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity assignSoftwareModules(@PathVariable final Long distributionSetId, - @RequestBody final List softwareModuleIDs) { + @Override + public ResponseEntity assignSoftwareModules(final Long distributionSetId, + final List softwareModuleIDs) { // check if distribution set exists otherwise throw exception // immediately final DistributionSet ds = findDistributionSetWithExceptionIfNotFound(distributionSetId); final Set softwareModuleToBeAssigned = new HashSet<>(); for (final SoftwareModuleAssigmentRest sm : softwareModuleIDs) { - final SoftwareModule softwareModule = softwareManagement.findSoftwareModuleById(sm.getId()); + final SoftwareModule softwareModule = this.softwareManagement.findSoftwareModuleById(sm.getId()); if (softwareModule != null) { softwareModuleToBeAssigned.add(softwareModule); } else { @@ -538,63 +308,23 @@ public class DistributionSetResource { } } // Add Softwaremodules to DisSet only if all of them were found - distributionSetManagement.assignSoftwareModules(ds, softwareModuleToBeAssigned); + this.distributionSetManagement.assignSoftwareModules(ds, softwareModuleToBeAssigned); return new ResponseEntity<>(HttpStatus.OK); } - /** - * Deletes the assignment of the software module form the distribution set. - * - * @param distributionSetId - * the ID of the distribution set to reject the software module - * for - * @param softwareModuleId - * the software module id to get rejected form the distribution - * set - * @return status OK if rejection was successful. - * @throws EntityNotFoundException - * in case no distribution set with the given - * {@code distributionSetId} exists. - */ - @RequestMapping(method = RequestMethod.DELETE, value = "/{distributionSetId}/assignedSM/{softwareModuleId}") - public ResponseEntity deleteAssignSoftwareModules(@PathVariable final Long distributionSetId, - @PathVariable final Long softwareModuleId) { + @Override + public ResponseEntity deleteAssignSoftwareModules(final Long distributionSetId, final Long softwareModuleId) { // check if distribution set and software module exist otherwise throw // exception immediately final DistributionSet ds = findDistributionSetWithExceptionIfNotFound(distributionSetId); final SoftwareModule sm = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId); - distributionSetManagement.unassignSoftwareModule(ds, sm); + this.distributionSetManagement.unassignSoftwareModule(ds, sm); return ResponseEntity.ok().build(); } - /** - * Handles the GET request for retrieving the assigned software modules of a - * specific distribution set. - * - * @param distributionSetId - * the ID of the distribution to retrieve - * @param pagingOffsetParam - * the offset of list of sets for pagination, might not be - * present in the rest request then default value will be applied - * @param pagingLimitParam - * the limit of the paged request, might not be present in the - * rest request then default value will be applied - * @param sortParam - * the sorting parameter in the request URL, syntax - * {@code field:direction, field:direction} - * @return a list of the assigned software modules of a distribution set - * with status OK, if none is assigned than {@code null} - * @throws EntityNotFoundException - * in case no distribution set with the given - * {@code distributionSetId} exists. - */ - @RequestMapping(method = RequestMethod.GET, value = "/{distributionSetId}/assignedSM", produces = { - "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity getAssignedSoftwareModules( - @PathVariable final Long distributionSetId, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam) { + @Override + public ResponseEntity getAssignedSoftwareModules(final Long distributionSetId, + final int pagingOffsetParam, final int pagingLimitParam, final String sortParam) { // check if distribution set exists otherwise throw exception // immediately final DistributionSet foundDs = findDistributionSetWithExceptionIfNotFound(distributionSetId); @@ -602,7 +332,7 @@ public class DistributionSetResource { final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam); final Sort sorting = PagingUtility.sanitizeSoftwareModuleSortParam(sortParam); final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting); - final Page softwaremodules = softwareManagement.findSoftwareModuleByAssignedTo(pageable, + final Page softwaremodules = this.softwareManagement.findSoftwareModuleByAssignedTo(pageable, foundDs); return new ResponseEntity<>( new SoftwareModulePagedList(SoftwareModuleMapper.toResponse(softwaremodules.getContent()), @@ -611,7 +341,7 @@ public class DistributionSetResource { } private DistributionSet findDistributionSetWithExceptionIfNotFound(final Long distributionSetId) { - final DistributionSet set = distributionSetManagement.findDistributionSetById(distributionSetId); + final DistributionSet set = this.distributionSetManagement.findDistributionSetById(distributionSetId); if (set == null) { throw new EntityNotFoundException("DistributionSet with Id {" + distributionSetId + "} does not exist"); } @@ -620,7 +350,7 @@ public class DistributionSetResource { } private SoftwareModule findSoftwareModuleWithExceptionIfNotFound(final Long softwareModuleId) { - final SoftwareModule sm = softwareManagement.findSoftwareModuleById(softwareModuleId); + final SoftwareModule sm = this.softwareManagement.findSoftwareModuleById(softwareModuleId); if (sm == null) { throw new EntityNotFoundException("SoftwareModule with Id {" + softwareModuleId + "} does not exist"); } diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/DistributionSetTagResource.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/DistributionSetTagResource.java index 99e22436e..c395863ab 100644 --- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/DistributionSetTagResource.java +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/DistributionSetTagResource.java @@ -19,6 +19,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetTag; import org.eclipse.hawkbit.repository.model.DistributionSetTagAssigmentResult; import org.eclipse.hawkbit.repository.rsql.RSQLUtility; +import org.eclipse.hawkbit.rest.resource.api.DistributionSetTagRestApi; import org.eclipse.hawkbit.rest.resource.model.distributionset.DistributionSetsRest; import org.eclipse.hawkbit.rest.resource.model.tag.AssignedDistributionSetRequestBody; import org.eclipse.hawkbit.rest.resource.model.tag.DistributionSetTagAssigmentResultRest; @@ -34,13 +35,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** @@ -48,8 +43,7 @@ import org.springframework.web.bind.annotation.RestController; * */ @RestController -@RequestMapping(RestConstants.DISTRIBUTIONSET_TAG_V1_REQUEST_MAPPING) -public class DistributionSetTagResource { +public class DistributionSetTagResource implements DistributionSetTagRestApi { private static final Logger LOG = LoggerFactory.getLogger(DistributionSetTagResource.class); @Autowired @@ -58,32 +52,9 @@ public class DistributionSetTagResource { @Autowired private DistributionSetManagement distributionSetManagement; - /** - * Handles the GET request of retrieving all DistributionSet tags. - * - * @param pagingOffsetParam - * the offset of list of DistributionSet tags for pagination, - * might not be present in the rest request then default value - * will be applied - * @param pagingLimitParam - * the limit of the paged request, might not be present in the - * rest request then default value will be applied - * @param sortParam - * the sorting parameter in the request URL, syntax - * {@code field:direction, field:direction} - * @param rsqlParam - * the search parameter in the request URL, syntax - * {@code q=name==abc} - * @return a list of all target tags for a defined or default page request - * with status OK. The response is always paged. In any failure the - * JsonResponseExceptionHandler is handling the response. - */ - @RequestMapping(method = RequestMethod.GET, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity getDistributionSetTags( - @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) { + @Override + public ResponseEntity getDistributionSetTags(final int pagingOffsetParam, final int pagingLimitParam, + final String sortParam, final String rsqlParam) { final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam); final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam); @@ -93,11 +64,11 @@ public class DistributionSetTagResource { final Slice findTargetsAll; final Long countTargetsAll; if (rsqlParam == null) { - findTargetsAll = tagManagement.findAllDistributionSetTags(pageable); - countTargetsAll = tagManagement.countTargetTags(); + findTargetsAll = this.tagManagement.findAllDistributionSetTags(pageable); + countTargetsAll = this.tagManagement.countTargetTags(); } else { - final Page findTargetPage = tagManagement + final Page findTargetPage = this.tagManagement .findAllDistributionSetTags(RSQLUtility.parse(rsqlParam, TagFields.class), pageable); countTargetsAll = findTargetPage.getTotalElements(); findTargetsAll = findTargetPage; @@ -108,141 +79,63 @@ public class DistributionSetTagResource { return new ResponseEntity<>(new TagPagedList(rest, countTargetsAll), HttpStatus.OK); } - /** - * Handles the GET request of retrieving a single distribution set tag. - * - * @param distributionsetTagId - * the ID of the distribution set tag to retrieve - * - * @return a single distribution set tag with status OK. - * @throws EntityNotFoundException - * in case the given {@code distributionsetTagId} doesn't - * exists. - */ - @RequestMapping(method = RequestMethod.GET, value = "/{distributionsetTagId}", produces = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity getDistributionSetTag(@PathVariable final Long distributionsetTagId) { + @Override + public ResponseEntity getDistributionSetTag(final Long distributionsetTagId) { final DistributionSetTag tag = findDistributionTagById(distributionsetTagId); return new ResponseEntity<>(TagMapper.toResponse(tag), HttpStatus.OK); } - /** - * Handles the POST request of creating new distribution set tag. The - * request body must always be a list of tags. - * - * @param tags - * the distribution set tags to be created. - * @return In case all modules could successful created the ResponseEntity - * with status code 201 - Created. The Response Body are the created - * distribution set tags but without ResponseBody. - */ - @RequestMapping(method = RequestMethod.POST, consumes = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity createDistributionSetTags(@RequestBody final List tags) { + @Override + public ResponseEntity createDistributionSetTags(final List tags) { LOG.debug("creating {} ds tags", tags.size()); - final List createdTags = tagManagement + final List createdTags = this.tagManagement .createDistributionSetTags(TagMapper.mapDistributionSetTagFromRequest(tags)); return new ResponseEntity<>(TagMapper.toResponseDistributionSetTag(createdTags), HttpStatus.CREATED); } - /** - * - * Handles the PUT request of updating a single distribution set tag. - * - * @param distributionsetTagId - * the ID of the distribution set tag - * @param restDSTagRest - * the the request body to be updated - * @return status OK if update is successful and the updated distribution - * set tag. - * @throws EntityNotFoundException - * in case the given {@code distributionsetTagId} doesn't - * exists. - */ - @RequestMapping(method = RequestMethod.PUT, value = "/{distributionsetTagId}", consumes = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity updateDistributionSetTag(@PathVariable final Long distributionsetTagId, - @RequestBody final TagRequestBodyPut restDSTagRest) { + @Override + public ResponseEntity updateDistributionSetTag(final Long distributionsetTagId, + final TagRequestBodyPut restDSTagRest) { LOG.debug("update {} ds tag", restDSTagRest); final DistributionSetTag distributionSetTag = findDistributionTagById(distributionsetTagId); TagMapper.updateTag(restDSTagRest, distributionSetTag); - final DistributionSetTag updateDistributionSetTag = tagManagement.updateDistributionSetTag(distributionSetTag); + final DistributionSetTag updateDistributionSetTag = this.tagManagement + .updateDistributionSetTag(distributionSetTag); LOG.debug("ds tag updated"); return new ResponseEntity<>(TagMapper.toResponse(updateDistributionSetTag), HttpStatus.OK); } - /** - * Handles the DELETE request for a single distribution set tag. - * - * @param distributionsetTagId - * the ID of the distribution set tag - * @return status OK if delete as successfully. - * @throws EntityNotFoundException - * in case the given {@code distributionsetTagId} doesn't - * exists. - * - */ - @RequestMapping(method = RequestMethod.DELETE, value = "/{distributionsetTagId}") - public ResponseEntity deleteDistributionSetTag(@PathVariable final Long distributionsetTagId) { + @Override + public ResponseEntity deleteDistributionSetTag(final Long distributionsetTagId) { LOG.debug("Delete {} distribution set tag", distributionsetTagId); final DistributionSetTag tag = findDistributionTagById(distributionsetTagId); - tagManagement.deleteDistributionSetTag(tag.getName()); + this.tagManagement.deleteDistributionSetTag(tag.getName()); return new ResponseEntity<>(HttpStatus.OK); } - /** - * Handles the GET request of retrieving all assigned distribution sets by - * the given tag id. - * - * @param distributionsetTagId - * the ID of the distribution set tag - * - * @return the list of assigned distribution sets. - * @throws EntityNotFoundException - * in case the given {@code distributionsetTagId} doesn't - * exists. - */ - @RequestMapping(method = RequestMethod.GET, value = RestConstants.DISTRIBUTIONSET_REQUEST_MAPPING) - public ResponseEntity getAssignedDistributionSets( - @PathVariable final Long distributionsetTagId) { + @Override + public ResponseEntity getAssignedDistributionSets(final Long distributionsetTagId) { final DistributionSetTag tag = findDistributionTagById(distributionsetTagId); return new ResponseEntity<>( DistributionSetMapper.toResponseDistributionSets(tag.getAssignedToDistributionSet()), HttpStatus.OK); } - /** - * Handles the POST request to toggle the assignment of distribution sets by - * the given tag id. - * - * @param distributionsetTagIds - * the ID of the distribution set tag to retrieve - * @param assignedDSRequestBodies - * list of distribution set ids to be toggled - * - * @return the list of assigned distribution sets and unassigned - * distribution sets. - * @throws EntityNotFoundException - * in case the given {@code distributionsetTagId} doesn't - * exists. - */ - @RequestMapping(method = RequestMethod.POST, value = RestConstants.DISTRIBUTIONSET_REQUEST_MAPPING - + "/toggleTagAssignment") - public ResponseEntity toggleTagAssignment( - @PathVariable final Long distributionsetTagId, - @RequestBody final List assignedDSRequestBodies) { + @Override + public ResponseEntity toggleTagAssignment(final Long distributionsetTagId, + final List assignedDSRequestBodies) { LOG.debug("Toggle distribution set assignment {} for ds tag {}", assignedDSRequestBodies.size(), distributionsetTagId); final DistributionSetTag tag = findDistributionTagById(distributionsetTagId); - final DistributionSetTagAssigmentResult assigmentResult = distributionSetManagement + final DistributionSetTagAssigmentResult assigmentResult = this.distributionSetManagement .toggleTagAssignment(findDistributionSetIds(assignedDSRequestBodies), tag.getName()); final DistributionSetTagAssigmentResultRest tagAssigmentResultRest = new DistributionSetTagAssigmentResultRest(); @@ -257,44 +150,20 @@ public class DistributionSetTagResource { return new ResponseEntity<>(tagAssigmentResultRest, HttpStatus.OK); } - /** - * Handles the POST request to assign distribution sets to the given tag id. - * - * @param distributionsetTagId - * the ID of the distribution set tag to retrieve - * @param assignedDSRequestBodies - * list of distribution sets ids to be assigned - * - * @return the list of assigned distribution set. - * @throws EntityNotFoundException - * in case the given {@code distributionsetTagId} doesn't - * exists. - */ - @RequestMapping(method = RequestMethod.POST, value = RestConstants.DISTRIBUTIONSET_REQUEST_MAPPING) - public ResponseEntity assignDistributionSets(@PathVariable final Long distributionsetTagId, - @RequestBody final List assignedDSRequestBodies) { + @Override + public ResponseEntity assignDistributionSets(final Long distributionsetTagId, + final List assignedDSRequestBodies) { LOG.debug("Assign DistributionSet {} for ds tag {}", assignedDSRequestBodies.size(), distributionsetTagId); final DistributionSetTag tag = findDistributionTagById(distributionsetTagId); - final List assignedDs = distributionSetManagement + final List assignedDs = this.distributionSetManagement .assignTag(findDistributionSetIds(assignedDSRequestBodies), tag); LOG.debug("Assignd DistributionSet {}", assignedDs.size()); return new ResponseEntity<>(DistributionSetMapper.toResponseDistributionSets(assignedDs), HttpStatus.OK); } - /** - * Handles the DELETE request to unassign all distribution set from the - * given tag id. - * - * @param distributionsetTagId - * the ID of the distribution set tag to retrieve - * @return http status code - * @throws EntityNotFoundException - * in case the given {@code distributionsetTagId} doesn't - * exists. - */ - @RequestMapping(method = RequestMethod.DELETE, value = RestConstants.DISTRIBUTIONSET_REQUEST_MAPPING) - public ResponseEntity unassignDistributionSets(@PathVariable final Long distributionsetTagId) { + @Override + public ResponseEntity unassignDistributionSets(final Long distributionsetTagId) { LOG.debug("Unassign all DS for ds tag {}", distributionsetTagId); final DistributionSetTag tag = findDistributionTagById(distributionsetTagId); if (tag.getAssignedToDistributionSet() == null) { @@ -302,36 +171,22 @@ public class DistributionSetTagResource { return new ResponseEntity<>(HttpStatus.OK); } - final List distributionSets = distributionSetManagement.unAssignAllDistributionSetsByTag(tag); + final List distributionSets = this.distributionSetManagement + .unAssignAllDistributionSetsByTag(tag); LOG.debug("Unassigned ds {}", distributionSets.size()); return new ResponseEntity<>(HttpStatus.OK); } - /** - * Handles the DELETE request to unassign one distribution set from the - * given tag id. - * - * @param distributionsetTagId - * the ID of the distribution set tag - * @param distributionsetId - * the ID of the distribution set to unassign - * @return http status code - * @throws EntityNotFoundException - * in case the given {@code distributionsetTagId} doesn't - * exists. - */ - @RequestMapping(method = RequestMethod.DELETE, value = RestConstants.DISTRIBUTIONSET_REQUEST_MAPPING - + "/{distributionsetId}") - public ResponseEntity unassignDistributionSet(@PathVariable final Long distributionsetTagId, - @PathVariable final Long distributionsetId) { + @Override + public ResponseEntity unassignDistributionSet(final Long distributionsetTagId, final Long distributionsetId) { LOG.debug("Unassign ds {} for ds tag {}", distributionsetId, distributionsetTagId); final DistributionSetTag tag = findDistributionTagById(distributionsetTagId); - distributionSetManagement.unAssignTag(distributionsetId, tag); + this.distributionSetManagement.unAssignTag(distributionsetId, tag); return new ResponseEntity<>(HttpStatus.OK); } private DistributionSetTag findDistributionTagById(final Long distributionsetTagId) { - final DistributionSetTag tag = tagManagement.findDistributionSetTagById(distributionsetTagId); + final DistributionSetTag tag = this.tagManagement.findDistributionSetTagById(distributionsetTagId); if (tag == null) { throw new EntityNotFoundException("Distribution Tag with Id {" + distributionsetTagId + "} does not exist"); } diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/DistributionSetTypeMapper.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/DistributionSetTypeMapper.java index 0550e0fc5..b32302705 100644 --- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/DistributionSetTypeMapper.java +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/DistributionSetTypeMapper.java @@ -18,6 +18,7 @@ import org.eclipse.hawkbit.repository.SoftwareManagement; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; +import org.eclipse.hawkbit.rest.resource.api.DistributionSetTypeRestApi; import org.eclipse.hawkbit.rest.resource.model.distributionsettype.DistributionSetTypeRequestBodyPost; import org.eclipse.hawkbit.rest.resource.model.distributionsettype.DistributionSetTypeRest; import org.eclipse.hawkbit.rest.resource.model.distributionsettype.DistributionSetTypesRest; @@ -87,7 +88,7 @@ final class DistributionSetTypeMapper { } static List toListResponse(final List types) { - final List response = new ArrayList(); + final List response = new ArrayList<>(); for (final DistributionSetType dsType : types) { response.add(toResponse(dsType)); } @@ -101,13 +102,13 @@ final class DistributionSetTypeMapper { result.setKey(type.getKey()); result.setModuleId(type.getId()); - result.add(linkTo(methodOn(DistributionSetTypeResource.class).getDistributionSetType(result.getModuleId())) + result.add(linkTo(methodOn(DistributionSetTypeRestApi.class).getDistributionSetType(result.getModuleId())) .withRel("self")); - result.add(linkTo(methodOn(DistributionSetTypeResource.class).getMandatoryModules(result.getModuleId())) + result.add(linkTo(methodOn(DistributionSetTypeRestApi.class).getMandatoryModules(result.getModuleId())) .withRel(RestConstants.DISTRIBUTIONSETTYPE_V1_MANDATORY_MODULES)); - result.add(linkTo(methodOn(DistributionSetTypeResource.class).getOptionalModules(result.getModuleId())) + result.add(linkTo(methodOn(DistributionSetTypeRestApi.class).getOptionalModules(result.getModuleId())) .withRel(RestConstants.DISTRIBUTIONSETTYPE_V1_OPTIONAL_MODULES)); return result; diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/DistributionSetTypeResource.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/DistributionSetTypeResource.java index 19cf94f04..a5c8544fb 100644 --- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/DistributionSetTypeResource.java +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/DistributionSetTypeResource.java @@ -19,6 +19,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.rsql.RSQLUtility; +import org.eclipse.hawkbit.rest.resource.api.DistributionSetTypeRestApi; import org.eclipse.hawkbit.rest.resource.model.IdRest; import org.eclipse.hawkbit.rest.resource.model.distributionsettype.DistributionSetTypePagedList; import org.eclipse.hawkbit.rest.resource.model.distributionsettype.DistributionSetTypeRequestBodyPost; @@ -33,12 +34,9 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -47,12 +45,9 @@ import org.springframework.web.bind.annotation.RestController; * {@link Artifact} CRUD operations. * * - * - * */ @RestController -@RequestMapping(RestConstants.DISTRIBUTIONSETTYPE_V1_REQUEST_MAPPING) -public class DistributionSetTypeResource { +public class DistributionSetTypeResource implements DistributionSetTypeRestApi { @Autowired private SoftwareManagement softwareManagement; @@ -60,30 +55,7 @@ public class DistributionSetTypeResource { @Autowired private DistributionSetManagement distributionSetManagement; - /** - * Handles the GET request of retrieving all {@link DistributionSetType}s - * within SP. - * - * @param pagingOffsetParam - * the offset of list of modules for pagination, might not be - * present in the rest request then default value will be applied - * @param pagingLimitParam - * the limit of the paged request, might not be present in the - * rest request then default value will be applied - * @param sortParam - * the sorting parameter in the request URL, syntax - * {@code field:direction, field:direction} - * @param rsqlParam - * the search parameter in the request URL, syntax - * {@code q=name==abc} - * - * @return a list of all {@link DistributionSetType} for a defined or - * default page request with status OK. The response is always - * paged. In any failure the JsonResponseExceptionHandler is - * handling the response. - */ - @RequestMapping(method = RequestMethod.GET, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) - + @Override public ResponseEntity getDistributionSetTypes( @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, @@ -92,7 +64,7 @@ public class DistributionSetTypeResource { final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam); final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam); - final Sort sorting = PagingUtility.sanitizeSoftwareModuleSortParam(sortParam); + final Sort sorting = PagingUtility.sanitizeDistributionSetTypeSortParam(sortParam); final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting); @@ -112,19 +84,7 @@ public class DistributionSetTypeResource { return new ResponseEntity<>(new DistributionSetTypePagedList(rest, countModulesAll), HttpStatus.OK); } - /** - * Handles the GET request of retrieving a single - * {@link DistributionSetType} within SP. - * - * @param distributionSetTypeId - * the ID of the module type to retrieve - * - * @return a single softwareModule with status OK. - * @throws EntityNotFoundException - * in case no with the given {@code softwareModuleId} exists. - */ - @RequestMapping(method = RequestMethod.GET, value = "/{distributionSetTypeId}", produces = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }) + @Override public ResponseEntity getDistributionSetType( @PathVariable final Long distributionSetTypeId) { final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId); @@ -132,15 +92,7 @@ public class DistributionSetTypeResource { return new ResponseEntity<>(DistributionSetTypeMapper.toResponse(foundType), HttpStatus.OK); } - /** - * Handles the DELETE request for a single Distribution Set Type within SP. - * - * @param distributionSetTypeId - * the ID of the module to retrieve - * @return status OK if delete as sucessfull. - * - */ - @RequestMapping(method = RequestMethod.DELETE, value = "/{distributionSetTypeId}") + @Override public ResponseEntity deleteDistributionSetType(@PathVariable final Long distributionSetTypeId) { final DistributionSetType module = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId); @@ -149,17 +101,7 @@ public class DistributionSetTypeResource { return new ResponseEntity<>(HttpStatus.OK); } - /** - * Handles the PUT request of updating a Distribution Set Type within SP. - * - * @param distributionSetTypeId - * the ID of the software module in the URL - * @param restDistributionSetType - * the module type to be updated. - * @return status OK if update is successful - */ - @RequestMapping(method = RequestMethod.PUT, value = "/{distributionSetTypeId}", consumes = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + @Override public ResponseEntity updateDistributionSetType( @PathVariable final Long distributionSetTypeId, @RequestBody final DistributionSetTypeRequestBodyPut restDistributionSetType) { @@ -176,19 +118,7 @@ public class DistributionSetTypeResource { return new ResponseEntity<>(DistributionSetTypeMapper.toResponse(updatedDistributionSetType), HttpStatus.OK); } - /** - * Handles the POST request of creating new {@link DistributionSetType}s - * within SP. The request body must always be a list of types. - * - * @param distributionSetTypes - * the modules to be created. - * @return In case all modules could successful created the ResponseEntity - * with status code 201 - Created but without ResponseBody. In any - * failure the JsonResponseExceptionHandler is handling the - * response. - */ - @RequestMapping(method = RequestMethod.POST, consumes = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + @Override public ResponseEntity createDistributionSetTypes( @RequestBody final List distributionSetTypes) { @@ -208,17 +138,7 @@ public class DistributionSetTypeResource { return module; } - /** - * Handles the GET request of retrieving the list of mandatory software - * module types in that distribution set type. - * - * @param distributionSetTypeId - * of the {@link DistributionSetType}. - * @return Unpaged list of module types and OK in case of success. - */ - @RequestMapping(method = RequestMethod.GET, value = "/{distributionSetTypeId}/" - + RestConstants.DISTRIBUTIONSETTYPE_V1_MANDATORY_MODULE_TYPES, produces = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }) + @Override public ResponseEntity getMandatoryModules(@PathVariable final Long distributionSetTypeId) { final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId); @@ -229,19 +149,7 @@ public class DistributionSetTypeResource { return new ResponseEntity<>(rest, HttpStatus.OK); } - /** - * Handles the GET request of retrieving the single mandatory software - * module type in that distribution set type. - * - * @param distributionSetTypeId - * of the {@link DistributionSetType}. - * @param softwareModuleTypeId - * of {@link SoftwareModuleType}. - * @return Unpaged list of module types and OK in case of success. - */ - @RequestMapping(method = RequestMethod.GET, value = "/{distributionSetTypeId}/" - + RestConstants.DISTRIBUTIONSETTYPE_V1_MANDATORY_MODULE_TYPES - + "/{softwareModuleTypeId}", produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + @Override public ResponseEntity getMandatoryModule(@PathVariable final Long distributionSetTypeId, @PathVariable final Long softwareModuleTypeId) { @@ -257,19 +165,7 @@ public class DistributionSetTypeResource { return new ResponseEntity<>(SoftwareModuleTypeMapper.toResponse(foundSmType), HttpStatus.OK); } - /** - * Handles the GET request of retrieving the single optional software module - * type in that distribution set type. - * - * @param distributionSetTypeId - * of the {@link DistributionSetType}. - * @param softwareModuleTypeId - * of {@link SoftwareModuleType}. - * @return Unpaged list of module types and OK in case of success. - */ - @RequestMapping(method = RequestMethod.GET, value = "/{distributionSetTypeId}/" - + RestConstants.DISTRIBUTIONSETTYPE_V1_OPTIONAL_MODULE_TYPES - + "/{softwareModuleTypeId}", produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + @Override public ResponseEntity getOptionalModule(@PathVariable final Long distributionSetTypeId, @PathVariable final Long softwareModuleTypeId) { @@ -285,17 +181,7 @@ public class DistributionSetTypeResource { return new ResponseEntity<>(SoftwareModuleTypeMapper.toResponse(foundSmType), HttpStatus.OK); } - /** - * Handles the GET request of retrieving the list of optional software - * module types in that distribution set type. - * - * @param distributionSetTypeId - * of the {@link DistributionSetType}. - * @return Unpaged list of module types and OK in case of success. - */ - @RequestMapping(method = RequestMethod.GET, value = "/{distributionSetTypeId}/" - + RestConstants.DISTRIBUTIONSETTYPE_V1_OPTIONAL_MODULE_TYPES, produces = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }) + @Override public ResponseEntity getOptionalModules(@PathVariable final Long distributionSetTypeId) { final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId); @@ -306,20 +192,7 @@ public class DistributionSetTypeResource { return new ResponseEntity<>(rest, HttpStatus.OK); } - /** - * Handles DELETE request for removing a mandatory module from the - * {@link DistributionSetType}. - * - * @param distributionSetTypeId - * of the {@link DistributionSetType}. - * @param softwareModuleTypeId - * of the {@link SoftwareModuleType} to remove - * - * @return OK if the request was successful - */ - @RequestMapping(method = RequestMethod.DELETE, value = "/{distributionSetTypeId}/" - + RestConstants.DISTRIBUTIONSETTYPE_V1_MANDATORY_MODULE_TYPES - + "/{softwareModuleTypeId}", produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + @Override public ResponseEntity removeMandatoryModule(@PathVariable final Long distributionSetTypeId, @PathVariable final Long softwareModuleTypeId) { @@ -339,20 +212,7 @@ public class DistributionSetTypeResource { return new ResponseEntity<>(HttpStatus.OK); } - /** - * Handles DELETE request for removing an optional module from the - * {@link DistributionSetType}. - * - * @param distributionSetTypeId - * of the {@link DistributionSetType}. - * @param softwareModuleTypeId - * of the {@link SoftwareModuleType} to remove - * - * @return OK if the request was successful - */ - @RequestMapping(method = RequestMethod.DELETE, value = "/{distributionSetTypeId}/" - + RestConstants.DISTRIBUTIONSETTYPE_V1_OPTIONAL_MODULE_TYPES - + "/{softwareModuleTypeId}", produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + @Override public ResponseEntity removeOptionalModule(@PathVariable final Long distributionSetTypeId, @PathVariable final Long softwareModuleTypeId) { final DistributionSetType foundType = findDistributionSetTypeWithExceptionIfNotFound(distributionSetTypeId); @@ -371,21 +231,7 @@ public class DistributionSetTypeResource { return new ResponseEntity<>(HttpStatus.OK); } - /** - * Handles the POST request for adding a mandatory software module type to a - * distribution set type. - * - * @param distributionSetTypeId - * of the {@link DistributionSetType}. - * @param smtId - * of the {@link SoftwareModuleType} to add - * - * @return OK if the request was successful - */ - @RequestMapping(method = RequestMethod.POST, value = "/{distributionSetTypeId}/" - + RestConstants.DISTRIBUTIONSETTYPE_V1_MANDATORY_MODULE_TYPES, consumes = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }) + @Override public ResponseEntity addMandatoryModule(@PathVariable final Long distributionSetTypeId, @RequestBody final IdRest smtId) { @@ -400,21 +246,7 @@ public class DistributionSetTypeResource { return new ResponseEntity<>(HttpStatus.OK); } - /** - * Handles the POST request for adding an optional software module type to a - * distribution set type. - * - * @param distributionSetTypeId - * of the {@link DistributionSetType}. - * @param smtId - * of the {@link SoftwareModuleType} to add - * - * @return OK if the request was successful - */ - @RequestMapping(method = RequestMethod.POST, value = "/{distributionSetTypeId}/" - + RestConstants.DISTRIBUTIONSETTYPE_V1_OPTIONAL_MODULE_TYPES, consumes = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }) + @Override public ResponseEntity addOptionalModule(@PathVariable final Long distributionSetTypeId, @RequestBody final IdRest smtId) { diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/DownloadArtifactResource.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/DownloadArtifactResource.java new file mode 100644 index 000000000..00f8f4756 --- /dev/null +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/DownloadArtifactResource.java @@ -0,0 +1,92 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; +import org.eclipse.hawkbit.repository.ArtifactManagement; +import org.eclipse.hawkbit.repository.SoftwareManagement; +import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.model.LocalArtifact; +import org.eclipse.hawkbit.repository.model.SoftwareModule; +import org.eclipse.hawkbit.rest.resource.helper.RestResourceConversionHelper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author Jonathan Knoblauch + * + */ +@RestController +@RequestMapping(RestConstants.SOFTWAREMODULE_V1_REQUEST_MAPPING) +public class DownloadArtifactResource { + + @Autowired + private SoftwareManagement softwareManagement; + + @Autowired + private ArtifactManagement artifactManagement; + + /** + * Handles the GET request for downloading an artifact. + * + * @param softwareModuleId + * of the parent SoftwareModule + * @param artifactId + * of the related LocalArtifact + * @param servletResponse + * of the servlet + * @param request + * of the client + * + * @return responseEntity with status ok if successful + */ + @RequestMapping(method = RequestMethod.GET, value = "/{softwareModuleId}/artifacts/{artifactId}/download") + @ResponseBody + public ResponseEntity downloadArtifact(@PathVariable("softwareModuleId") final Long softwareModuleId, + @PathVariable("artifactId") final Long artifactId, final HttpServletResponse servletResponse, + final HttpServletRequest request) { + final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, artifactId); + + if (null == module || !module.getLocalArtifact(artifactId).isPresent()) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + + final LocalArtifact artifact = module.getLocalArtifact(artifactId).get(); + final DbArtifact file = artifactManagement.loadLocalArtifactBinary(artifact); + + final String ifMatch = request.getHeader("If-Match"); + if (ifMatch != null && !RestResourceConversionHelper.matchesHttpHeader(ifMatch, artifact.getSha1Hash())) { + return new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED); + } + + return RestResourceConversionHelper.writeFileResponse(artifact, servletResponse, request, file); + + } + + private SoftwareModule findSoftwareModuleWithExceptionIfNotFound(final Long softwareModuleId, + final Long artifactId) { + final SoftwareModule module = softwareManagement.findSoftwareModuleById(softwareModuleId); + if (module == null) { + throw new EntityNotFoundException("SoftwareModule with Id {" + softwareModuleId + "} does not exist"); + } else if (artifactId != null && !module.getLocalArtifact(artifactId).isPresent()) { + throw new EntityNotFoundException("Artifact with Id {" + artifactId + "} does not exist"); + } + return module; + } + +} diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/PagingUtility.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/PagingUtility.java index afefedb5c..ac45796e5 100644 --- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/PagingUtility.java +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/PagingUtility.java @@ -12,8 +12,12 @@ import org.eclipse.hawkbit.repository.ActionFields; import org.eclipse.hawkbit.repository.ActionStatusFields; import org.eclipse.hawkbit.repository.DistributionSetFields; import org.eclipse.hawkbit.repository.DistributionSetMetadataFields; +import org.eclipse.hawkbit.repository.DistributionSetTypeFields; +import org.eclipse.hawkbit.repository.RolloutFields; +import org.eclipse.hawkbit.repository.RolloutGroupFields; import org.eclipse.hawkbit.repository.SoftwareModuleFields; import org.eclipse.hawkbit.repository.SoftwareModuleMetadataFields; +import org.eclipse.hawkbit.repository.SoftwareModuleTypeFields; import org.eclipse.hawkbit.repository.TargetFields; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; @@ -21,11 +25,6 @@ import org.springframework.data.domain.Sort.Direction; /** * Utility class for for paged body generation. * - * - * - * - * - * */ public final class PagingUtility { /* @@ -51,80 +50,92 @@ public final class PagingUtility { } static Sort sanitizeTargetSortParam(final String sortParam) { - final Sort sorting; - if (sortParam != null) { - sorting = new Sort(SortUtility.parse(TargetFields.class, sortParam)); - } else { - // default sort - sorting = new Sort(Direction.ASC, TargetFields.NAME.getFieldName()); + if (sortParam == null) { + // default + return new Sort(Direction.ASC, TargetFields.NAME.getFieldName()); } - return sorting; + return new Sort(SortUtility.parse(TargetFields.class, sortParam)); } static Sort sanitizeSoftwareModuleSortParam(final String sortParam) { - final Sort sorting; - if (sortParam != null) { - sorting = new Sort(SortUtility.parse(SoftwareModuleFields.class, sortParam)); - } else { - // default sort - sorting = new Sort(Direction.ASC, SoftwareModuleFields.NAME.getFieldName()); + if (sortParam == null) { + // default + return new Sort(Direction.ASC, SoftwareModuleFields.NAME.getFieldName()); } - return sorting; + return new Sort(SortUtility.parse(SoftwareModuleFields.class, sortParam)); + } + + static Sort sanitizeSoftwareModuleTypeSortParam(final String sortParam) { + if (sortParam == null) { + // default + return new Sort(Direction.ASC, SoftwareModuleTypeFields.NAME.getFieldName()); + } + return new Sort(SortUtility.parse(SoftwareModuleTypeFields.class, sortParam)); } static Sort sanitizeDistributionSetSortParam(final String sortParam) { - final Sort sorting; - if (sortParam != null) { - sorting = new Sort(SortUtility.parse(DistributionSetFields.class, sortParam)); - } else { - // default sort - sorting = new Sort(Direction.ASC, DistributionSetFields.NAME.getFieldName()); + if (sortParam == null) { + // default + return new Sort(Direction.ASC, DistributionSetFields.NAME.getFieldName()); } - return sorting; + return new Sort(SortUtility.parse(DistributionSetFields.class, sortParam)); + } + + static Sort sanitizeDistributionSetTypeSortParam(final String sortParam) { + if (sortParam == null) { + // default + return new Sort(Direction.ASC, DistributionSetTypeFields.NAME.getFieldName()); + } + return new Sort(SortUtility.parse(DistributionSetTypeFields.class, sortParam)); } static Sort sanitizeActionSortParam(final String sortParam) { - final Sort sorting; - if (sortParam != null) { - sorting = new Sort(SortUtility.parse(ActionFields.class, sortParam)); - } else { - // default sort - sorting = new Sort(Direction.ASC, ActionFields.ID.getFieldName()); + if (sortParam == null) { + // default sort is DESC in case of action to match behavior + // of management UI (last entry on top) + return new Sort(Direction.DESC, ActionFields.ID.getFieldName()); } - return sorting; + return new Sort(SortUtility.parse(ActionFields.class, sortParam)); } static Sort sanitizeActionStatusSortParam(final String sortParam) { - final Sort sorting; - if (sortParam != null) { - sorting = new Sort(SortUtility.parse(ActionStatusFields.class, sortParam)); - } else { - // default sort - sorting = new Sort(Direction.ASC, ActionStatusFields.ID.getFieldName()); + if (sortParam == null) { + // default sort is DESC in case of action status to match behavior + // of management UI (last entry on top) + return new Sort(Direction.DESC, ActionStatusFields.ID.getFieldName()); } - return sorting; + return new Sort(SortUtility.parse(ActionStatusFields.class, sortParam)); } static Sort sanitizeDistributionSetMetadataSortParam(final String sortParam) { - final Sort sorting; - if (sortParam != null) { - sorting = new Sort(SortUtility.parse(DistributionSetMetadataFields.class, sortParam)); - } else { - // default sort - sorting = new Sort(Direction.ASC, DistributionSetMetadataFields.KEY.getFieldName()); + if (sortParam == null) { + // default + return new Sort(Direction.ASC, DistributionSetMetadataFields.KEY.getFieldName()); } - return sorting; + return new Sort(SortUtility.parse(DistributionSetMetadataFields.class, sortParam)); } static Sort sanitizeSoftwareModuleMetadataSortParam(final String sortParam) { - final Sort sorting; - if (sortParam != null) { - sorting = new Sort(SortUtility.parse(SoftwareModuleMetadataFields.class, sortParam)); - } else { - // default sort - sorting = new Sort(Direction.ASC, SoftwareModuleMetadataFields.KEY.getFieldName()); + if (sortParam == null) { + // default + return new Sort(Direction.ASC, SoftwareModuleMetadataFields.KEY.getFieldName()); } - return sorting; + return new Sort(SortUtility.parse(SoftwareModuleMetadataFields.class, sortParam)); } + static Sort sanitizeRolloutSortParam(final String sortParam) { + if (sortParam == null) { + // default + return new Sort(Direction.ASC, RolloutFields.NAME.getFieldName()); + } + return new Sort(SortUtility.parse(RolloutFields.class, sortParam)); + } + + static Sort sanitizeRolloutGroupSortParam(final String sortParam) { + if (sortParam == null) { + // default + return new Sort(Direction.ASC, RolloutGroupFields.NAME.getFieldName()); + } + return new Sort(SortUtility.parse(RolloutGroupFields.class, sortParam)); + } } diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/ResponseExceptionHandler.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/ResponseExceptionHandler.java index d222a96fd..25ddc7c4c 100644 --- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/ResponseExceptionHandler.java +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/ResponseExceptionHandler.java @@ -64,7 +64,9 @@ public class ResponseExceptionHandler { ERROR_TO_HTTP_STATUS.put(SpServerError.SP_DS_TYPE_UNDEFINED, HttpStatus.BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_REPO_TENANT_NOT_EXISTS, HttpStatus.BAD_REQUEST); ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ENTITY_LOCKED, HttpStatus.LOCKED); - + ERROR_TO_HTTP_STATUS.put(SpServerError.SP_ROLLOUT_ILLEGAL_STATE, HttpStatus.BAD_REQUEST); + ERROR_TO_HTTP_STATUS.put(SpServerError.SP_CONFIGURATION_VALUE_INVALID, HttpStatus.BAD_REQUEST); + ERROR_TO_HTTP_STATUS.put(SpServerError.SP_CONFIGURATION_KEY_INVALID, HttpStatus.BAD_REQUEST); } private static HttpStatus getStatusOrDefault(final SpServerError error) { @@ -84,8 +86,7 @@ public class ResponseExceptionHandler { * as entity. */ @ExceptionHandler(SpServerRtException.class) - public ResponseEntity handleSpServerRtExceptions(final HttpServletRequest request, - final Exception ex) { + public ResponseEntity handleSpServerRtExceptions(final HttpServletRequest request, final Exception ex) { LOG.debug("Handling exception of request {}", request.getRequestURL()); final ExceptionInfo response = new ExceptionInfo(); final HttpStatus responseStatus; diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/RolloutMapper.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/RolloutMapper.java new file mode 100644 index 000000000..c6be219ba --- /dev/null +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/RolloutMapper.java @@ -0,0 +1,164 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.hawkbit.repository.model.Action.ActionType; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorAction; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessAction; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition; +import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus; +import org.eclipse.hawkbit.rest.resource.api.RolloutRestApi; +import org.eclipse.hawkbit.rest.resource.helper.RestResourceConversionHelper; +import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutCondition.Condition; +import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutErrorAction.ErrorAction; +import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutResponseBody; +import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutRestRequestBody; +import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutSuccessAction.SuccessAction; +import org.eclipse.hawkbit.rest.resource.model.rolloutgroup.RolloutGroupResponseBody; + +/** + * A mapper which maps repository model to RESTful model representation and + * back. + * + * + */ +final class RolloutMapper { + + private static final String NOT_SUPPORTED = " is not supported"; + + private RolloutMapper() { + // Utility class + } + + static List toResponseRollout(final List rollouts) { + final List result = new ArrayList<>(rollouts.size()); + rollouts.forEach(r -> result.add(toResponseRollout(r))); + return result; + } + + static RolloutResponseBody toResponseRollout(final Rollout rollout) { + final RolloutResponseBody body = new RolloutResponseBody(); + body.setCreatedAt(rollout.getCreatedAt()); + body.setCreatedBy(rollout.getCreatedBy()); + body.setDescription(rollout.getDescription()); + body.setLastModifiedAt(rollout.getLastModifiedAt()); + body.setLastModifiedBy(rollout.getLastModifiedBy()); + body.setName(rollout.getName()); + body.setRolloutId(rollout.getId()); + body.setTargetFilterQuery(rollout.getTargetFilterQuery()); + body.setDistributionSetId(rollout.getDistributionSet().getId()); + body.setStatus(rollout.getStatus().toString().toLowerCase()); + body.setTotalTargets(rollout.getTotalTargets()); + + for (final TotalTargetCountStatus.Status status : TotalTargetCountStatus.Status.values()) { + body.getTotalTargetsPerStatus().put(status.name().toLowerCase(), + rollout.getTotalTargetCountStatus().getTotalTargetCountByStatus(status)); + } + + body.add(linkTo(methodOn(RolloutRestApi.class).getRollout(rollout.getId())).withRel("self")); + body.add(linkTo(methodOn(RolloutRestApi.class).start(rollout.getId(), false)).withRel("start")); + body.add(linkTo(methodOn(RolloutRestApi.class).start(rollout.getId(), true)).withRel("startAsync")); + body.add(linkTo(methodOn(RolloutRestApi.class).pause(rollout.getId())).withRel("pause")); + body.add(linkTo(methodOn(RolloutRestApi.class).resume(rollout.getId())).withRel("resume")); + body.add(linkTo(methodOn(RolloutRestApi.class).getRolloutGroups(rollout.getId(), + Integer.parseInt(RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET), + Integer.parseInt(RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT), null, null)).withRel("groups")); + return body; + } + + static Rollout fromRequest(final RolloutRestRequestBody restRequest, final DistributionSet distributionSet, + final String filterQuery) { + final Rollout rollout = new Rollout(); + rollout.setName(restRequest.getName()); + rollout.setDescription(restRequest.getDescription()); + rollout.setDistributionSet(distributionSet); + rollout.setTargetFilterQuery(filterQuery); + final ActionType convertActionType = RestResourceConversionHelper.convertActionType(restRequest.getType()); + if (convertActionType != null) { + rollout.setActionType(convertActionType); + } + if (restRequest.getForcetime() != null) { + rollout.setForcedTime(restRequest.getForcetime()); + + } + return rollout; + } + + static List toResponseRolloutGroup(final List rollouts) { + final List result = new ArrayList<>(rollouts.size()); + rollouts.forEach(r -> result.add(toResponseRolloutGroup(r))); + return result; + } + + static RolloutGroupResponseBody toResponseRolloutGroup(final RolloutGroup rolloutGroup) { + final RolloutGroupResponseBody body = new RolloutGroupResponseBody(); + body.setCreatedAt(rolloutGroup.getCreatedAt()); + body.setCreatedBy(rolloutGroup.getCreatedBy()); + body.setDescription(rolloutGroup.getDescription()); + body.setLastModifiedAt(rolloutGroup.getLastModifiedAt()); + body.setLastModifiedBy(rolloutGroup.getLastModifiedBy()); + body.setName(rolloutGroup.getName()); + body.setRolloutGroupId(rolloutGroup.getId()); + body.setStatus(rolloutGroup.getStatus().toString().toLowerCase()); + body.add(linkTo( + methodOn(RolloutRestApi.class).getRolloutGroup(rolloutGroup.getRollout().getId(), rolloutGroup.getId())) + .withRel("self")); + return body; + } + + static RolloutGroupErrorCondition mapErrorCondition(final Condition condition) { + if (Condition.THRESHOLD.equals(condition)) { + return RolloutGroupErrorCondition.THRESHOLD; + } + throw new IllegalArgumentException(createIllegalArgumentLiteral(condition)); + } + + static RolloutGroupSuccessCondition mapFinishCondition(final Condition condition) { + if (Condition.THRESHOLD.equals(condition)) { + return RolloutGroupSuccessCondition.THRESHOLD; + } + throw new IllegalArgumentException(createIllegalArgumentLiteral(condition)); + } + + static Condition map(final RolloutGroupSuccessCondition rolloutCondition) { + if (RolloutGroupSuccessCondition.THRESHOLD.equals(rolloutCondition)) { + return Condition.THRESHOLD; + } + throw new IllegalArgumentException("Rollout group condition " + rolloutCondition + NOT_SUPPORTED); + } + + static RolloutGroupErrorAction map(final ErrorAction action) { + if (ErrorAction.PAUSE.equals(action)) { + return RolloutGroupErrorAction.PAUSE; + } + throw new IllegalArgumentException("Error Action " + action + NOT_SUPPORTED); + } + + static RolloutGroupSuccessAction map(final SuccessAction action) { + if (SuccessAction.NEXTGROUP.equals(action)) { + return RolloutGroupSuccessAction.NEXTGROUP; + } + throw new IllegalArgumentException("Success Action " + action + NOT_SUPPORTED); + } + + private static String createIllegalArgumentLiteral(final Condition condition) { + return "Condition " + condition + NOT_SUPPORTED; + } + +} diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/RolloutResource.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/RolloutResource.java new file mode 100644 index 000000000..e00d07191 --- /dev/null +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/RolloutResource.java @@ -0,0 +1,253 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource; + +import java.util.List; + +import org.eclipse.hawkbit.repository.DistributionSetManagement; +import org.eclipse.hawkbit.repository.RolloutFields; +import org.eclipse.hawkbit.repository.RolloutGroupFields; +import org.eclipse.hawkbit.repository.RolloutGroupManagement; +import org.eclipse.hawkbit.repository.RolloutManagement; +import org.eclipse.hawkbit.repository.TargetFields; +import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupConditions; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorAction; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondition; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessAction; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.rsql.RSQLUtility; +import org.eclipse.hawkbit.rest.resource.api.RolloutRestApi; +import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutPagedList; +import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutResponseBody; +import org.eclipse.hawkbit.rest.resource.model.rollout.RolloutRestRequestBody; +import org.eclipse.hawkbit.rest.resource.model.rolloutgroup.RolloutGroupPagedList; +import org.eclipse.hawkbit.rest.resource.model.rolloutgroup.RolloutGroupResponseBody; +import org.eclipse.hawkbit.rest.resource.model.target.TargetPagedList; +import org.eclipse.hawkbit.rest.resource.model.target.TargetRest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * REST Resource handling rollout CRUD operations. + * + */ +@RestController +public class RolloutResource implements RolloutRestApi { + + private static final String DOES_NOT_EXIST = "} does not exist"; + + @Autowired + private RolloutManagement rolloutManagement; + + @Autowired + private RolloutGroupManagement rolloutGroupManagement; + + @Autowired + private DistributionSetManagement distributionSetManagement; + + @Override + public ResponseEntity getRollouts(final int pagingOffsetParam, final int pagingLimitParam, + final String sortParam, final String rsqlParam) { + + final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam); + final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam); + final Sort sorting = PagingUtility.sanitizeRolloutSortParam(sortParam); + + final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting); + + final Page findModulesAll; + if (rsqlParam != null) { + findModulesAll = this.rolloutManagement + .findAllWithDetailedStatusByPredicate(RSQLUtility.parse(rsqlParam, RolloutFields.class), pageable); + } else { + findModulesAll = this.rolloutManagement.findAll(pageable); + } + + final List rest = RolloutMapper.toResponseRollout(findModulesAll.getContent()); + return new ResponseEntity<>(new RolloutPagedList(rest, findModulesAll.getTotalElements()), HttpStatus.OK); + } + + @Override + public ResponseEntity getRollout(final Long rolloutId) { + final Rollout findRolloutById = findRolloutOrThrowException(rolloutId); + return new ResponseEntity<>(RolloutMapper.toResponseRollout(findRolloutById), HttpStatus.OK); + } + + @Override + public ResponseEntity create(@RequestBody final RolloutRestRequestBody rolloutRequestBody) { + + // first check the given RSQL query if it's well formed, otherwise and + // exception is thrown + RSQLUtility.parse(rolloutRequestBody.getTargetFilterQuery(), TargetFields.class); + + final DistributionSet distributionSet = findDistributionSetOrThrowException(rolloutRequestBody); + + // success condition + RolloutGroupSuccessCondition successCondition = null; + String successConditionExpr = null; + // success action + RolloutGroupSuccessAction successAction = null; + String successActionExpr = null; + // error condition + RolloutGroupErrorCondition errorCondition = null; + // error action + String errorConditionExpr = null; + RolloutGroupErrorAction errorAction = null; + String errorActionExpr = null; + if (rolloutRequestBody.getSuccessCondition() != null) { + successCondition = RolloutMapper + .mapFinishCondition(rolloutRequestBody.getSuccessCondition().getCondition()); + successConditionExpr = rolloutRequestBody.getSuccessCondition().getExpression(); + } + if (rolloutRequestBody.getSuccessAction() != null) { + successAction = RolloutMapper.map(rolloutRequestBody.getSuccessAction().getAction()); + successActionExpr = rolloutRequestBody.getSuccessAction().getExpression(); + } + if (rolloutRequestBody.getErrorCondition() != null) { + errorCondition = RolloutMapper.mapErrorCondition(rolloutRequestBody.getErrorCondition().getCondition()); + errorConditionExpr = rolloutRequestBody.getErrorCondition().getExpression(); + } + if (rolloutRequestBody.getErrorAction() != null) { + errorAction = RolloutMapper.map(rolloutRequestBody.getErrorAction().getAction()); + errorActionExpr = rolloutRequestBody.getErrorAction().getExpression(); + } + + final RolloutGroupConditions rolloutGroupConditions = new RolloutGroup.RolloutGroupConditionBuilder() + .successCondition(successCondition, successConditionExpr) + .successAction(successAction, successActionExpr).errorCondition(errorCondition, errorConditionExpr) + .errorAction(errorAction, errorActionExpr).build(); + final Rollout rollout = this.rolloutManagement.createRollout( + RolloutMapper.fromRequest(rolloutRequestBody, distributionSet, + rolloutRequestBody.getTargetFilterQuery()), + rolloutRequestBody.getAmountGroups(), rolloutGroupConditions); + + return ResponseEntity.status(HttpStatus.CREATED).body(RolloutMapper.toResponseRollout(rollout)); + } + + @Override + public ResponseEntity start(final Long rolloutId, final boolean startAsync) { + final Rollout rollout = findRolloutOrThrowException(rolloutId); + if (startAsync) { + this.rolloutManagement.startRolloutAsync(rollout); + } else { + this.rolloutManagement.startRollout(rollout); + } + return ResponseEntity.ok().build(); + } + + @Override + public ResponseEntity pause(final Long rolloutId) { + final Rollout rollout = findRolloutOrThrowException(rolloutId); + this.rolloutManagement.pauseRollout(rollout); + return ResponseEntity.ok().build(); + } + + @Override + public ResponseEntity resume(final Long rolloutId) { + final Rollout rollout = findRolloutOrThrowException(rolloutId); + this.rolloutManagement.resumeRollout(rollout); + return ResponseEntity.ok().build(); + } + + @Override + public ResponseEntity getRolloutGroups(final Long rolloutId, final int pagingOffsetParam, + final int pagingLimitParam, final String sortParam, final String rsqlParam) { + final Rollout rollout = findRolloutOrThrowException(rolloutId); + + final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam); + final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam); + final Sort sorting = PagingUtility.sanitizeRolloutGroupSortParam(sortParam); + + final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting); + + final Page findRolloutGroupsAll; + if (rsqlParam != null) { + findRolloutGroupsAll = this.rolloutGroupManagement.findRolloutGroupsByPredicate(rollout, + RSQLUtility.parse(rsqlParam, RolloutGroupFields.class), pageable); + } else { + findRolloutGroupsAll = this.rolloutGroupManagement.findRolloutGroupsByRolloutId(rolloutId, pageable); + } + + final List rest = RolloutMapper + .toResponseRolloutGroup(findRolloutGroupsAll.getContent()); + return new ResponseEntity<>(new RolloutGroupPagedList(rest, findRolloutGroupsAll.getTotalElements()), + HttpStatus.OK); + } + + @Override + public ResponseEntity getRolloutGroup(final Long rolloutId, final Long groupId) { + findRolloutOrThrowException(rolloutId); + final RolloutGroup rolloutGroup = findRolloutGroupOrThrowException(groupId); + return ResponseEntity.ok(RolloutMapper.toResponseRolloutGroup(rolloutGroup)); + } + + @Override + public ResponseEntity getRolloutGroupTargets(final Long rolloutId, final Long groupId, + final int pagingOffsetParam, final int pagingLimitParam, final String sortParam, final String rsqlParam) { + findRolloutOrThrowException(rolloutId); + final RolloutGroup rolloutGroup = findRolloutGroupOrThrowException(groupId); + + final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam); + final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam); + final Sort sorting = PagingUtility.sanitizeTargetSortParam(sortParam); + + final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting); + + final Page rolloutGroupTargets; + if (rsqlParam != null) { + final Specification rsqlSpecification = RSQLUtility.parse(rsqlParam, TargetFields.class); + rolloutGroupTargets = this.rolloutGroupManagement.findRolloutGroupTargets(rolloutGroup, rsqlSpecification, + pageable); + } else { + final Page pageTargets = this.rolloutGroupManagement.findRolloutGroupTargets(rolloutGroup, + pageable); + rolloutGroupTargets = pageTargets; + } + final List rest = TargetMapper.toResponse(rolloutGroupTargets.getContent()); + return new ResponseEntity<>(new TargetPagedList(rest, rolloutGroupTargets.getTotalElements()), HttpStatus.OK); + } + + private Rollout findRolloutOrThrowException(final Long rolloutId) { + final Rollout rollout = this.rolloutManagement.findRolloutWithDetailedStatus(rolloutId); + if (rollout == null) { + throw new EntityNotFoundException("Rollout with Id {" + rolloutId + DOES_NOT_EXIST); + } + return rollout; + } + + private RolloutGroup findRolloutGroupOrThrowException(final Long rolloutGroupId) { + final RolloutGroup rolloutGroup = this.rolloutGroupManagement.findRolloutGroupById(rolloutGroupId); + if (rolloutGroup == null) { + throw new EntityNotFoundException("Group with Id {" + rolloutGroupId + DOES_NOT_EXIST); + } + return rolloutGroup; + } + + private DistributionSet findDistributionSetOrThrowException(final RolloutRestRequestBody rolloutRequestBody) { + final DistributionSet ds = this.distributionSetManagement + .findDistributionSetById(rolloutRequestBody.getDistributionSetId()); + if (ds == null) { + throw new EntityNotFoundException( + "DistributionSet with Id {" + rolloutRequestBody.getDistributionSetId() + DOES_NOT_EXIST); + } + return ds; + } +} diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SoftwareModuleMapper.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SoftwareModuleMapper.java index 947c216dd..1a3ce603f 100644 --- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SoftwareModuleMapper.java +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SoftwareModuleMapper.java @@ -21,6 +21,8 @@ import org.eclipse.hawkbit.repository.model.LocalArtifact; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; +import org.eclipse.hawkbit.rest.resource.api.SoftwareModuleRestAPI; +import org.eclipse.hawkbit.rest.resource.api.SoftwareModuleTypeRestApi; import org.eclipse.hawkbit.rest.resource.model.MetadataRest; import org.eclipse.hawkbit.rest.resource.model.artifact.ArtifactHash; import org.eclipse.hawkbit.rest.resource.model.artifact.ArtifactRest; @@ -142,13 +144,13 @@ public final class SoftwareModuleMapper { response.setType(baseSofwareModule.getType().getKey()); response.setVendor(baseSofwareModule.getVendor()); - response.add(linkTo(methodOn(SoftwareModuleResource.class).getArtifacts(response.getModuleId())) + response.add(linkTo(methodOn(SoftwareModuleRestAPI.class).getArtifacts(response.getModuleId())) .withRel(RestConstants.SOFTWAREMODULE_V1_ARTIFACT)); - response.add(linkTo(methodOn(SoftwareModuleResource.class).getSoftwareModule(response.getModuleId())) + response.add(linkTo(methodOn(SoftwareModuleRestAPI.class).getSoftwareModule(response.getModuleId())) .withRel("self")); response.add(linkTo( - methodOn(SoftwareModuleTypeResource.class).getSoftwareModuleType(baseSofwareModule.getType().getId())) + methodOn(SoftwareModuleTypeRestApi.class).getSoftwareModuleType(baseSofwareModule.getType().getId())) .withRel(RestConstants.SOFTWAREMODULE_V1_TYPE)); response.add(linkTo(methodOn(SoftwareModuleResource.class).getMetadata(response.getModuleId(), @@ -178,13 +180,13 @@ public final class SoftwareModuleMapper { RestModelMapper.mapBaseToBase(artifactRest, artifact); - artifactRest.add(linkTo(methodOn(SoftwareModuleResource.class).getArtifact(artifact.getSoftwareModule().getId(), + artifactRest.add(linkTo(methodOn(SoftwareModuleRestAPI.class).getArtifact(artifact.getSoftwareModule().getId(), artifact.getId())).withRel("self")); if (artifact instanceof LocalArtifact) { - artifactRest.add( - linkTo(methodOn(SoftwareModuleResource.class).downloadArtifact(artifact.getSoftwareModule().getId(), - artifact.getId(), null, null)).withRel("download")); + artifactRest.add(linkTo(methodOn(DownloadArtifactResource.class) + .downloadArtifact(artifact.getSoftwareModule().getId(), artifact.getId(), null, null)) + .withRel("download")); } return artifactRest; diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SoftwareModuleResource.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SoftwareModuleResource.java index 5c60334cd..dfbac1a6f 100644 --- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SoftwareModuleResource.java +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SoftwareModuleResource.java @@ -11,22 +11,17 @@ package org.eclipse.hawkbit.rest.resource; import java.io.IOException; import java.util.List; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.eclipse.hawkbit.artifact.repository.model.DbArtifact; import org.eclipse.hawkbit.repository.ArtifactManagement; import org.eclipse.hawkbit.repository.SoftwareManagement; import org.eclipse.hawkbit.repository.SoftwareModuleFields; import org.eclipse.hawkbit.repository.SoftwareModuleMetadataFields; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.model.Artifact; -import org.eclipse.hawkbit.repository.model.LocalArtifact; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModuleMetadata; import org.eclipse.hawkbit.repository.model.SwMetadataCompositeKey; import org.eclipse.hawkbit.repository.rsql.RSQLUtility; -import org.eclipse.hawkbit.rest.resource.helper.RestResourceConversionHelper; +import org.eclipse.hawkbit.rest.resource.api.SoftwareModuleRestAPI; import org.eclipse.hawkbit.rest.resource.model.MetadataRest; import org.eclipse.hawkbit.rest.resource.model.MetadataRestPageList; import org.eclipse.hawkbit.rest.resource.model.artifact.ArtifactRest; @@ -44,14 +39,10 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @@ -59,13 +50,9 @@ import org.springframework.web.multipart.MultipartFile; * REST Resource handling for {@link SoftwareModule} and related * {@link Artifact} CRUD operations. * - * - * - * */ @RestController -@RequestMapping(RestConstants.SOFTWAREMODULE_V1_REQUEST_MAPPING) -public class SoftwareModuleResource { +public class SoftwareModuleResource implements SoftwareModuleRestAPI { private static final Logger LOG = LoggerFactory.getLogger(SoftwareModuleResource.class); @Autowired @@ -74,25 +61,7 @@ public class SoftwareModuleResource { @Autowired private SoftwareManagement softwareManagement; - /** - * Handles POST request for artifact upload. - * - * @param softwareModuleId - * of the parent {@link SoftwareModule} - * @param file - * that has to be uploaded - * @param optionalFileName - * to override {@link MultipartFile#getOriginalFilename()} - * @param md5Sum - * checksum for uploaded content check - * @param sha1Sum - * checksum for uploaded content check - * - * @return {@link ResponseEntity} if status {@link HttpStatus#CREATED} if - * successful - */ - @RequestMapping(method = RequestMethod.POST, value = "/{softwareModuleId}/artifacts", produces = { - "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + @Override public ResponseEntity uploadArtifact(@PathVariable final Long softwareModuleId, @RequestParam("file") final MultipartFile file, @RequestParam(value = "filename", required = false) final String optionalFileName, @@ -123,78 +92,14 @@ public class SoftwareModuleResource { } - /** - * Handles the GET request of retrieving all meta data of artifacts assigned - * to a software module. - * - * @param softwareModuleId - * of the parent {@link SoftwareModule} - * - * @return {@link ResponseEntity} with status {@link HttpStatus#OK} if - * successful - */ - @RequestMapping(method = RequestMethod.GET, value = "/{softwareModuleId}/artifacts", produces = { - "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) - @ResponseBody + @Override public ResponseEntity getArtifacts(@PathVariable final Long softwareModuleId) { final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null); return new ResponseEntity<>(SoftwareModuleMapper.artifactsToResponse(module.getArtifacts()), HttpStatus.OK); } - /** - * Handles the GET request for downloading an artifact. - * - * @param softwareModuleId - * of the parent {@link SoftwareModule} - * @param artifactId - * of the related {@link LocalArtifact} - * @param servletResponse - * of the servlet - * @param request - * of the client - * - * @return {@link ResponseEntity} with status {@link HttpStatus#OK} if - * successful - */ - @RequestMapping(method = RequestMethod.GET, value = "/{softwareModuleId}/artifacts/{artifactId}/download") - @ResponseBody - public ResponseEntity downloadArtifact(@PathVariable final Long softwareModuleId, - @PathVariable final Long artifactId, final HttpServletResponse servletResponse, - final HttpServletRequest request) { - final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, artifactId); - - if (null == module || !module.getLocalArtifact(artifactId).isPresent()) { - return new ResponseEntity<>(HttpStatus.NOT_FOUND); - } - - final LocalArtifact artifact = module.getLocalArtifact(artifactId).get(); - final DbArtifact file = artifactManagement.loadLocalArtifactBinary(artifact); - - final String ifMatch = request.getHeader("If-Match"); - if (ifMatch != null && !RestResourceConversionHelper.matchesHttpHeader(ifMatch, artifact.getSha1Hash())) { - return new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED); - } - - return RestResourceConversionHelper.writeFileResponse(artifact, servletResponse, request, file); - - } - - /** - * Handles the GET request of retrieving a single Artifact meta data - * request. - * - * @param softwareModuleId - * of the parent {@link SoftwareModule} - * @param artifactId - * of the related {@link LocalArtifact} - * - * @return {@link ResponseEntity} with status {@link HttpStatus#OK} if - * successful - */ - @RequestMapping(method = RequestMethod.GET, value = "/{softwareModuleId}/artifacts/{artifactId}", produces = { - "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) - @ResponseBody + @Override public ResponseEntity getArtifact(@PathVariable final Long softwareModuleId, @PathVariable final Long artifactId) { final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, artifactId); @@ -203,18 +108,7 @@ public class SoftwareModuleResource { HttpStatus.OK); } - /** - * Handles the DELETE request for a single SoftwareModule within SP. - * - * @param softwareModuleId - * the ID of the module that has the artifact - * @param artifactId - * of the artifact to be deleted - * - * @return status OK if delete as successful. - */ - @RequestMapping(method = RequestMethod.DELETE, value = "/{softwareModuleId}/artifacts/{artifactId}") - @ResponseBody + @Override public ResponseEntity deleteArtifact(@PathVariable final Long softwareModuleId, @PathVariable final Long artifactId) { findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, artifactId); @@ -225,27 +119,7 @@ public class SoftwareModuleResource { } - /** - * Handles the GET request of retrieving all softwaremodules within SP. - * - * @param pagingOffsetParam - * the offset of list of modules for pagination, might not be - * present in the rest request then default value will be applied - * @param pagingLimitParam - * the limit of the paged request, might not be present in the - * rest request then default value will be applied - * @param sortParam - * the sorting parameter in the request URL, syntax - * {@code field:direction, field:direction} - * @param rsqlParam - * the search parameter in the request URL, syntax - * {@code q=name==abc} - * - * @return a list of all modules for a defined or default page request with - * status OK. The response is always paged. In any failure the - * JsonResponseExceptionHandler is handling the response. - */ - @RequestMapping(method = RequestMethod.GET, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + @Override public ResponseEntity getSoftwareModules( @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, @@ -273,38 +147,14 @@ public class SoftwareModuleResource { return new ResponseEntity<>(new SoftwareModulePagedList(rest, countModulesAll), HttpStatus.OK); } - /** - * Handles the GET request of retrieving a single software module within SP. - * - * @param softwareModuleId - * the ID of the module to retrieve - * - * @return a single softwareModule with status OK. - * @throws EntityNotFoundException - * in case no with the given {@code softwareModuleId} exists. - */ - @RequestMapping(method = RequestMethod.GET, value = "/{softwareModuleId}", produces = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }) + @Override public ResponseEntity getSoftwareModule(@PathVariable final Long softwareModuleId) { final SoftwareModule findBaseSoftareModule = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null); return new ResponseEntity<>(SoftwareModuleMapper.toResponse(findBaseSoftareModule), HttpStatus.OK); } - /** - * Handles the POST request of creating new softwaremodules within SP. The - * request body must always be a list of modules. The requests is delgating - * to the {@link SoftwareManagement#createSoftwareModule(Iterable)}. - * - * @param softwareModules - * the modules to be created. - * @return In case all modules could successful created the ResponseEntity - * with status code 201 - Created but without ResponseBody. In any - * failure the JsonResponseExceptionHandler is handling the - * response. - */ - @RequestMapping(method = RequestMethod.POST, consumes = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + @Override public ResponseEntity createSoftwareModules( @RequestBody final List softwareModules) { LOG.debug("creating {} softwareModules", softwareModules.size()); @@ -316,18 +166,7 @@ public class SoftwareModuleResource { HttpStatus.CREATED); } - /** - * Handles the PUT request of updating a software module within SP. - * {@link SoftwareManagement#createSoftwareModule(Iterable)}. - * - * @param softwareModuleId - * the ID of the software module in the URL - * @param restSoftwareModule - * the modules to be updated. - * @return status OK if update is successful - */ - @RequestMapping(method = RequestMethod.PUT, value = "/{softwareModuleId}", consumes = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + @Override public ResponseEntity updateSoftwareModule(@PathVariable final Long softwareModuleId, @RequestBody final SoftwareModuleRequestBodyPut restSoftwareModule) { final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null); @@ -344,15 +183,7 @@ public class SoftwareModuleResource { return new ResponseEntity<>(SoftwareModuleMapper.toResponse(updateSoftwareModule), HttpStatus.OK); } - /** - * Handles the DELETE request for a single softwaremodule within SP. - * - * @param softwareModuleId - * the ID of the module to retrieve - * @return status OK if delete as sucessfull. - * - */ - @RequestMapping(method = RequestMethod.DELETE, value = "/{softwareModuleId}") + @Override public ResponseEntity deleteSoftwareModule(@PathVariable final Long softwareModuleId) { final SoftwareModule module = findSoftwareModuleWithExceptionIfNotFound(softwareModuleId, null); @@ -361,28 +192,7 @@ public class SoftwareModuleResource { return new ResponseEntity<>(HttpStatus.OK); } - /** - * Gets a paged list of meta data for a software module. - * - * @param softwareModuleId - * the ID of the software module for the meta data - * @param pagingOffsetParam - * the offset of list of meta data for pagination, might not be - * present in the rest request then default value will be applied - * @param pagingLimitParam - * the limit of the paged request, might not be present in the - * rest request then default value will be applied - * @param sortParam - * the sorting parameter in the request URL, syntax - * {@code field:direction, field:direction} - * @param rsqlParam - * the search parameter in the request URL, syntax - * {@code q=key==abc} - * @return status OK if get request is successful with the paged list of - * meta data - */ - @RequestMapping(method = RequestMethod.GET, value = "/{softwareModuleId}/metadata", produces = { - MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) + @Override public ResponseEntity getMetadata(@PathVariable final Long softwareModuleId, @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, @@ -412,18 +222,7 @@ public class SoftwareModuleResource { HttpStatus.OK); } - /** - * Gets a single meta data value for a specific key of a software module. - * - * @param softwareModuleId - * the ID of the software module to get the meta data from - * @param metadataKey - * the key of the meta data entry to retrieve the value from - * @return status OK if get request is successful with the value of the meta - * data - */ - @RequestMapping(method = RequestMethod.GET, value = "/{softwareModuleId}/metadata/{metadataKey}", produces = { - MediaType.APPLICATION_JSON_VALUE }) + @Override public ResponseEntity getMetadataValue(@PathVariable final Long softwareModuleId, @PathVariable final String metadataKey) { // check if distribution set exists otherwise throw exception @@ -433,18 +232,7 @@ public class SoftwareModuleResource { return ResponseEntity. ok(SoftwareModuleMapper.toResponseSwMetadata(findOne)); } - /** - * Updates a single meta data value of a software module. - * - * @param softwareModuleId - * the ID of the software module to update the meta data entry - * @param metadataKey - * the key of the meta data to update the value - * @return status OK if the update request is successful and the updated - * meta data result - */ - @RequestMapping(method = RequestMethod.PUT, value = "/{softwareModuleId}/metadata/{metadataKey}", produces = { - MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) + @Override public ResponseEntity updateMetadata(@PathVariable final Long softwareModuleId, @PathVariable final String metadataKey, @RequestBody final MetadataRest metadata) { // check if software module exists otherwise throw exception immediately @@ -454,16 +242,7 @@ public class SoftwareModuleResource { return ResponseEntity.ok(SoftwareModuleMapper.toResponseSwMetadata(updated)); } - /** - * Deletes a single meta data entry from the software module. - * - * @param softwareModuleId - * the ID of the software module to delete the meta data entry - * @param metadataKey - * the key of the meta data to delete - * @return status OK if the delete request is successful - */ - @RequestMapping(method = RequestMethod.DELETE, value = "/{softwareModuleId}/metadata/{metadataKey}") + @Override public ResponseEntity deleteMetadata(@PathVariable final Long softwareModuleId, @PathVariable final String metadataKey) { // check if software module exists otherwise throw exception immediately @@ -472,19 +251,7 @@ public class SoftwareModuleResource { return ResponseEntity.ok().build(); } - /** - * Creates a list of meta data for a specific software module. - * - * @param softwareModuleId - * the ID of the distribution set to create meta data for - * @param metadataRest - * the list of meta data entries to create - * @return status created if post request is successful with the value of - * the created meta data - */ - @RequestMapping(method = RequestMethod.POST, value = "/{softwareModuleId}/metadata", consumes = { - MediaType.APPLICATION_JSON_VALUE, - "application/hal+json" }, produces = { MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) + @Override public ResponseEntity> createMetadata(@PathVariable final Long softwareModuleId, @RequestBody final List metadataRest) { // check if software module exists otherwise throw exception immediately diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SoftwareModuleTypeMapper.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SoftwareModuleTypeMapper.java index d187aa63a..bd2c5f1c1 100644 --- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SoftwareModuleTypeMapper.java +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SoftwareModuleTypeMapper.java @@ -16,6 +16,7 @@ import java.util.Collection; import java.util.List; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; +import org.eclipse.hawkbit.rest.resource.api.SoftwareModuleTypeRestApi; import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypeRequestBodyPost; import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypeRest; import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypesRest; @@ -58,7 +59,7 @@ final class SoftwareModuleTypeMapper { } static List toListResponse(final Collection types) { - final List response = new ArrayList(); + final List response = new ArrayList<>(); for (final SoftwareModuleType softwareModule : types) { response.add(toResponse(softwareModule)); } @@ -73,7 +74,7 @@ final class SoftwareModuleTypeMapper { result.setMaxAssignments(type.getMaxAssignments()); result.setModuleId(type.getId()); - result.add(linkTo(methodOn(SoftwareModuleTypeResource.class).getSoftwareModuleType(result.getModuleId())) + result.add(linkTo(methodOn(SoftwareModuleTypeRestApi.class).getSoftwareModuleType(result.getModuleId())) .withRel("self")); return result; diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SoftwareModuleTypeResource.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SoftwareModuleTypeResource.java index 621efc690..4259dcc90 100644 --- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SoftwareModuleTypeResource.java +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SoftwareModuleTypeResource.java @@ -10,8 +10,6 @@ package org.eclipse.hawkbit.rest.resource; import java.util.List; -import javax.persistence.EntityManager; - import org.eclipse.hawkbit.repository.SoftwareManagement; import org.eclipse.hawkbit.repository.SoftwareModuleTypeFields; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; @@ -19,6 +17,7 @@ import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.rsql.RSQLUtility; +import org.eclipse.hawkbit.rest.resource.api.SoftwareModuleTypeRestApi; import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypePagedList; import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypeRequestBodyPost; import org.eclipse.hawkbit.rest.resource.model.softwaremoduletype.SoftwareModuleTypeRequestBodyPut; @@ -30,75 +29,38 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * REST Resource handling for {@link SoftwareModule} and related * {@link Artifact} CRUD operations. * - * - * - * */ @RestController -@RequestMapping(RestConstants.SOFTWAREMODULETYPE_V1_REQUEST_MAPPING) -public class SoftwareModuleTypeResource { +public class SoftwareModuleTypeResource implements SoftwareModuleTypeRestApi { @Autowired private SoftwareManagement softwareManagement; - @Autowired - private EntityManager entityManager; - - /** - * Handles the GET request of retrieving all {@link SoftwareModuleType}s - * within SP. - * - * @param pagingOffsetParam - * the offset of list of modules for pagination, might not be - * present in the rest request then default value will be applied - * @param pagingLimitParam - * the limit of the paged request, might not be present in the - * rest request then default value will be applied - * @param sortParam - * the sorting parameter in the request URL, syntax - * {@code field:direction, field:direction} - * @param rsqlParam - * the search parameter in the request URL, syntax - * {@code q=name==abc} - * - * @return a list of all module type for a defined or default page request - * with status OK. The response is always paged. In any failure the - * JsonResponseExceptionHandler is handling the response. - */ - @RequestMapping(method = RequestMethod.GET, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity getTypes( - @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) { + @Override + public ResponseEntity getTypes(final int pagingOffsetParam, final int pagingLimitParam, + final String sortParam, final String rsqlParam) { final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam); final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam); - final Sort sorting = PagingUtility.sanitizeSoftwareModuleSortParam(sortParam); + final Sort sorting = PagingUtility.sanitizeSoftwareModuleTypeSortParam(sortParam); final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting); final Slice findModuleTypessAll; Long countModulesAll; if (rsqlParam != null) { - findModuleTypessAll = softwareManagement.findSoftwareModuleTypesByPredicate( + findModuleTypessAll = this.softwareManagement.findSoftwareModuleTypesByPredicate( RSQLUtility.parse(rsqlParam, SoftwareModuleTypeFields.class), pageable); countModulesAll = ((Page) findModuleTypessAll).getTotalElements(); } else { - findModuleTypessAll = softwareManagement.findSoftwareModuleTypesAll(pageable); - countModulesAll = softwareManagement.countSoftwareModuleTypesAll(); + findModuleTypessAll = this.softwareManagement.findSoftwareModuleTypesAll(pageable); + countModulesAll = this.softwareManagement.countSoftwareModuleTypesAll(); } final List rest = SoftwareModuleTypeMapper @@ -106,56 +68,25 @@ public class SoftwareModuleTypeResource { return new ResponseEntity<>(new SoftwareModuleTypePagedList(rest, countModulesAll), HttpStatus.OK); } - /** - * Handles the GET request of retrieving a single software module type - * within SP. - * - * @param softwareModuleTypeId - * the ID of the module type to retrieve - * - * @return a single softwareModule with status OK. - * @throws EntityNotFoundException - * in case no with the given {@code softwareModuleId} exists. - */ - @RequestMapping(method = RequestMethod.GET, value = "/{softwareModuleTypeId}", produces = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity getSoftwareModuleType(@PathVariable final Long softwareModuleTypeId) { + @Override + public ResponseEntity getSoftwareModuleType(final Long softwareModuleTypeId) { final SoftwareModuleType foundType = findSoftwareModuleTypeWithExceptionIfNotFound(softwareModuleTypeId); return new ResponseEntity<>(SoftwareModuleTypeMapper.toResponse(foundType), HttpStatus.OK); } - /** - * Handles the DELETE request for a single software module type within SP. - * - * @param softwareModuleTypeId - * the ID of the module to retrieve - * @return status OK if delete as sucessfull. - * - */ - @RequestMapping(method = RequestMethod.DELETE, value = "/{softwareModuleTypeId}") - public ResponseEntity deleteSoftwareModuleType(@PathVariable final Long softwareModuleTypeId) { + @Override + public ResponseEntity deleteSoftwareModuleType(final Long softwareModuleTypeId) { final SoftwareModuleType module = findSoftwareModuleTypeWithExceptionIfNotFound(softwareModuleTypeId); - softwareManagement.deleteSoftwareModuleType(module); + this.softwareManagement.deleteSoftwareModuleType(module); return new ResponseEntity<>(HttpStatus.OK); } - /** - * Handles the PUT request of updating a software module type within SP. - * - * @param softwareModuleTypeId - * the ID of the software module in the URL - * @param restSoftwareModuleType - * the module type to be updated. - * @return status OK if update is successful - */ - @RequestMapping(method = RequestMethod.PUT, value = "/{softwareModuleTypeId}", consumes = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity updateSoftwareModuleType( - @PathVariable final Long softwareModuleTypeId, - @RequestBody final SoftwareModuleTypeRequestBodyPut restSoftwareModuleType) { + @Override + public ResponseEntity updateSoftwareModuleType(final Long softwareModuleTypeId, + final SoftwareModuleTypeRequestBodyPut restSoftwareModuleType) { final SoftwareModuleType type = findSoftwareModuleTypeWithExceptionIfNotFound(softwareModuleTypeId); // only description can be modified @@ -163,27 +94,15 @@ public class SoftwareModuleTypeResource { type.setDescription(restSoftwareModuleType.getDescription()); } - final SoftwareModuleType updatedSoftwareModuleType = softwareManagement.updateSoftwareModuleType(type); + final SoftwareModuleType updatedSoftwareModuleType = this.softwareManagement.updateSoftwareModuleType(type); return new ResponseEntity<>(SoftwareModuleTypeMapper.toResponse(updatedSoftwareModuleType), HttpStatus.OK); } - /** - * Handles the POST request of creating new {@link SoftwareModuleType}s - * within SP. The request body must always be a list of types. - * - * @param softwareModuleTypes - * the modules to be created. - * @return In case all modules could successful created the ResponseEntity - * with status code 201 - Created but without ResponseBody. In any - * failure the JsonResponseExceptionHandler is handling the - * response. - */ - @RequestMapping(method = RequestMethod.POST, consumes = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + @Override public ResponseEntity createSoftwareModuleTypes( - @RequestBody final List softwareModuleTypes) { + final List softwareModuleTypes) { - final List createdSoftwareModules = softwareManagement + final List createdSoftwareModules = this.softwareManagement .createSoftwareModuleTypes(SoftwareModuleTypeMapper.smFromRequest(softwareModuleTypes)); return new ResponseEntity<>(SoftwareModuleTypeMapper.toTypesResponse(createdSoftwareModules), @@ -191,7 +110,7 @@ public class SoftwareModuleTypeResource { } private SoftwareModuleType findSoftwareModuleTypeWithExceptionIfNotFound(final Long softwareModuleTypeId) { - final SoftwareModuleType module = softwareManagement.findSoftwareModuleTypeById(softwareModuleTypeId); + final SoftwareModuleType module = this.softwareManagement.findSoftwareModuleTypeById(softwareModuleTypeId); if (module == null) { throw new EntityNotFoundException( "SoftwareModuleType with Id {" + softwareModuleTypeId + "} does not exist"); diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SystemManagementResource.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SystemManagementResource.java index 7213eae6d..b3a461915 100644 --- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SystemManagementResource.java +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SystemManagementResource.java @@ -17,11 +17,10 @@ import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; import org.eclipse.hawkbit.report.model.SystemUsageReport; import org.eclipse.hawkbit.report.model.TenantUsage; import org.eclipse.hawkbit.repository.SystemManagement; -import org.eclipse.hawkbit.repository.model.TenantConfiguration; -import org.eclipse.hawkbit.rest.resource.model.system.CacheRest; -import org.eclipse.hawkbit.rest.resource.model.system.SystemStatisticsRest; -import org.eclipse.hawkbit.rest.resource.model.system.TenantConfigurationRest; -import org.eclipse.hawkbit.rest.resource.model.system.TenantSystemUsageRest; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.rest.resource.model.systemmanagement.CacheRest; +import org.eclipse.hawkbit.rest.resource.model.systemmanagement.SystemStatisticsRest; +import org.eclipse.hawkbit.rest.resource.model.systemmanagement.TenantSystemUsageRest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -32,7 +31,6 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @@ -53,6 +51,9 @@ public class SystemManagementResource { @Autowired private SystemManagement systemManagement; + @Autowired + private TenantConfigurationManagement tenantConfigurationManagement; + @Autowired private CacheManager cacheManager; @@ -129,24 +130,6 @@ public class SystemManagementResource { return ResponseEntity.ok(cacheNames); } - /** - * Adds or updates a configuration for a specific tenant to the tenant - * configuration. - * - * @param configuration - * the configuration value to add or update - * @param key - * the key of the configuration to add or update - * @return the response entity with status OK. - */ - @RequestMapping(method = RequestMethod.PUT, value = "/conf/{key}") - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN) - public ResponseEntity addUpdateConfig(@RequestBody final TenantConfigurationRest configuration, - @PathVariable final String key) { - systemManagement.addOrUpdateConfiguration(new TenantConfiguration(key, configuration.getValue())); - return ResponseEntity.ok().build(); - } - private CacheRest cacheRest(final Cache cache) { final Object nativeCache = cache.getNativeCache(); if (nativeCache instanceof com.google.common.cache.Cache) { diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SystemMapper.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SystemMapper.java new file mode 100644 index 000000000..0f8119b53 --- /dev/null +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SystemMapper.java @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.repository.model.TenantConfigurationValue; +import org.eclipse.hawkbit.rest.resource.model.system.TenantConfigurationValueRest; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; + +/** + * A mapper which maps repository model to RESTful model representation and + * back. + */ +public class SystemMapper { + + private SystemMapper() { + // Utility class + } + + /** + * @param tenantConfigurationManagement + * instance of TenantConfigurationManagement + * @return a map of all existing configuration values + */ + public static Map toResponse( + final TenantConfigurationManagement tenantConfigurationManagement) { + + final Map configurationMap = new HashMap<>(); + + for (final TenantConfigurationKey key : TenantConfigurationKey.values()) { + configurationMap.put(key.getKeyName(), + toResponse(key.getKeyName(), tenantConfigurationManagement.getConfigurationValue(key))); + } + + return configurationMap; + } + + /** + * maps a TenantConfigurationValue from the repository model to a + * TenantConfigurationValueRest, the RESTful model. + * + * @param repoConfValue + * configuration value as repository model + * @return configuration value as RESTful model + */ + public static TenantConfigurationValueRest toResponse(final String key, + final TenantConfigurationValue repoConfValue) { + final TenantConfigurationValueRest restConfValue = new TenantConfigurationValueRest(); + + restConfValue.setValue(repoConfValue.getValue()); + restConfValue.setGlobal(repoConfValue.isGlobal()); + restConfValue.setCreatedAt(repoConfValue.getCreatedAt()); + restConfValue.setCreatedBy(repoConfValue.getCreatedBy()); + restConfValue.setLastModifiedAt(repoConfValue.getLastModifiedAt()); + restConfValue.setLastModifiedBy(repoConfValue.getLastModifiedBy()); + + restConfValue.add(linkTo(methodOn(SystemResource.class).getConfigurationValue(key)).withRel("self")); + + return restConfValue; + } +} diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SystemResource.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SystemResource.java new file mode 100644 index 000000000..9f2201868 --- /dev/null +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/SystemResource.java @@ -0,0 +1,134 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; + +import java.util.Map; + +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.repository.model.TenantConfigurationValue; +import org.eclipse.hawkbit.rest.resource.model.system.TenantConfigurationValueRequest; +import org.eclipse.hawkbit.rest.resource.model.system.TenantConfigurationValueRest; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.hateoas.ResourceSupport; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +/** + * REST Resource handling tenant specific configuration operations. + * + * + * + * + */ +@RestController +@RequestMapping(RestConstants.SYSTEM_V1_REQUEST_MAPPING) +public class SystemResource { + + private static final Logger LOG = LoggerFactory.getLogger(SystemResource.class); + + @Autowired + private TenantConfigurationManagement tenantConfigurationManagement; + + @RequestMapping(method = RequestMethod.GET, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getSystem() { + final ResourceSupport resourceSupport = new ResourceSupport(); + resourceSupport.add(linkTo(methodOn(SystemResource.class).getSystemConfiguration()).withRel("configs")); + return ResponseEntity.ok(resourceSupport); + } + + /** + * @return a Map of all configuration values. + */ + @RequestMapping(method = RequestMethod.GET, value = "/configs", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity> getSystemConfiguration() { + return new ResponseEntity<>(SystemMapper.toResponse(tenantConfigurationManagement), HttpStatus.OK); + } + + /** + * Handles the DELETE request of deleting a tenant specific configuration + * value within SP. + * + * @param keyName + * the Name of the configuration key + * @return If the given configuration value exists and could be deleted Http + * OK. In any failure the JsonResponseExceptionHandler is handling + * the response. + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/configs/{keyName}", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity deleteConfigurationValue(@PathVariable final String keyName) { + + final TenantConfigurationKey configKey = TenantConfigurationKey.fromKeyName(keyName); + + tenantConfigurationManagement.deleteConfiguration(configKey); + + LOG.debug("{} config value deleted, return status {}", keyName, HttpStatus.OK); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + /** + * Handles the GET request of deleting a tenant specific configuration value + * within SP. + * + * @param keyName + * the Name of the configuration key + * @return If the given configuration value exists and could be get Http OK. + * In any failure the JsonResponseExceptionHandler is handling the + * response. + */ + @RequestMapping(method = RequestMethod.GET, value = "/configs/{keyName}", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity getConfigurationValue(@PathVariable final String keyName) { + + final TenantConfigurationKey configKey = TenantConfigurationKey.fromKeyName(keyName); + + LOG.debug("{} config value getted, return status {}", keyName, HttpStatus.OK); + return new ResponseEntity<>(SystemMapper.toResponse(configKey.getKeyName(), + tenantConfigurationManagement.getConfigurationValue(configKey)), HttpStatus.OK); + } + + /** + * Handles the GET request of deleting a tenant specific configuration value + * within SP. + * + * @param keyName + * the Name of the configuration key + * @param configurationValueRest + * the new value for the configuration + * @return If the given configuration value exists and could be get Http OK. + * In any failure the JsonResponseExceptionHandler is handling the + * response. + */ + @RequestMapping(method = RequestMethod.PUT, value = "/configs/{keyName}", consumes = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity updateConfigurationValue(@PathVariable final String keyName, + @RequestBody final TenantConfigurationValueRequest configurationValueRest) { + + final TenantConfigurationKey configKey = TenantConfigurationKey.fromKeyName(keyName); + + final TenantConfigurationValue updatedValue = tenantConfigurationManagement + + .addOrUpdateConfiguration(configKey, configurationValueRest.getValue()); + return new ResponseEntity<>(SystemMapper.toResponse(keyName, updatedValue), HttpStatus.OK); + } + +} \ No newline at end of file diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/TagMapper.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/TagMapper.java index 80a06ab2d..b5e84abe8 100644 --- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/TagMapper.java +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/TagMapper.java @@ -17,6 +17,8 @@ import java.util.List; import org.eclipse.hawkbit.repository.model.DistributionSetTag; import org.eclipse.hawkbit.repository.model.Tag; import org.eclipse.hawkbit.repository.model.TargetTag; +import org.eclipse.hawkbit.rest.resource.api.DistributionSetTagRestApi; +import org.eclipse.hawkbit.rest.resource.api.TargetTagRestApi; import org.eclipse.hawkbit.rest.resource.model.tag.TagRequestBodyPut; import org.eclipse.hawkbit.rest.resource.model.tag.TagRest; import org.eclipse.hawkbit.rest.resource.model.tag.TagsRest; @@ -53,9 +55,9 @@ final class TagMapper { mapTag(response, targetTag); - response.add(linkTo(methodOn(TargetTagResource.class).getTargetTag(targetTag.getId())).withRel("self")); + response.add(linkTo(methodOn(TargetTagRestApi.class).getTargetTag(targetTag.getId())).withRel("self")); - response.add(linkTo(methodOn(TargetTagResource.class).getAssignedTargets(targetTag.getId())) + response.add(linkTo(methodOn(TargetTagRestApi.class).getAssignedTargets(targetTag.getId())) .withRel("assignedTargets")); return response; @@ -83,12 +85,11 @@ final class TagMapper { mapTag(response, distributionSetTag); - response.add( - linkTo(methodOn(DistributionSetTagResource.class).getDistributionSetTag(distributionSetTag.getId())) - .withRel("self")); + response.add(linkTo(methodOn(DistributionSetTagRestApi.class).getDistributionSetTag(distributionSetTag.getId())) + .withRel("self")); response.add(linkTo( - methodOn(DistributionSetTagResource.class).getAssignedDistributionSets(distributionSetTag.getId())) + methodOn(DistributionSetTagRestApi.class).getAssignedDistributionSets(distributionSetTag.getId())) .withRel("assignedDistributionSets")); return response; diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/TargetMapper.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/TargetMapper.java index a79e136cb..b6c6c1539 100644 --- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/TargetMapper.java +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/TargetMapper.java @@ -23,6 +23,7 @@ import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetInfo.PollStatus; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; +import org.eclipse.hawkbit.rest.resource.api.TargetRestApi; import org.eclipse.hawkbit.rest.resource.model.PollStatusRest; import org.eclipse.hawkbit.rest.resource.model.action.ActionRest; import org.eclipse.hawkbit.rest.resource.model.action.ActionStatusRest; @@ -45,18 +46,18 @@ final public class TargetMapper { /** * Add links to a target response. - * + * * @param response * the target response */ public static void addTargetLinks(final TargetRest response) { - response.add(linkTo(methodOn(TargetResource.class).getAssignedDistributionSet(response.getControllerId())) + response.add(linkTo(methodOn(TargetRestApi.class).getAssignedDistributionSet(response.getControllerId())) .withRel(RestConstants.TARGET_V1_ASSIGNED_DISTRIBUTION_SET)); - response.add(linkTo(methodOn(TargetResource.class).getInstalledDistributionSet(response.getControllerId())) + response.add(linkTo(methodOn(TargetRestApi.class).getInstalledDistributionSet(response.getControllerId())) .withRel(RestConstants.TARGET_V1_INSTALLED_DISTRIBUTION_SET)); - response.add(linkTo(methodOn(TargetResource.class).getAttributes(response.getControllerId())) + response.add(linkTo(methodOn(TargetRestApi.class).getAttributes(response.getControllerId())) .withRel(RestConstants.TARGET_V1_ATTRIBUTES)); - response.add(linkTo(methodOn(TargetResource.class).getActionHistory(response.getControllerId(), 0, + response.add(linkTo(methodOn(TargetRestApi.class).getActionHistory(response.getControllerId(), 0, RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE, ActionFields.ID.getFieldName() + ":" + SortDirection.DESC, null)) .withRel(RestConstants.TARGET_V1_ACTIONS)); @@ -64,7 +65,7 @@ final public class TargetMapper { /** * Add the pollstatus to a target response. - * + * * @param target * the target * @param targetRest @@ -85,7 +86,7 @@ final public class TargetMapper { /** * Create a response which includes links and pollstatus for all targets. - * + * * @param targets * the targets * @return the response @@ -105,7 +106,7 @@ final public class TargetMapper { /** * Create a response for targets. - * + * * @param targets * list of targets * @return the response @@ -123,7 +124,7 @@ final public class TargetMapper { /** * Create a response for target. - * + * * @param target * the target * @return the response @@ -163,7 +164,7 @@ final public class TargetMapper { targetRest.setInstalledAt(installationDate); } - targetRest.add(linkTo(methodOn(TargetResource.class).getTarget(target.getControllerId())).withRel("self")); + targetRest.add(linkTo(methodOn(TargetRestApi.class).getTarget(target.getControllerId())).withRel("self")); return targetRest; } @@ -210,7 +211,7 @@ final public class TargetMapper { RestModelMapper.mapBaseToBase(result, action); - result.add(linkTo(methodOn(TargetResource.class).getAction(targetId, action.getId())).withRel("self")); + result.add(linkTo(methodOn(TargetRestApi.class).getAction(targetId, action.getId())).withRel("self")); return result; } @@ -296,8 +297,8 @@ final public class TargetMapper { final ActionStatusRest result = new ActionStatusRest(); result.setMessages(actionStatus.getMessages()); - result.setReportedAt(action.getCreatedAt()); - result.setStatusId(action.getId()); + result.setReportedAt(actionStatus.getCreatedAt()); + result.setStatusId(actionStatus.getId()); result.setType(getNameOfActionStatusType(actionStatus.getStatus())); return result; diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/TargetResource.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/TargetResource.java index 7b78b0f21..faa31a7cc 100644 --- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/TargetResource.java +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/TargetResource.java @@ -15,20 +15,19 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import javax.persistence.EntityManager; - import org.eclipse.hawkbit.repository.ActionFields; import org.eclipse.hawkbit.repository.ActionStatusFields; import org.eclipse.hawkbit.repository.DeploymentManagement; import org.eclipse.hawkbit.repository.TargetFields; import org.eclipse.hawkbit.repository.TargetManagement; -import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.rsql.RSQLUtility; +import org.eclipse.hawkbit.rest.resource.api.DistributionSetRestApi; +import org.eclipse.hawkbit.rest.resource.api.TargetRestApi; import org.eclipse.hawkbit.rest.resource.helper.RestResourceConversionHelper; import org.eclipse.hawkbit.rest.resource.model.action.ActionPagedList; import org.eclipse.hawkbit.rest.resource.model.action.ActionRest; @@ -49,25 +48,15 @@ import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * REST Resource handling target CRUD operations. - * - * - * - * */ @RestController -@RequestMapping(RestConstants.TARGET_V1_REQUEST_MAPPING) -public class TargetResource { +public class TargetResource implements TargetRestApi { private static final Logger LOG = LoggerFactory.getLogger(TargetResource.class); @Autowired @@ -76,21 +65,8 @@ public class TargetResource { @Autowired private DeploymentManagement deploymentManagement; - @Autowired - private EntityManager entityManager; - - /** - * Handles the GET request of retrieving a single target within SP. - * - * @param targetId - * the ID of the target to retrieve - * @return a single target with status OK. - * @throws EntityNotFoundException - * in case no target with the given {@code targetId} exists. - */ - @RequestMapping(method = RequestMethod.GET, value = "/{targetId}", produces = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity getTarget(@PathVariable final String targetId) { + @Override + public ResponseEntity getTarget(final String targetId) { final Target findTarget = findTargetWithExceptionIfNotFound(targetId); // to single response include poll status final TargetRest response = TargetMapper.toResponse(findTarget); @@ -100,31 +76,9 @@ public class TargetResource { return new ResponseEntity<>(response, HttpStatus.OK); } - /** - * Handles the GET request of retrieving all targets within SP. - * - * @param pagingOffsetParam - * the offset of list of targets for pagination, might not be - * present in the rest request then default value will be applied - * @param pagingLimitParam - * the limit of the paged request, might not be present in the - * rest request then default value will be applied - * @param sortParam - * the sorting parameter in the request URL, syntax - * {@code field:direction, field:direction} - * @param rsqlParam - * the search parameter in the request URL, syntax - * {@code q=name==abc} - * @return a list of all targets for a defined or default page request with - * status OK. The response is always paged. In any failure the - * JsonResponseExceptionHandler is handling the response. - */ - @RequestMapping(method = RequestMethod.GET, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity getTargets( - @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) { + @Override + public ResponseEntity getTargets(final int pagingOffsetParam, final int pagingLimitParam, + final String sortParam, final String rsqlParam) { final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam); final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam); @@ -134,58 +88,29 @@ public class TargetResource { final Slice findTargetsAll; final Long countTargetsAll; if (rsqlParam != null) { - final Page findTargetPage = targetManagement + final Page findTargetPage = this.targetManagement .findTargetsAll(RSQLUtility.parse(rsqlParam, TargetFields.class), pageable); countTargetsAll = findTargetPage.getTotalElements(); findTargetsAll = findTargetPage; } else { - findTargetsAll = targetManagement.findTargetsAll(pageable); - countTargetsAll = targetManagement.countTargetsAll(); + findTargetsAll = this.targetManagement.findTargetsAll(pageable); + countTargetsAll = this.targetManagement.countTargetsAll(); } final List rest = TargetMapper.toResponse(findTargetsAll.getContent()); return new ResponseEntity<>(new TargetPagedList(rest, countTargetsAll), HttpStatus.OK); } - /** - * Handles the POST request of creating new targets within SP. The request - * body must always be a list of targets. The requests is delegating to the - * {@link TargetManagement#createTarget(Iterable)}. - * - * @param targets - * the targets to be created. - * @return In case all targets could successful created the ResponseEntity - * with status code 201 with a list of successfully created - * entities. In any failure the JsonResponseExceptionHandler is - * handling the response. - */ - @RequestMapping(method = RequestMethod.POST, consumes = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity createTargets(@RequestBody final List targets) { + @Override + public ResponseEntity createTargets(final List targets) { LOG.debug("creating {} targets", targets.size()); - final Iterable createdTargets = targetManagement.createTargets(TargetMapper.fromRequest(targets)); + final Iterable createdTargets = this.targetManagement.createTargets(TargetMapper.fromRequest(targets)); LOG.debug("{} targets created, return status {}", targets.size(), HttpStatus.CREATED); return new ResponseEntity<>(TargetMapper.toResponse(createdTargets), HttpStatus.CREATED); } - /** - * Handles the PUT request of updating a target within SP. The ID is within - * the URL path of the request. A given ID in the request body is ignored. - * It's not possible to set fields to {@code null} values. - * - * @param targetId - * the path parameter which contains the ID of the target - * @param targetRest - * the request body which contains the fields which should be - * updated, fields which are not given are ignored for the - * udpate. - * @return the updated target response which contains all fields also fields - * which have not updated - */ - @RequestMapping(method = RequestMethod.PUT, value = "/{targetId}", consumes = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity updateTarget(@PathVariable final String targetId, - @RequestBody final TargetRequestBody targetRest) { + @Override + public ResponseEntity updateTarget(final String targetId, final TargetRequestBody targetRest) { final Target existingTarget = findTargetWithExceptionIfNotFound(targetId); LOG.debug("updating target {}", existingTarget.getId()); if (targetRest.getDescription() != null) { @@ -194,42 +119,21 @@ public class TargetResource { if (targetRest.getName() != null) { existingTarget.setName(targetRest.getName()); } - final Target updateTarget = targetManagement.updateTarget(existingTarget); + final Target updateTarget = this.targetManagement.updateTarget(existingTarget); return new ResponseEntity<>(TargetMapper.toResponse(updateTarget), HttpStatus.OK); } - /** - * Handles the DELETE request of deleting a target within SP. - * - * @param targetId - * the ID of the target to be deleted - * @return If the given targetId could exists and could be deleted Http OK. - * In any failure the JsonResponseExceptionHandler is handling the - * response. - */ - @RequestMapping(method = RequestMethod.DELETE, value = "/{targetId}", produces = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity deleteTarget(@PathVariable final String targetId) { + @Override + public ResponseEntity deleteTarget(final String targetId) { final Target target = findTargetWithExceptionIfNotFound(targetId); - targetManagement.deleteTargets(target.getId()); + this.targetManagement.deleteTargets(target.getId()); LOG.debug("{} target deleted, return status {}", targetId, HttpStatus.OK); return new ResponseEntity<>(HttpStatus.OK); } - /** - * Handles the GET request of retrieving the attributes of a specific - * target. - * - * @param targetId - * the ID of the target to retrieve the attributes. - * @return the target attributes as map response with status OK - * @throws EntityNotFoundException - * in case no target with the given {@code targetId} exists. - */ - @RequestMapping(method = RequestMethod.GET, value = "/{targetId}/attributes", produces = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity getAttributes(@PathVariable final String targetId) { + @Override + public ResponseEntity getAttributes(final String targetId) { final Target foundTarget = findTargetWithExceptionIfNotFound(targetId); final Map controllerAttributes = foundTarget.getTargetInfo().getControllerAttributes(); if (controllerAttributes.isEmpty()) { @@ -242,36 +146,9 @@ public class TargetResource { return new ResponseEntity<>(result, HttpStatus.OK); } - /** - * Handles the GET request of retrieving the {@link Action}s of a specific - * target. - * - * @param targetId - * to load actions for - * @param pagingOffsetParam - * the offset of list of targets for pagination, might not be - * present in the rest request then default value will be applied - * @param pagingLimitParam - * the limit of the paged request, might not be present in the - * rest request then default value will be applied - * @param sortParam - * the sorting parameter in the request URL, syntax - * {@code field:direction, field:direction} - * @param rsqlParam - * the search parameter in the request URL, syntax - * {@code q=status==pending} - * @return a list of all {@link Action}s for a defined or default page - * request with status OK. The response is always paged. In any - * failure the JsonResponseExceptionHandler is handling the - * response. - */ - @RequestMapping(method = RequestMethod.GET, value = "/{targetId}/actions", produces = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity getActionHistory(@PathVariable final String targetId, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) { + @Override + public ResponseEntity getActionHistory(final String targetId, final int pagingOffsetParam, + final int pagingLimitParam, final String sortParam, final String rsqlParam) { final Target foundTarget = findTargetWithExceptionIfNotFound(targetId); @@ -284,11 +161,11 @@ public class TargetResource { final Long totalActionCount; if (rsqlParam != null) { final Specification parse = RSQLUtility.parse(rsqlParam, ActionFields.class); - activeActions = deploymentManagement.findActionsByTarget(parse, foundTarget, pageable); - totalActionCount = deploymentManagement.countActionsByTarget(parse, foundTarget); + activeActions = this.deploymentManagement.findActionsByTarget(parse, foundTarget, pageable); + totalActionCount = this.deploymentManagement.countActionsByTarget(parse, foundTarget); } else { - activeActions = deploymentManagement.findActionsByTarget(foundTarget, pageable); - totalActionCount = deploymentManagement.countActionsByTarget(foundTarget); + activeActions = this.deploymentManagement.findActionsByTarget(foundTarget, pageable); + totalActionCount = this.deploymentManagement.countActionsByTarget(foundTarget); } return new ResponseEntity<>( @@ -296,20 +173,8 @@ public class TargetResource { HttpStatus.OK); } - /** - * Handles the GET request of retrieving a specific {@link Action}s of a - * specific {@link Target}. - * - * @param targetId - * to load the action for - * @param actionId - * to load - * @return the action - */ - @RequestMapping(method = RequestMethod.GET, value = "/{targetId}/actions/{actionId}", produces = { - "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity getAction(@PathVariable final String targetId, - @PathVariable final Long actionId) { + @Override + public ResponseEntity getAction(final String targetId, final Long actionId) { final Target target = findTargetWithExceptionIfNotFound(targetId); final Action action = findActionWithExceptionIfNotFound(actionId); @@ -322,14 +187,14 @@ public class TargetResource { if (!action.isCancelingOrCanceled()) { result.add(linkTo( - methodOn(DistributionSetResource.class).getDistributionSet(action.getDistributionSet().getId())) + methodOn(DistributionSetRestApi.class).getDistributionSet(action.getDistributionSet().getId())) .withRel("distributionset")); } else if (action.isCancelingOrCanceled()) { - result.add(linkTo(methodOn(TargetResource.class).getAction(targetId, action.getId())) + result.add(linkTo(methodOn(TargetRestApi.class).getAction(targetId, action.getId())) .withRel(RestConstants.TARGET_V1_CANCELED_ACTION)); } - result.add(linkTo(methodOn(TargetResource.class).getActionStatusList(targetId, action.getId(), 0, + result.add(linkTo(methodOn(TargetRestApi.class).getActionStatusList(targetId, action.getId(), 0, RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE, ActionStatusFields.ID.getFieldName() + ":" + SortDirection.DESC)) .withRel(RestConstants.TARGET_V1_ACTION_STATUS)); @@ -337,32 +202,16 @@ public class TargetResource { return new ResponseEntity<>(result, HttpStatus.OK); } - /** - * Handles the DELETE request of canceling an specific {@link Action}s of a - * specific {@link Target}. - * - * @param targetId - * the ID of the target in the URL path parameter - * @param actionId - * the ID of the action in the URL path parameter - * @param force - * optional parameter, which indicates a force cancel - * @return status no content in case cancellation was successful - * @throws CancelActionNotAllowedException - * if the given action is not active and cannot be canceled. - * @throws EntityNotFoundException - * if the target or the action is not found - */ - @RequestMapping(method = RequestMethod.DELETE, value = "/{targetId}/actions/{actionId}") - public ResponseEntity cancelAction(@PathVariable final String targetId, @PathVariable final Long actionId, + @Override + public ResponseEntity cancelAction(final String targetId, final Long actionId, @RequestParam(required = false, defaultValue = "false") final boolean force) { final Target target = findTargetWithExceptionIfNotFound(targetId); final Action action = findActionWithExceptionIfNotFound(actionId); if (force) { - deploymentManagement.forceQuitAction(action, target); + this.deploymentManagement.forceQuitAction(action); } else { - deploymentManagement.cancelAction(action, target); + this.deploymentManagement.cancelAction(action, target); } // both functions will throw an exception, when action is in wrong // state, which is mapped by ResponseExceptionHandler. @@ -370,35 +219,9 @@ public class TargetResource { return new ResponseEntity<>(HttpStatus.NO_CONTENT); } - /** - * Handles the GET request of retrieving the {@link ActionStatus}s of a - * specific target and action. - * - * @param targetId - * of the the action - * @param actionId - * of the status we are intend to load - * @param pagingOffsetParam - * the offset of list of targets for pagination, might not be - * present in the rest request then default value will be applied - * @param pagingLimitParam - * the limit of the paged request, might not be present in the - * rest request then default value will be applied - * @param sortParam - * the sorting parameter in the request URL, syntax - * {@code field:direction, field:direction} - * @return a list of all {@link ActionStatus}s for a defined or default page - * request with status OK. The response is always paged. In any - * failure the JsonResponseExceptionHandler is handling the - * response. - */ - @RequestMapping(method = RequestMethod.GET, value = "/{targetId}/actions/{actionId}/status", produces = { - "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity getActionStatusList(@PathVariable final String targetId, - @PathVariable final Long actionId, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam) { + @Override + public ResponseEntity getActionStatusList(final String targetId, final Long actionId, + final int pagingOffsetParam, final int pagingLimitParam, final String sortParam) { final Target target = findTargetWithExceptionIfNotFound(targetId); @@ -412,7 +235,7 @@ public class TargetResource { final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam); final Sort sorting = PagingUtility.sanitizeActionStatusSortParam(sortParam); - final Page statusList = deploymentManagement.findActionStatusMessagesByActionInDescOrder( + final Page statusList = this.deploymentManagement.findActionStatusByAction( new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting), action, true); return new ResponseEntity<>( @@ -422,20 +245,8 @@ public class TargetResource { } - /** - * Handles the GET request of retrieving the assigned distribution set of an - * specific target. - * - * @param targetId - * the ID of the target to retrieve the assigned distribution - * @return the assigned distribution set with status OK, if none is assigned - * than {@code null} content (e.g. "{}") - * @throws EntityNotFoundException - * in case no target with the given {@code targetId} exists. - */ - @RequestMapping(method = RequestMethod.GET, value = "/{targetId}/assignedDS", produces = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity getAssignedDistributionSet(@PathVariable final String targetId) { + @Override + public ResponseEntity getAssignedDistributionSet(final String targetId) { final Target findTarget = findTargetWithExceptionIfNotFound(targetId); final DistributionSetRest distributionSetRest = DistributionSetMapper .toResponse(findTarget.getAssignedDistributionSet()); @@ -448,28 +259,14 @@ public class TargetResource { return new ResponseEntity<>(distributionSetRest, retStatus); } - /** - * Changes the assigned distribution set of a target. - * - * @param targetId - * of the target to change - * @param dsId - * of the distributionset that is to be assigned - * @return {@link HttpStatus#OK} - * - * @throws EntityNotFoundException - * in case no target with the given {@code targetId} exists. - * - */ - @RequestMapping(method = RequestMethod.POST, value = "/{targetId}/assignedDS", consumes = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity postAssignedDistributionSet(@PathVariable final String targetId, - @RequestBody final DistributionSetAssigmentRest dsId) { + @Override + public ResponseEntity postAssignedDistributionSet(final String targetId, + final DistributionSetAssigmentRest dsId) { findTargetWithExceptionIfNotFound(targetId); final ActionType type = (dsId.getType() != null) ? RestResourceConversionHelper.convertActionType(dsId.getType()) : ActionType.FORCED; - final Iterator changed = deploymentManagement + final Iterator changed = this.deploymentManagement .assignDistributionSet(dsId.getId(), type, dsId.getForcetime(), targetId).getAssignedTargets() .iterator(); if (changed.hasNext()) { @@ -482,20 +279,8 @@ public class TargetResource { } - /** - * Handles the GET request of retrieving the installed distribution set of - * an specific target. - * - * @param targetId - * the ID of the target to retrieve - * @return the assigned installed set with status OK, if none is installed - * than {@code null} content (e.g. "{}") - * @throws EntityNotFoundException - * in case no target with the given {@code targetId} exists. - */ - @RequestMapping(method = RequestMethod.GET, value = "/{targetId}/installedDS", produces = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity getInstalledDistributionSet(@PathVariable final String targetId) { + @Override + public ResponseEntity getInstalledDistributionSet(final String targetId) { final Target findTarget = findTargetWithExceptionIfNotFound(targetId); final DistributionSetRest distributionSetRest = DistributionSetMapper .toResponse(findTarget.getTargetInfo().getInstalledDistributionSet()); @@ -509,7 +294,7 @@ public class TargetResource { } private Target findTargetWithExceptionIfNotFound(final String targetId) { - final Target findTarget = targetManagement.findTargetByControllerID(targetId); + final Target findTarget = this.targetManagement.findTargetByControllerID(targetId); if (findTarget == null) { throw new EntityNotFoundException("Target with Id {" + targetId + "} does not exist"); } @@ -517,7 +302,7 @@ public class TargetResource { } private Action findActionWithExceptionIfNotFound(final Long actionId) { - final Action findAction = deploymentManagement.findAction(actionId); + final Action findAction = this.deploymentManagement.findAction(actionId); if (findAction == null) { throw new EntityNotFoundException("Action with Id {" + actionId + "} does not exist"); } diff --git a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/TargetTagResource.java b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/TargetTagResource.java index ef10efbad..76209a958 100644 --- a/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/TargetTagResource.java +++ b/hawkbit-rest-resource/src/main/java/org/eclipse/hawkbit/rest/resource/TargetTagResource.java @@ -11,8 +11,6 @@ package org.eclipse.hawkbit.rest.resource; import java.util.List; import java.util.stream.Collectors; -import javax.persistence.EntityManager; - import org.eclipse.hawkbit.repository.TagFields; import org.eclipse.hawkbit.repository.TagManagement; import org.eclipse.hawkbit.repository.TargetManagement; @@ -21,6 +19,7 @@ import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetTagAssigmentResult; import org.eclipse.hawkbit.repository.rsql.RSQLUtility; +import org.eclipse.hawkbit.rest.resource.api.TargetTagRestApi; import org.eclipse.hawkbit.rest.resource.model.tag.AssignedTargetRequestBody; import org.eclipse.hawkbit.rest.resource.model.tag.TagPagedList; import org.eclipse.hawkbit.rest.resource.model.tag.TagRequestBodyPut; @@ -36,13 +35,8 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** @@ -50,10 +44,8 @@ import org.springframework.web.bind.annotation.RestController; * */ @RestController -@RequestMapping(RestConstants.TARGET_TAG_V1_REQUEST_MAPPING) -public class TargetTagResource { +public class TargetTagResource implements TargetTagRestApi { private static final Logger LOG = LoggerFactory.getLogger(TargetTagResource.class); - private static final String TARGET_TAG_TAGERTS_REQUEST_MAPPING = RestConstants.TARGET_TAG_TAGERTS_REQUEST_MAPPING; @Autowired private TagManagement tagManagement; @@ -61,34 +53,9 @@ public class TargetTagResource { @Autowired private TargetManagement targetManagement; - @Autowired - private EntityManager entityManager; - - /** - * Handles the GET request of retrieving all target tags. - * - * @param pagingOffsetParam - * the offset of list of target tags for pagination, might not be - * present in the rest request then default value will be applied - * @param pagingLimitParam - * the limit of the paged request, might not be present in the - * rest request then default value will be applied - * @param sortParam - * the sorting parameter in the request URL, syntax - * {@code field:direction, field:direction} - * @param rsqlParam - * the search parameter in the request URL, syntax - * {@code q=name==abc} - * @return a list of all target tags for a defined or default page request - * with status OK. The response is always paged. In any failure the - * JsonResponseExceptionHandler is handling the response. - */ - @RequestMapping(method = RequestMethod.GET, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity getTargetTags( - @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = RestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, - @RequestParam(value = RestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam) { + @Override + public ResponseEntity getTargetTags(final int pagingOffsetParam, final int pagingLimitParam, + final String sortParam, final String rsqlParam) { final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam); final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam); @@ -98,11 +65,11 @@ public class TargetTagResource { final Slice findTargetsAll; final Long countTargetsAll; if (rsqlParam == null) { - findTargetsAll = tagManagement.findAllTargetTags(pageable); - countTargetsAll = tagManagement.countTargetTags(); + findTargetsAll = this.tagManagement.findAllTargetTags(pageable); + countTargetsAll = this.tagManagement.countTargetTags(); } else { - final Page findTargetPage = tagManagement + final Page findTargetPage = this.tagManagement .findAllTargetTags(RSQLUtility.parse(rsqlParam, TagFields.class), pageable); countTargetsAll = findTargetPage.getTotalElements(); findTargetsAll = findTargetPage; @@ -113,127 +80,57 @@ public class TargetTagResource { return new ResponseEntity<>(new TagPagedList(rest, countTargetsAll), HttpStatus.OK); } - /** - * Handles the GET request of retrieving a single target tag. - * - * @param targetTagId - * the ID of the target tag to retrieve - * - * @return a single target tag with status OK. - * @throws EntityNotFoundException - * in case the given {@code targetTagId} doesn't exists. - */ - @RequestMapping(method = RequestMethod.GET, value = "/{targetTagId}", produces = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity getTargetTag(@PathVariable final Long targetTagId) { + @Override + public ResponseEntity getTargetTag(final Long targetTagId) { final TargetTag tag = findTargetTagById(targetTagId); return new ResponseEntity<>(TagMapper.toResponse(tag), HttpStatus.OK); } - /** - * Handles the POST request of creating new target tag. The request body - * must always be a list of tags. - * - * @param tags - * the target tags to be created. - * @return In case all modules could successful created the ResponseEntity - * with status code 201 - Created. The Response Body are the created - * target tags but without ResponseBody. - */ - @RequestMapping(method = RequestMethod.POST, consumes = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + @Override public ResponseEntity createTargetTags(@RequestBody final List tags) { LOG.debug("creating {} target tags", tags.size()); - final List createdTargetTags = tagManagement + final List createdTargetTags = this.tagManagement .createTargetTags(TagMapper.mapTargeTagFromRequest(tags)); return new ResponseEntity<>(TagMapper.toResponse(createdTargetTags), HttpStatus.CREATED); } - /** - * - * Handles the PUT request of updating a single targetr tag. - * - * @param targetTagId - * the ID of the target tag - * @param restTargetTagRest - * the the request body to be updated - * @return status OK if update is successful and the updated target tag. - * @throws EntityNotFoundException - * in case the given {@code targetTagId} doesn't exists. - */ - @RequestMapping(method = RequestMethod.PUT, value = "/{targetTagId}", consumes = { "application/hal+json", - MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) - public ResponseEntity updateTagretTag(@PathVariable final Long targetTagId, - @RequestBody final TagRequestBodyPut restTargetTagRest) { + @Override + public ResponseEntity updateTagretTag(final Long targetTagId, final TagRequestBodyPut restTargetTagRest) { LOG.debug("update {} target tag", restTargetTagRest); final TargetTag targetTag = findTargetTagById(targetTagId); TagMapper.updateTag(restTargetTagRest, targetTag); - final TargetTag updateTargetTag = tagManagement.updateTargetTag(targetTag); + final TargetTag updateTargetTag = this.tagManagement.updateTargetTag(targetTag); LOG.debug("target tag updated"); return new ResponseEntity<>(TagMapper.toResponse(updateTargetTag), HttpStatus.OK); } - /** - * Handles the DELETE request for a single target tag. - * - * @param targetTagId - * the ID of the target tag - * @return status OK if delete as successfully. - * @throws EntityNotFoundException - * in case the given {@code targetTagId} doesn't exists. - * - */ - @RequestMapping(method = RequestMethod.DELETE, value = "/{targetTagId}") - public ResponseEntity deleteTargetTag(@PathVariable final Long targetTagId) { + @Override + public ResponseEntity deleteTargetTag(final Long targetTagId) { LOG.debug("Delete {} target tag", targetTagId); final TargetTag targetTag = findTargetTagById(targetTagId); - tagManagement.deleteTargetTag(targetTag.getName()); + this.tagManagement.deleteTargetTag(targetTag.getName()); return new ResponseEntity<>(HttpStatus.OK); } - /** - * Handles the GET request of retrieving all assigned targets by the given - * tag id. - * - * @param targetTagId - * the ID of the target tag to retrieve - * - * @return the list of assigned targets. - * @throws EntityNotFoundException - * in case the given {@code targetTagId} doesn't exists. - */ - @RequestMapping(method = RequestMethod.GET, value = TARGET_TAG_TAGERTS_REQUEST_MAPPING) - public ResponseEntity getAssignedTargets(@PathVariable final Long targetTagId) { + @Override + public ResponseEntity getAssignedTargets(final Long targetTagId) { final TargetTag targetTag = findTargetTagById(targetTagId); return new ResponseEntity<>(TargetMapper.toResponseWithLinksAndPollStatus(targetTag.getAssignedToTargets()), HttpStatus.OK); } - /** - * Handles the POST request to toggle the assignment of targets by the given - * tag id. - * - * @param targetTagId - * the ID of the target tag to retrieve - * @param assignedTargetRequestBodies - * list of target ids to be toggled - * - * @return the list of assigned targets and unassigned targets. - * @throws EntityNotFoundException - * in case the given {@code targetTagId} doesn't exists. - */ - @RequestMapping(method = RequestMethod.POST, value = TARGET_TAG_TAGERTS_REQUEST_MAPPING + "/toggleTagAssignment") - public ResponseEntity toggleTagAssignment(@PathVariable final Long targetTagId, - @RequestBody final List assignedTargetRequestBodies) { + @Override + public ResponseEntity toggleTagAssignment(final Long targetTagId, + final List assignedTargetRequestBodies) { LOG.debug("Toggle Target assignment {} for target tag {}", assignedTargetRequestBodies.size(), targetTagId); final TargetTag targetTag = findTargetTagById(targetTagId); - final TargetTagAssigmentResult assigmentResult = targetManagement + final TargetTagAssigmentResult assigmentResult = this.targetManagement .toggleTagAssignment(findTargetControllerIds(assignedTargetRequestBodies), targetTag.getName()); final TargetTagAssigmentResultRest tagAssigmentResultRest = new TargetTagAssigmentResultRest(); @@ -242,71 +139,38 @@ public class TargetTagResource { return new ResponseEntity<>(tagAssigmentResultRest, HttpStatus.OK); } - /** - * Handles the POST request to assign targets to the given tag id. - * - * @param targetTagId - * the ID of the target tag to retrieve - * @param assignedTargetRequestBodies - * list of target ids to be assigned - * - * @return the list of assigned targets. - * @throws EntityNotFoundException - * in case the given {@code targetTagId} doesn't exists. - */ - @RequestMapping(method = RequestMethod.POST, value = TARGET_TAG_TAGERTS_REQUEST_MAPPING) - public ResponseEntity assignTargets(@PathVariable final Long targetTagId, - @RequestBody final List assignedTargetRequestBodies) { + @Override + public ResponseEntity assignTargets(final Long targetTagId, + final List assignedTargetRequestBodies) { LOG.debug("Assign Targets {} for target tag {}", assignedTargetRequestBodies.size(), targetTagId); final TargetTag targetTag = findTargetTagById(targetTagId); - final List assignedTarget = targetManagement + final List assignedTarget = this.targetManagement .assignTag(findTargetControllerIds(assignedTargetRequestBodies), targetTag); return new ResponseEntity<>(TargetMapper.toResponseWithLinksAndPollStatus(assignedTarget), HttpStatus.OK); } - /** - * Handles the DELETE request to unassign all targets from the given tag id. - * - * @param targetTagId - * the ID of the target tag to retrieve - * @return http status code - * @throws EntityNotFoundException - * in case the given {@code targetTagId} doesn't exists. - */ - @RequestMapping(method = RequestMethod.DELETE, value = TARGET_TAG_TAGERTS_REQUEST_MAPPING) - public ResponseEntity unassignTargets(@PathVariable final Long targetTagId) { + @Override + public ResponseEntity unassignTargets(final Long targetTagId) { LOG.debug("Unassign all Targets for target tag {}", targetTagId); final TargetTag targetTag = findTargetTagById(targetTagId); if (targetTag.getAssignedToTargets() == null) { LOG.debug("No assigned targets found"); return new ResponseEntity<>(HttpStatus.OK); } - targetManagement.unAssignAllTargetsByTag(targetTag); + this.targetManagement.unAssignAllTargetsByTag(targetTag); return new ResponseEntity<>(HttpStatus.OK); } - /** - * Handles the DELETE request to unassign one target from the given tag id. - * - * @param targetTagId - * the ID of the target tag - * @param controllerId - * the ID of the target to unassign - * @return http status code - * @throws EntityNotFoundException - * in case the given {@code targetTagId} doesn't exists. - */ - @RequestMapping(method = RequestMethod.DELETE, value = TARGET_TAG_TAGERTS_REQUEST_MAPPING + "/{controllerId}") - public ResponseEntity unassignTarget(@PathVariable final Long targetTagId, - @PathVariable final String controllerId) { + @Override + public ResponseEntity unassignTarget(final Long targetTagId, final String controllerId) { LOG.debug("Unassign target {} for target tag {}", controllerId, targetTagId); final TargetTag targetTag = findTargetTagById(targetTagId); - targetManagement.unAssignTag(controllerId, targetTag); + this.targetManagement.unAssignTag(controllerId, targetTag); return new ResponseEntity<>(HttpStatus.OK); } private TargetTag findTargetTagById(final Long targetTagId) { - final TargetTag tag = tagManagement.findTargetTagById(targetTagId); + final TargetTag tag = this.tagManagement.findTargetTagById(targetTagId); if (tag == null) { throw new EntityNotFoundException("Target Tag with Id {" + targetTagId + "} does not exist"); } diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/controller/ArtifactDownloadTest.java b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/controller/ArtifactDownloadTest.java index a87ce748a..b49f34b5f 100644 --- a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/controller/ArtifactDownloadTest.java +++ b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/controller/ArtifactDownloadTest.java @@ -57,13 +57,10 @@ import ru.yandex.qatools.allure.annotations.Stories; /** * Test artifact downloads from the controller. * - * - * - * */ @ActiveProfiles({ "im", "test" }) -@Features("Component Tests - Controller RESTful API") +@Features("Component Tests - Direct Device Integration API") @Stories("Artifact Download Resource") public class ArtifactDownloadTest extends AbstractIntegrationTestWithMongoDB { @@ -285,7 +282,8 @@ public class ArtifactDownloadTest extends AbstractIntegrationTestWithMongoDB { .andExpect(header().string("Content-Disposition", "attachment;filename=" + artifact.getFilename())) .andReturn(); - assertTrue(Arrays.equals(result.getResponse().getContentAsByteArray(), random)); + assertTrue("The same file that was uploaded is expected when downloaded", + Arrays.equals(result.getResponse().getContentAsByteArray(), random)); // download complete assertThat(downLoadProgress).isEqualTo(10); @@ -393,7 +391,8 @@ public class ArtifactDownloadTest extends AbstractIntegrationTestWithMongoDB { .andExpect(header().longValue("Last-Modified", artifact.getCreatedAt())) .andExpect(header().string("Content-Disposition", "attachment;filename=file1")).andReturn(); - assertTrue(Arrays.equals(result.getResponse().getContentAsByteArray(), random)); + assertTrue("The same file that was uploaded is expected when downloaded", + Arrays.equals(result.getResponse().getContentAsByteArray(), random)); // one (update) action assertThat(actionRepository.findByTargetAndDistributionSet(pageReq, target, ds).getContent()).hasSize(1); diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/controller/CancelActionTest.java b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/controller/CancelActionTest.java index 267b95408..d7e0351cf 100644 --- a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/controller/CancelActionTest.java +++ b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/controller/CancelActionTest.java @@ -40,7 +40,7 @@ import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Stories; @ActiveProfiles({ "im", "test" }) -@Features("Component Tests - Controller RESTful API") +@Features("Component Tests - Direct Device Integration API") @Stories("Cancel Action Resource") public class CancelActionTest extends AbstractIntegrationTest { @@ -56,7 +56,8 @@ public class CancelActionTest extends AbstractIntegrationTest { final List toAssign = new ArrayList(); toAssign.add(savedTarget); - final Action updateAction = deploymentManagement.assignDistributionSet(ds, toAssign).getActions().get(0); + final Action updateAction = deploymentManagement + .findActionWithDetails(deploymentManagement.assignDistributionSet(ds, toAssign).getActions().get(0)); final Action cancelAction = deploymentManagement.cancelAction(updateAction, targetManagement.findTargetByControllerID(savedTarget.getControllerId())); @@ -112,7 +113,8 @@ public class CancelActionTest extends AbstractIntegrationTest { final List toAssign = new ArrayList(); toAssign.add(savedTarget); - final Action updateAction = deploymentManagement.assignDistributionSet(ds, toAssign).getActions().get(0); + final Action updateAction = deploymentManagement + .findActionWithDetails(deploymentManagement.assignDistributionSet(ds, toAssign).getActions().get(0)); long current = System.currentTimeMillis(); mvc.perform(get("/{tenant}/controller/v1/4712", tenantAware.getCurrentTenant())) @@ -227,7 +229,8 @@ public class CancelActionTest extends AbstractIntegrationTest { final Target savedTarget = targetManagement.createTarget(target); final List toAssign = new ArrayList(); toAssign.add(savedTarget); - final Action updateAction = deploymentManagement.assignDistributionSet(ds, toAssign).getActions().get(0); + final Action updateAction = deploymentManagement + .findActionWithDetails(deploymentManagement.assignDistributionSet(ds, toAssign).getActions().get(0)); return deploymentManagement.cancelAction(updateAction, targetManagement.findTargetByControllerID(savedTarget.getControllerId())); @@ -243,8 +246,8 @@ public class CancelActionTest extends AbstractIntegrationTest { final Target savedTarget = targetManagement.createTarget(target); - final Action updateAction = deploymentManagement.assignDistributionSet(ds.getId(), new String[] { "4712" }) - .getActions().get(0); + final Action updateAction = deploymentManagement.findActionWithDetails( + deploymentManagement.assignDistributionSet(ds.getId(), new String[] { "4712" }).getActions().get(0)); // cancel action manually final Action cancelAction = deploymentManagement.cancelAction(updateAction, @@ -340,12 +343,12 @@ public class CancelActionTest extends AbstractIntegrationTest { final Target savedTarget = targetManagement.createTarget(target); - final Action updateAction = deploymentManagement.assignDistributionSet(ds.getId(), new String[] { "4712" }) - .getActions().get(0); - final Action updateAction2 = deploymentManagement.assignDistributionSet(ds2.getId(), new String[] { "4712" }) - .getActions().get(0); - final Action updateAction3 = deploymentManagement.assignDistributionSet(ds3.getId(), new String[] { "4712" }) - .getActions().get(0); + final Action updateAction = deploymentManagement.findActionWithDetails( + deploymentManagement.assignDistributionSet(ds.getId(), new String[] { "4712" }).getActions().get(0)); + final Action updateAction2 = deploymentManagement.findActionWithDetails( + deploymentManagement.assignDistributionSet(ds2.getId(), new String[] { "4712" }).getActions().get(0)); + final Action updateAction3 = deploymentManagement.findActionWithDetails( + deploymentManagement.assignDistributionSet(ds3.getId(), new String[] { "4712" }).getActions().get(0)); assertThat(actionStatusRepository.findAll()).hasSize(3); @@ -456,8 +459,8 @@ public class CancelActionTest extends AbstractIntegrationTest { final List toAssign = new ArrayList(); toAssign.add(target); - final Action action = deploymentManagement.assignDistributionSet(ds.getId(), new String[] { "4712" }) - .getActions().get(0); + final Action action = deploymentManagement.findActionWithDetails( + deploymentManagement.assignDistributionSet(ds.getId(), new String[] { "4712" }).getActions().get(0)); final Action cancelAction = deploymentManagement.cancelAction(action, targetManagement.findTargetByControllerID(target.getControllerId())); diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/controller/ConfigDataTest.java b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/controller/ConfigDataTest.java index 4b6a135b2..a2b26c218 100644 --- a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/controller/ConfigDataTest.java +++ b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/controller/ConfigDataTest.java @@ -35,7 +35,7 @@ import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Stories; @ActiveProfiles({ "im", "test" }) -@Features("Component Tests - Controller RESTful API") +@Features("Component Tests - Direct Device Integration API") @Stories("Config Data Resource") public class ConfigDataTest extends AbstractIntegrationTest { diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/controller/DeploymentBaseTest.java b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/controller/DeploymentBaseTest.java index e0a4b8e5c..2b2b7e597 100644 --- a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/controller/DeploymentBaseTest.java +++ b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/controller/DeploymentBaseTest.java @@ -52,7 +52,7 @@ import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Stories; @ActiveProfiles({ "im", "test" }) -@Features("Component Tests - Controller RESTful API") +@Features("Component Tests - Direct Device Integration API") @Stories("Deployment Action Resource") public class DeploymentBaseTest extends AbstractIntegrationTestWithMongoDB { @@ -484,7 +484,8 @@ public class DeploymentBaseTest extends AbstractIntegrationTestWithMongoDB { final DistributionSet savedSet = TestDataUtil.generateDistributionSet("", softwareManagement, distributionSetManagement); - final Action action1 = deploymentManagement.assignDistributionSet(savedSet, toAssign).getActions().get(0); + final Action action1 = deploymentManagement.findActionWithDetails( + deploymentManagement.assignDistributionSet(savedSet, toAssign).getActions().get(0)); mvc.perform( get("/{tenant}/controller/v1/4712/deploymentBase/" + action1.getId(), tenantAware.getCurrentTenant())) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); @@ -544,12 +545,12 @@ public class DeploymentBaseTest extends AbstractIntegrationTestWithMongoDB { final List toAssign = new ArrayList(); toAssign.add(savedTarget1); - final Action action1 = deploymentManagement.assignDistributionSet(ds1.getId(), new String[] { "4712" }) - .getActions().get(0); - final Action action2 = deploymentManagement.assignDistributionSet(ds2.getId(), new String[] { "4712" }) - .getActions().get(0); - final Action action3 = deploymentManagement.assignDistributionSet(ds3.getId(), new String[] { "4712" }) - .getActions().get(0); + final Action action1 = deploymentManagement.findActionWithDetails( + deploymentManagement.assignDistributionSet(ds1.getId(), new String[] { "4712" }).getActions().get(0)); + final Action action2 = deploymentManagement.findActionWithDetails( + deploymentManagement.assignDistributionSet(ds2.getId(), new String[] { "4712" }).getActions().get(0)); + final Action action3 = deploymentManagement.findActionWithDetails( + deploymentManagement.assignDistributionSet(ds3.getId(), new String[] { "4712" }).getActions().get(0)); Target myT = targetManagement.findTargetByControllerID("4712"); assertThat(myT.getTargetInfo().getUpdateStatus()).isEqualTo(TargetUpdateStatus.PENDING); diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/controller/RootControllerTest.java b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/controller/RootControllerTest.java index 7dce6dc35..a6e457f3a 100644 --- a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/controller/RootControllerTest.java +++ b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/controller/RootControllerTest.java @@ -34,6 +34,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.rest.resource.JsonBuilder; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.eclipse.hawkbit.util.IpUtil; import org.junit.Test; import org.springframework.hateoas.MediaTypes; @@ -45,13 +46,11 @@ import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Stories; @ActiveProfiles({ "im", "test" }) -@Features("Component Tests - Controller RESTful API") +@Features("Component Tests - Direct Device Integration API") @Stories("Root Poll Resource") -// TODO: fully document tests -> @Description for long text and reasonable -// method name as short text public class RootControllerTest extends AbstractIntegrationTestWithMongoDB { - @Test() + @Test @Description("Ensures that targets cannot be created e.g. in plug'n play scenarios when tenant does not exists but can be created if the tenant exists.") @WithUser(tenantId = "tenantDoesNotExists", allSpPermissions = true, authorities = "ROLE_CONTROLLER", autoCreateTenant = false) public void targetCannotBeRegisteredIfTenantDoesNotExistsButWhenExists() throws Exception { @@ -73,8 +72,9 @@ public class RootControllerTest extends AbstractIntegrationTestWithMongoDB { } @Test + @Description("Ensures that target poll request does not change audit data on the entity.") @WithUser(principal = "knownPrincipal", authorities = { SpPermission.READ_TARGET, SpPermission.UPDATE_TARGET, - SpPermission.CREATE_TARGET }) + SpPermission.CREATE_TARGET }, allSpPermissions = false) public void targetPollDoesNotModifyAuditData() throws Exception { // create target first with "knownPrincipal" user and audit data final String knownTargetControllerId = "target1"; @@ -104,11 +104,13 @@ public class RootControllerTest extends AbstractIntegrationTestWithMongoDB { } @Test + @Description("Ensures that server returns a not found response in case of empty controlloer ID.") public void rootRsWithoutId() throws Exception { mvc.perform(get("/controller/v1/")).andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound()); } @Test + @Description("Ensures that the system creates a new target in plug and play manner, i.e. target is authenticated but does not exist yet.") public void rootRsPlugAndPlay() throws Exception { final long current = System.currentTimeMillis(); @@ -133,6 +135,29 @@ public class RootControllerTest extends AbstractIntegrationTestWithMongoDB { } @Test + @Description("Ensures that tenant specific polling time, which is saved in the db, is delivered to the controller.") + @WithUser(principal = "knownpricipal", allSpPermissions = false) + public void pollWithModifiedGloablPollingTime() throws Exception { + securityRule.runAs( + WithSpringAuthorityRule.withUser("tenantadmin", SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION), + () -> { + tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.POLLING_TIME_INTERVAL, + "00:02:00"); + return null; + }); + + securityRule.runAs( + WithSpringAuthorityRule.withUser("controller", SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS), () -> { + mvc.perform(get("/{tenant}/controller/v1/4711", tenantAware.getCurrentTenant())) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaTypes.HAL_JSON)) + .andExpect(jsonPath("$config.polling.sleep", equalTo("00:02:00"))); + return null; + }); + } + + @Test + @Description("Ensures that etag check results in not modified response if provided etag by client is identical to entity in repository.") public void rootRsNotModified() throws Exception { final String etag = mvc.perform(get("/{tenant}/controller/v1/4711", tenantAware.getCurrentTenant())) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) @@ -197,6 +222,8 @@ public class RootControllerTest extends AbstractIntegrationTestWithMongoDB { } @Test + @Description("Ensures that the target state machine of a precomissioned target switches from " + + "UNKNOWN to REGISTERED when the target polls for the first time.") public void rootRsPrecommissioned() throws Exception { final Target target = new Target("4711"); targetManagement.createTarget(target); @@ -219,6 +246,7 @@ public class RootControllerTest extends AbstractIntegrationTestWithMongoDB { } @Test + @Description("Ensures that the source IP address of the polling target is correctly stored in repository") public void rootRsPlugAndPlayIpAddress() throws Exception { // test final String knownControllerId1 = "0815"; diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/ConfigurationResourceTest.java b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/ConfigurationResourceTest.java new file mode 100644 index 000000000..39e571a2c --- /dev/null +++ b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/ConfigurationResourceTest.java @@ -0,0 +1,146 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource; + +import static org.fest.assertions.api.Assertions.assertThat; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.Matchers.hasSize; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.eclipse.hawkbit.AbstractIntegrationTest; +import org.eclipse.hawkbit.MockMvcResultPrinter; +import org.eclipse.hawkbit.exception.SpServerError; +import org.eclipse.hawkbit.repository.model.TenantConfigurationValue; +import org.eclipse.hawkbit.rest.resource.model.ExceptionInfo; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.ResultActions; + +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Component Tests - Management RESTful API") +@Stories("ConfigurationResource") +public class ConfigurationResourceTest extends AbstractIntegrationTest { + + private static String BASE_JSON_REQUEST_STRING = "{\"value\":\"%s\"}"; + + @Test + @Description("perform a GET request on all existing configurations.") + public void getConfigurationValues() throws Exception { + + final ResultActions resultActions = mvc.perform(get(RestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs/")) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(jsonPath("$.*", hasSize(TenantConfigurationKey.values().length))); + + for (final TenantConfigurationKey key : TenantConfigurationKey.values()) { + + final TenantConfigurationValue confValue = tenantConfigurationManagement.getConfigurationValue(key); + resultActions.andExpect(jsonPath("$.['" + key.getKeyName() + "'].value", equalTo(confValue.getValue()))) + .andExpect(jsonPath("$.['" + key.getKeyName() + "'].global", equalTo(confValue.isGlobal()))); + } + } + + @Test + @Description("perform a GET request on a existing configuration key.") + public void getConfigurationValue() throws Exception { + + final TenantConfigurationKey key = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME; + final String notGlobalValue = "notTheGlobalHeaderAuthoryName"; + + tenantConfigurationManagement.addOrUpdateConfiguration(key, notGlobalValue); + + mvc.perform(get(RestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs/{configId}/", key.getKeyName())) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(jsonPath("value", equalTo(notGlobalValue))).andExpect(jsonPath("global", equalTo(false))) + .andExpect(jsonPath("createdAt", notNullValue())).andExpect(jsonPath("createdBy", notNullValue())); + } + + @Test + @Description("perform a PUT request on a existing configuration key with a valid value.") + public void putConfigurationValue() throws Exception { + + final TenantConfigurationKey key = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME; + final String testValue = "12:12:12"; + + mvc.perform(put(RestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs/{configId}/", key.getKeyName()) + .content(String.format(BASE_JSON_REQUEST_STRING, testValue)).contentType(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); + + assertThat(tenantConfigurationManagement.getConfigurationValue(key, String.class).getValue()) + .isEqualTo(testValue); + } + + @Test + @Description("perform a DELETE request on a existing configuration key.") + public void deleteConfigurationValue() throws Exception { + + final TenantConfigurationKey key = TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME; + final String notGlobalValue = "notTheGlobalHeaderAuthoryName"; + + tenantConfigurationManagement.addOrUpdateConfiguration(key, notGlobalValue); + assertThat(tenantConfigurationManagement.getConfigurationValue(key, String.class).isGlobal()).isEqualTo(false); + + assertThat(tenantConfigurationManagement.getConfigurationValue(key, String.class).getValue()) + .isEqualTo(notGlobalValue); + + mvc.perform(delete(RestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs/{configId}/", key.getKeyName())) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isNoContent()); + + assertThat(tenantConfigurationManagement.getConfigurationValue(key, String.class).isGlobal()).isEqualTo(true); + assertThat(tenantConfigurationManagement.getConfigurationValue(key, String.class).getValue()) + .isNotEqualTo(notGlobalValue); + } + + @Test + @Description("perform a (put) request on a not existing configuration key.") + public void putInvalidConfigurationKey() throws Exception { + + final String notExistingKey = "notExistingKey"; + final String testValue = "12:12:12"; + + final MvcResult mvcResult = mvc + .perform(put(RestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs/{configId}", notExistingKey) + .content(String.format(BASE_JSON_REQUEST_STRING, testValue)) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest()).andReturn(); + + // verify response json exception message + final ExceptionInfo exceptionInfo = ResourceUtility + .convertException(mvcResult.getResponse().getContentAsString()); + assertThat(exceptionInfo.getErrorCode()).isEqualTo(SpServerError.SP_CONFIGURATION_KEY_INVALID.getKey()); + } + + @Test + @Description("perform a put request with a not matching configuration value.") + public void putInvalidConfigurationValue() throws Exception { + + final TenantConfigurationKey key = TenantConfigurationKey.POLLING_TIME_INTERVAL; + final String testValue = "invalidFormattedDuration"; + + final MvcResult mvcResult = mvc + .perform(put(RestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs/{configId}", key.getKeyName()) + .content(String.format(BASE_JSON_REQUEST_STRING, testValue)) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest()).andReturn(); + + // verify response json exception message + final ExceptionInfo exceptionInfo = ResourceUtility + .convertException(mvcResult.getResponse().getContentAsString()); + assertThat(exceptionInfo.getErrorCode()).isEqualTo(SpServerError.SP_CONFIGURATION_VALUE_INVALID.getKey()); + } +} diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/DistributionSetResourceTest.java b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/DistributionSetResourceTest.java index 7eccf7196..6254da992 100644 --- a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/DistributionSetResourceTest.java +++ b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/DistributionSetResourceTest.java @@ -47,7 +47,6 @@ import org.eclipse.hawkbit.repository.model.Target; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import org.junit.Ignore; import org.junit.Test; import org.springframework.context.annotation.Description; import org.springframework.http.MediaType; @@ -59,15 +58,8 @@ import com.jayway.jsonpath.JsonPath; import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Stories; -/** - * - * - * - */ -@Features("Component Tests - Management RESTful API") +@Features("Component Tests - Management API") @Stories("Distribution Set Resource") -// TODO: fully document tests -> @Description for long text and reasonable -// method name as short text public class DistributionSetResourceTest extends AbstractIntegrationTest { @Test @@ -235,6 +227,7 @@ public class DistributionSetResourceTest extends AbstractIntegrationTest { } @Test + @Description("Ensures that multi target assignment through API is reflected by the repository.") public void assignMultipleTargetsToDistributionSet() throws Exception { // prepare distribution set final Set createDistributionSetsAlphabetical = createDistributionSetsAlphabetical(1); @@ -255,9 +248,13 @@ public class DistributionSetResourceTest extends AbstractIntegrationTest { .andExpect(status().isOk()).andExpect(jsonPath("$.assigned", equalTo(knownTargetIds.length - 1))) .andExpect(jsonPath("$.alreadyAssigned", equalTo(1))) .andExpect(jsonPath("$.total", equalTo(knownTargetIds.length))); + + assertThat(targetManagement.findTargetByAssignedDistributionSet(createdDs.getId(), pageReq).getContent()) + .as("Five targets in repository have DS assigned").hasSize(5); } @Test + @Description("Ensures that assigned targets of DS are returned as reflected by the repository.") public void getAssignedTargetsOfDistributionSet() throws Exception { // prepare distribution set final String knownTargetId = "knownTargetId1"; @@ -273,6 +270,7 @@ public class DistributionSetResourceTest extends AbstractIntegrationTest { } @Test + @Description("Ensures that assigned targets of DS are returned as persisted in the repository.") public void getAssignedTargetsOfDistributionSetIsEmpty() throws Exception { final Set createDistributionSetsAlphabetical = createDistributionSetsAlphabetical(1); final DistributionSet createdDs = createDistributionSetsAlphabetical.iterator().next(); @@ -283,6 +281,7 @@ public class DistributionSetResourceTest extends AbstractIntegrationTest { } @Test + @Description("Ensures that installed targets of DS are returned as persisted in the repository.") public void getInstalledTargetsOfDistributionSet() throws Exception { // prepare distribution set final String knownTargetId = "knownTargetId1"; @@ -305,46 +304,50 @@ public class DistributionSetResourceTest extends AbstractIntegrationTest { } @Test + @Description("Ensures that DS in repository are listed with proper paging properties.") public void getDistributionSetsWithoutAddtionalRequestParameters() throws Exception { - final int modules = 5; - createDistributionSetsAlphabetical(modules); + final int sets = 5; + createDistributionSetsAlphabetical(sets); mvc.perform(get(RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING)).andDo(MockMvcResultPrinter.print()) .andExpect(status().isOk()) - .andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_TOTAL, equalTo(modules))) - .andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_SIZE, equalTo(modules))) - .andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_CONTENT, hasSize(modules))); + .andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_TOTAL, equalTo(sets))) + .andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_SIZE, equalTo(sets))) + .andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_CONTENT, hasSize(sets))); } @Test + @Description("Ensures that DS in repository are listed with proper paging results with paging limit parameter.") public void getDistributionSetsWithPagingLimitRequestParameter() throws Exception { - final int modules = 5; + final int sets = 5; final int limitSize = 1; - createDistributionSetsAlphabetical(modules); + createDistributionSetsAlphabetical(sets); mvc.perform(get(RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING) .param(RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, String.valueOf(limitSize))) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) - .andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_TOTAL, equalTo(modules))) + .andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_TOTAL, equalTo(sets))) .andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_SIZE, equalTo(limitSize))) .andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_CONTENT, hasSize(limitSize))); } @Test + @Description("Ensures that DS in repository are listed with proper paging results with paging limit and offset parameter.") public void getDistributionSetsWithPagingLimitAndOffsetRequestParameter() throws Exception { - final int modules = 5; + final int sets = 5; final int offsetParam = 2; - final int expectedSize = modules - offsetParam; - createDistributionSetsAlphabetical(modules); + final int expectedSize = sets - offsetParam; + createDistributionSetsAlphabetical(sets); mvc.perform(get(RestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING) .param(RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, String.valueOf(offsetParam)) - .param(RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, String.valueOf(modules))) + .param(RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, String.valueOf(sets))) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) - .andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_TOTAL, equalTo(modules))) + .andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_TOTAL, equalTo(sets))) .andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_SIZE, equalTo(expectedSize))) .andExpect(jsonPath(TargetResourceTest.JSON_PATH_PAGED_LIST_CONTENT, hasSize(expectedSize))); } @Test @WithUser(principal = "uploadTester", allSpPermissions = true) + @Description("Ensures that multiple DS requested are listed with expected payload.") public void getDistributionSets() throws Exception { // prepare test data assertThat(distributionSetManagement.findDistributionSetsAll(pageReq, false, true)).hasSize(0); @@ -389,6 +392,7 @@ public class DistributionSetResourceTest extends AbstractIntegrationTest { @Test @WithUser(principal = "uploadTester", allSpPermissions = true) + @Description("Ensures that single DS requested by ID is listed with expected payload.") public void getDistributionSet() throws Exception { final DistributionSet set = createTestDistributionSet(softwareManagement, distributionSetManagement); @@ -420,6 +424,7 @@ public class DistributionSetResourceTest extends AbstractIntegrationTest { @Test @WithUser(principal = "uploadTester", allSpPermissions = true) + @Description("Ensures that multipe DS posted to API are created in the repository.") public void createDistributionSets() throws JSONException, Exception { assertThat(distributionSetManagement.findDistributionSetsAll(pageReq, false, true)).hasSize(0); @@ -534,7 +539,8 @@ public class DistributionSetResourceTest extends AbstractIntegrationTest { } @Test - public void testDeleteUnassignedistributionSet() throws Exception { + @Description("Ensures that DS deletion request to API is reflected by the repository.") + public void deleteUnassignedistributionSet() throws Exception { // prepare test data assertThat(distributionSetManagement.findDistributionSetsAll(pageReq, false, true)).hasSize(0); @@ -553,7 +559,8 @@ public class DistributionSetResourceTest extends AbstractIntegrationTest { } @Test - public void testDeleteAssignedDistributionSet() throws Exception { + @Description("Ensures that assigned DS deletion request to API is reflected by the repository by means of deleted flag set.") + public void deleteAssignedDistributionSet() throws Exception { // prepare test data assertThat(distributionSetManagement.findDistributionSetsAll(pageReq, false, true)).hasSize(0); @@ -574,6 +581,7 @@ public class DistributionSetResourceTest extends AbstractIntegrationTest { } @Test + @Description("Ensures that DS property update request to API is reflected by the repository.") public void updateDistributionSet() throws Exception { // prepare test data @@ -601,6 +609,7 @@ public class DistributionSetResourceTest extends AbstractIntegrationTest { } @Test + @Description("Ensures that the server reacts properly to invalid requests (URI, Media Type, Methods) with correct reponses.") public void invalidRequestsOnDistributionSetsResource() throws Exception { final DistributionSet set = TestDataUtil.generateDistributionSet("one", softwareManagement, distributionSetManagement); @@ -642,6 +651,7 @@ public class DistributionSetResourceTest extends AbstractIntegrationTest { } @Test + @Description("Ensures that the metadata creation through API is reflected by the repository.") public void createMetadata() throws Exception { final DistributionSet testDS = TestDataUtil.generateDistributionSet("one", softwareManagement, distributionSetManagement); @@ -674,6 +684,7 @@ public class DistributionSetResourceTest extends AbstractIntegrationTest { } @Test + @Description("Ensures that a metadata update through API is reflected by the repository.") public void updateMetadata() throws Exception { // prepare and create metadata for update final String knownKey = "knownKey"; @@ -700,6 +711,7 @@ public class DistributionSetResourceTest extends AbstractIntegrationTest { } @Test + @Description("Ensures that a metadata entry deletion through API is reflected by the repository.") public void deleteMetadata() throws Exception { // prepare and create metadata for deletion final String knownKey = "knownKey"; @@ -722,6 +734,7 @@ public class DistributionSetResourceTest extends AbstractIntegrationTest { } @Test + @Description("Ensures that a metadata entry selection through API reflectes the repository content.") public void getSingleMetadata() throws Exception { // prepare and create metadata final String knownKey = "knownKey"; @@ -737,6 +750,7 @@ public class DistributionSetResourceTest extends AbstractIntegrationTest { } @Test + @Description("Ensures that a metadata entry paged list selection through API reflectes the repository content.") public void getPagedListofMetadata() throws Exception { final int totalMetadata = 10; @@ -760,6 +774,7 @@ public class DistributionSetResourceTest extends AbstractIntegrationTest { } @Test + @Description("Ensures that a DS search with query parameters returns the expected result.") public void searchDistributionSetRsql() throws Exception { final String dsSuffix = "test"; final int amount = 10; @@ -776,11 +791,13 @@ public class DistributionSetResourceTest extends AbstractIntegrationTest { } - @Ignore @Test + @Description("Ensures that a DS search with complete==true parameter returns only DS that are actually completely filled with mandatory modules.") public void filterDistributionSetComplete() throws Exception { final int amount = 10; TestDataUtil.generateDistributionSets(amount, softwareManagement, distributionSetManagement); + distributionSetManagement.createDistributionSet(new DistributionSet("incomplete", "2", "incomplete", + distributionSetManagement.findDistributionSetTypeByKey("ecl_os"), null)); final String rsqlFindLikeDs1OrDs2 = "complete==" + Boolean.TRUE; @@ -790,6 +807,7 @@ public class DistributionSetResourceTest extends AbstractIntegrationTest { } @Test + @Description("Ensures that a DS assigned target search with controllerId==1 parameter returns only the target with the given ID.") public void searchDistributionSetAssignedTargetsRsql() throws Exception { // prepare distribution set final Set createDistributionSetsAlphabetical = createDistributionSetsAlphabetical(1); @@ -815,6 +833,7 @@ public class DistributionSetResourceTest extends AbstractIntegrationTest { } @Test + @Description("Ensures that a DS metadata filtered query with value==knownValue1 parameter returns only the metadata entries with that value.") public void searchDistributionSetMetadataRsql() throws Exception { final int totalMetadata = 10; final String knownKeyPrefix = "knownKey"; diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/DistributionSetTypeResourceTest.java b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/DistributionSetTypeResourceTest.java index 266b284ca..9f3d2a89d 100644 --- a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/DistributionSetTypeResourceTest.java +++ b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/DistributionSetTypeResourceTest.java @@ -46,11 +46,8 @@ import ru.yandex.qatools.allure.annotations.Stories; /** * Test for {@link DistributionSetTypeResource}. * - * - * - * */ -@Features("Component Tests - Management RESTful API") +@Features("Component Tests - Management API") @Stories("Distribution Set Type Resource") public class DistributionSetTypeResourceTest extends AbstractIntegrationTest { @@ -92,6 +89,43 @@ public class DistributionSetTypeResourceTest extends AbstractIntegrationTest { .andExpect(jsonPath("$total", equalTo(4))); } + @Test + @WithUser(principal = "uploadTester", allSpPermissions = true) + @Description("Checks the correct behaviour of /rest/v1/distributionsettypes GET requests with sorting by KEY.") + public void getDistributionSetTypesSortedByKey() throws Exception { + + DistributionSetType testType = distributionSetManagement + .createDistributionSetType(new DistributionSetType("zzzzz", "TestName123", "Desc123")); + testType.setDescription("Desc1234"); + testType = distributionSetManagement.updateDistributionSetType(testType); + + // descending + mvc.perform(get("/rest/v1/distributionsettypes").accept(MediaType.APPLICATION_JSON) + .param(RestConstants.REQUEST_PARAMETER_SORTING, "KEY:DESC")).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content.[0].id", equalTo(testType.getId().intValue()))) + .andExpect(jsonPath("$content.[0].name", equalTo("TestName123"))) + .andExpect(jsonPath("$content.[0].description", equalTo("Desc1234"))) + .andExpect(jsonPath("$content.[0].createdBy", equalTo("uploadTester"))) + .andExpect(jsonPath("$content.[0].createdAt", equalTo(testType.getCreatedAt()))) + .andExpect(jsonPath("$content.[0].lastModifiedBy", equalTo("uploadTester"))) + .andExpect(jsonPath("$content.[0].lastModifiedAt", equalTo(testType.getLastModifiedAt()))) + .andExpect(jsonPath("$content.[0].key", equalTo("zzzzz"))).andExpect(jsonPath("$total", equalTo(4))); + + // ascending + mvc.perform(get("/rest/v1/distributionsettypes").accept(MediaType.APPLICATION_JSON) + .param(RestConstants.REQUEST_PARAMETER_SORTING, "KEY:ASC")).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content.[3].id", equalTo(testType.getId().intValue()))) + .andExpect(jsonPath("$content.[3].name", equalTo("TestName123"))) + .andExpect(jsonPath("$content.[3].description", equalTo("Desc1234"))) + .andExpect(jsonPath("$content.[3].createdBy", equalTo("uploadTester"))) + .andExpect(jsonPath("$content.[3].createdAt", equalTo(testType.getCreatedAt()))) + .andExpect(jsonPath("$content.[3].lastModifiedBy", equalTo("uploadTester"))) + .andExpect(jsonPath("$content.[3].lastModifiedAt", equalTo(testType.getLastModifiedAt()))) + .andExpect(jsonPath("$content.[3].key", equalTo("zzzzz"))).andExpect(jsonPath("$total", equalTo(4))); + } + @Test @WithUser(principal = "uploadTester", allSpPermissions = true) @Description("Checks the correct behaviour of /rest/v1/distributionsettypes POST requests.") diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/DownloadResourceTest.java b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/DownloadResourceTest.java index cc8f4f6b7..ee0fa52fe 100644 --- a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/DownloadResourceTest.java +++ b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/DownloadResourceTest.java @@ -30,11 +30,7 @@ import org.springframework.context.annotation.Description; import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Stories; -/** - * - * - */ -@Features("Component Tests- Download Restful API") +@Features("Component Tests - Management API") @Stories("Download Resource") public class DownloadResourceTest extends AbstractIntegrationTestWithMongoDB { diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/JsonBuilder.java b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/JsonBuilder.java index bbed30c38..dec0bb070 100644 --- a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/JsonBuilder.java +++ b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/JsonBuilder.java @@ -16,6 +16,7 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetType; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupConditions; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.model.Target; @@ -40,9 +41,9 @@ public abstract class JsonBuilder { try { builder.append(new JSONObject().put("name", module.getName()) .put("description", module.getDescription()).put("type", module.getType().getKey()) - .put("id", Long.MAX_VALUE).put("vendor", module.getVendor()).put("version", module.getVersion()) - .put("createdAt", "0").put("updatedAt", "0").put("createdBy", "fghdfkjghdfkjh") - .put("updatedBy", "fghdfkjghdfkjh").toString()); + .put("id", Long.MAX_VALUE).put("vendor", module.getVendor()) + .put("version", module.getVersion()).put("createdAt", "0").put("updatedAt", "0") + .put("createdBy", "fghdfkjghdfkjh").put("updatedBy", "fghdfkjghdfkjh").toString()); } catch (final Exception e) { e.printStackTrace(); } @@ -184,13 +185,15 @@ public abstract class JsonBuilder { final List messages = new ArrayList(); messages.add(message); - return new JSONObject().put("id", id).put("time", "20140511T121314") + return new JSONObject() + .put("id", id) + .put("time", "20140511T121314") .put("status", - new JSONObject().put("execution", execution) + new JSONObject() + .put("execution", execution) .put("result", new JSONObject().put("finished", finished).put("progress", - new JSONObject().put("cnt", 2).put("of", 5))) - .put("details", messages)) + new JSONObject().put("cnt", 2).put("of", 5))).put("details", messages)) .toString(); } @@ -378,9 +381,9 @@ public abstract class JsonBuilder { for (final Target target : targets) { try { builder.append(new JSONObject().put("controllerId", target.getControllerId()) - .put("description", target.getDescription()).put("name", target.getName()).put("createdAt", "0") - .put("updatedAt", "0").put("createdBy", "fghdfkjghdfkjh").put("updatedBy", "fghdfkjghdfkjh") - .toString()); + .put("description", target.getDescription()).put("name", target.getName()) + .put("createdAt", "0").put("updatedAt", "0").put("createdBy", "fghdfkjghdfkjh") + .put("updatedBy", "fghdfkjghdfkjh").toString()); } catch (final Exception e) { e.printStackTrace(); } @@ -395,6 +398,40 @@ public abstract class JsonBuilder { return builder.toString(); } + public static String rollout(final String name, final String description, final int groupSize, + final long distributionSetId, final String targetFilterQuery, final RolloutGroupConditions conditions) { + final JSONObject json = new JSONObject(); + json.put("name", name); + json.put("description", description); + json.put("amountGroups", groupSize); + json.put("distributionSetId", distributionSetId); + json.put("targetFilterQuery", targetFilterQuery); + + if (conditions != null) { + final JSONObject successCondition = new JSONObject(); + json.put("successCondition", successCondition); + successCondition.put("condition", conditions.getSuccessCondition().toString()); + successCondition.put("expression", conditions.getSuccessConditionExp().toString()); + + final JSONObject successAction = new JSONObject(); + json.put("successAction", successAction); + successAction.put("action", conditions.getSuccessAction().toString()); + successAction.put("expression", conditions.getSuccessActionExp().toString()); + + final JSONObject errorCondition = new JSONObject(); + json.put("errorCondition", errorCondition); + errorCondition.put("condition", conditions.getErrorCondition().toString()); + errorCondition.put("expression", conditions.getErrorConditionExp().toString()); + + final JSONObject errorAction = new JSONObject(); + json.put("errorAction", errorAction); + errorAction.put("action", conditions.getErrorAction().toString()); + errorAction.put("expression", conditions.getErrorActionExp().toString()); + } + + return json.toString(); + } + public static String cancelActionFeedback(final String id, final String execution) throws JSONException { return cancelActionFeedback(id, execution, RandomStringUtils.randomAscii(1000)); @@ -404,7 +441,9 @@ public abstract class JsonBuilder { throws JSONException { final List messages = new ArrayList(); messages.add(message); - return new JSONObject().put("id", id).put("time", "20140511T121314") + return new JSONObject() + .put("id", id) + .put("time", "20140511T121314") .put("status", new JSONObject().put("execution", execution) .put("result", new JSONObject().put("finished", "success")).put("details", messages)) @@ -414,12 +453,13 @@ public abstract class JsonBuilder { public static String configData(final String id, final Map attributes, final String execution) throws JSONException { - return new JSONObject().put("id", id).put("time", "20140511T121314") + return new JSONObject() + .put("id", id) + .put("time", "20140511T121314") .put("status", new JSONObject().put("execution", execution) .put("result", new JSONObject().put("finished", "success")) - .put("details", new ArrayList())) - .put("data", attributes).toString(); + .put("details", new ArrayList())).put("data", attributes).toString(); } diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/RolloutResourceTest.java b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/RolloutResourceTest.java new file mode 100644 index 000000000..f27eb5186 --- /dev/null +++ b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/RolloutResourceTest.java @@ -0,0 +1,596 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource; + +import static org.fest.assertions.api.Assertions.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.notNullValue; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.List; +import java.util.concurrent.Callable; + +import org.eclipse.hawkbit.AbstractIntegrationTest; +import org.eclipse.hawkbit.MockMvcResultPrinter; +import org.eclipse.hawkbit.TestDataUtil; +import org.eclipse.hawkbit.WithUser; +import org.eclipse.hawkbit.repository.RolloutGroupManagement; +import org.eclipse.hawkbit.repository.RolloutManagement; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.Rollout; +import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus; +import org.eclipse.hawkbit.repository.model.RolloutGroup; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupConditionBuilder; +import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition; +import org.eclipse.hawkbit.repository.model.Target; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Description; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.http.MediaType; + +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +/** + * Tests for covering the {@link RolloutResource}. + */ +@Features("Component Tests - Management API") +@Stories("Rollout Resource") +public class RolloutResourceTest extends AbstractIntegrationTest { + + @Autowired + private RolloutManagement rolloutManagement; + + @Autowired + private RolloutGroupManagement rolloutGroupManagement; + + @Test + @Description("Testing that creating rollout with wrong body returns bad request") + public void createRolloutWithInvalidBodyReturnsBadRequest() throws Exception { + mvc.perform(post("/rest/v1/rollouts").content("invalid body").contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("errorCode", equalTo("hawkbit.server.error.rest.body.notReadable"))); + } + + @Test + @Description("Testing that creating rollout with insufficient permission returns forbidden") + @WithUser(allSpPermissions = true, removeFromAllPermission = "ROLLOUT_MANAGEMENT") + public void createRolloutWithInsufficientPermissionReturnsForbidden() throws Exception { + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + mvc.perform(post("/rest/v1/rollouts") + .content(JsonBuilder.rollout("name", "desc", 10, dsA.getId(), "name==test", null)) + .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().is(403)).andReturn(); + } + + @Test + @Description("Testing that creating rollout with not exisiting distribution set returns not found") + public void createRolloutWithNotExistingDistributionSetReturnsNotFound() throws Exception { + mvc.perform(post("/rest/v1/rollouts").content(JsonBuilder.rollout("name", "desc", 10, 1234, "name==test", null)) + .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isNotFound()).andReturn(); + } + + @Test + @Description("Testing that creating rollout with not valid formed target filter query returns bad request") + public void createRolloutWithNotWellFormedFilterReturnsBadRequest() throws Exception { + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + mvc.perform(post("/rest/v1/rollouts") + .content(JsonBuilder.rollout("name", "desc", 10, dsA.getId(), "name=test", null)) + .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest()) + .andExpect(jsonPath("errorCode", equalTo("hawkbit.server.error.rest.param.rsqlParamSyntax"))) + .andReturn(); + } + + @Description("Ensures that the repository refuses to create rollout without a defined target filter set.") + public void missingTargetFilterQueryInRollout() throws Exception { + + final String targetFilterQuery = null; + + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + mvc.perform(post("/rest/v1/rollouts") + .content(JsonBuilder.rollout("rollout1", "desc", 10, dsA.getId(), targetFilterQuery, null)) + .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest()) + .andExpect(jsonPath("errorCode", equalTo("hawkbit.server.error.rest.param.rsqlParamSyntax"))) + .andReturn(); + + } + + @Test + @Description("Testing that rollout can be created") + public void createRollout() throws Exception { + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + postRollout("rollout1", 10, dsA.getId(), "name==target1"); + } + + @Test + @Description("Testing the empty list is returned if no rollout exists") + public void noRolloutReturnsEmptyList() throws Exception { + mvc.perform(get("/rest/v1/rollouts")).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(0))).andExpect(jsonPath("$total", equalTo(0))); + } + + @Test + @Description("Testing that rollout paged list contains rollouts") + public void rolloutPagedListContainsAllRollouts() throws Exception { + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + // setup - create 2 rollouts + postRollout("rollout1", 10, dsA.getId(), "name==target1"); + postRollout("rollout2", 5, dsA.getId(), "name==target2"); + + mvc.perform(get("/rest/v1/rollouts")).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(2))).andExpect(jsonPath("$total", equalTo(2))) + .andExpect(jsonPath("content[0].name", equalTo("rollout1"))) + .andExpect(jsonPath("content[0].status", equalTo("ready"))) + .andExpect(jsonPath("content[0].targetFilterQuery", equalTo("name==target1"))) + .andExpect(jsonPath("content[0].distributionSetId", equalTo(dsA.getId().intValue()))) + .andExpect(jsonPath("content[1].name", equalTo("rollout2"))) + .andExpect(jsonPath("content[1].status", equalTo("ready"))) + .andExpect(jsonPath("content[1].targetFilterQuery", equalTo("name==target2"))) + .andExpect(jsonPath("content[1].distributionSetId", equalTo(dsA.getId().intValue()))); + } + + @Test + @Description("Testing that rollout paged list is limited by the query param limit") + public void rolloutPagedListIsLimitedToQueryParam() throws Exception { + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + // setup - create 2 rollouts + postRollout("rollout1", 10, dsA.getId(), "name==target1"); + postRollout("rollout2", 5, dsA.getId(), "name==target2"); + + mvc.perform(get("/rest/v1/rollouts?limit=1")).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(1))).andExpect(jsonPath("$total", equalTo(2))); + } + + @Test + @Description("Testing that rollout paged list is limited by the query param limit") + public void retrieveRolloutGroupsForSpecificRollout() throws Exception { + // setup + final int amountTargets = 20; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*"); + + // retrieve rollout groups from created rollout + mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups", rollout.getId())) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(4))).andExpect(jsonPath("$total", equalTo(4))) + .andExpect(jsonPath("$content[0].status", equalTo("ready"))) + .andExpect(jsonPath("$content[1].status", equalTo("ready"))) + .andExpect(jsonPath("$content[2].status", equalTo("ready"))) + .andExpect(jsonPath("$content[3].status", equalTo("ready"))); + } + + @Test + @Description("Testing that starting the rollout switches the state to running") + public void startingRolloutSwitchesIntoRunningState() throws Exception { + // setup + final int amountTargets = 20; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*"); + + // starting rollout + mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); + + // check rollout is in running state + mvc.perform(get("/rest/v1/rollouts/{rolloutId}", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("id", equalTo(rollout.getId().intValue()))) + .andExpect(jsonPath("status", equalTo("running"))); + } + + @Test + @Description("Testing that pausing the rollout switches the state to paused") + public void pausingRolloutSwitchesIntoPausedState() throws Exception { + // setup + final int amountTargets = 20; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*"); + + // starting rollout + mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); + + // pausing rollout + mvc.perform(post("/rest/v1/rollouts/{rolloutId}/pause", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); + + // check rollout is in running state + mvc.perform(get("/rest/v1/rollouts/{rolloutId}", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("id", equalTo(rollout.getId().intValue()))) + .andExpect(jsonPath("status", equalTo("paused"))); + } + + @Test + @Description("Testing that resuming the rollout switches the state to running") + public void resumingRolloutSwitchesIntoRunningState() throws Exception { + // setup + final int amountTargets = 20; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*"); + + // starting rollout + mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); + + // pausing rollout + mvc.perform(post("/rest/v1/rollouts/{rolloutId}/pause", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); + + // resume rollout + mvc.perform(post("/rest/v1/rollouts/{rolloutId}/resume", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); + + // check rollout is in running state + mvc.perform(get("/rest/v1/rollouts/{rolloutId}", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("id", equalTo(rollout.getId().intValue()))) + .andExpect(jsonPath("status", equalTo("running"))); + } + + @Test + @Description("Testing that an already started rollout cannot be started again and returns bad request") + public void startingAlreadyStartedRolloutReturnsBadRequest() throws Exception { + // setup + final int amountTargets = 20; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*"); + + // starting rollout + mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); + + // starting rollout - already started should lead into bad request + mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("errorCode", equalTo("hawkbit.server.error.rollout.illegalstate"))); + } + + @Test + @Description("Testing that resuming a rollout which is not started leads to bad request") + public void resumingNotStartedRolloutReturnsBadRequest() throws Exception { + // setup + final int amountTargets = 20; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*"); + + // resume not yet started rollout + mvc.perform(post("/rest/v1/rollouts/{rolloutId}/resume", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("errorCode", equalTo("hawkbit.server.error.rollout.illegalstate"))); + } + + @Test + @Description("Testing that starting rollout the first rollout group is in running state") + public void startingRolloutFirstRolloutGroupIsInRunningState() throws Exception { + // setup + final int amountTargets = 10; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 2, dsA.getId(), "controllerId==rollout*"); + + // starting rollout + mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId())).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); + + // retrieve rollout groups from created rollout - 2 groups exists + // (amountTargets / groupSize = 2) + mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups?sort=ID:ASC", rollout.getId())) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(2))).andExpect(jsonPath("$total", equalTo(2))) + .andExpect(jsonPath("$content[0].status", equalTo("running"))) + .andExpect(jsonPath("$content[1].status", equalTo("scheduled"))); + } + + @Test + @Description("Testing that a single rollout group can be retrieved") + public void retrieveSingleRolloutGroup() throws Exception { + // setup + final int amountTargets = 10; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*"); + + final RolloutGroup firstGroup = rolloutGroupManagement + .findRolloutGroupsByRolloutId(rollout.getId(), new PageRequest(0, 1, Direction.ASC, "id")).getContent() + .get(0); + + // retrieve single rollout group with known ID + mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups/{groupId}", rollout.getId(), firstGroup.getId())) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("id", equalTo(firstGroup.getId().intValue()))) + .andExpect(jsonPath("status", equalTo("ready"))).andExpect(jsonPath("name", notNullValue())); + } + + @Test + @Description("Testing that the targets of rollout group can be retrieved") + public void retrieveTargetsFromRolloutGroup() throws Exception { + // setup + final int amountTargets = 10; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 2, dsA.getId(), "controllerId==rollout*"); + + final RolloutGroup firstGroup = rolloutGroupManagement + .findRolloutGroupsByRolloutId(rollout.getId(), new PageRequest(0, 1, Direction.ASC, "id")).getContent() + .get(0); + + // retrieve targets from the first rollout group with known ID + mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups/{groupId}/targets", rollout.getId(), + firstGroup.getId())).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(5))).andExpect(jsonPath("$total", equalTo(5))); + } + + @Test + @Description("Testing that the targets of rollout group can be retrieved with rsql query param") + public void retrieveTargetsFromRolloutGroupWithQuery() throws Exception { + // setup + final int amountTargets = 10; + final List targets = targetManagement + .createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 2, dsA.getId(), "controllerId==rollout*"); + + final RolloutGroup firstGroup = rolloutGroupManagement + .findRolloutGroupsByRolloutId(rollout.getId(), new PageRequest(0, 1, Direction.ASC, "id")).getContent() + .get(0); + + // retrieve targets from the first rollout group with known ID + mvc.perform( + get("/rest/v1/rollouts/{rolloutId}/deploygroups/{groupId}/targets", rollout.getId(), firstGroup.getId()) + .param("q", "controllerId==" + targets.get(0).getControllerId())) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(1))).andExpect(jsonPath("$total", equalTo(1))); + } + + @Test + @Description("Testing that the targets of rollout group can be retrieved after the rollout has been started") + public void retrieveTargetsFromRolloutGroupAfterRolloutIsStarted() throws Exception { + // setup + final int amountTargets = 10; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 2, dsA.getId(), "controllerId==rollout*"); + + rolloutManagement.startRollout(rollout); + + final RolloutGroup firstGroup = rolloutGroupManagement + .findRolloutGroupsByRolloutId(rollout.getId(), new PageRequest(0, 1, Direction.ASC, "id")).getContent() + .get(0); + + // retrieve targets from the first rollout group with known ID + mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups/{groupId}/targets", rollout.getId(), + firstGroup.getId())).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(5))).andExpect(jsonPath("$total", equalTo(5))); + } + + @Test + @Description("Start the rollout in async mode") + public void startingRolloutSwitchesIntoRunningStateAsync() throws Exception { + + final int amountTargets = 1000; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*"); + + // starting rollout + mvc.perform(post("/rest/v1/rollouts/{rolloutId}/start", rollout.getId()) + .param(RestConstants.REQUEST_PARAMETER_ASYNC, "true")).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()); + + // check if running + assertThat(doWithTimeout(() -> getRollout(rollout.getId()), result -> success(result), 60_000, 100)) + .isNotNull(); + } + + @Test + @Description("Testing that rollout paged list with rsql parameter") + public void getRolloutWithRSQLParam() throws Exception { + + final int amountTargetsRollout1 = 25; + final int amountTargetsRollout2 = 25; + final int amountTargetsRollout3 = 25; + final int amountTargetsOther = 25; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargetsRollout1, "rollout1", "rollout1")); + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargetsRollout2, "rollout2", "rollout2")); + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargetsRollout3, "rollout3", "rollout3")); + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargetsOther, "other1", "other1")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + createRollout("rollout1", 5, dsA.getId(), "controllerId==rollout1*"); + final Rollout rollout2 = createRollout("rollout2", 5, dsA.getId(), "controllerId==rollout2*"); + createRollout("rollout3", 5, dsA.getId(), "controllerId==rollout3*"); + createRollout("other1", 5, dsA.getId(), "controllerId==other1*"); + + mvc.perform(get("/rest/v1/rollouts").param(RestConstants.REQUEST_PARAMETER_SEARCH, "name==*2")) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(1))).andExpect(jsonPath("$total", equalTo(1))) + .andExpect(jsonPath("$content[0].name", equalTo(rollout2.getName()))); + + mvc.perform(get("/rest/v1/rollouts").param(RestConstants.REQUEST_PARAMETER_SEARCH, "name==rollout*")) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(3))).andExpect(jsonPath("$total", equalTo(3))); + + mvc.perform(get("/rest/v1/rollouts").param(RestConstants.REQUEST_PARAMETER_SEARCH, "name==*1")) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(2))).andExpect(jsonPath("$total", equalTo(2))); + + } + + @Test + @Description("Testing that rolloutgroup paged list with rsql parameter") + public void retrieveRolloutGroupsForSpecificRolloutWithRSQLParam() throws Exception { + // setup + final int amountTargets = 20; + targetManagement.createTargets(TestDataUtil.buildTargetFixtures(amountTargets, "rollout", "rollout")); + final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, + distributionSetManagement); + + // create rollout including the created targets with prefix 'rollout' + final Rollout rollout = createRollout("rollout1", 4, dsA.getId(), "controllerId==rollout*"); + + // retrieve rollout groups from created rollout + mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups", rollout.getId()) + .param(RestConstants.REQUEST_PARAMETER_SEARCH, "name==group-1")).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(1))).andExpect(jsonPath("$total", equalTo(1))) + .andExpect(jsonPath("$content[0].name", equalTo("group-1"))); + + mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups", rollout.getId()) + .param(RestConstants.REQUEST_PARAMETER_SEARCH, "name==group*")).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(4))).andExpect(jsonPath("$total", equalTo(4))); + + mvc.perform(get("/rest/v1/rollouts/{rolloutId}/deploygroups", rollout.getId()) + .param(RestConstants.REQUEST_PARAMETER_SEARCH, "name==group-1,name==group-2")) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content", hasSize(2))).andExpect(jsonPath("$total", equalTo(2))); + + } + + protected T doWithTimeout(final Callable callable, final SuccessCondition successCondition, + final long timeout, final long pollInterval) throws Exception // NOPMD + { + + if (pollInterval < 0) { + throw new IllegalArgumentException("pollInterval must non negative"); + } + + long duration = 0; + Exception exception = null; + T returnValue = null; + while (untilTimeoutReached(timeout, duration)) { + try { + returnValue = callable.call(); + // clear exception + exception = null; + } catch (final Exception ex) { + exception = ex; + } + Thread.sleep(pollInterval); + duration += pollInterval > 0 ? pollInterval : 1; + if (exception == null && successCondition.success(returnValue)) { + return returnValue; + } else { + returnValue = null; + } + } + if (exception != null) { + throw exception; + } + return returnValue; + } + + protected boolean untilTimeoutReached(final long timeout, final long duration) { + return duration <= timeout || timeout < 0; + } + + private void postRollout(final String name, final int groupSize, final long distributionSetId, + final String targetFilterQuery) throws Exception { + mvc.perform(post("/rest/v1/rollouts") + .content(JsonBuilder.rollout(name, "desc", groupSize, distributionSetId, targetFilterQuery, null)) + .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated()).andReturn(); + } + + private Rollout createRollout(final String name, final int amountGroups, final long distributionSetId, + final String targetFilterQuery) { + final Rollout rollout = new Rollout(); + rollout.setDistributionSet(distributionSetManagement.findDistributionSetById(distributionSetId)); + rollout.setName(name); + rollout.setTargetFilterQuery(targetFilterQuery); + return rolloutManagement.createRollout(rollout, amountGroups, new RolloutGroupConditionBuilder() + .successCondition(RolloutGroupSuccessCondition.THRESHOLD, "100").build()); + } + + protected boolean success(final Rollout result) { + if (null != result && result.getStatus() == RolloutStatus.RUNNING) { + return true; + } + return false; + } + + public Rollout getRollout(final Long rolloutId) throws Exception { + return rolloutManagement.findRolloutById(rolloutId); + } + +} diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SMRessourceMisingMongoDbConnectionTest.java b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SMRessourceMisingMongoDbConnectionTest.java index f9be088a4..664dc861c 100644 --- a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SMRessourceMisingMongoDbConnectionTest.java +++ b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SMRessourceMisingMongoDbConnectionTest.java @@ -23,13 +23,16 @@ import org.junit.Test; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.web.servlet.MvcResult; +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + /** * Tests {@link SoftwareModuleResource} in case of missing MongoDB connection. * - * - * - * */ +@Features("Component Tests - Management API") +@Stories("Download Resource") public class SMRessourceMisingMongoDbConnectionTest extends AbstractIntegrationTest { @BeforeClass @@ -40,7 +43,8 @@ public class SMRessourceMisingMongoDbConnectionTest extends AbstractIntegrationT } @Test - public void testMissingMongoDbConnection() throws Exception { + @Description("Ensures that the correct error code is returned in case MongoDB unavailable.") + public void missingMongoDbConnectionResultsInErrorAtUpload() throws Exception { assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0); assertThat(artifactRepository.findAll()).hasSize(0); diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SoftwareModuleResourceTest.java b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SoftwareModuleResourceTest.java index 8079b5d52..0b8503d6b 100644 --- a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SoftwareModuleResourceTest.java +++ b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SoftwareModuleResourceTest.java @@ -25,6 +25,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.io.ByteArrayInputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -49,6 +50,7 @@ import org.eclipse.hawkbit.rest.resource.model.artifact.ArtifactRest; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import org.junit.Before; import org.junit.Test; import org.springframework.http.MediaType; import org.springframework.mock.web.MockMultipartFile; @@ -65,10 +67,17 @@ import ru.yandex.qatools.allure.annotations.Stories; * Tests for {@link SoftwareModuleResource} {@link RestController}. * */ -@Features("Component Tests - Management RESTful API") +@Features("Component Tests - Management API") @Stories("Software Module Resource") public class SoftwareModuleResourceTest extends AbstractIntegrationTestWithMongoDB { + @Before + public void assertPreparationOfRepo() { + assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).as("no softwaremodule should be founded") + .hasSize(0); + assertThat(artifactRepository.findAll()).as("no artifacts should be founded").hasSize(0); + } + @Test @Description("Tests the update of software module metadata. It is verfied that only the selected fields for the update are really updated and the modification values are filled (i.e. updated by and at).") @WithUser(principal = "smUpdateTester", allSpPermissions = true) @@ -81,18 +90,14 @@ public class SoftwareModuleResourceTest extends AbstractIntegrationTestWithMongo final String updateVendor = "newVendor1"; final String updateDescription = "newDescription1"; - final SoftwareModule ah = softwareManagement - .createSoftwareModule(new SoftwareModule(appType, "agent-hub", "1.0.1", null, "")); - final SoftwareModule jvm = softwareManagement - .createSoftwareModule(new SoftwareModule(runtimeType, "oracle-jre", "1.7.2", null, "")); - final SoftwareModule os = softwareManagement - .createSoftwareModule(new SoftwareModule(osType, "poky", "3.0.2", null, "")); + softwareManagement.createSoftwareModule(new SoftwareModule(appType, "agent-hub", "1.0.1", null, "")); + softwareManagement.createSoftwareModule(new SoftwareModule(runtimeType, "oracle-jre", "1.7.2", null, "")); + softwareManagement.createSoftwareModule(new SoftwareModule(osType, "poky", "3.0.2", null, "")); SoftwareModule sm = new SoftwareModule(osType, knownSWName, knownSWVersion, knownSWDescription, knownSWVendor); sm = softwareManagement.createSoftwareModule(sm); - assertThat(sm.getName()).isEqualTo(knownSWName); - assertThat(sm.getName()).isEqualTo(knownSWName); + assertThat(sm.getName()).as("Wrong name of the software module").isEqualTo(knownSWName); final String body = new JSONObject().put("vendor", updateVendor).put("description", updateDescription) .put("name", "nameShouldNotBeChanged").toString(); @@ -112,20 +117,9 @@ public class SoftwareModuleResourceTest extends AbstractIntegrationTestWithMongo } - /** - * Test method for - * {@link org.eclipse.hawkbit.rest.resource.SoftwareModuleResource#uploadArtifact(java.lang.Long, org.springframework.web.multipart.MultipartFile)} - * . - * - * @throws Exception - * if test fails - */ @Test @Description("Tests the uppload of an artifact binary. The upload is executed and the content checked in the repository for completenes.") public void uploadArtifact() throws Exception { - // prepare repo - assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0); - assertThat(artifactRepository.findAll()).hasSize(0); SoftwareModule sm = new SoftwareModule(osType, "name 1", "version 1", null, null); sm = softwareManagement.createSoftwareModule(sm); assertThat(artifactRepository.findAll()).hasSize(0); @@ -152,36 +146,41 @@ public class SoftwareModuleResourceTest extends AbstractIntegrationTestWithMongo .convertArtifactResponse(mvcResult.getResponse().getContentAsString()); final Long artId = ((LocalArtifact) softwareManagement.findSoftwareModuleWithDetails(sm.getId()).getArtifacts() .get(0)).getId(); - assertThat(artResult.getArtifactId()).isEqualTo(artId); + assertThat(artResult.getArtifactId()).as("Wrong artifact id").isEqualTo(artId); assertThat(JsonPath.compile("$_links.self.href").read(mvcResult.getResponse().getContentAsString()).toString()) + .as("Link contains no self url") .isEqualTo("http://localhost/rest/v1/softwaremodules/" + sm.getId() + "/artifacts/" + artId); assertThat( JsonPath.compile("$_links.download.href").read(mvcResult.getResponse().getContentAsString()).toString()) - .isEqualTo("http://localhost/rest/v1/softwaremodules/" + sm.getId() + "/artifacts/" + artId - + "/download"); + .as("response contains no download url ").isEqualTo("http://localhost/rest/v1/softwaremodules/" + + sm.getId() + "/artifacts/" + artId + "/download"); + assertArtifact(sm, random); + } + + private void assertArtifact(final SoftwareModule sm, final byte[] random) throws IOException { // check result in db... // repo - assertThat(artifactRepository.findAll()).hasSize(1); + assertThat(artifactRepository.findAll()).as("Wrong artifact size").hasSize(1); // binary - assertTrue(IOUtils.contentEquals(new ByteArrayInputStream(random), - artifactManagement - .loadLocalArtifactBinary((LocalArtifact) softwareManagement - .findSoftwareModuleWithDetails(sm.getId()).getArtifacts().get(0)) - .getFileInputStream())); + assertTrue("Wrong artifact content", + IOUtils.contentEquals(new ByteArrayInputStream(random), + artifactManagement + .loadLocalArtifactBinary((LocalArtifact) softwareManagement + .findSoftwareModuleWithDetails(sm.getId()).getArtifacts().get(0)) + .getFileInputStream())); // hashes assertThat(artifactManagement.findLocalArtifactByFilename("origFilename").get(0).getSha1Hash()) - .isEqualTo(HashGeneratorUtils.generateSHA1(random)); + .as("Wrong sha1 hash").isEqualTo(HashGeneratorUtils.generateSHA1(random)); assertThat(artifactManagement.findLocalArtifactByFilename("origFilename").get(0).getMd5Hash()) - .isEqualTo(HashGeneratorUtils.generateMD5(random)); + .as("Wrong md5 hash").isEqualTo(HashGeneratorUtils.generateMD5(random)); // metadata assertThat(((LocalArtifact) softwareManagement.findSoftwareModuleWithDetails(sm.getId()).getArtifacts().get(0)) - .getFilename()).isEqualTo("origFilename"); - + .getFilename()).as("wrong metadata of the filename").isEqualTo("origFilename"); } @Test @@ -203,9 +202,6 @@ public class SoftwareModuleResourceTest extends AbstractIntegrationTestWithMongo @Test @Description("Verfies that the system does not accept identical artifacts uploads for the same software module. Expected response: CONFLICT") public void duplicateUploadArtifact() throws Exception { - assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0); - assertThat(artifactRepository.findAll()).hasSize(0); - SoftwareModule sm = new SoftwareModule(osType, "name 1", "version 1", null, null); sm = softwareManagement.createSoftwareModule(sm); @@ -228,9 +224,6 @@ public class SoftwareModuleResourceTest extends AbstractIntegrationTestWithMongo @Test @Description("verfies that option to upload artifacts with a custom defined by metadata, i.e. not the file name of the binary itself.") public void uploadArtifactWithCustomName() throws Exception { - // prepare repo - assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0); - assertThat(artifactRepository.findAll()).hasSize(0); SoftwareModule sm = new SoftwareModule(osType, "name 1", "version 1", null, null); sm = softwareManagement.createSoftwareModule(sm); assertThat(artifactRepository.findAll()).hasSize(0); @@ -245,22 +238,19 @@ public class SoftwareModuleResourceTest extends AbstractIntegrationTestWithMongo .andDo(MockMvcResultPrinter.print()).andExpect(status().isCreated()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$providedFilename", equalTo("customFilename"))).andExpect(status().isCreated()); - ; // check result in db... // repo - assertThat(artifactRepository.findAll()).hasSize(1); + assertThat(artifactRepository.findAll()).as("Artifact size is wring").hasSize(1); // hashes - assertThat(artifactManagement.findLocalArtifactByFilename("customFilename")).hasSize(1); + assertThat(artifactManagement.findLocalArtifactByFilename("customFilename")).as("Local artifact is wrong") + .hasSize(1); } @Test @Description("Verfies that the system refuses upload of an artifact where the provided hash sums do not match. Expected result: BAD REQUEST") public void uploadArtifactWithHashCheck() throws Exception { - // prepare repo - assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0); - assertThat(artifactRepository.findAll()).hasSize(0); SoftwareModule sm = new SoftwareModule(osType, "name 1", "version 1", null, null); sm = softwareManagement.createSoftwareModule(sm); assertThat(artifactRepository.findAll()).hasSize(0); @@ -280,7 +270,8 @@ public class SoftwareModuleResourceTest extends AbstractIntegrationTestWithMongo // check error result ExceptionInfo exceptionInfo = ResourceUtility.convertException(mvcResult.getResponse().getContentAsString()); - assertThat(exceptionInfo.getErrorCode()).isEqualTo(SpServerError.SP_ARTIFACT_UPLOAD_FAILED_SHA1_MATCH.getKey()); + assertThat(exceptionInfo.getErrorCode()).as("Exception contains wrong error code") + .isEqualTo(SpServerError.SP_ARTIFACT_UPLOAD_FAILED_SHA1_MATCH.getKey()); // wrong md5 mvcResult = mvc @@ -290,42 +281,20 @@ public class SoftwareModuleResourceTest extends AbstractIntegrationTestWithMongo // check error result exceptionInfo = ResourceUtility.convertException(mvcResult.getResponse().getContentAsString()); - assertThat(exceptionInfo.getErrorCode()).isEqualTo(SpServerError.SP_ARTIFACT_UPLOAD_FAILED_MD5_MATCH.getKey()); + assertThat(exceptionInfo.getErrorCode()).as("Exception contains wrong error code") + .isEqualTo(SpServerError.SP_ARTIFACT_UPLOAD_FAILED_MD5_MATCH.getKey()); mvc.perform(fileUpload("/rest/v1/softwaremodules/{smId}/artifacts", sm.getId()).file(file) .param("md5sum", md5sum).param("sha1sum", sha1sum)).andDo(MockMvcResultPrinter.print()) .andExpect(status().isCreated()); - // check result... - // repo - assertThat(artifactRepository.findAll()).hasSize(1); - - // binary - assertTrue(IOUtils.contentEquals(new ByteArrayInputStream(random), - artifactManagement - .loadLocalArtifactBinary((LocalArtifact) softwareManagement - .findSoftwareModuleWithDetails(sm.getId()).getArtifacts().get(0)) - .getFileInputStream())); - - // hashes - assertThat(artifactManagement.findLocalArtifactByFilename("origFilename").get(0).getSha1Hash()) - .isEqualTo(HashGeneratorUtils.generateSHA1(random)); - - assertThat(artifactManagement.findLocalArtifactByFilename("origFilename").get(0).getMd5Hash()) - .isEqualTo(md5sum); - - // metadata - assertThat(((LocalArtifact) softwareManagement.findSoftwareModuleWithDetails(sm.getId()).getArtifacts().get(0)) - .getFilename()).isEqualTo("origFilename"); + assertArtifact(sm, random); } @Test @Description("Tests binary download of an artifact including verfication that the downloaded binary is consistent and that the etag header is as expected identical to the SHA1 hash of the file.") public void downloadArtifact() throws Exception { - assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0); - assertThat(artifactRepository.findAll()).hasSize(0); - SoftwareModule sm = new SoftwareModule(osType, "name 1", "version 1", null, null); sm = softwareManagement.createSoftwareModule(sm); @@ -342,7 +311,7 @@ public class SoftwareModuleResourceTest extends AbstractIntegrationTestWithMongo .andExpect(header().string("ETag", artifact.getSha1Hash())) .andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM)).andReturn(); - assertTrue(Arrays.equals(result.getResponse().getContentAsByteArray(), random)); + assertTrue("Wrong response content", Arrays.equals(result.getResponse().getContentAsByteArray(), random)); final MvcResult result2 = mvc .perform(get("/rest/v1/softwaremodules/{smId}/artifacts/{artId}/download", sm.getId(), @@ -350,19 +319,16 @@ public class SoftwareModuleResourceTest extends AbstractIntegrationTestWithMongo .andExpect(header().string("ETag", artifact2.getSha1Hash())) .andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM)).andReturn(); - assertTrue(Arrays.equals(result2.getResponse().getContentAsByteArray(), random)); + assertTrue("Response has wrong response content", + Arrays.equals(result2.getResponse().getContentAsByteArray(), random)); - assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(1); - assertThat(artifactRepository.findAll()).hasSize(2); + assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).as("Softwaremodule size is wrong").hasSize(1); + assertThat(artifactRepository.findAll()).as("Wrong artifact repostiory").hasSize(2); } @Test @Description("Verifies the listing of one defined artifact assigned to a given software module. That includes the artifact metadata and download links.") public void getArtifact() throws Exception { - // check baseline - assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0); - assertThat(artifactRepository.findAll()).hasSize(0); - // prepare data for test SoftwareModule sm = new SoftwareModule(osType, "name 1", "version 1", null, null); sm = softwareManagement.createSoftwareModule(sm); @@ -548,8 +514,6 @@ public class SoftwareModuleResourceTest extends AbstractIntegrationTestWithMongo @WithUser(principal = "uploadTester", allSpPermissions = true) @Description("Test retrieval of all software modules the user has access to.") public void getSoftwareModules() throws Exception { - assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0); - SoftwareModule os = new SoftwareModule(osType, "name1", "version1", "description1", "vendor1"); os = softwareManagement.createSoftwareModule(os); @@ -612,14 +576,12 @@ public class SoftwareModuleResourceTest extends AbstractIntegrationTestWithMongo .andExpect(jsonPath("$content.[?(@.id==" + ah.getId() + ")][0]._links.self.href", equalTo("http://localhost/rest/v1/softwaremodules/" + ah.getId()))); - assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(3); + assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).as("Softwaremodule size is wrong").hasSize(3); } @Test @Description("Test the various filter parameters, e.g. filter by name or type of the module.") public void getSoftwareModulesWithFilterParameters() throws Exception { - assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0); - SoftwareModule os1 = new SoftwareModule(osType, "osName1", "1.0.0", "description1", "vendor1"); os1 = softwareManagement.createSoftwareModule(os1); @@ -712,8 +674,6 @@ public class SoftwareModuleResourceTest extends AbstractIntegrationTestWithMongo @WithUser(principal = "uploadTester", allSpPermissions = true) @Description("Tests GET request on /rest/v1/softwaremodules/{smId}.") public void getSoftareModule() throws Exception { - assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0); - SoftwareModule os = new SoftwareModule(osType, "name1", "version1", "description1", "vendor1"); os = softwareManagement.createSoftwareModule(os); @@ -771,15 +731,13 @@ public class SoftwareModuleResourceTest extends AbstractIntegrationTestWithMongo .andExpect(jsonPath("$_links.artifacts.href", equalTo("http://localhost/rest/v1/softwaremodules/" + ah.getId() + "/artifacts"))); - assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(3); + assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).as("Softwaremodule size is wrong").hasSize(3); } @Test @WithUser(principal = "uploadTester", allSpPermissions = true) @Description("Verfies that the create request actually results in the creation of the modules in the repository.") public void createSoftwareModules() throws JSONException, Exception { - assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(0); - final SoftwareModule os = new SoftwareModule(osType, "name1", "version1", "description1", "vendor1"); final SoftwareModule jvm = new SoftwareModule(runtimeType, "name2", "version1", "description1", "vendor1"); final SoftwareModule ah = new SoftwareModule(appType, "name3", "version1", "description1", "vendor1"); @@ -824,74 +782,75 @@ public class SoftwareModuleResourceTest extends AbstractIntegrationTestWithMongo assertThat( JsonPath.compile("[0]_links.self.href").read(mvcResult.getResponse().getContentAsString()).toString()) + .as("Response contains invalid self href") .isEqualTo("http://localhost/rest/v1/softwaremodules/" + osCreated.getId()); assertThat(JsonPath.compile("[0]_links.artifacts.href").read(mvcResult.getResponse().getContentAsString()) - .toString()).isEqualTo("http://localhost/rest/v1/softwaremodules/" + osCreated.getId() + "/artifacts"); + .toString()).as("Response contains invalid artifacts href") + .isEqualTo("http://localhost/rest/v1/softwaremodules/" + osCreated.getId() + "/artifacts"); assertThat( JsonPath.compile("[1]_links.self.href").read(mvcResult.getResponse().getContentAsString()).toString()) + .as("Response contains invalid self href") .isEqualTo("http://localhost/rest/v1/softwaremodules/" + jvmCreated.getId()); assertThat(JsonPath.compile("[1]_links.artifacts.href").read(mvcResult.getResponse().getContentAsString()) - .toString()).isEqualTo("http://localhost/rest/v1/softwaremodules/" + jvmCreated.getId() + "/artifacts"); + .toString()).as("Response contains invalid artfacts href") + .isEqualTo("http://localhost/rest/v1/softwaremodules/" + jvmCreated.getId() + "/artifacts"); assertThat( JsonPath.compile("[2]_links.self.href").read(mvcResult.getResponse().getContentAsString()).toString()) + .as("Response contains links self href") .isEqualTo("http://localhost/rest/v1/softwaremodules/" + ahCreated.getId()); assertThat(JsonPath.compile("[2]_links.artifacts.href").read(mvcResult.getResponse().getContentAsString()) - .toString()).isEqualTo("http://localhost/rest/v1/softwaremodules/" + ahCreated.getId() + "/artifacts"); + .toString()).as("Response contains invalid artifacts href") + .isEqualTo("http://localhost/rest/v1/softwaremodules/" + ahCreated.getId() + "/artifacts"); - assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(3); + assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).as("Wrong softwaremodule size").hasSize(3); assertThat(softwareManagement.findSoftwareModulesByType(pageReq, osType).getContent().get(0).getName()) - .isEqualTo(os.getName()); + .as("Softwaremoudle name is wrong").isEqualTo(os.getName()); assertThat(softwareManagement.findSoftwareModulesByType(pageReq, osType).getContent().get(0).getCreatedBy()) - .isEqualTo("uploadTester"); + .as("Softwaremoudle created by is wrong").isEqualTo("uploadTester"); assertThat(softwareManagement.findSoftwareModulesByType(pageReq, osType).getContent().get(0).getCreatedAt()) - .isGreaterThanOrEqualTo(current); + .as("Softwaremoudle created at is wrong").isGreaterThanOrEqualTo(current); assertThat(softwareManagement.findSoftwareModulesByType(pageReq, runtimeType).getContent().get(0).getName()) - .isEqualTo(jvm.getName()); + .as("Softwaremoudle name is wrong").isEqualTo(jvm.getName()); assertThat(softwareManagement.findSoftwareModulesByType(pageReq, appType).getContent().get(0).getName()) - .isEqualTo(ah.getName()); + .as("Softwaremoudle name is wrong").isEqualTo(ah.getName()); } @Test @Description("Verifies successfull deletion of software modules that are not in use, i.e. assigned to a DS.") public void deleteUnassignedSoftwareModule() throws Exception { - assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).isEmpty(); - assertThat(artifactRepository.findAll()).isEmpty(); SoftwareModule sm = new SoftwareModule(osType, "name 1", "version 1", null, null); sm = softwareManagement.createSoftwareModule(sm); final byte random[] = RandomStringUtils.random(5 * 1024).getBytes(); - final Artifact artifact = artifactManagement.createLocalArtifact(new ByteArrayInputStream(random), sm.getId(), - "file1", false); + artifactManagement.createLocalArtifact(new ByteArrayInputStream(random), sm.getId(), "file1", false); - assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(1); - assertThat(artifactRepository.findAll()).hasSize(1); - assertThat(softwareModuleRepository.findAll()).hasSize(1); + assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).as("Softwaremoudle size is wrong").hasSize(1); + assertThat(artifactRepository.findAll()).as("artifact site is wrong").hasSize(1); + assertThat(softwareModuleRepository.findAll()).as("Softwaremoudle size is wrong").hasSize(1); mvc.perform(delete("/rest/v1/softwaremodules/{smId}", sm.getId())).andDo(MockMvcResultPrinter.print()) .andExpect(status().isOk()); - assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).isEmpty(); - assertThat(softwareModuleRepository.findAll()).isEmpty(); - assertThat(artifactRepository.findAll()).isEmpty(); + assertThat(softwareManagement.findSoftwareModulesAll(pageReq)) + .as("After delete no softwarmodule should be available").isEmpty(); + assertThat(softwareModuleRepository.findAll()).as("After delete no softwarmodule should be available") + .isEmpty(); + assertThat(artifactRepository.findAll()).as("After delete no artifact should be available").isEmpty(); } @Test @Description("Verifies successfull deletion of software modules that are in use, i.e. assigned to a DS which should result in movinf the module to the archive.") public void deleteAssignedSoftwareModule() throws Exception { - // check baseline - assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).isEmpty(); - assertThat(artifactRepository.findAll()).isEmpty(); - final DistributionSet ds1 = TestDataUtil.generateDistributionSet("a", softwareManagement, distributionSetManagement); final byte random[] = RandomStringUtils.random(5 * 1024).getBytes(); - final LocalArtifact artifact = artifactManagement.createLocalArtifact(new ByteArrayInputStream(random), + artifactManagement.createLocalArtifact(new ByteArrayInputStream(random), ds1.findFirstModuleByType(appType).getId(), "file1", false); assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(3); @@ -906,17 +865,17 @@ public class SoftwareModuleResourceTest extends AbstractIntegrationTestWithMongo .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); // all 3 are now marked as deleted - assertThat(softwareManagement.findSoftwareModulesAll(pageReq).getNumber()).isEqualTo(0); - assertThat(softwareModuleRepository.findAll()).hasSize(3); - assertThat(artifactRepository.findAll()).hasSize(1); + assertThat(softwareManagement.findSoftwareModulesAll(pageReq).getNumber()) + .as("After delete no softwarmodule should be available").isEqualTo(0); + assertThat(softwareModuleRepository.findAll()).as("After delete no softwarmodule should marked as deleted") + .hasSize(3); + assertThat(artifactRepository.findAll()).as("After delete artifact should available for marked as deleted sm's") + .hasSize(1); } @Test @Description("Tests the deletion of an artifact including verfication that the artifact is actually erased in the repository and removed from the software module.") public void deleteArtifact() throws Exception { - assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).isEmpty(); - assertThat(artifactRepository.findAll()).isEmpty(); - // Create 1 SM SoftwareModule sm = new SoftwareModule(osType, "name 1", "version 1", null, null); sm = softwareManagement.createSoftwareModule(sm); @@ -926,8 +885,7 @@ public class SoftwareModuleResourceTest extends AbstractIntegrationTestWithMongo // Create 2 artifacts final LocalArtifact artifact = artifactManagement.createLocalArtifact(new ByteArrayInputStream(random), sm.getId(), "file1", false); - final LocalArtifact artifact2 = artifactManagement.createLocalArtifact(new ByteArrayInputStream(random), - sm.getId(), "file2", false); + artifactManagement.createLocalArtifact(new ByteArrayInputStream(random), sm.getId(), "file2", false); // check repo before delete assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(1); @@ -940,9 +898,12 @@ public class SoftwareModuleResourceTest extends AbstractIntegrationTestWithMongo .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); // check that only one artifact is still alive and still assigned - assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).hasSize(1); - assertThat(artifactRepository.findAll()).hasSize(1); - assertThat(softwareManagement.findSoftwareModuleWithDetails(sm.getId()).getArtifacts()).hasSize(1); + assertThat(softwareManagement.findSoftwareModulesAll(pageReq)).as("After the sm should be marked as deleted") + .hasSize(1); + assertThat(artifactRepository.findAll()).as("After delete artifact should available for marked as deleted sm's") + .hasSize(1); + assertThat(softwareManagement.findSoftwareModuleWithDetails(sm.getId()).getArtifacts()) + .as("After delete artifact should available for marked as deleted sm's").hasSize(1); } @@ -972,8 +933,8 @@ public class SoftwareModuleResourceTest extends AbstractIntegrationTestWithMongo final SoftwareModuleMetadata metaKey1 = softwareManagement.findOne(new SwMetadataCompositeKey(sm, knownKey1)); final SoftwareModuleMetadata metaKey2 = softwareManagement.findOne(new SwMetadataCompositeKey(sm, knownKey2)); - assertThat(metaKey1.getValue()).isEqualTo(knownValue1); - assertThat(metaKey2.getValue()).isEqualTo(knownValue2); + assertThat(metaKey1.getValue()).as("Metadata key is wrong").isEqualTo(knownValue1); + assertThat(metaKey2.getValue()).as("Metadata key is wrong").isEqualTo(knownValue2); } @Test @@ -997,7 +958,7 @@ public class SoftwareModuleResourceTest extends AbstractIntegrationTestWithMongo .andExpect(jsonPath("key", equalTo(knownKey))).andExpect(jsonPath("value", equalTo(updateValue))); final SoftwareModuleMetadata assertDS = softwareManagement.findOne(new SwMetadataCompositeKey(sm, knownKey)); - assertThat(assertDS.getValue()).isEqualTo(updateValue); + assertThat(assertDS.getValue()).as("Metadata is wrong").isEqualTo(updateValue); } @Test diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SoftwareModuleTypeResourceTest.java b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SoftwareModuleTypeResourceTest.java index c6aedddd4..a85e25bed 100644 --- a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SoftwareModuleTypeResourceTest.java +++ b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SoftwareModuleTypeResourceTest.java @@ -43,11 +43,8 @@ import ru.yandex.qatools.allure.annotations.Stories; /** * Test for {@link SoftwareModuleTypeResource}. * - * - * - * */ -@Features("Component Tests - Management RESTful API") +@Features("Component Tests - Management API") @Stories("Software Module Type Resource") public class SoftwareModuleTypeResourceTest extends AbstractIntegrationTest { @@ -93,6 +90,46 @@ public class SoftwareModuleTypeResourceTest extends AbstractIntegrationTest { .andExpect(jsonPath("$total", equalTo(4))); } + @Test + @WithUser(principal = "uploadTester", allSpPermissions = true) + @Description("Checks the correct behaviour of /rest/v1/softwaremoduletypes GET requests with sorting by MAXASSIGNMENTS field.") + public void getSoftwareModuleTypesSortedByMaxAssignments() throws Exception { + SoftwareModuleType testType = softwareManagement + .createSoftwareModuleType(new SoftwareModuleType("test123", "TestName123", "Desc123", 5)); + testType.setDescription("Desc1234"); + testType = softwareManagement.updateSoftwareModuleType(testType); + + // descending + mvc.perform(get("/rest/v1/softwaremoduletypes").accept(MediaType.APPLICATION_JSON) + .param(RestConstants.REQUEST_PARAMETER_SORTING, "MAXASSIGNMENTS:DESC")) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content.[0].id", equalTo(testType.getId().intValue()))) + .andExpect(jsonPath("$content.[0].name", equalTo("TestName123"))) + .andExpect(jsonPath("$content.[0].description", equalTo("Desc1234"))) + .andExpect(jsonPath("$content.[0].createdBy", equalTo("uploadTester"))) + .andExpect(jsonPath("$content.[0].createdAt", equalTo(testType.getCreatedAt()))) + .andExpect(jsonPath("$content.[0].lastModifiedBy", equalTo("uploadTester"))) + .andExpect(jsonPath("$content.[0].lastModifiedAt", equalTo(testType.getLastModifiedAt()))) + .andExpect(jsonPath("$content.[0].maxAssignments", equalTo(5))) + .andExpect(jsonPath("$content.[0].key", equalTo("test123"))).andExpect(jsonPath("$total", equalTo(4))); + + // ascending + mvc.perform(get("/rest/v1/softwaremoduletypes").accept(MediaType.APPLICATION_JSON) + .param(RestConstants.REQUEST_PARAMETER_SORTING, "MAXASSIGNMENTS:ASC")) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$content.[3].id", equalTo(testType.getId().intValue()))) + .andExpect(jsonPath("$content.[3].name", equalTo("TestName123"))) + .andExpect(jsonPath("$content.[3].description", equalTo("Desc1234"))) + .andExpect(jsonPath("$content.[3].createdBy", equalTo("uploadTester"))) + .andExpect(jsonPath("$content.[3].createdAt", equalTo(testType.getCreatedAt()))) + .andExpect(jsonPath("$content.[3].lastModifiedBy", equalTo("uploadTester"))) + .andExpect(jsonPath("$content.[3].lastModifiedAt", equalTo(testType.getLastModifiedAt()))) + .andExpect(jsonPath("$content.[3].maxAssignments", equalTo(5))) + .andExpect(jsonPath("$content.[3].key", equalTo("test123"))).andExpect(jsonPath("$total", equalTo(4))); + } + @Test @WithUser(principal = "uploadTester", allSpPermissions = true) @Description("Checks the correct behaviour of /rest/v1/softwaremoduletypes POST requests.") diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SortUtilityTest.java b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SortUtilityTest.java index 7ca7814b2..09a90c39c 100644 --- a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SortUtilityTest.java +++ b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SortUtilityTest.java @@ -8,7 +8,8 @@ */ package org.eclipse.hawkbit.rest.resource; -import static org.junit.Assert.assertEquals; +import static org.fest.assertions.api.Assertions.assertThat; +import static org.junit.Assert.fail; import java.util.List; @@ -16,9 +17,15 @@ import org.eclipse.hawkbit.repository.TargetFields; import org.junit.Test; import org.springframework.data.domain.Sort.Order; +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + /** * */ +@Features("Component Tests - Management API") +@Stories("Sorting parameter") public class SortUtilityTest { private static final String SORT_PARAM_1 = "NAME:ASC"; private static final String SORT_PARAM_2 = "NAME:ASC, DESCRIPTION:DESC"; @@ -29,36 +36,55 @@ public class SortUtilityTest { private static final String WRONG_FIELD_PARAM = "ASDF:ASC"; @Test + @Description("Ascending sorting based on name.") public void parseSortParam1() { - final List parse = SortUtility.parse(TargetFields.class, SORT_PARAM_1); - assertEquals(1, parse.size()); + assertThat(1).as("Count of parsing parameter").isEqualTo(parse.size()); } @Test + @Description("Ascending sorting based on name and descending sorting based on description.") public void parseSortParam2() { final List parse = SortUtility.parse(TargetFields.class, SORT_PARAM_2); - assertEquals(2, parse.size()); - } - - @Test(expected = SortParameterSyntaxErrorException.class) - public void parseWrongSyntaxParam() { - SortUtility.parse(TargetFields.class, SYNTAX_FAILURE_SORT_PARAM); + assertThat(2).as("Count of parsing parameter").isEqualTo(parse.size()); } @Test + @Description("Sorting with wrong syntax leads to SortParameterSyntaxErrorException.") + public void parseWrongSyntaxParam() { + try { + SortUtility.parse(TargetFields.class, SYNTAX_FAILURE_SORT_PARAM); + fail("SortParameterSyntaxErrorException expected because of wrong syntax"); + } catch (final SortParameterSyntaxErrorException e) { + } + } + + @Test + @Description("Sorting based on name with case sensitive is possible.") public void parsingIsNotCaseSensitive() { SortUtility.parse(TargetFields.class, CASE_INSENSITIVE_DIRECTION_PARAM); SortUtility.parse(TargetFields.class, CASE_INSENSITIVE_DIRECTION_PARAM_1); } - @Test(expected = SortParameterUnsupportedDirectionException.class) + @Test + @Description("Sorting with unknown direction order leads to SortParameterUnsupportedDirectionException.") public void parseWrongDirectionParam() { - SortUtility.parse(TargetFields.class, WRONG_DIRECTION_PARAM); + try { + SortUtility.parse(TargetFields.class, WRONG_DIRECTION_PARAM); + fail("SortParameterUnsupportedDirectionException expected because of unknown direction order"); + } catch (final SortParameterUnsupportedDirectionException e) { + } + } - @Test(expected = SortParameterUnsupportedFieldException.class) + @Test + @Description("Sorting with unknown field leads to SortParameterUnsupportedFieldException.") public void parseWrongFieldParam() { - SortUtility.parse(TargetFields.class, WRONG_FIELD_PARAM); + try { + SortUtility.parse(TargetFields.class, WRONG_FIELD_PARAM); + fail("SortParameterUnsupportedFieldException expected because of unknown field"); + } catch (final SortParameterUnsupportedFieldException e) { + } + } } diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SuccessCondition.java b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SuccessCondition.java new file mode 100644 index 000000000..be6e166ef --- /dev/null +++ b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SuccessCondition.java @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.rest.resource; + +/** + * + * @author Dennis Melzer + * + * @param + */ +public interface SuccessCondition { + + /** + * + * @param result + * @return + */ + boolean success(final T result); + +} diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SystemManagementResourceTest.java b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SystemManagementResourceTest.java index 8d8f9a2e4..76cd68d64 100644 --- a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SystemManagementResourceTest.java +++ b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/SystemManagementResourceTest.java @@ -40,7 +40,7 @@ import ru.yandex.qatools.allure.annotations.Stories; * * */ -@Features("Component Tests - System Management RESTful API") +@Features("Component Tests - Management API") @Stories("System Management Resource") public class SystemManagementResourceTest extends AbstractIntegrationTestWithMongoDB { diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/TargetResourceTest.java b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/TargetResourceTest.java index 9db578fe4..a8ebfdd9d 100644 --- a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/TargetResourceTest.java +++ b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/TargetResourceTest.java @@ -11,6 +11,7 @@ package org.eclipse.hawkbit.rest.resource; import static org.fest.assertions.api.Assertions.assertThat; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasKey; import static org.hamcrest.Matchers.hasSize; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; @@ -34,6 +35,7 @@ import org.eclipse.hawkbit.TestDataUtil; import org.eclipse.hawkbit.WithUser; import org.eclipse.hawkbit.exception.SpServerError; import org.eclipse.hawkbit.im.authentication.SpPermission; +import org.eclipse.hawkbit.repository.ActionFields; import org.eclipse.hawkbit.repository.ActionStatusFields; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.model.Action; @@ -66,16 +68,11 @@ import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Stories; /** - * * Spring MVC Tests against the TargetResource. * - * - * */ -@Features("Component Tests - Management RESTful API") +@Features("Component Tests - Management API") @Stories("Target Resource") -// TODO: fully document tests -> @Description for long text and reasonable -// method name as short text public class TargetResourceTest extends AbstractIntegrationTest { private static final String TARGET_DESCRIPTION_TEST = "created in test"; @@ -103,10 +100,8 @@ public class TargetResourceTest extends AbstractIntegrationTest { private static final String JSON_PATH_CONTROLLERID = JSON_PATH_ROOT + JSON_PATH_FIELD_CONTROLLERID; private static final String JSON_PATH_DESCRIPTION = JSON_PATH_ROOT + JSON_PATH_FIELD_DESCRIPTION; - // TODO kzimmerm: test *modified after entity change - @Test - // MECS-1064 + @Description("Ensures that actions list is in exptected order.") public void getActionStatusReturnsCorrectType() throws Exception { final int limitSize = 2; final String knownTargetId = "targetId"; @@ -116,31 +111,29 @@ public class TargetResourceTest extends AbstractIntegrationTest { new ActionStatus(actions.get(0), Status.FINISHED, System.currentTimeMillis(), "testmessage"), actions.get(0)); - final PageRequest pageRequest = new PageRequest(0, 1000, Direction.ASC, ActionStatusFields.ID.getFieldName()); + final PageRequest pageRequest = new PageRequest(0, 1000, Direction.ASC, ActionFields.ID.getFieldName()); + final ActionStatus status = deploymentManagement + .findActionsByTarget(pageRequest, targetManagement.findTargetByControllerID(knownTargetId)).getContent() + .get(0).getActionStatus().stream().sorted((e1, e2) -> Long.compare(e2.getId(), e1.getId())) + .collect(Collectors.toList()).get(0); - // limit to 1 - first page -> standard cancel message - final Long reportAt = deploymentManagement - .findActionsByTarget(pageRequest, targetManagement.findTargetByControllerID(knownTargetId)).getContent() - .get(0).getCreatedAt(); - final Long id = deploymentManagement - .findActionsByTarget(pageRequest, targetManagement.findTargetByControllerID(knownTargetId)).getContent() - .get(0).getId(); mvc.perform(get(RestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/" + RestConstants.TARGET_V1_ACTIONS + "/" + actions.get(0).getId() + "/status") .param(RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, String.valueOf(limitSize)) - .param(RestConstants.REQUEST_PARAMETER_SORTING, "ID:ASC")) + .param(RestConstants.REQUEST_PARAMETER_SORTING, "ID:DESC")) .andExpect(status().isOk()).andDo(MockMvcResultPrinter.print()) .andExpect(jsonPath(JSON_PATH_PAGED_LIST_TOTAL, equalTo(3))) .andExpect(jsonPath(JSON_PATH_PAGED_LIST_SIZE, equalTo(limitSize))) .andExpect(jsonPath(JSON_PATH_PAGED_LIST_CONTENT, hasSize(limitSize))) - .andExpect(jsonPath("content.[0].id", equalTo(id.intValue()))) + .andExpect(jsonPath("content.[0].id", equalTo(status.getId().intValue()))) .andExpect(jsonPath("content.[0].type", equalTo("finished"))) .andExpect(jsonPath("content.[0].messages", hasSize(1))) - .andExpect(jsonPath("content.[0].reportedAt", equalTo(reportAt))) + .andExpect(jsonPath("content.[0].reportedAt", equalTo(status.getCreatedAt().longValue()))) .andExpect(jsonPath("content.[1].type", equalTo("canceling"))); } @Test + @Description("Ensures that security token is not returned if user does not have READ_TARGET_SEC_TOKEN permission.") @WithUser(allSpPermissions = false, authorities = { SpPermission.READ_TARGET, SpPermission.CREATE_TARGET }) public void securityTokenIsNotInResponseIfMissingPermission() throws Exception { @@ -152,6 +145,7 @@ public class TargetResourceTest extends AbstractIntegrationTest { } @Test + @Description("Ensures that security token is returned if user does have READ_TARGET_SEC_TOKEN permission.") @WithUser(allSpPermissions = false, authorities = { SpPermission.READ_TARGET, SpPermission.CREATE_TARGET, SpPermission.READ_TARGET_SEC_TOKEN }) public void securityTokenIsInResponseWithCorrectPermission() throws Exception { @@ -164,6 +158,7 @@ public class TargetResourceTest extends AbstractIntegrationTest { } @Test + @Description("Ensures that that IP address is in result as stored in the repository.") public void addressAndIpAddressInTargetResult() throws Exception { // prepare targets with IP final String knownControllerId1 = "0815"; @@ -195,6 +190,7 @@ public class TargetResourceTest extends AbstractIntegrationTest { } @Test + @Description("Ensures that actions history is returned as defined by filter status==pending,status==finished.") public void searchActionsRsql() throws Exception { // prepare test @@ -227,9 +223,10 @@ public class TargetResourceTest extends AbstractIntegrationTest { } @Test + @Description("Ensures that a deletion of an active action results in cancelation triggered.") public void cancelActionOK() throws Exception { // prepare test - Target tA = createTargetAndStartAction(); + final Target tA = createTargetAndStartAction(); // test - cancel the active action mvc.perform(delete(RestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/actions/{actionId}", @@ -250,9 +247,10 @@ public class TargetResourceTest extends AbstractIntegrationTest { } @Test - public void cancelAnCancelActionIsNotAllowed() throws Exception { + @Description("Ensures that method not allowed is returned if cancelation is triggered on already canceled action.") + public void cancelAndCancelActionIsNotAllowed() throws Exception { // prepare test - Target tA = createTargetAndStartAction(); + final Target tA = createTargetAndStartAction(); // cancel the active action deploymentManagement.cancelAction(tA.getActions().get(0), tA); @@ -272,7 +270,7 @@ public class TargetResourceTest extends AbstractIntegrationTest { @Description("Force Quit an Action, which is already canceled. Expected Result is an HTTP response code 204.") public void forceQuitAnCanceledActionReturnsOk() throws Exception { - Target tA = createTargetAndStartAction(); + final Target tA = createTargetAndStartAction(); // cancel the active action deploymentManagement.cancelAction(tA.getActions().get(0), tA); @@ -293,7 +291,7 @@ public class TargetResourceTest extends AbstractIntegrationTest { @Description("Force Quit an Action, which is not canceled. Expected Result is an HTTP response code 405.") public void forceQuitAnNotCanceledActionReturnsMethodNotAllowed() throws Exception { - Target tA = createTargetAndStartAction(); + final Target tA = createTargetAndStartAction(); // test - cancel an cancel action returns forbidden mvc.perform(delete(RestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/actions/{actionId}?force=true", @@ -302,6 +300,7 @@ public class TargetResourceTest extends AbstractIntegrationTest { } @Test + @Description("Ensures that deletion is executed if permitted.") public void deleteTargetReturnsOK() throws Exception { final String knownControllerId = "knownControllerIdDelete"; targetManagement.createTarget(new Target(knownControllerId)); @@ -314,6 +313,7 @@ public class TargetResourceTest extends AbstractIntegrationTest { } @Test + @Description("Ensures that deletion is refused with not found if target does not exist.") public void deleteTargetWhichDoesNotExistsLeadsToEntityNotFound() throws Exception { final String knownControllerId = "knownControllerIdDelete"; @@ -322,6 +322,7 @@ public class TargetResourceTest extends AbstractIntegrationTest { } @Test + @Description("Ensures that update is refused with not found if target does not exist.") public void updateTargetWhichDoesNotExistsLeadsToEntityNotFound() throws Exception { final String knownControllerId = "knownControllerIdUpdate"; mvc.perform(put(RestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownControllerId).content("{}") @@ -330,6 +331,7 @@ public class TargetResourceTest extends AbstractIntegrationTest { } @Test + @Description("Ensures that target update request is reflected by repository.") public void updateTargetDescription() throws Exception { final String knownControllerId = "123"; final String knownNewDescription = "a new desc updated over rest"; @@ -354,6 +356,7 @@ public class TargetResourceTest extends AbstractIntegrationTest { } @Test + @Description("Ensures that target query returns list of targets in defined format.") public void getTargetWithoutAddtionalRequestParameters() throws Exception { final int knownTargetAmount = 3; final String idA = "a"; @@ -393,6 +396,7 @@ public class TargetResourceTest extends AbstractIntegrationTest { } @Test + @Description("Ensures that target query returns list of targets in defined format in size reduced by given limit parameter.") public void getTargetWithPagingLimitRequestParameter() throws Exception { final int knownTargetAmount = 3; final int limitSize = 1; @@ -413,10 +417,10 @@ public class TargetResourceTest extends AbstractIntegrationTest { .andExpect(jsonPath("$content.[?(@.name==" + idA + ")][0].controllerId", equalTo(idA))) .andExpect(jsonPath("$content.[?(@.name==" + idA + ")][0].createdBy", equalTo("bumlux"))) .andExpect(jsonPath("$content.[?(@.name==" + idA + ")][0].updateStatus", equalTo("unknown"))); - } @Test + @Description("Ensures that target query returns list of targets in defined format in size reduced by given limit and offset parameter.") public void getTargetWithPagingLimitAndOffsetRequestParameter() throws Exception { final int knownTargetAmount = 5; final int offsetParam = 2; @@ -834,7 +838,8 @@ public class TargetResourceTest extends AbstractIntegrationTest { final List actions = generateTargetWithTwoUpdatesWithOneOverride(knownTargetId); mvc.perform(get( - RestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/" + RestConstants.TARGET_V1_ACTIONS)) + RestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/" + RestConstants.TARGET_V1_ACTIONS) + .param(RestConstants.REQUEST_PARAMETER_SORTING, "ID:ASC")) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(jsonPath("content.[1].id", equalTo(actions.get(1).getId().intValue()))) .andExpect(jsonPath("content.[1].type", equalTo("update"))) @@ -851,6 +856,108 @@ public class TargetResourceTest extends AbstractIntegrationTest { .andExpect(jsonPath(JSON_PATH_PAGED_LIST_CONTENT, hasSize(2))); } + @Test + @Description("Verfies that the API returns the status list with expected content.") + public void getMultipleActionStatus() throws Exception { + final String knownTargetId = "targetId"; + final Action action = generateTargetWithTwoUpdatesWithOneOverride(knownTargetId).get(0); + // retrieve list in default descending order for actionstaus entries + final List actionStatus = action.getActionStatus().stream() + .sorted((e1, e2) -> Long.compare(e2.getId(), e1.getId())).collect(Collectors.toList()); + + // sort is default descending order, latest status first + mvc.perform(get(RestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/" + + RestConstants.TARGET_V1_ACTIONS + "/" + action.getId() + "/" + RestConstants.TARGET_V1_ACTION_STATUS)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(jsonPath("content.[0].id", equalTo(actionStatus.get(0).getId().intValue()))) + .andExpect(jsonPath("content.[0].type", equalTo("canceling"))) + .andExpect(jsonPath("content.[0].messages", hasItem("manual cancelation requested"))) + .andExpect(jsonPath("content.[0].reportedAt", equalTo(actionStatus.get(0).getCreatedAt()))) + .andExpect(jsonPath("content.[1].id", equalTo(actionStatus.get(1).getId().intValue()))) + .andExpect(jsonPath("content.[1].type", equalTo("running"))) + .andExpect(jsonPath("content.[1].reportedAt", equalTo(actionStatus.get(1).getCreatedAt()))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_TOTAL, equalTo(2))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_SIZE, equalTo(2))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_CONTENT, hasSize(2))); + } + + @Test + @Description("Verfies that the API returns the status list with expected content sorted by reportedAt field.") + public void getMultipleActionStatusSortedByReportedAt() throws Exception { + final String knownTargetId = "targetId"; + final Action action = generateTargetWithTwoUpdatesWithOneOverride(knownTargetId).get(0); + final List actionStatus = action.getActionStatus().stream() + .sorted((e1, e2) -> Long.compare(e1.getId(), e2.getId())).collect(Collectors.toList()); + + // descending order + mvc.perform(get(RestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/" + + RestConstants.TARGET_V1_ACTIONS + "/" + action.getId() + "/" + RestConstants.TARGET_V1_ACTION_STATUS) + .param(RestConstants.REQUEST_PARAMETER_SORTING, "REPORTEDAT:DESC")) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(jsonPath("content.[0].id", equalTo(actionStatus.get(1).getId().intValue()))) + .andExpect(jsonPath("content.[0].type", equalTo("canceling"))) + .andExpect(jsonPath("content.[0].messages", hasItem("manual cancelation requested"))) + .andExpect(jsonPath("content.[0].reportedAt", equalTo(actionStatus.get(1).getCreatedAt()))) + .andExpect(jsonPath("content.[1].id", equalTo(actionStatus.get(0).getId().intValue()))) + .andExpect(jsonPath("content.[1].type", equalTo("running"))) + .andExpect(jsonPath("content.[1].reportedAt", equalTo(actionStatus.get(0).getCreatedAt()))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_TOTAL, equalTo(2))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_SIZE, equalTo(2))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_CONTENT, hasSize(2))); + + // ascending order + mvc.perform(get(RestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/" + + RestConstants.TARGET_V1_ACTIONS + "/" + action.getId() + "/" + RestConstants.TARGET_V1_ACTION_STATUS) + .param(RestConstants.REQUEST_PARAMETER_SORTING, "REPORTEDAT:ASC")) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(jsonPath("content.[1].id", equalTo(actionStatus.get(1).getId().intValue()))) + .andExpect(jsonPath("content.[1].type", equalTo("canceling"))) + .andExpect(jsonPath("content.[1].messages", hasItem("manual cancelation requested"))) + .andExpect(jsonPath("content.[1].reportedAt", equalTo(actionStatus.get(1).getCreatedAt()))) + .andExpect(jsonPath("content.[0].id", equalTo(actionStatus.get(0).getId().intValue()))) + .andExpect(jsonPath("content.[0].type", equalTo("running"))) + .andExpect(jsonPath("content.[0].reportedAt", equalTo(actionStatus.get(0).getCreatedAt()))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_TOTAL, equalTo(2))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_SIZE, equalTo(2))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_CONTENT, hasSize(2))); + } + + @Test + @Description("Verfies that the API returns the status list with expected content split into two pages.") + public void getMultipleActionStatusWithPagingLimitRequestParameter() throws Exception { + final String knownTargetId = "targetId"; + + final Action action = generateTargetWithTwoUpdatesWithOneOverride(knownTargetId).get(0); + final List actionStatus = action.getActionStatus().stream() + .sorted((e1, e2) -> Long.compare(e1.getId(), e2.getId())).collect(Collectors.toList()); + + // Page 1 + mvc.perform(get(RestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/" + + RestConstants.TARGET_V1_ACTIONS + "/" + action.getId() + "/" + RestConstants.TARGET_V1_ACTION_STATUS) + .param(RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, String.valueOf(1))) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(jsonPath("content.[0].id", equalTo(actionStatus.get(1).getId().intValue()))) + .andExpect(jsonPath("content.[0].type", equalTo("canceling"))) + .andExpect(jsonPath("content.[0].messages", hasItem("manual cancelation requested"))) + .andExpect(jsonPath("content.[0].reportedAt", equalTo(actionStatus.get(1).getCreatedAt()))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_TOTAL, equalTo(2))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_SIZE, equalTo(1))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_CONTENT, hasSize(1))); + + // Page 2 + mvc.perform(get(RestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/" + + RestConstants.TARGET_V1_ACTIONS + "/" + action.getId() + "/" + RestConstants.TARGET_V1_ACTION_STATUS) + .param(RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, String.valueOf(1)) + .param(RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, String.valueOf(1))) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(jsonPath("content.[0].id", equalTo(actionStatus.get(0).getId().intValue()))) + .andExpect(jsonPath("content.[0].type", equalTo("running"))) + .andExpect(jsonPath("content.[0].reportedAt", equalTo(actionStatus.get(0).getCreatedAt()))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_TOTAL, equalTo(2))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_SIZE, equalTo(1))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_CONTENT, hasSize(1))); + } + @Test public void getMultipleActionsWithPagingLimitRequestParameter() throws Exception { final String knownTargetId = "targetId"; @@ -859,7 +966,8 @@ public class TargetResourceTest extends AbstractIntegrationTest { // page 1: one entry mvc.perform(get( RestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/" + RestConstants.TARGET_V1_ACTIONS) - .param(RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, String.valueOf(1))) + .param(RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, String.valueOf(1)) + .param(RestConstants.REQUEST_PARAMETER_SORTING, "ID:ASC")) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(jsonPath("content.[0].id", equalTo(actions.get(0).getId().intValue()))) .andExpect(jsonPath("content.[0].type", equalTo("cancel"))) @@ -874,7 +982,9 @@ public class TargetResourceTest extends AbstractIntegrationTest { mvc.perform(get( RestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/" + RestConstants.TARGET_V1_ACTIONS) .param(RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, String.valueOf(1)) - .param(RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, String.valueOf(1))) + .param(RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, String.valueOf(1)) + .param(RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, String.valueOf(1)) + .param(RestConstants.REQUEST_PARAMETER_SORTING, "ID:ASC")) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andExpect(jsonPath("content.[0].id", equalTo(actions.get(1).getId().intValue()))) .andExpect(jsonPath("content.[0].type", equalTo("update"))) @@ -902,7 +1012,8 @@ public class TargetResourceTest extends AbstractIntegrationTest { + "?offset=0&limit=50&sort=id:DESC"; } - private List generateTargetWithTwoUpdatesWithOneOverride(final String knownTargetId) { + private List generateTargetWithTwoUpdatesWithOneOverride(final String knownTargetId) + throws InterruptedException { final PageRequest pageRequest = new PageRequest(0, 100, Direction.ASC, ActionStatusFields.ID.getFieldName()); @@ -920,6 +1031,8 @@ public class TargetResourceTest extends AbstractIntegrationTest { final List updatedTargets = deploymentManagement.assignDistributionSet(one, targets) .getAssignedTargets(); // 2nd update + // sleep 10ms to ensure that we can sort by reportedAt + Thread.sleep(10); deploymentManagement.assignDistributionSet(two, updatedTargets); // two updates, one cancelation @@ -946,54 +1059,6 @@ public class TargetResourceTest extends AbstractIntegrationTest { equalTo(generateStatusreferenceLink(knownTargetId, actions.get(1))))); } - @Test - public void getActionStatusWithMultipleResultsWithPagingLimitRequestParameter() throws Exception { - final int limitSize = 1; - final String knownTargetId = "targetId"; - final List actions = generateTargetWithTwoUpdatesWithOneOverride(knownTargetId); - actions.get(0).setStatus(Status.RUNNING); - controllerManagament.addUpdateActionStatus( - new ActionStatus(actions.get(0), Status.RUNNING, System.currentTimeMillis(), "testmessage"), - actions.get(0)); - - final PageRequest pageRequest = new PageRequest(0, 1000, Direction.ASC, ActionStatusFields.ID.getFieldName()); - - // limit to 1 - first page -> standard cancel message - Long reportAt = deploymentManagement - .findActionsByTarget(pageRequest, targetManagement.findTargetByControllerID(knownTargetId)).getContent() - .get(0).getCreatedAt(); - Long id = deploymentManagement - .findActionsByTarget(pageRequest, targetManagement.findTargetByControllerID(knownTargetId)).getContent() - .get(0).getId(); - mvc.perform(get(RestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/" - + RestConstants.TARGET_V1_ACTIONS + "/" + actions.get(0).getId() + "/status") - .param(RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, String.valueOf(limitSize))) - .andExpect(status().isOk()).andDo(MockMvcResultPrinter.print()) - .andExpect(jsonPath(JSON_PATH_PAGED_LIST_TOTAL, equalTo(3))) - .andExpect(jsonPath(JSON_PATH_PAGED_LIST_SIZE, equalTo(limitSize))) - .andExpect(jsonPath(JSON_PATH_PAGED_LIST_CONTENT, hasSize(limitSize))) - .andExpect(jsonPath("content.[0].id", equalTo(id.intValue()))) - .andExpect(jsonPath("content.[0].type", equalTo("running"))) - .andExpect(jsonPath("content.[0].messages", hasSize(1))) - .andExpect(jsonPath("content.[0].reportedAt", equalTo(reportAt))); - - // limit to 1 - first page -> added custom message - reportAt = deploymentManagement - .findActionsByTarget(pageRequest, targetManagement.findTargetByControllerID(knownTargetId)).getContent() - .get(1).getCreatedAt(); - id = deploymentManagement - .findActionsByTarget(pageRequest, targetManagement.findTargetByControllerID(knownTargetId)).getContent() - .get(1).getCreatedAt(); - - mvc.perform(get(RestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/" - + RestConstants.TARGET_V1_ACTIONS + "/" + actions.get(0).getId() + "/status") - .param(RestConstants.REQUEST_PARAMETER_PAGING_LIMIT, String.valueOf(limitSize)) - .param(RestConstants.REQUEST_PARAMETER_PAGING_OFFSET, String.valueOf(1))) - .andExpect(status().isOk()).andDo(MockMvcResultPrinter.print()) - .andExpect(jsonPath(JSON_PATH_PAGED_LIST_TOTAL, equalTo(3))) - .andExpect(jsonPath(JSON_PATH_PAGED_LIST_SIZE, equalTo(1))); - } - @Test public void assignDistributionSetToTarget() throws Exception { @@ -1232,7 +1297,8 @@ public class TargetResourceTest extends AbstractIntegrationTest { // prepare test final DistributionSet dsA = TestDataUtil.generateDistributionSet("", softwareManagement, distributionSetManagement); - Target tA = targetManagement.createTarget(TestDataUtil.buildTargetFixture("target-id-A", "first description")); + final Target tA = targetManagement + .createTarget(TestDataUtil.buildTargetFixture("target-id-A", "first description")); // assign a distribution set so we get an active update action deploymentManagement.assignDistributionSet(dsA, Lists.newArrayList(tA)); // verify active action diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/model/ExceptionInfoTest.java b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/model/ExceptionInfoTest.java index e9601d693..d240a814b 100644 --- a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/model/ExceptionInfoTest.java +++ b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/model/ExceptionInfoTest.java @@ -15,10 +15,17 @@ import java.util.List; import org.junit.Test; +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Unit Tests - Management API") +@Stories("Error Handling") public class ExceptionInfoTest { @Test - public void setterAndGetter() { + @Description("Ensures that setters and getters match on teh payload.") + public void setterAndGetterOnExceptionInfo() { final String knownExceptionClass = "hawkbit.test.exception.Class"; final String knownErrorCode = "hawkbit.error.code.Known"; final String knownMessage = "a known message"; @@ -32,10 +39,15 @@ public class ExceptionInfoTest { underTest.setMessage(knownMessage); underTest.setParameters(knownParameters); - assertThat(underTest.getErrorCode()).isEqualTo(knownErrorCode); - assertThat(underTest.getExceptionClass()).isEqualTo(knownExceptionClass); - assertThat(underTest.getMessage()).isEqualTo(knownMessage); - assertThat(underTest.getParameters()).isEqualTo(knownParameters); + assertThat(underTest.getErrorCode()).as("The error code should match with the known error code in the test") + .isEqualTo(knownErrorCode); + assertThat(underTest.getExceptionClass()) + .as("The exception class should match with the known error code in the test") + .isEqualTo(knownExceptionClass); + assertThat(underTest.getMessage()).as("The message should match with the known error code in the test") + .isEqualTo(knownMessage); + assertThat(underTest.getParameters()).as("The parameters should match with the known error code in the test") + .isEqualTo(knownParameters); } } diff --git a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/model/PagedListTest.java b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/model/PagedListTest.java index 0c47856dd..49e13c0b4 100644 --- a/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/model/PagedListTest.java +++ b/hawkbit-rest-resource/src/test/java/org/eclipse/hawkbit/rest/resource/model/PagedListTest.java @@ -15,36 +15,46 @@ import java.util.List; import org.junit.Test; +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Unit Tests - Management API") +@Stories("Paged List Handling") public class PagedListTest { @Test(expected = NullPointerException.class) + @Description("Ensures that a null payload entitiy throws an exception.") public void createListWithNullContentThrowsException() { new PagedList<>(null, 0); } @Test + @Description("Create list with payload and verify content.") public void createListWithContent() { final long knownTotal = 2; final List knownContentList = new ArrayList<>(); knownContentList.add("content1"); knownContentList.add("content2"); - final PagedList pagedList = new PagedList<>(knownContentList, knownTotal); + assertListSize(knownTotal, knownContentList); + } - assertThat(pagedList.getTotal()).isEqualTo(knownTotal); - assertThat(pagedList.getSize()).isEqualTo(knownContentList.size()); + private void assertListSize(final long knownTotal, final List knownContentList) { + final PagedList pagedList = new PagedList<>(knownContentList, knownTotal); + assertThat(pagedList.getTotal()).as("total size is wrong").isEqualTo(knownTotal); + assertThat(pagedList.getSize()).as("list size is wrong").isEqualTo(knownContentList.size()); } @Test + @Description("Create list with payload and verify size values.") public void createListWithSmallerTotalThanContentSizeIsOk() { final long knownTotal = 0; final List knownContentList = new ArrayList<>(); knownContentList.add("content1"); knownContentList.add("content2"); - final PagedList pagedList = new PagedList<>(knownContentList, knownTotal); - assertThat(pagedList.getTotal()).isEqualTo(knownTotal); - assertThat(pagedList.getSize()).isEqualTo(knownContentList.size()); + assertListSize(knownTotal, knownContentList); } } diff --git a/hawkbit-rest-resource/src/test/resources/application-test.properties b/hawkbit-rest-resource/src/test/resources/application-test.properties index bdd959ca2..92506caa4 100644 --- a/hawkbit-rest-resource/src/test/resources/application-test.properties +++ b/hawkbit-rest-resource/src/test/resources/application-test.properties @@ -24,7 +24,7 @@ hawkbit.server.database=H2 hawkbit.server.database.env=TEST spring.main.show_banner=false -hawkbit.server.controller.security.authentication.header=true +hawkbit.server.ddi.security.authentication.header=true hawkbit.server.artifact.repo.upload.maxFileSize=5MB diff --git a/hawkbit-rest-resource/src/test/resources/log4j2.xml b/hawkbit-rest-resource/src/test/resources/log4j2.xml index 26437af34..98ea99ac9 100644 --- a/hawkbit-rest-resource/src/test/resources/log4j2.xml +++ b/hawkbit-rest-resource/src/test/resources/log4j2.xml @@ -17,7 +17,7 @@ - + diff --git a/hawkbit-security-core/pom.xml b/hawkbit-security-core/pom.xml index 011acc95b..a3b262726 100644 --- a/hawkbit-security-core/pom.xml +++ b/hawkbit-security-core/pom.xml @@ -59,6 +59,11 @@ org.springframework.boot spring-boot + + org.springframework.boot + spring-boot-configuration-processor + true + diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java index 2e46d74c7..5d4ab9283 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java @@ -23,8 +23,7 @@ import org.springframework.security.core.GrantedAuthority; * The Permissions cover CRUD for two data areas of SP:
*
* XX_Target_CRUD which covers the following entities: {@link Target} entities - * including metadata, {@link TargetTag}s, {@link Action}s, - * {@link TargetRegistrationRule}s
+ * including metadata, {@link TargetTag}s, {@link TargetRegistrationRule}s
* XX_Repository CRUD which covers: {@link DistributionSet}s, * {@link SoftwareModule}s, DS Tags
*

@@ -131,6 +130,11 @@ public final class SpPermission { */ public static final String TENANT_CONFIGURATION = "TENANT_CONFIGURATION"; + /** + * Permission to administrate a rollout management. + */ + public static final String ROLLOUT_MANAGEMENT = "ROLLOUT_MANAGEMENT"; + private SpPermission() { // Constants only } @@ -178,6 +182,12 @@ public final class SpPermission { */ public static final String CONTROLLER_ROLE_ANONYMOUS = "ROLE_CONTROLLER_ANONYMOUS"; + /** + * The role which contains the spring security context in case the + * system is executing code which is necessary to be privileged. + */ + public static final String SYSTEM_ROLE = "ROLE_SYSTEM_CODE"; + /** * The spring security eval expression operator {@code or}. */ @@ -265,8 +275,15 @@ public final class SpPermission { * context contains the anoynmous role or the controller specific role * {@link SpPermission#CONTROLLER_ROLE}. */ - public static final String IS_CONTROLLER = "hasAnyRole('" + CONTROLLER_ROLE_ANONYMOUS + "', '" + CONTROLLER_ROLE - + "')"; + public static final String IS_CONTROLLER = "hasAnyRole('" + CONTROLLER_ROLE_ANONYMOUS + "', '" + + CONTROLLER_ROLE + "')"; + + /** + * Spring security eval hasAnyRole expression to check if the spring + * context contains system code role + * {@link SpringEvalExpressions#SYSTEM_ROLE}. + */ + public static final String IS_SYSTEM_CODE = HAS_AUTH_PREFIX + SYSTEM_ROLE + HAS_AUTH_SUFFIX; /** * Spring security eval hasAuthority expression to check if spring @@ -276,6 +293,28 @@ public final class SpPermission { public static final String HAS_AUTH_CREATE_REPOSITORY_AND_CREATE_TARGET = HAS_AUTH_PREFIX + CREATE_REPOSITORY + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + CREATE_TARGET + HAS_AUTH_SUFFIX; + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#ROLLOUT_MANAGEMENT} + */ + public static final String HAS_AUTH_ROLLOUT_MANAGEMENT_READ = HAS_AUTH_PREFIX + ROLLOUT_MANAGEMENT + + HAS_AUTH_SUFFIX; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#ROLLOUT_MANAGEMENT} and + * {@link SpPermission#UPDATE_TARGET}. + */ + public static final String HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE = HAS_AUTH_PREFIX + ROLLOUT_MANAGEMENT + + HAS_AUTH_SUFFIX + HAS_AUTH_AND + HAS_AUTH_PREFIX + UPDATE_TARGET + HAS_AUTH_SUFFIX; + + /** + * Spring security eval hasAuthority expression to check if spring + * context contains {@link SpPermission#TENANT_CONFIGURATION} + */ + public static final String HAS_AUTH_TENANT_CONFIGURATION = HAS_AUTH_PREFIX + TENANT_CONFIGURATION + + HAS_AUTH_SUFFIX; + private SpringEvalExpressions() { // utility class } diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/DdiSecurityProperties.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/DdiSecurityProperties.java new file mode 100644 index 000000000..ce1ff91d8 --- /dev/null +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/DdiSecurityProperties.java @@ -0,0 +1,219 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.security; + +import java.util.List; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * The common properties for DDI security. + */ +@ConfigurationProperties("hawkbit.server.ddi.security") +public class DdiSecurityProperties { + + private final Rp rp = new Rp(); + private final Authentication authentication = new Authentication(); + + public Authentication getAuthentication() { + return authentication; + } + + public Rp getRp() { + return rp; + } + + /** + * Reverse proxy configuration. Defines the security properties for + * authenticating controllers behind a reverse proxy which terminates the + * SSL session at the reverse proxy but adding request header which contains + * the CN of the certificate. + */ + public static class Rp { + + /** + * HTTP header field for common name of a DDI target client certificate. + */ + private String cnHeader = "X-Ssl-Client-Cn"; + + /** + * HTTP header field for issuer hash of a DDI target client certificate. + */ + private String sslIssuerHashHeader = "X-Ssl-Issuer-Hash-%d"; + + /** + * List of trusted (reverse proxy) IP addresses for performing DDI + * client certificate authentication. + */ + private List trustedIPs; + + /** + * @return the cnHeader + */ + public String getCnHeader() { + return cnHeader; + } + + /** + * @param cnHeader + * the cnHeader to set + */ + public void setCnHeader(final String cnHeader) { + this.cnHeader = cnHeader; + } + + /** + * @return the sslIssuerHashHeader + */ + public String getSslIssuerHashHeader() { + return sslIssuerHashHeader; + } + + /** + * @param sslIssuerHashHeader + * the sslIssuerHashHeader to set + */ + public void setSslIssuerHashHeader(final String sslIssuerHashHeader) { + this.sslIssuerHashHeader = sslIssuerHashHeader; + } + + /** + * @return the trustedIPs + */ + public List getTrustedIPs() { + return trustedIPs; + } + + /** + * @param trustedIPs + * the trustedIPs to set + */ + public void setTrustedIPs(final List trustedIPs) { + this.trustedIPs = trustedIPs; + } + + } + + /** + * DDI Authentication options. + */ + public static class Authentication { + private final Anonymous anonymous = new Anonymous(); + private final Targettoken targettoken = new Targettoken(); + private final Gatewaytoken gatewaytoken = new Gatewaytoken(); + + public Anonymous getAnonymous() { + return anonymous; + } + + public Gatewaytoken getGatewaytoken() { + return gatewaytoken; + } + + public Targettoken getTargettoken() { + return targettoken; + } + + /** + * Target token authentication. Tokens are defined per target. + * + */ + public static class Targettoken { + /** + * Set to true to enable target token authentication. + */ + private boolean enabled = false; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(final boolean enabled) { + this.enabled = enabled; + } + + } + + /** + * Gateway token authentication. Tokens are defined per tenant. Use with + * care! + * + */ + public static class Gatewaytoken { + + /** + * Gateway token based authentication enabled. + */ + private boolean enabled = false; + + /** + * Default gateway token name. + */ + private String name = ""; + + /** + * Default gateway token itself. + */ + private String key = ""; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(final boolean enabled) { + this.enabled = enabled; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public String getKey() { + return key; + } + + public void setKey(final String key) { + this.key = key; + } + + } + + /** + * Anonymous authentication. + */ + public static class Anonymous { + + /** + * Set to true to enable anonymous DDI client authentication. + */ + private boolean enabled = false; + + /** + * @param enabled + * the enabled to set + */ + public void setEnabled(final boolean enabled) { + this.enabled = enabled; + } + + /** + * @return the enabled + */ + public boolean isEnabled() { + return enabled; + } + } + + } + +} diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/HawkbitSecurityProperties.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/HawkbitSecurityProperties.java new file mode 100644 index 000000000..6192a8f31 --- /dev/null +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/HawkbitSecurityProperties.java @@ -0,0 +1,196 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.security; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * Security related hawkbit configuration. + * + */ +@Component +@ConfigurationProperties("hawkbit.server.security") +public class HawkbitSecurityProperties { + + private final Clients clients = new Clients(); + private final Dos dos = new Dos(); + private final Xframe xframe = new Xframe(); + + public Dos getDos() { + return dos; + } + + public Clients getClients() { + return clients; + } + + public Xframe getXframe() { + return xframe; + } + + /** + * Defines the XFrameOption policy. + * + */ + public static class Xframe { + + /** + * XFrame option. Allowed values: SAMEORIGIN, DENY, ALLOW-FROM + */ + private String option = "DENY"; + + /** + * ALLOW-FROM defined URL, has to be filled in case ALLOW-FROM option is + * selected. + */ + private String allowfrom = ""; + + public String getOption() { + return option; + } + + public void setOption(final String option) { + this.option = option; + } + + public String getAllowfrom() { + return allowfrom; + } + + public void setAllowfrom(final String allowfrom) { + this.allowfrom = allowfrom; + } + + } + + /** + * Security configuration related to clients. + * + */ + public static class Clients { + + /** + * Blacklisted client (IP addresses) for for DDI and Management API. + */ + private String blacklist = ""; + + /** + * Name of the http header from which the remote ip is extracted. + */ + private String remoteIpHeader = "X-Forwarded-For"; + + public String getBlacklist() { + return blacklist; + } + + public void setBlacklist(final String blacklist) { + this.blacklist = blacklist; + } + + public String getRemoteIpHeader() { + return remoteIpHeader; + } + + public void setRemoteIpHeader(final String remoteIpHeader) { + this.remoteIpHeader = remoteIpHeader; + } + } + + /** + * Denial of service protection related properties. + * + */ + public static class Dos { + + /** + * Maximum number of status updates that the controller can report for + * an action (0 to disable). + */ + private int maxStatusEntriesPerAction = 1000; + + /** + * Maximum number of attributes that the controller can report; + */ + private int maxAttributeEntriesPerTarget = 100; + + private final Filter filter = new Filter(); + + public Filter getFilter() { + return filter; + } + + public int getMaxStatusEntriesPerAction() { + return maxStatusEntriesPerAction; + } + + public void setMaxStatusEntriesPerAction(final int maxStatusEntriesPerAction) { + this.maxStatusEntriesPerAction = maxStatusEntriesPerAction; + } + + public int getMaxAttributeEntriesPerTarget() { + return maxAttributeEntriesPerTarget; + } + + public void setMaxAttributeEntriesPerTarget(final int maxAttributeEntriesPerTarget) { + this.maxAttributeEntriesPerTarget = maxAttributeEntriesPerTarget; + } + + /** + * Configuration for hawkBits DOS prevention filter. This is usually an + * infrastructure topic but might be useful in some cases. + * + */ + public static class Filter { + + /** + * White list of peer IP addresses for DOS filter (regular + * expression). + */ + private String whitelist = "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|192\\.168\\.\\d{1,3}\\.\\d{1,3}|169\\.254\\.\\d{1,3}\\.\\d{1,3}|127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}"; + + /** + * # Maximum number of allowed REST read/GET requests per second per + * client. + */ + int maxRead = 200; + + /** + * Maximum number of allowed REST write/(PUT/POST/etc.) requests per + * second per client. + */ + int maxWrite = 50; + + public String getWhitelist() { + return whitelist; + } + + public void setWhitelist(final String whitelist) { + this.whitelist = whitelist; + } + + public int getMaxRead() { + return maxRead; + } + + public void setMaxRead(final int maxRead) { + this.maxRead = maxRead; + } + + public int getMaxWrite() { + return maxWrite; + } + + public void setMaxWrite(final int maxWrite) { + this.maxWrite = maxWrite; + } + + } + } +} diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java index a3cb56d91..2d8ac52df 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java @@ -8,6 +8,8 @@ */ package org.eclipse.hawkbit.security; +import java.util.concurrent.atomic.AtomicInteger; + import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails; import org.eclipse.hawkbit.tenancy.TenantAware; import org.springframework.security.core.Authentication; @@ -27,6 +29,7 @@ import org.springframework.security.core.context.SecurityContextHolder; public class SecurityContextTenantAware implements TenantAware { private static final ThreadLocal TENANT_THREAD_LOCAL = new ThreadLocal<>(); + private static final ThreadLocal RUN_AS_DEPTH = new ThreadLocal<>(); /* * (non-Javadoc) @@ -56,11 +59,21 @@ public class SecurityContextTenantAware implements TenantAware { */ @Override public T runAsTenant(final String tenant, final TenantRunner callable) { + AtomicInteger runAsDepth = RUN_AS_DEPTH.get(); + if (runAsDepth == null) { + runAsDepth = new AtomicInteger(1); + RUN_AS_DEPTH.set(runAsDepth); + } else { + runAsDepth.incrementAndGet(); + } TENANT_THREAD_LOCAL.set(tenant); try { return callable.run(); } finally { - TENANT_THREAD_LOCAL.remove(); + if (runAsDepth.decrementAndGet() <= 0) { + RUN_AS_DEPTH.remove(); + TENANT_THREAD_LOCAL.remove(); + } } } } diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityProperties.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityProperties.java deleted file mode 100644 index b9bf45fd0..000000000 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityProperties.java +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright (c) 2015 Bosch Software Innovations GmbH and others. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.eclipse.hawkbit.security; - -import java.util.List; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * The common properties for security. - * - * - * - */ -@ConfigurationProperties -public class SecurityProperties { - - @Value("${hawkbit.server.controller.security.rp.cnHeader:X-Ssl-Client-Cn}") - private String rpCnHeader; - - @Value("${hawkbit.server.controller.security.rp.sslIssuerHashHeader:X-Ssl-Issuer-Hash-%d}") - private String rpSslIssuerHashHeader; - - @Value("${hawkbit.server.controller.security.rp.trustedIPs:#{null}}") - private List rpTrustedIPs; - - @Value("${hawkbit.server.controller.security.authentication.anonymous.enabled:false}") - private Boolean anonymousEnabled; - - public String getRpCnHeader() { - return rpCnHeader; - } - - public String getRpSslIssuerHashHeader() { - return rpSslIssuerHashHeader; - } - - public List getRpTrustedIPs() { - return rpTrustedIPs; - } - - public Boolean getAnonymousEnabled() { - return anonymousEnabled; - } - - public void setRpCnHeader(final String rpCnHeader) { - this.rpCnHeader = rpCnHeader; - } - - public void setRpSslIssuerHashHeader(final String rpSslIssuerHashHeader) { - this.rpSslIssuerHashHeader = rpSslIssuerHashHeader; - } - - public void setRpTrustedIPs(final List rpTrustedIPs) { - this.rpTrustedIPs = rpTrustedIPs; - } - - public void setAnonymousEnabled(final Boolean anonymousEnabled) { - this.anonymousEnabled = anonymousEnabled; - } -} diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java new file mode 100644 index 000000000..b22b54e39 --- /dev/null +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java @@ -0,0 +1,118 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.security; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; + +import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; +import org.eclipse.hawkbit.tenancy.TenantAware; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextImpl; +import org.springframework.stereotype.Service; + +import com.google.common.base.Throwables; + +/** + * + */ +@Service +public class SystemSecurityContext { + + private static final Logger logger = LoggerFactory.getLogger(SystemSecurityContext.class); + + private final TenantAware tenantAware; + + /** + * Autowired constructor. + * + * @param tenantAware + * the tenant aware bean to retrieve the current tenant + */ + @Autowired + public SystemSecurityContext(final TenantAware tenantAware) { + this.tenantAware = tenantAware; + } + + public T runAsSystem(final Callable callable) { + final SecurityContext oldContext = SecurityContextHolder.getContext(); + try { + logger.debug("entering system code execution"); + return tenantAware.runAsTenant(tenantAware.getCurrentTenant(), () -> { + try { + setSystemContext(); + return callable.call(); + } catch (final Exception e) { + throw Throwables.propagate(e); + } + }); + + } finally { + SecurityContextHolder.setContext(oldContext); + logger.debug("leaving system code execution"); + } + } + + private static void setSystemContext() { + final SecurityContextImpl securityContextImpl = new SecurityContextImpl(); + securityContextImpl.setAuthentication(new SystemCodeAuthentication()); + SecurityContextHolder.setContext(securityContextImpl); + } + + public static class SystemCodeAuthentication implements Authentication { + + private static final long serialVersionUID = 1L; + private static final List AUTHORITIES = Collections + .singletonList(new SimpleGrantedAuthority(SpringEvalExpressions.SYSTEM_ROLE)); + + @Override + public String getName() { + return null; + } + + @Override + public Collection getAuthorities() { + return AUTHORITIES; + } + + @Override + public Object getCredentials() { + return null; + } + + @Override + public Object getDetails() { + return null; + } + + @Override + public Object getPrincipal() { + return null; + } + + @Override + public boolean isAuthenticated() { + return true; + } + + @Override + public void setAuthenticated(final boolean isAuthenticated) { + // not needed + } + } +} diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/util/IpUtil.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/util/IpUtil.java index 0068fd0c8..4e08d8bfe 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/util/IpUtil.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/util/IpUtil.java @@ -20,9 +20,6 @@ import com.google.common.net.HttpHeaders; /** * A utility which determines the correct IP of a connected {@link Target}. E.g * from a {@link HttpServletRequest}. - * - * - * * */ public final class IpUtil { @@ -95,7 +92,6 @@ public final class IpUtil { if (isIpV6) { return URI.create(scheme + SCHEME_SEPERATOR + "[" + host + "]"); } - return URI.create(scheme + SCHEME_SEPERATOR + host); } @@ -104,12 +100,14 @@ public final class IpUtil { * * @param host * the host + * @param exchange + * the exchange will store in the path * @return the {@link URI} * @throws IllegalArgumentException * If the given string not parsable */ - public static URI createAmqpUri(final String host) { - return createUri(AMPQP_SCHEME, host); + public static URI createAmqpUri(final String host, final String exchange) { + return createUri(AMPQP_SCHEME, host).resolve("/" + exchange); } /** diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/util/SPInfo.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/util/SPInfo.java index 3a2a696df..feb94c0d7 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/util/SPInfo.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/util/SPInfo.java @@ -11,90 +11,24 @@ package org.eclipse.hawkbit.util; import javax.servlet.MultipartConfigElement; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.EnvironmentAware; -import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; /** * Bean which contains all informations about the SP software, e.g. like * version, built time etc. from the environment. * - * - * - * */ @Component -public class SPInfo implements EnvironmentAware { +public class SPInfo { // package private for testing purposes static final String UNKNOWN_VERSION = "unknown"; static final String UNKNOWN_CREDENTIAL = "unknown credential"; - private Environment environmentData; - @Autowired private MultipartConfigElement configElement; - /* - * (non-Javadoc) - * - * @see org.springframework.context.EnvironmentAware#setEnvironment(org. - * springframework.core.env. Environment) - */ - @Override - public void setEnvironment(final Environment environment) { - this.environmentData = environment; - } - - /** - * @return the version in string format, e.g. 1.0.0 or {@code "UNKNOWN"} in - * case the SP version info cannot be determined. - */ - public String getVersion() { - if (environmentData != null) { - return environmentData.getProperty("info.build.version", UNKNOWN_VERSION); - } - return UNKNOWN_VERSION; - } - - public String getSupportEmail() { - if (environmentData != null) { - return environmentData.getProperty("hawkbit.server.email.support"); - } - return ""; - } - - public String getRequestAccountEmail() { - if (environmentData != null) { - return environmentData.getProperty("hawkbit.server.email.request.account"); - } - return ""; - } - - public String getDemoTenant() { - if (environmentData != null) { - return environmentData.getProperty("hawkbit.server.demo.tenant"); - } - return UNKNOWN_CREDENTIAL; - } - - public String getDemoUser() { - if (environmentData != null) { - return environmentData.getProperty("hawkbit.server.demo.user"); - } - return UNKNOWN_CREDENTIAL; - - } - - public String getDemoPassword() { - if (environmentData != null) { - return environmentData.getProperty("hawkbit.server.demo.password"); - } - return UNKNOWN_CREDENTIAL; - - } - /** * @return the max file size to upload artifact files in bytes which has * been configured. diff --git a/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/ExcludePathAwareShallowETagFilterTest.java b/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/ExcludePathAwareShallowETagFilterTest.java index ce663fbfb..0b2c0b116 100644 --- a/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/ExcludePathAwareShallowETagFilterTest.java +++ b/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/ExcludePathAwareShallowETagFilterTest.java @@ -28,6 +28,11 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Unit Tests - Security") +@Stories("Exclude path aware shallow ETag filter") @RunWith(MockitoJUnitRunner.class) public class ExcludePathAwareShallowETagFilterTest { @@ -56,7 +61,8 @@ public class ExcludePathAwareShallowETagFilterTest { filterUnderTest.doFilterInternal(servletRequestMock, servletResponseMock, filterChainMock); // verify no eTag header is set and response has not been changed - assertThat(servletResponseMock.getHeader("ETag")).isNull(); + assertThat(servletResponseMock.getHeader("ETag")) + .as("ETag header should not be set during downloading, too expensive").isNull(); // the servlet response must be the same mock! verify(filterChainMock, times(1)).doFilter(servletRequestMock, servletResponseMock); } diff --git a/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/security/SecurityTokenGeneratorTest.java b/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/security/SecurityTokenGeneratorTest.java index 405d6d010..b83b81df7 100644 --- a/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/security/SecurityTokenGeneratorTest.java +++ b/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/security/SecurityTokenGeneratorTest.java @@ -13,8 +13,14 @@ import java.security.NoSuchAlgorithmException; import org.junit.Test; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Unit Tests - Security") +@Stories("SecurityToken Generator Test") public class SecurityTokenGeneratorTest { + // FIXME: figure what is this all about?? @Test public void test() throws NoSuchAlgorithmException, UnsupportedEncodingException { final SecurityTokenGenerator securityTokenGenerator = new SecurityTokenGenerator(); diff --git a/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/util/IpUtilTest.java b/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/util/IpUtilTest.java index e1e809ab8..9eb83d2a9 100644 --- a/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/util/IpUtilTest.java +++ b/hawkbit-security-core/src/test/java/org/eclipse/hawkbit/util/IpUtilTest.java @@ -33,8 +33,8 @@ import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Stories; @RunWith(MockitoJUnitRunner.class) -@Features("IpUtil Test") -@Stories("Tests the created uris") +@Features("Unit Tests - Security") +@Stories("IP Util Test") public class IpUtilTest { @Mock @@ -53,7 +53,8 @@ public class IpUtilTest { final URI remoteAddr = IpUtil.getClientIpFromRequest(requestMock, "bumlux"); // verify - assertThat(remoteAddr).isEqualTo(knownRemoteClientIP); + assertThat(remoteAddr).as("The remote address should be as the known client IP address") + .isEqualTo(knownRemoteClientIP); verify(requestMock, times(1)).getHeader("bumlux"); verify(requestMock, times(1)).getRemoteAddr(); } @@ -71,7 +72,8 @@ public class IpUtilTest { final URI remoteAddr = IpUtil.getClientIpFromRequest(requestMock, "X-Forwarded-For"); // verify - assertThat(remoteAddr).isEqualTo(knownRemoteClientIP); + assertThat(remoteAddr).as("The remote address should be as the known client IP address") + .isEqualTo(knownRemoteClientIP); verify(requestMock, times(1)).getHeader(HttpHeaders.X_FORWARDED_FOR); verify(requestMock, times(0)).getRemoteAddr(); } @@ -94,45 +96,50 @@ public class IpUtilTest { } private void assertHttpUri(final String host, final URI httpUri) { - assertTrue(IpUtil.isHttpUri(httpUri)); - assertFalse(IpUtil.isAmqpUri(httpUri)); - assertEquals(host, httpUri.getHost()); - assertEquals("http", httpUri.getScheme()); + assertTrue("The given URI has an http scheme", IpUtil.isHttpUri(httpUri)); + assertFalse("The given URI is not an AMQP scheme", IpUtil.isAmqpUri(httpUri)); + assertEquals("The URI hosts matches the given host", host, httpUri.getHost()); + assertEquals("The given URI scheme is http", "http", httpUri.getScheme()); } @Test @Description("Tests create amqp uri ipv4 and ipv6") public void testCreateAmqpUri() { final String ipv4 = "10.99.99.1"; - URI amqpUri = IpUtil.createAmqpUri(ipv4); + URI amqpUri = IpUtil.createAmqpUri(ipv4, "path"); assertAmqpUri(ipv4, amqpUri); final String host = "myhost"; - amqpUri = IpUtil.createAmqpUri(host); + amqpUri = IpUtil.createAmqpUri(host, "path"); assertAmqpUri(host, amqpUri); final String ipv6 = "0:0:0:0:0:0:0:1"; - amqpUri = IpUtil.createAmqpUri(ipv6); + amqpUri = IpUtil.createAmqpUri(ipv6, "path"); assertAmqpUri("[" + ipv6 + "]", amqpUri); } - private void assertAmqpUri(final String host, final URI httpUri) { - assertTrue(IpUtil.isAmqpUri(httpUri)); - assertFalse(IpUtil.isHttpUri(httpUri)); - assertEquals(host, httpUri.getHost()); - assertEquals("amqp", httpUri.getScheme()); + private void assertAmqpUri(final String host, final URI amqpUri) { + assertTrue("The given URI is an AMQP scheme", IpUtil.isAmqpUri(amqpUri)); + assertFalse("The given URI is not an HTTP scheme", IpUtil.isHttpUri(amqpUri)); + assertEquals("The given host matches the URI host", host, amqpUri.getHost()); + assertEquals("The given URI has an AMQP scheme", "amqp", amqpUri.getScheme()); + assertEquals("The given URI has an AMQP path", "/path", amqpUri.getRawPath()); } - @Test(expected = IllegalArgumentException.class) + @Test @Description("Tests create invalid uri") public void testCreateInvalidUri() { final String host = "10.99.99.1"; final URI testUri = IpUtil.createUri("test", host); - assertFalse(IpUtil.isAmqpUri(testUri)); - assertFalse(IpUtil.isHttpUri(testUri)); - assertEquals(host, testUri.getHost()); - IpUtil.createUri(":/", host); - fail(); + assertFalse("The given URI is not an AMQP address", IpUtil.isAmqpUri(testUri)); + assertFalse("The given URI is not an HTTP address", IpUtil.isHttpUri(testUri)); + assertEquals("The given host matches the URI host", host, testUri.getHost()); + try { + IpUtil.createUri(":/", host); + fail("Missing expected IllegalArgumentException due invalid URI"); + } catch (final IllegalArgumentException e) { + // expected + } } } diff --git a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/AbstractControllerAuthenticationFilter.java b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/AbstractControllerAuthenticationFilter.java index bbffc8748..d22c432cf 100644 --- a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/AbstractControllerAuthenticationFilter.java +++ b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/AbstractControllerAuthenticationFilter.java @@ -9,7 +9,7 @@ package org.eclipse.hawkbit.security; import org.eclipse.hawkbit.dmf.json.model.TenantSecruityToken; -import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.slf4j.Logger; @@ -26,14 +26,16 @@ public abstract class AbstractControllerAuthenticationFilter implements PreAuthe private static final Logger LOGGER = LoggerFactory.getLogger(AbstractControllerAuthenticationFilter.class); - protected final SystemManagement systemManagement; + protected final TenantConfigurationManagement tenantConfigurationManagement; protected final TenantAware tenantAware; private final SecurityConfigurationKeyTenantRunner configurationKeyTenantRunner; + protected final SystemSecurityContext systemSecurityContext; - protected AbstractControllerAuthenticationFilter(final SystemManagement systemManagement, - final TenantAware tenantAware) { - this.systemManagement = systemManagement; + protected AbstractControllerAuthenticationFilter(final TenantConfigurationManagement systemManagement, + final TenantAware tenantAware, final SystemSecurityContext systemSecurityContext) { + this.tenantConfigurationManagement = systemManagement; this.tenantAware = tenantAware; + this.systemSecurityContext = systemSecurityContext; this.configurationKeyTenantRunner = new SecurityConfigurationKeyTenantRunner(); } @@ -53,8 +55,10 @@ public abstract class AbstractControllerAuthenticationFilter implements PreAuthe private final class SecurityConfigurationKeyTenantRunner implements TenantAware.TenantRunner { @Override public Boolean run() { + LOGGER.trace("retrieving configuration value for configuration key {}", getTenantConfigurationKey()); - return systemManagement.getConfigurationValue(getTenantConfigurationKey(), Boolean.class); + return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement + .getConfigurationValue(getTenantConfigurationKey(), Boolean.class).getValue()); } } diff --git a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticateSecurityTokenFilter.java b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticateSecurityTokenFilter.java index 3cf09cff8..c30f60711 100644 --- a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticateSecurityTokenFilter.java +++ b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticateSecurityTokenFilter.java @@ -12,7 +12,7 @@ import org.eclipse.hawkbit.dmf.json.model.TenantSecruityToken; import org.eclipse.hawkbit.im.authentication.SpPermission; import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails; import org.eclipse.hawkbit.repository.ControllerManagement; -import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.slf4j.Logger; @@ -45,8 +45,8 @@ public class ControllerPreAuthenticateSecurityTokenFilter extends AbstractContro /** * Constructor. * - * @param systemManagement - * the system management service to retrieve configuration + * @param tenantConfigurationManagement + * the tenant management service to retrieve configuration * properties * @param controllerManagement * the controller management to retrieve the specific target @@ -54,10 +54,15 @@ public class ControllerPreAuthenticateSecurityTokenFilter extends AbstractContro * @param tenantAware * the tenant aware service to get configuration for the specific * tenant + * @param systemSecurityContext + * the system security context to get access to tenant + * configuration */ - public ControllerPreAuthenticateSecurityTokenFilter(final SystemManagement systemManagement, - final ControllerManagement controllerManagement, final TenantAware tenantAware) { - super(systemManagement, tenantAware); + public ControllerPreAuthenticateSecurityTokenFilter( + final TenantConfigurationManagement tenantConfigurationManagement, + final ControllerManagement controllerManagement, final TenantAware tenantAware, + final SystemSecurityContext systemSecurityContext) { + super(tenantConfigurationManagement, tenantAware, systemSecurityContext); this.controllerManagement = controllerManagement; } diff --git a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedGatewaySecurityTokenFilter.java b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedGatewaySecurityTokenFilter.java index 7e8848ed8..765589df4 100644 --- a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedGatewaySecurityTokenFilter.java +++ b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedGatewaySecurityTokenFilter.java @@ -9,7 +9,7 @@ package org.eclipse.hawkbit.security; import org.eclipse.hawkbit.dmf.json.model.TenantSecruityToken; -import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.slf4j.Logger; @@ -39,16 +39,20 @@ public class ControllerPreAuthenticatedGatewaySecurityTokenFilter extends Abstra /** * Constructor. * - * @param systemManagement - * the system management service to retrieve configuration + * @param tenantConfigurationManagement + * the tenant management service to retrieve configuration * properties * @param tenantAware * the tenant aware service to get configuration for the specific * tenant + * @param systemSecurityContext + * the system security context to get access to tenant + * configuration */ - public ControllerPreAuthenticatedGatewaySecurityTokenFilter(final SystemManagement systemManagement, - final TenantAware tenantAware) { - super(systemManagement, tenantAware); + public ControllerPreAuthenticatedGatewaySecurityTokenFilter( + final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware, + final SystemSecurityContext systemSecurityContext) { + super(tenantConfigurationManagement, tenantAware, systemSecurityContext); } @Override @@ -84,8 +88,12 @@ public class ControllerPreAuthenticatedGatewaySecurityTokenFilter extends Abstra public String run() { LOGGER.trace("retrieving configuration value for configuration key {}", TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY); - return systemManagement.getConfigurationValue( - TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, String.class); + + return systemSecurityContext + .runAsSystem(() -> tenantConfigurationManagement + .getConfigurationValue( + TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, String.class) + .getValue()); } } diff --git a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedSecurityHeaderFilter.java b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedSecurityHeaderFilter.java index 066efca53..ef8b5fbf7 100644 --- a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedSecurityHeaderFilter.java +++ b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedSecurityHeaderFilter.java @@ -9,7 +9,7 @@ package org.eclipse.hawkbit.security; import org.eclipse.hawkbit.dmf.json.model.TenantSecruityToken; -import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; import org.slf4j.Logger; @@ -56,18 +56,20 @@ public class ControllerPreAuthenticatedSecurityHeaderFilter extends AbstractCont * @param caAuthorityNameHeader * the http-header which holds the ca-authority name of the * certificate - * @param systemManagement - * the system management service to retrieve configuration - * properties to check if the header authentication is enabled - * for this tenant + * @param tenantConfigurationManagement + * the tenant management service to retrieve configuration + * properties * @param tenantAware * the tenant aware service to get configuration for the specific * tenant + * @param systemSecurityContext + * the system security context to get access to tenant + * configuration */ public ControllerPreAuthenticatedSecurityHeaderFilter(final String caCommonNameHeader, - final String caAuthorityNameHeader, final SystemManagement systemManagement, - final TenantAware tenantAware) { - super(systemManagement, tenantAware); + final String caAuthorityNameHeader, final TenantConfigurationManagement tenantConfigurationManagement, + final TenantAware tenantAware, final SystemSecurityContext systemSecurityContext) { + super(tenantConfigurationManagement, tenantAware, systemSecurityContext); this.caCommonNameHeader = caCommonNameHeader; this.sslIssuerHashBasicHeader = caAuthorityNameHeader; } @@ -142,8 +144,8 @@ public class ControllerPreAuthenticatedSecurityHeaderFilter extends AbstractCont private final class GetSecurityAuthorityNameTenantRunner implements TenantAware.TenantRunner { @Override public String run() { - return systemManagement.getConfigurationValue( - TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class); + return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement.getConfigurationValue( + TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class).getValue()); } } } diff --git a/hawkbit-test-report/pom.xml b/hawkbit-test-report/pom.xml index 6e58cb61a..3e9182f0a 100644 --- a/hawkbit-test-report/pom.xml +++ b/hawkbit-test-report/pom.xml @@ -18,7 +18,7 @@ 0.2.0-SNAPSHOT hawkbit-test-report - Hawkbit :: Test Report + hawkBit :: Test Report pom @@ -75,8 +75,7 @@ - - + \ No newline at end of file diff --git a/hawkbit-ui/pom.xml b/hawkbit-ui/pom.xml index f0fa058cf..e0bde576a 100644 --- a/hawkbit-ui/pom.xml +++ b/hawkbit-ui/pom.xml @@ -30,6 +30,7 @@ ${project.build.directory}/classes/VAADIN/widgetsets ${project.build.directory}/classes/VAADIN/widgetsets + src/main/resources true false @@ -57,26 +58,24 @@ - - net.alchim31.maven - yuicompressor-maven-plugin - 1.5.0 - - - - compress - - - - - - **/*.css - - true - ${basedir}/src/main/resources/VAADIN/themes/hawkbit/styles.css - true - - + + org.apache.maven.plugins + maven-jar-plugin + + + true + + true + true + + + CustomRenderers + 1 + org.eclipse.hawkbit.ui.customrenderers.CustomRendererWidgetSet + + + + @@ -114,7 +113,7 @@ com.vaadin vaadin-maven-plugin - [7.5.2,) + [7.6.3,) compile-theme @@ -172,8 +171,11 @@ com.vaadin vaadin-server
- - + + com.vaadin + vaadin-client + + com.vaadin vaadin-push @@ -212,6 +214,19 @@ org.vaadin.addons tokenfield + + org.vaadin.alump.distributionbar + dbar-addon + + + org.vaadin.addons + contextmenu + + + org.springframework.boot + spring-boot-configuration-processor + true + diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/repository/SpPermissionChecker.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/repository/SpPermissionChecker.java index 0df7af872..1bb103e51 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/repository/SpPermissionChecker.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/repository/SpPermissionChecker.java @@ -139,4 +139,42 @@ public class SpPermissionChecker implements Serializable { return hasReadDistributionPermission() && permissionService.hasPermission(SpPermission.DELETE_REPOSITORY); } + /** + * Gets the SP rollout create permission. + * + * @return permission for rollout update + */ + public boolean hasRolloutUpdatePermission() { + return hasUpdateTargetPermission() && hasReadDistributionPermission() + && permissionService.hasPermission(SpPermission.ROLLOUT_MANAGEMENT); + } + + /** + * Gets the SP rollout create permission. + * + * @return permission for rollout create + */ + public boolean hasRolloutCreatePermission() { + return hasUpdateTargetPermission() && hasReadDistributionPermission() + && permissionService.hasPermission(SpPermission.ROLLOUT_MANAGEMENT); + } + + /** + * + * Gets the SP rollout read permission. + * + * @return Gets the SP rollout read permission. + */ + public boolean hasRolloutReadPermission() { + return permissionService.hasPermission(SpPermission.ROLLOUT_MANAGEMENT); + } + + /** + * Gets the SP rollout targets read permission. + * + * @return permission to read rollout targets + */ + public boolean hasRolloutTargetsReadPermission() { + return hasTargetReadPermission() && permissionService.hasPermission(SpPermission.ROLLOUT_MANAGEMENT); + } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/AppWidgetSet.gwt.xml b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/AppWidgetSet.gwt.xml index 8811defaf..c6ee9b5b5 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/AppWidgetSet.gwt.xml +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/AppWidgetSet.gwt.xml @@ -16,9 +16,29 @@ - + + + + + + + + + + + + + + + + + + - - + diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/HawkbitUI.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/HawkbitUI.java index 1c81610e2..f4c26a960 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/HawkbitUI.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/HawkbitUI.java @@ -14,12 +14,11 @@ import java.util.Set; import javax.servlet.http.Cookie; -import org.eclipse.hawkbit.eventbus.event.EntityEvent; -import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails; import org.eclipse.hawkbit.ui.components.SPUIErrorHandler; import org.eclipse.hawkbit.ui.menu.DashboardEvent.PostViewChangeEvent; import org.eclipse.hawkbit.ui.menu.DashboardMenu; import org.eclipse.hawkbit.ui.menu.DashboardMenuItem; +import org.eclipse.hawkbit.ui.push.EventPushStrategy; import org.eclipse.hawkbit.ui.utils.I18N; import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; import org.eclipse.hawkbit.ui.utils.SpringContextHelper; @@ -28,14 +27,8 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.core.io.Resource; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.vaadin.spring.events.EventBus; -import org.vaadin.spring.events.EventBus.SessionEventBus; -import com.google.common.eventbus.AllowConcurrentEvents; -import com.google.common.eventbus.Subscribe; import com.vaadin.annotations.Title; import com.vaadin.navigator.Navigator; import com.vaadin.navigator.View; @@ -45,9 +38,6 @@ import com.vaadin.server.ClientConnector.DetachListener; import com.vaadin.server.Responsive; import com.vaadin.server.VaadinRequest; import com.vaadin.server.VaadinService; -import com.vaadin.server.VaadinSession; -import com.vaadin.server.VaadinSession.State; -import com.vaadin.server.WrappedSession; import com.vaadin.spring.navigator.SpringViewProvider; import com.vaadin.ui.Component; import com.vaadin.ui.CssLayout; @@ -71,11 +61,13 @@ public class HawkbitUI extends DefaultHawkbitUI implements DetachListener { private static final String EMPTY_VIEW = ""; + private transient EventPushStrategy pushStrategy; + @Autowired private SpringViewProvider viewProvider; @Autowired - private ApplicationContext context; + private transient ApplicationContext context; @Autowired private I18N i18n; @@ -89,65 +81,40 @@ public class HawkbitUI extends DefaultHawkbitUI implements DetachListener { private ErrorView errorview; @Autowired - protected EventBus.SessionEventBus eventBus; + protected transient EventBus.SessionEventBus eventBus; /** - * An {@link com.google.common.eventbus.EventBus} subscriber which - * subscribes {@link EntityEvent} from the repository to dispatch these - * events to the UI {@link SessionEventBus}. - * - * @param event - * the entity event which has been published from the repository + * Default constructor. */ - @Subscribe - @AllowConcurrentEvents - public void dispatch(final org.eclipse.hawkbit.eventbus.event.Event event) { - final VaadinSession session = getSession(); - if (session != null && session.getState() == State.OPEN) { - final WrappedSession wrappedSession = session.getSession(); - if (wrappedSession != null) { - final SecurityContext userContext = (SecurityContext) wrappedSession - .getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); - if (eventSecurityCheck(userContext, event)) { - final SecurityContext oldContext = SecurityContextHolder.getContext(); - try { - access(new DispatcherRunnable(eventBus, session, userContext, event)); - } finally { - SecurityContextHolder.setContext(oldContext); - } - } - } - } + public HawkbitUI() { + // is empty, is ok. } - protected boolean eventSecurityCheck(final SecurityContext userContext, - final org.eclipse.hawkbit.eventbus.event.Event event) { - if (userContext != null && userContext.getAuthentication() != null) { - final Object tenantAuthenticationDetails = userContext.getAuthentication().getDetails(); - if (tenantAuthenticationDetails instanceof TenantAwareAuthenticationDetails) { - return ((TenantAwareAuthenticationDetails) tenantAuthenticationDetails).getTenant() - .equalsIgnoreCase(event.getTenant()); - } - } - return false; + /** + * Constructor taking the push strategy. + * + * @param pushStrategy + * the strategy to push events from the backend to the UI + */ + public HawkbitUI(final EventPushStrategy pushStrategy) { + this.pushStrategy = pushStrategy; } - /* - * (non-Javadoc) - * - * @see - * com.vaadin.server.ClientConnector.DetachListener#detach(com.vaadin.server - * .ClientConnector. DetachEvent) - */ @Override public void detach(final DetachEvent event) { LOG.info("ManagementUI is detached uiid - {}", getUIId()); - + eventBus.unsubscribe(this); + if (pushStrategy != null) { + pushStrategy.clean(); + } } @Override protected void init(final VaadinRequest vaadinRequest) { LOG.info("ManagementUI init starts uiid - {}", getUI().getUIId()); + if (pushStrategy != null) { + pushStrategy.init(getUI()); + } addDetachListener(this); SpringContextHelper.setContext(context); @@ -210,7 +177,7 @@ public class HawkbitUI extends DefaultHawkbitUI implements DetachListener { navigator.addView(EMPTY_VIEW, new Navigator.EmptyView()); // set locale is required for I18N class also, to get the locale from // cookie - final String locale = getLocaleId(SPUIDefinitions.AVAILABLE_LOCALES); + final String locale = getLocaleId(SPUIDefinitions.getAvailableLocales()); setLocale(new Locale(locale)); UI.getCurrent().setErrorHandler(new SPUIErrorHandler()); @@ -225,7 +192,7 @@ public class HawkbitUI extends DefaultHawkbitUI implements DetachListener { /** * Get Specific Locale. - * + * * @param availableLocalesInApp * as set * @return String as preferred locale diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/UiProperties.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/UiProperties.java new file mode 100644 index 000000000..da4e2d50e --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/UiProperties.java @@ -0,0 +1,280 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.ui; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * Properties for Management UI customization. + * + */ +@Component +@ConfigurationProperties("hawkbit.server.ui") +public class UiProperties { + + /** + * Demo account login information. + * + */ + public static class Demo { + + /** + * Demo tenant. + */ + private String tenant = ""; + /** + * Demo user name. + */ + private String user = ""; + + /** + * Demo user password. + */ + private String password = ""; + + public String getPassword() { + return password; + } + + public String getTenant() { + return tenant; + } + + public String getUser() { + return user; + } + + public void setPassword(final String password) { + this.password = password; + } + + public void setTenant(final String tenant) { + this.tenant = tenant; + } + + public void setUser(final String user) { + this.user = user; + } + + } + /** + * Links to potentially other systems (e.g. support, user management, + * documentation etc.). + * + */ + public static class Links { + /** + * Configuration of UI documentation links. + * + */ + public static class Documentation { + /** + * Link to root of documentation and user guides. + */ + private String root = ""; + + /** + * Link to documentation of deployment view. + */ + private String deploymentView = ""; + + /** + * Link to documentation of distribution view. + */ + private String distributionView = ""; + + /** + * Link to documentation of upload view. + */ + private String uploadView = ""; + + /** + * Link to documentation of system configuration view. + */ + private String systemConfigurationView = ""; + + /** + * Link to security related documentation. + */ + private String security = ""; + + /** + * Link to target filter view. + */ + private String targetfilterView = ""; + + /** + * Link to documentation of rollout view. + */ + private String rolloutView = ""; + + public String getDeploymentView() { + return deploymentView; + } + + public String getDistributionView() { + return distributionView; + } + + public String getRolloutView() { + return rolloutView; + } + + public String getRoot() { + return root; + } + + public String getSecurity() { + return security; + } + + public String getSystemConfigurationView() { + return systemConfigurationView; + } + + public String getTargetfilterView() { + return targetfilterView; + } + + public String getUploadView() { + return uploadView; + } + + public void setDeploymentView(final String deploymentView) { + this.deploymentView = deploymentView; + } + + public void setDistributionView(final String distributionView) { + this.distributionView = distributionView; + } + + public void setRolloutView(final String rolloutView) { + this.rolloutView = rolloutView; + } + + public void setRoot(final String root) { + this.root = root; + } + + public void setSecurity(final String security) { + this.security = security; + } + + public void setSystemConfigurationView(final String systemConfigurationView) { + this.systemConfigurationView = systemConfigurationView; + } + + public void setTargetfilterView(final String targetfilterView) { + this.targetfilterView = targetfilterView; + } + + public void setUploadView(final String uploadView) { + this.uploadView = uploadView; + } + + } + + private final Documentation documentation = new Documentation(); + + /** + * Link to product support. + */ + private String support = ""; + + /** + * Link to request a system account, access. + */ + private String requestAccount = ""; + + /** + * Link to user management. + */ + private String userManagement = ""; + + public Documentation getDocumentation() { + return documentation; + } + + public String getRequestAccount() { + return requestAccount; + } + + public String getSupport() { + return support; + } + + public String getUserManagement() { + return userManagement; + } + + public void setRequestAccount(final String requestAccount) { + this.requestAccount = requestAccount; + } + + public void setSupport(final String support) { + this.support = support; + } + + public void setUserManagement(final String userManagement) { + this.userManagement = userManagement; + } + + } + /** + * Configuration of login view. + * + */ + public static class Login { + + /** + * Cookie configuration for login credential cookie. + * + */ + public static class Cookie { + /** + * Secure cookie enabled. + */ + private boolean secure = true; + + public boolean isSecure() { + return secure; + } + + public void setSecure(final boolean secure) { + this.secure = secure; + } + } + + private final Cookie cookie = new Cookie(); + + public Cookie getCookie() { + return cookie; + } + } + + private final Links links = new Links(); + + private final Login login = new Login(); + + private final Demo demo = new Demo(); + + public Demo getDemo() { + return demo; + } + + public Links getLinks() { + return links; + } + + public Login getLogin() { + return login; + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/UploadArtifactViewMenuItem.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/UploadArtifactViewMenuItem.java index 7f31f75b1..05fbbd1d3 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/UploadArtifactViewMenuItem.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/UploadArtifactViewMenuItem.java @@ -25,7 +25,7 @@ import com.vaadin.server.Resource; * */ @Component -@Order(400) +@Order(500) public class UploadArtifactViewMenuItem implements DashboardMenuItem { private static final long serialVersionUID = 4096851897640769726L; diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/details/ArtifactDetailsLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/details/ArtifactDetailsLayout.java index d0ba20db6..1bc9da65c 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/details/ArtifactDetailsLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/details/ArtifactDetailsLayout.java @@ -208,14 +208,13 @@ public class ArtifactDetailsLayout extends VerticalLayout { } private Container createArtifactLazyQueryContainer() { - final Map queryConfiguration = new HashMap(); + final Map queryConfiguration = new HashMap<>(); return getArtifactLazyQueryContainer(queryConfiguration); } private LazyQueryContainer getArtifactLazyQueryContainer(final Map queryConfig) { - final BeanQueryFactory artifactQF = new BeanQueryFactory( - ArtifactBeanQuery.class); + final BeanQueryFactory artifactQF = new BeanQueryFactory<>(ArtifactBeanQuery.class); artifactQF.setQueryConfiguration(queryConfig); final LazyQueryContainer artifactCont = new LazyQueryContainer(new LazyQueryDefinition(true, 10, "id"), artifactQF); @@ -431,7 +430,7 @@ public class ArtifactDetailsLayout extends VerticalLayout { titleOfArtifactDetails.setContentMode(ContentMode.HTML); } } - final Map queryConfiguration = new HashMap(); + final Map queryConfiguration = new HashMap<>(); if (baseSwModuleId != null) { queryConfiguration.put(SPUIDefinitions.BY_BASE_SOFTWARE_MODULE, baseSwModuleId); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/event/UploadViewAcceptCriteria.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/event/UploadViewAcceptCriteria.java index a4ae322fa..a3f045a08 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/event/UploadViewAcceptCriteria.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/event/UploadViewAcceptCriteria.java @@ -47,12 +47,6 @@ public class UploadViewAcceptCriteria extends AbstractAcceptCriteria { @Autowired private transient EventBus.SessionEventBus eventBus; - /* - * (non-Javadoc) - * - * @see hawkbit.server.ui.common.AbstractAcceptCriteria#analyseDragComponent - * (com.vaadin.event .dd.DragAndDropEvent, com.vaadin.ui.Component) - */ @Override protected void analyseDragComponent(final Component compsource) { final String sourceID = getComponentId(compsource); @@ -60,24 +54,11 @@ public class UploadViewAcceptCriteria extends AbstractAcceptCriteria { eventBus.publish(this, event); } - /* - * (non-Javadoc) - * - * @see - * org.eclipse.hawkbit.server.ui.common.AbstractAcceptCriteria#hideDropHints - * () - */ @Override protected void hideDropHints() { eventBus.publish(this, DragEvent.HIDE_DROP_HINT); } - /* - * (non-Javadoc) - * - * @see - * org.eclipse.hawkbit.server.ui.common.AbstractAcceptCriteria#invalidDrop() - */ @Override protected void invalidDrop() { uiNotification.displayValidationError(SPUILabelDefinitions.ACTION_NOT_ALLOWED); @@ -92,41 +73,23 @@ public class UploadViewAcceptCriteria extends AbstractAcceptCriteria { return id; } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.server.ui.common.AbstractAcceptCriteria# - * getDropHintConfigurations() - */ @Override protected Map getDropHintConfigurations() { return DROP_HINTS_CONFIGS; } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.server.ui.common.AbstractAcceptCriteria# - * publishDragStartEvent(java.lang.Object) - */ @Override protected void publishDragStartEvent(final Object event) { eventBus.publish(this, event); } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.server.ui.common.AbstractAcceptCriteria# - * getDropConfigurations() - */ @Override protected Map> getDropConfigurations() { return DROP_CONFIGS; } private static Map> createDropConfigurations() { - final Map> config = new HashMap>(); + final Map> config = new HashMap<>(); // Delete drop area droppable components config.put(SPUIComponetIdProvider.DELETE_BUTTON_WRAPPER_ID, Arrays.asList( SPUIComponetIdProvider.UPLOAD_SOFTWARE_MODULE_TABLE, SPUIComponetIdProvider.UPLOAD_TYPE_BUTTON_PREFIX)); @@ -135,7 +98,7 @@ public class UploadViewAcceptCriteria extends AbstractAcceptCriteria { } private static Map createDropHintConfigurations() { - final Map config = new HashMap(); + final Map config = new HashMap<>(); config.put(SPUIComponetIdProvider.UPLOAD_TYPE_BUTTON_PREFIX, UploadArtifactUIEvent.SOFTWARE_TYPE_DRAG_START); config.put(SPUIComponetIdProvider.UPLOAD_SOFTWARE_MODULE_TABLE, UploadArtifactUIEvent.SOFTWARE_DRAG_START); return config; diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/footer/SMDeleteActionsLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/footer/SMDeleteActionsLayout.java index 841256da4..ef4b17da7 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/footer/SMDeleteActionsLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/footer/SMDeleteActionsLayout.java @@ -8,6 +8,7 @@ */ package org.eclipse.hawkbit.ui.artifacts.footer; +import java.util.HashSet; import java.util.Set; import javax.annotation.PostConstruct; @@ -21,6 +22,7 @@ import org.eclipse.hawkbit.ui.common.footer.AbstractDeleteActionsLayout; import org.eclipse.hawkbit.ui.management.event.DragEvent; import org.eclipse.hawkbit.ui.utils.I18N; import org.eclipse.hawkbit.ui.utils.SPUIComponetIdProvider; +import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions; import org.eclipse.hawkbit.ui.utils.UINotification; import org.springframework.beans.factory.annotation.Autowired; @@ -35,6 +37,7 @@ import com.vaadin.spring.annotation.ViewScope; import com.vaadin.ui.Component; import com.vaadin.ui.Label; import com.vaadin.ui.Table; +import com.vaadin.ui.Table.TableTransferable; import com.vaadin.ui.UI; /** @@ -68,12 +71,7 @@ public class SMDeleteActionsLayout extends AbstractDeleteActionsLayout { @Autowired private UploadViewAcceptCriteria uploadViewAcceptCriteria; - /* - * (non-Javadoc) - * - * @see - * org.eclipse.hawkbit.server.ui.common.footer.DeleteActionsLayout#init() - */ + @Override @PostConstruct protected void init() { super.init(); @@ -131,23 +129,11 @@ public class SMDeleteActionsLayout extends AbstractDeleteActionsLayout { } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.server.ui.common.footer.DeleteActionsLayout# - * hasDeletePermission() - */ @Override protected boolean hasDeletePermission() { return permChecker.hasDeleteDistributionPermission(); } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.server.ui.common.footer.DeleteActionsLayout# - * hasUpdatePermission() - */ @Override protected boolean hasUpdatePermission() { /** @@ -158,12 +144,6 @@ public class SMDeleteActionsLayout extends AbstractDeleteActionsLayout { return false; } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.server.ui.common.footer.DeleteActionsLayout# - * getDeleteAreaLabel() - */ @Override protected String getDeleteAreaLabel() { return i18n.get("label.software.module.drop.area"); @@ -174,29 +154,17 @@ public class SMDeleteActionsLayout extends AbstractDeleteActionsLayout { return SPUIComponetIdProvider.DELETE_BUTTON_WRAPPER_ID; } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.server.ui.common.footer.DeleteActionsLayout# - * getDeleteLayoutAcceptCriteria() - */ @Override protected AcceptCriterion getDeleteLayoutAcceptCriteria() { return uploadViewAcceptCriteria; } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.server.ui.common.footer.DeleteActionsLayout# - * processDroppedComponent(com.vaadin .event.dd.DragAndDropEvent) - */ @Override protected void processDroppedComponent(final DragAndDropEvent event) { final Component sourceComponent = event.getTransferable().getSourceComponent(); if (sourceComponent instanceof Table) { final Table sourceTable = (Table) event.getTransferable().getSourceComponent(); - addToDeleteList(sourceTable); + addToDeleteList(sourceTable, (TableTransferable) event.getTransferable()); updateSWActionCount(); } if (sourceComponent.getId().startsWith(SPUIComponetIdProvider.UPLOAD_TYPE_BUTTON_PREFIX)) { @@ -221,41 +189,33 @@ public class SMDeleteActionsLayout extends AbstractDeleteActionsLayout { artifactUploadState.getSelectedDeleteSWModuleTypes().add(swModuleTypeName); } - private void addToDeleteList(final Table sourceTable) { - final Set swModuleIds = (Set) sourceTable.getValue(); - swModuleIds.forEach(id -> { + private void addToDeleteList(final Table sourceTable, final TableTransferable transferable) { + @SuppressWarnings("unchecked") + final Set swModuleSelected = (Set) sourceTable.getValue(); + final Set swModuleIdNameSet = new HashSet<>(); + if (!swModuleSelected.contains(transferable.getData(SPUIDefinitions.ITEMID))) { + swModuleIdNameSet.add((Long) transferable.getData(SPUIDefinitions.ITEMID)); + } else { + swModuleIdNameSet.addAll(swModuleSelected); + } + swModuleIdNameSet.forEach(id -> { final String swModuleName = (String) sourceTable.getContainerDataSource().getItem(id) .getItemProperty(SPUILabelDefinitions.NAME_VERSION).getValue(); artifactUploadState.getDeleteSofwareModules().put(id, swModuleName); }); } - /** - * Update the software module delete count. - */ private void updateSWActionCount() { final int count = artifactUploadState.getDeleteSofwareModules().size() + artifactUploadState.getSelectedDeleteSWModuleTypes().size(); updateActionsCount(count); } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.server.ui.common.footer.DeleteActionsLayout# - * getNoActionsButtonLabel() - */ @Override protected String getNoActionsButtonLabel() { return i18n.get("button.no.actions"); } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.server.ui.common.footer.DeleteActionsLayout# - * getActionsButtonLabel() - */ @Override protected String getActionsButtonLabel() { return i18n.get("button.actions"); @@ -266,25 +226,11 @@ public class SMDeleteActionsLayout extends AbstractDeleteActionsLayout { updateSWActionCount(); } - /* - * (non-Javadoc) - * - * @see - * org.eclipse.hawkbit.server.ui.common.footer.AbstractDeleteActionsLayout# - * getUnsavedActionsWindowCaption () - */ @Override protected String getUnsavedActionsWindowCaption() { return i18n.get("caption.save.window"); } - /* - * (non-Javadoc) - * - * @see - * org.eclipse.hawkbit.server.ui.common.footer.AbstractDeleteActionsLayout# - * unsavedActionsWindowClosed() - */ @Override protected void unsavedActionsWindowClosed() { final String message = uploadViewConfirmationWindowLayout.getConsolidatedMessage(); @@ -293,26 +239,12 @@ public class SMDeleteActionsLayout extends AbstractDeleteActionsLayout { } } - /* - * (non-Javadoc) - * - * @see - * org.eclipse.hawkbit.server.ui.common.footer.AbstractDeleteActionsLayout# - * getUnsavedActionsWindowContent () - */ @Override protected Component getUnsavedActionsWindowContent() { uploadViewConfirmationWindowLayout.init(); return uploadViewConfirmationWindowLayout; } - /* - * (non-Javadoc) - * - * @see - * org.eclipse.hawkbit.server.ui.common.footer.AbstractDeleteActionsLayout# - * hasUnsavedActions() - */ @Override protected boolean hasUnsavedActions() { return !artifactUploadState.getDeleteSofwareModules().isEmpty() @@ -329,23 +261,11 @@ public class SMDeleteActionsLayout extends AbstractDeleteActionsLayout { return null; } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.ui.common.footer.AbstractDeleteActionsLayout# - * isBulkUploadAllowed() - */ @Override protected boolean hasBulkUploadPermission() { return false; } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.ui.common.footer.AbstractDeleteActionsLayout# - * showBulkUploadWindow() - */ @Override protected void showBulkUploadWindow() { /** @@ -353,12 +273,6 @@ public class SMDeleteActionsLayout extends AbstractDeleteActionsLayout { */ } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.ui.common.footer.AbstractDeleteActionsLayout# - * restoreBulkUploadStatusCount() - */ @Override protected void restoreBulkUploadStatusCount() { /** diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/footer/UploadViewConfirmationWindowLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/footer/UploadViewConfirmationWindowLayout.java index 537a76a70..49e9ab59f 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/footer/UploadViewConfirmationWindowLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/footer/UploadViewConfirmationWindowLayout.java @@ -44,9 +44,9 @@ import com.vaadin.ui.themes.ValoTheme; /** * Abstract layout of confirm actions window. - * * - * + * + * */ @SpringComponent @ViewScope @@ -84,13 +84,13 @@ public class UploadViewConfirmationWindowLayout extends AbstractConfirmationWind /* * (non-Javadoc) - * + * * @see org.eclipse.hawkbit.server.ui.common.confirmwindow.layout. * AbstractConfirmationWindowLayout# getConfimrationTabs() */ @Override protected Map getConfimrationTabs() { - final Map tabs = new HashMap(); + final Map tabs = new HashMap<>(); if (!artifactUploadState.getDeleteSofwareModules().isEmpty()) { tabs.put(i18n.get("caption.delete.swmodule.accordion.tab"), createSMDeleteConfirmationTab()); } @@ -145,7 +145,7 @@ public class UploadViewConfirmationWindowLayout extends AbstractConfirmationWind /** * Get SWModule table container. - * + * * @return IndexedContainer */ @SuppressWarnings("unchecked") @@ -153,9 +153,8 @@ public class UploadViewConfirmationWindowLayout extends AbstractConfirmationWind final IndexedContainer swcontactContainer = new IndexedContainer(); swcontactContainer.addContainerProperty("SWModuleId", String.class, ""); swcontactContainer.addContainerProperty(SW_MODULE_NAME_MSG, String.class, ""); - Item item = null; for (final Long swModuleID : artifactUploadState.getDeleteSofwareModules().keySet()) { - item = swcontactContainer.addItem(swModuleID); + final Item item = swcontactContainer.addItem(swModuleID); item.getItemProperty("SWModuleId").setValue(swModuleID.toString()); item.getItemProperty(SW_MODULE_NAME_MSG) .setValue(artifactUploadState.getDeleteSofwareModules().get(swModuleID)); @@ -197,7 +196,7 @@ public class UploadViewConfirmationWindowLayout extends AbstractConfirmationWind for (final CustomFile customFile : artifactUploadState.getFileSelected()) { final String swNameVersion = HawkbitCommonUtil.getFormattedNameVersion( customFile.getBaseSoftwareModuleName(), customFile.getBaseSoftwareModuleVersion()); - if (HawkbitCommonUtil.bothSame(deleteSoftwareNameVersion, swNameVersion)) { + if (deleteSoftwareNameVersion != null && deleteSoftwareNameVersion.equals(swNameVersion)) { tobeRemoved.add(customFile); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/BaseSwModuleBeanQuery.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/BaseSwModuleBeanQuery.java index 04339c1a6..bb48c515a 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/BaseSwModuleBeanQuery.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/BaseSwModuleBeanQuery.java @@ -32,10 +32,6 @@ import com.google.common.base.Strings; * Simple implementation of generics bean query which dynamically loads a batch * of beans. * - * - * - * - * */ public class BaseSwModuleBeanQuery extends AbstractBeanQuery { private static final long serialVersionUID = 4362142538539335466L; @@ -76,7 +72,7 @@ public class BaseSwModuleBeanQuery extends AbstractBeanQuery loadBeans(final int startIndex, final int count) { final Slice swModuleBeans; - final List proxyBeans = new ArrayList(); + final List proxyBeans = new ArrayList<>(); if (type == null && Strings.isNullOrEmpty(searchText)) { swModuleBeans = getSoftwareManagementService() diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleDetails.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleDetails.java index 674152164..6eb3e3d5d 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleDetails.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleDetails.java @@ -70,6 +70,7 @@ public class SoftwareModuleDetails extends AbstractTableDetailsLayout { /** * Initialize the component. */ + @Override @PostConstruct protected void init() { super.init(); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleTable.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleTable.java index a4f8a8f70..aea80476a 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleTable.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleTable.java @@ -55,8 +55,6 @@ import com.vaadin.ui.UI; /** * Header of Software module table. * - * - * */ @SpringComponent @ViewScope @@ -82,6 +80,7 @@ public class SoftwareModuleTable extends AbstractTable { /** * Initialize the filter layout. */ + @Override @PostConstruct protected void init() { super.init(); @@ -110,37 +109,30 @@ public class SoftwareModuleTable extends AbstractTable { }); } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.server.ui.common.table.SPTable#getTableId() - */ @Override protected String getTableId() { return SPUIComponetIdProvider.UPLOAD_SOFTWARE_MODULE_TABLE; } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.server.ui.common.table.SPTable#createContainer() - */ @Override protected Container createContainer() { - final Map queryConfiguration = new HashMap(); - artifactUploadState.getSoftwareModuleFilters().getSearchText() - .ifPresent(value -> queryConfiguration.put(SPUIDefinitions.FILTER_BY_TEXT, value)); + final Map queryConfiguration = prepareQueryConfigFilters(); - artifactUploadState.getSoftwareModuleFilters().getSoftwareModuleType() - .ifPresent(type -> queryConfiguration.put(SPUIDefinitions.BY_SOFTWARE_MODULE_TYPE, type)); - - final BeanQueryFactory swQF = new BeanQueryFactory( - BaseSwModuleBeanQuery.class); + final BeanQueryFactory swQF = new BeanQueryFactory<>(BaseSwModuleBeanQuery.class); swQF.setQueryConfiguration(queryConfiguration); - final LazyQueryContainer container = new LazyQueryContainer( - new LazyQueryDefinition(true, SPUIDefinitions.PAGE_SIZE, "swId"), swQF); - return container; + return new LazyQueryContainer(new LazyQueryDefinition(true, SPUIDefinitions.PAGE_SIZE, "swId"), swQF); + } + + private Map prepareQueryConfigFilters() { + final Map queryConfig = new HashMap<>(); + artifactUploadState.getSoftwareModuleFilters().getSearchText() + .ifPresent(value -> queryConfig.put(SPUIDefinitions.FILTER_BY_TEXT, value)); + + artifactUploadState.getSoftwareModuleFilters().getSoftwareModuleType() + .ifPresent(type -> queryConfig.put(SPUIDefinitions.BY_SOFTWARE_MODULE_TYPE, type)); + + return queryConfig; } @Override @@ -265,16 +257,9 @@ public class SoftwareModuleTable extends AbstractTable { select(swModule.getId()); } - /* - * (non-Javadoc) - * - * @see - * org.eclipse.hawkbit.server.ui.common.table.SPTable#getTableVisibleColumns - * () - */ @Override protected List getTableVisibleColumns() { - final List columnList = new ArrayList(); + final List columnList = new ArrayList<>(); if (isMaximized()) { columnList.add(new TableColumn(SPUILabelDefinitions.VAR_NAME, i18n.get("header.name"), 0.2F)); columnList.add(new TableColumn(SPUILabelDefinitions.VAR_VERSION, i18n.get("header.version"), 0.1F)); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleTableHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleTableHeader.java index b08c4defb..71605ec9f 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleTableHeader.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtable/SoftwareModuleTableHeader.java @@ -62,6 +62,7 @@ public class SoftwareModuleTableHeader extends AbstractTableHeader { /** * Initialize the components. */ + @Override @PostConstruct protected void init() { super.init(); @@ -84,55 +85,26 @@ public class SoftwareModuleTableHeader extends AbstractTableHeader { eventbus.unsubscribe(this); } - /* - * (non-Javadoc) - * - * @see - * org.eclipse.hawkbit.server.ui.common.table.SPTableHeader#getHeaderCaption - * () - */ @Override protected String getHeaderCaption() { return i18n.get("upload.swModuleTable.header"); } - /* - * (non-Javadoc) - * - * @see - * org.eclipse.hawkbit.server.ui.common.table.SPTableHeader#getSearchBoxId() - */ @Override protected String getSearchBoxId() { return SPUIComponetIdProvider.SW_MODULE_SEARCH_TEXT_FIELD; } - /* - * (non-Javadoc) - * - * @see hawkbit.server.ui.common.table.SPTableHeader#getSearchRestIconId() - */ @Override protected String getSearchRestIconId() { return SPUIComponetIdProvider.SW_MODULE_SEARCH_RESET_ICON; } - /* - * (non-Javadoc) - * - * @see - * org.eclipse.hawkbit.server.ui.common.table.SPTableHeader#getAddIconId() - */ @Override protected String getAddIconId() { return SPUIComponetIdProvider.SW_MODULE_ADD_BUTTON; } - /* - * (non-Javadoc) - * - * @see hawkbit.server.ui.common.table.SPTableHeader#onLoadSearchBoxValue() - */ @Override protected String onLoadSearchBoxValue() { if (artifactUploadState.getSoftwareModuleFilters().getSearchText().isPresent()) { @@ -141,68 +113,34 @@ public class SoftwareModuleTableHeader extends AbstractTableHeader { return null; } - /* - * (non-Javadoc) - * - * @see - * org.eclipse.hawkbit.server.ui.common.table.SPTableHeader#getDropFilterId( - * ) - */ @Override protected String getDropFilterId() { /* No dropping on software module table header in Upload View */ return null; } - /* - * (non-Javadoc) - * - * @see hawkbit.server.ui.common.table.SPTableHeader#hasCreatePermission() - */ @Override protected boolean hasCreatePermission() { return permChecker.hasCreateDistributionPermission(); } - /* - * (non-Javadoc) - * - * @see hawkbit.server.ui.common.table.SPTableHeader#isDropHintRequired() - */ @Override protected boolean isDropHintRequired() { /* No dropping on software module table header in Upload View */ return false; } - /* - * (non-Javadoc) - * - * @see hawkbit.server.ui.common.table.SPTableHeader#isDropFilterRequired() - */ @Override protected boolean isDropFilterRequired() { /* No dropping on software module table header in Upload View */ return false; } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.server.ui.common.table.SPTableHeader# - * getShowFilterButtonLayoutId() - */ @Override protected String getShowFilterButtonLayoutId() { return "show.type.icon"; } - /* - * (non-Javadoc) - * - * @see hawkbit.server.ui.common.table.SPTableHeader#showFilterButtonsLayout - * () - */ @Override protected void showFilterButtonsLayout() { artifactUploadState.setSwTypeFilterClosed(false); @@ -210,37 +148,19 @@ public class SoftwareModuleTableHeader extends AbstractTableHeader { } - /* - * (non-Javadoc) - * - * @see - * org.eclipse.hawkbit.server.ui.common.table.SPTableHeader#resetSearchText( - * ) - */ @Override protected void resetSearchText() { - artifactUploadState.getSoftwareModuleFilters().setSearchText(null); - eventbus.publish(this, SMFilterEvent.REMOVER_FILTER_BY_TEXT); + if (artifactUploadState.getSoftwareModuleFilters().getSearchText().isPresent()) { + artifactUploadState.getSoftwareModuleFilters().setSearchText(null); + eventbus.publish(this, SMFilterEvent.REMOVER_FILTER_BY_TEXT); + } } - /* - * (non-Javadoc) - * - * @see - * org.eclipse.hawkbit.server.ui.common.table.SPTableHeader#getMaxMinIconId( - * ) - */ @Override protected String getMaxMinIconId() { return SPUIComponetIdProvider.SW_MAX_MIN_TABLE_ICON; } - /* - * (non-Javadoc) - * - * @see - * org.eclipse.hawkbit.server.ui.common.table.SPTableHeader#maximizeTable() - */ @Override public void maximizeTable() { artifactUploadState.setSwModuleTableMaximized(Boolean.TRUE); @@ -248,58 +168,28 @@ public class SoftwareModuleTableHeader extends AbstractTableHeader { } - /* - * (non-Javadoc) - * - * @see - * org.eclipse.hawkbit.server.ui.common.table.SPTableHeader#minimizeTable() - */ @Override public void minimizeTable() { artifactUploadState.setSwModuleTableMaximized(Boolean.FALSE); eventbus.publish(this, new SoftwareModuleEvent(SoftwareModuleEventType.MINIMIZED, null)); } - /* - * (non-Javadoc) - * - * @see hawkbit.server.ui.common.table.SPTableHeader#onLoadIsTableMaximized( - * ) - */ @Override public Boolean onLoadIsTableMaximized() { return artifactUploadState.isSwModuleTableMaximized(); } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.server.ui.common.table.SPTableHeader# - * onLoadIsShowFilterButtonDisplayed() - */ @Override public Boolean onLoadIsShowFilterButtonDisplayed() { return artifactUploadState.isSwTypeFilterClosed(); } - /* - * (non-Javadoc) - * - * @see hawkbit.server.ui.common.table.SPTableHeader#searchBy(java.lang. - * String) - */ @Override protected void searchBy(final String newSearchText) { artifactUploadState.getSoftwareModuleFilters().setSearchText(newSearchText); eventbus.publish(this, SMFilterEvent.FILTER_BY_TEXT); } - /* - * (non-Javadoc) - * - * @see hawkbit.server.ui.common.table.SPTableHeader#addNewItem(com.vaadin. - * ui.Button.ClickEvent) - */ @Override protected void addNewItem(final ClickEvent event) { final Window addSoftwareModule = softwareModuleAddUpdateWindow.createAddSoftwareModuleWindow(); @@ -308,11 +198,6 @@ public class SoftwareModuleTableHeader extends AbstractTableHeader { addSoftwareModule.setVisible(Boolean.TRUE); } - /* - * (non-Javadoc) - * - * @see hawkbit.server.ui.common.table.AbstractTableHeader#canAddNewItem() - */ @Override protected Boolean isAddNewItemAllowed() { return Boolean.TRUE; @@ -333,47 +218,21 @@ public class SoftwareModuleTableHeader extends AbstractTableHeader { return null; } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.server.ui.common.table.AbstractTableHeader# - * getBulkUploadIconId() - */ @Override protected String getBulkUploadIconId() { return null; } - /* - * (non-Javadoc) - * - * @see hawkbit.server.ui.common.table.AbstractTableHeader#bulkUpload(com. - * vaadin.ui.Button.ClickEvent ) - */ @Override protected void bulkUpload(final ClickEvent event) { - /** - * No implementation as no bulk upload is supported. - */ + // No implementation as no bulk upload is supported. } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.server.ui.common.table.AbstractTableHeader# - * isBulkUploadAllowed() - */ @Override protected Boolean isBulkUploadAllowed() { return Boolean.FALSE; } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.ui.common.table.AbstractTableHeader# - * isBulkUploadInProgress() - */ @Override protected boolean isBulkUploadInProgress() { return false; diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtype/CreateUpdateSoftwareTypeLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtype/CreateUpdateSoftwareTypeLayout.java index 824faa83b..dc6d1081e 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtype/CreateUpdateSoftwareTypeLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtype/CreateUpdateSoftwareTypeLayout.java @@ -193,7 +193,7 @@ public class CreateUpdateSoftwareTypeLayout extends CustomComponent implements C getPreviewButtonColor(DEFAULT_COLOR); tagColorPreviewBtn.setStyleName(TAG_DYNAMIC_STYLE); - selectors = new HashSet(); + selectors = new HashSet<>(); selectedColor = new Color(44, 151, 32); selPreview = new SpColorPickerPreview(selectedColor); @@ -438,17 +438,17 @@ public class CreateUpdateSoftwareTypeLayout extends CustomComponent implements C if (permChecker.hasUpdateDistributionPermission()) { optionValues.add(updateType.getValue()); } - createOptionGroup(optionValues); + createOptionGroupByValues(optionValues); } private void singleMultiOptionGroup() { final List optionValues = new ArrayList<>(); optionValues.add(singleAssign.getValue()); optionValues.add(multiAssign.getValue()); - assignOptionGroup(optionValues); + assignOptionGroupByValues(optionValues); } - private void createOptionGroup(final List tagOptions) { + private void createOptionGroupByValues(final List tagOptions) { createOptiongroup = new OptionGroup("", tagOptions); createOptiongroup.setStyleName(ValoTheme.OPTIONGROUP_SMALL); createOptiongroup.addStyleName("custom-option-group"); @@ -458,7 +458,7 @@ public class CreateUpdateSoftwareTypeLayout extends CustomComponent implements C } } - private void assignOptionGroup(final List tagOptions) { + private void assignOptionGroupByValues(final List tagOptions) { assignOptiongroup = new OptionGroup("", tagOptions); assignOptiongroup.setStyleName(ValoTheme.OPTIONGROUP_SMALL); assignOptiongroup.addStyleName("custom-option-group"); @@ -741,38 +741,17 @@ public class CreateUpdateSoftwareTypeLayout extends CustomComponent implements C return null; } - /* - * (non-Javadoc) - * - * @see com.vaadin.ui.components.colorpicker.HasColorChangeListener# - * addColorChangeListener(com.vaadin - * .ui.components.colorpicker.ColorChangeListener) - */ @Override public void addColorChangeListener(final ColorChangeListener listener) { LOG.debug("inside addColorChangeListener"); } - /* - * (non-Javadoc) - * - * @see com.vaadin.ui.components.colorpicker.HasColorChangeListener# - * removeColorChangeListener(com. - * vaadin.ui.components.colorpicker.ColorChangeListener) - */ @Override public void removeColorChangeListener(final ColorChangeListener listener) { LOG.debug("inside removeColorChangeListener"); } - /* - * (non-Javadoc) - * - * @see - * com.vaadin.ui.components.colorpicker.ColorSelector#setColor(com.vaadin. - * shared.ui.colorpicker .Color) - */ @Override public void setColor(final Color color) { if (color == null) { @@ -788,23 +767,11 @@ public class CreateUpdateSoftwareTypeLayout extends CustomComponent implements C } - /* - * (non-Javadoc) - * - * @see com.vaadin.ui.components.colorpicker.ColorSelector#getColor() - */ @Override public Color getColor() { return null; } - /* - * (non-Javadoc) - * - * @see - * com.vaadin.ui.components.colorpicker.ColorChangeListener#colorChanged(com - * .vaadin.ui.components .colorpicker.ColorChangeEvent) - */ @Override public void colorChanged(final ColorChangeEvent event) { setColor(event.getColor()); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtype/SMTypeFilterButtons.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtype/SMTypeFilterButtons.java index 43c2452c2..9f2aa9602 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtype/SMTypeFilterButtons.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtype/SMTypeFilterButtons.java @@ -35,8 +35,6 @@ import com.vaadin.spring.annotation.ViewScope; /** * Software module type filter buttons. * - * - * */ @SpringComponent @ViewScope @@ -59,6 +57,7 @@ public class SMTypeFilterButtons extends AbstractFilterButtons { * @param filterButtonClickBehaviour * the clickable behaviour. */ + @Override public void init(final AbstractFilterButtonClickBehaviour filterButtonClickBehaviour) { super.init(filterButtonClickBehaviour); eventBus.subscribe(this); @@ -86,58 +85,28 @@ public class SMTypeFilterButtons extends AbstractFilterButtons { } } - /* - * (non-Javadoc) - * - * @see - * org.eclipse.hawkbit.server.ui.layouts.SPFilterButtons#getButtonsTableId() - */ @Override protected String getButtonsTableId() { return SPUIComponetIdProvider.SW_MODULE_TYPE_TABLE_ID; } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.server.ui.layouts.SPFilterButtons# - * createButtonsLazyQueryContainer() - */ @Override protected LazyQueryContainer createButtonsLazyQueryContainer() { return HawkbitCommonUtil.createLazyQueryContainer( new BeanQueryFactory(SoftwareModuleTypeBeanQuery.class)); } - /* - * (non-Javadoc) - * - * @see hawkbit.server.ui.layouts.SPFilterButtons#isClickedByDefault(java. - * lang.Long) - */ @Override protected boolean isClickedByDefault(final Long buttonId) { return artifactUploadState.getSoftwareModuleFilters().getSoftwareModuleType().isPresent() && artifactUploadState .getSoftwareModuleFilters().getSoftwareModuleType().get().getId().equals(buttonId); } - /* - * (non-Javadoc) - * - * @see hawkbit.server.ui.layouts.SPFilterButtons#createButtonId(java.lang. - * String) - */ @Override protected String createButtonId(final String name) { return SPUIComponetIdProvider.SM_TYPE_FILTER_BTN_ID + name; } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.server.ui.common.filterlayout.SPFilterButtons# - * getFilterButtonDropHandler() - */ @Override protected DropHandler getFilterButtonDropHandler() { return new DropHandler() { @@ -155,13 +124,6 @@ public class SMTypeFilterButtons extends AbstractFilterButtons { }; } - /* - * (non-Javadoc) - * - * @see - * org.eclipse.hawkbit.server.ui.common.filterlayout.AbstractFilterButtons# - * getButttonWrapperId() - */ @Override protected String getButttonWrapperIdPrefix() { return SPUIComponetIdProvider.UPLOAD_TYPE_BUTTON_PREFIX; diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtype/SMTypeFilterHeader.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtype/SMTypeFilterHeader.java index d0292dd48..b79ecb742 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtype/SMTypeFilterHeader.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/smtype/SMTypeFilterHeader.java @@ -28,9 +28,6 @@ import com.vaadin.ui.Window; /** * Software module type filter buttons header. - * - * - * */ @SpringComponent @ViewScope @@ -53,6 +50,7 @@ public class SMTypeFilterHeader extends AbstractFilterHeader { /** * Initialize the components. */ + @Override @PostConstruct protected void init() { super.init(); @@ -61,32 +59,16 @@ public class SMTypeFilterHeader extends AbstractFilterHeader { } } - /* - * (non-Javadoc) - * - * @see hawkbit.server.ui.layouts.SPFilterHeader#hasCreateUpdatePermission() - */ @Override protected boolean hasCreateUpdatePermission() { return permChecker.hasCreateDistributionPermission() || permChecker.hasUpdateDistributionPermission(); } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.server.ui.layouts.SPFilterHeader#getTitle() - */ @Override protected String getTitle() { return SPUILabelDefinitions.TYPE; } - /* - * (non-Javadoc) - * - * @see hawkbit.server.ui.layouts.SPFilterHeader#settingsIconClicked(com. - * vaadin.ui.Button.ClickEvent ) - */ @Override protected void settingsIconClicked(final ClickEvent event) { final Window addUpdateWindow = createUpdateSWTypeLayout.getWindow(); @@ -94,58 +76,27 @@ public class SMTypeFilterHeader extends AbstractFilterHeader { addUpdateWindow.setVisible(Boolean.TRUE); } - /* - * (non-Javadoc) - * - * @see - * org.eclipse.hawkbit.server.ui.layouts.SPFilterHeader#dropHitsRequired() - */ @Override protected boolean dropHitsRequired() { return false; } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.server.ui.common.filterlayout.SPFilterHeader# - * hideFilterButtonLayout() - */ @Override protected void hideFilterButtonLayout() { artifactUploadState.setSwTypeFilterClosed(true); eventbus.publish(this, UploadArtifactUIEvent.HIDE_FILTER_BY_TYPE); } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.server.ui.common.filterlayout.SPFilterHeader# - * getConfigureFilterButtonId() - */ @Override protected String getConfigureFilterButtonId() { return SPUIDefinitions.ADD_SOFTWARE_MODULE_TYPE; } - /* - * (non-Javadoc) - * - * @see - * org.eclipse.hawkbit.server.ui.common.filterlayout.AbstractFilterHeader# - * getHideButtonId() - */ @Override protected String getHideButtonId() { return SPUIComponetIdProvider.SM_SHOW_FILTER_BUTTON_ID; } - /* - * (non-Javadoc) - * - * @see org.eclipse.hawkbit.ui.common.filterlayout.AbstractFilterHeader# - * isAddTagRequired() - */ @Override protected boolean isAddTagRequired() { return true; diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/state/ArtifactUploadState.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/state/ArtifactUploadState.java index d653bb371..a502b341c 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/state/ArtifactUploadState.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/state/ArtifactUploadState.java @@ -38,21 +38,21 @@ public class ArtifactUploadState implements Serializable { private final Map deleteSofwareModules = new HashMap<>(); - private final Set fileSelected = new HashSet(); + private final Set fileSelected = new HashSet<>(); private Long selectedBaseSwModuleId; private SoftwareModule selectedBaseSoftwareModule; - private final Map baseSwModuleList = new HashMap(); + private final Map baseSwModuleList = new HashMap<>(); private Set selectedSoftwareModules = Collections.emptySet(); private boolean swTypeFilterClosed = Boolean.FALSE; - private boolean isSwModuleTableMaximized = Boolean.FALSE; + private boolean swModuleTableMaximized = Boolean.FALSE; - private boolean isArtifactDetailsMaximized = Boolean.FALSE; + private boolean artifactDetailsMaximized = Boolean.FALSE; private final Set selectedDeleteSWModuleTypes = new HashSet<>(); @@ -60,7 +60,7 @@ public class ArtifactUploadState implements Serializable { /** * Set software. - * + * * @return */ public SoftwareModuleFilters getSoftwareModuleFilters() { @@ -85,7 +85,7 @@ public class ArtifactUploadState implements Serializable { * @return the selectedBaseSwModuleId */ public Optional getSelectedBaseSwModuleId() { - return this.selectedBaseSwModuleId != null ? Optional.of(this.selectedBaseSwModuleId) : Optional.empty(); + return selectedBaseSwModuleId != null ? Optional.of(selectedBaseSwModuleId) : Optional.empty(); } /** @@ -100,8 +100,7 @@ public class ArtifactUploadState implements Serializable { * @return the selectedBaseSoftwareModule */ public Optional getSelectedBaseSoftwareModule() { - return this.selectedBaseSoftwareModule == null ? Optional.empty() - : Optional.of(this.selectedBaseSoftwareModule); + return selectedBaseSoftwareModule == null ? Optional.empty() : Optional.of(selectedBaseSoftwareModule); } /** @@ -153,15 +152,15 @@ public class ArtifactUploadState implements Serializable { * @return the isSwModuleTableMaximized */ public boolean isSwModuleTableMaximized() { - return isSwModuleTableMaximized; + return swModuleTableMaximized; } /** * @param isSwModuleTableMaximized * the isSwModuleTableMaximized to set */ - public void setSwModuleTableMaximized(final boolean isSwModuleTableMaximized) { - this.isSwModuleTableMaximized = isSwModuleTableMaximized; + public void setSwModuleTableMaximized(final boolean swModuleTableMaximized) { + this.swModuleTableMaximized = swModuleTableMaximized; } public Set getSelectedDeleteSWModuleTypes() { @@ -172,15 +171,15 @@ public class ArtifactUploadState implements Serializable { * @return the isArtifactDetailsMaximized */ public boolean isArtifactDetailsMaximized() { - return isArtifactDetailsMaximized; + return artifactDetailsMaximized; } /** * @param isArtifactDetailsMaximized * the isArtifactDetailsMaximized to set */ - public void setArtifactDetailsMaximized(final boolean isArtifactDetailsMaximized) { - this.isArtifactDetailsMaximized = isArtifactDetailsMaximized; + public void setArtifactDetailsMaximized(final boolean artifactDetailsMaximized) { + this.artifactDetailsMaximized = artifactDetailsMaximized; } /** diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/state/CustomFile.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/state/CustomFile.java index ff350511a..3dbbcc7db 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/state/CustomFile.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/state/CustomFile.java @@ -10,8 +10,6 @@ package org.eclipse.hawkbit.ui.artifacts.state; import java.io.Serializable; -import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; - /** * Custom file to hold details of uploaded file. * @@ -168,36 +166,50 @@ public class CustomFile implements Serializable { return failureReason; } - /* - * (non-Javadoc) - * - * @see java.lang.Object#hashCode() - */ @Override - public int hashCode() { // NOSONAR - as this is generated + public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + (fileName == null ? 0 : fileName.hashCode()); + result = prime * result + ((baseSoftwareModuleName == null) ? 0 : baseSoftwareModuleName.hashCode()); + result = prime * result + ((baseSoftwareModuleVersion == null) ? 0 : baseSoftwareModuleVersion.hashCode()); + result = prime * result + ((fileName == null) ? 0 : fileName.hashCode()); return result; } - /* - * (non-Javadoc) - * - * @see java.lang.Object#equals(java.lang.Object) - */ @Override public boolean equals(final Object obj) { if (this == obj) { return true; } - if (!(obj instanceof CustomFile)) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { return false; } final CustomFile other = (CustomFile) obj; - return HawkbitCommonUtil.bothSame(fileName, other.fileName) - && HawkbitCommonUtil.bothSame(baseSoftwareModuleName, other.baseSoftwareModuleName) - && HawkbitCommonUtil.bothSame(baseSoftwareModuleVersion, other.baseSoftwareModuleVersion); + if (baseSoftwareModuleName == null) { + if (other.baseSoftwareModuleName != null) { + return false; + } + } else if (!baseSoftwareModuleName.equals(other.baseSoftwareModuleName)) { + return false; + } + if (baseSoftwareModuleVersion == null) { + if (other.baseSoftwareModuleVersion != null) { + return false; + } + } else if (!baseSoftwareModuleVersion.equals(other.baseSoftwareModuleVersion)) { + return false; + } + if (fileName == null) { + if (other.fileName != null) { + return false; + } + } else if (!fileName.equals(other.fileName)) { + return false; + } + return true; } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadConfirmationwindow.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadConfirmationwindow.java index 51ac43ba5..f009985f9 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadConfirmationwindow.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadConfirmationwindow.java @@ -56,11 +56,7 @@ import com.vaadin.ui.themes.ValoTheme; /** * Artifact upload confirmation popup. - * - * - * - * - * + * */ public class UploadConfirmationwindow implements Button.ClickListener { @@ -106,7 +102,7 @@ public class UploadConfirmationwindow implements Button.ClickListener { private IndexedContainer tabelContainer; - private final List uploadResultList = new ArrayList(); + private final List uploadResultList = new ArrayList<>(); private VerticalLayout uploadArtifactDetails; diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadHandler.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadHandler.java index 834246c63..8c1feeb33 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadHandler.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadHandler.java @@ -108,8 +108,10 @@ public class UploadHandler implements StreamVariable, Receiver, SucceededListene public OutputStream receiveUpload(final String fileName, final String mimeType) { this.fileName = fileName; this.mimeType = mimeType; + //reset has directory flag before upload + view.setHasDirectory(false); try { - if (view.validate()) { + if (view.checkIfSoftwareModuleIsSelected()) { if (view.checkForDuplicate(fileName)) { view.showDuplicateMessage(); } else { @@ -167,7 +169,7 @@ public class UploadHandler implements StreamVariable, Receiver, SucceededListene view.updateActionCount(); // display the duplicate message after streaming all files - view.displayDuplicateMessageAfterStreamingAll(); + view.displayDuplicateValidationMessage(); } /** @@ -292,7 +294,7 @@ public class UploadHandler implements StreamVariable, Receiver, SucceededListene if (view.enableProcessBtn()) { infoWindow.uploadSessionFinished(); } - view.displayDuplicateMessageAfterStreamingAll(); + view.displayDuplicateValidationMessage(); LOG.info("Streaming failed due to :{}", event.getException()); } @@ -316,7 +318,6 @@ public class UploadHandler implements StreamVariable, Receiver, SucceededListene } view.updateActionCount(); infoWindow.uploadFailed(event.getFilename(), failureReason); - LOG.info("Upload failed for file :{}", event.getReason()); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadLayout.java index e570c84c4..41304ddc9 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadLayout.java @@ -46,6 +46,7 @@ import org.vaadin.spring.events.EventBus; import org.vaadin.spring.events.EventScope; import org.vaadin.spring.events.annotation.EventBusListenerMethod; +import com.google.common.base.Strings; import com.vaadin.event.dd.DragAndDropEvent; import com.vaadin.event.dd.DropHandler; import com.vaadin.event.dd.acceptcriteria.AcceptAll; @@ -69,8 +70,6 @@ import com.vaadin.ui.VerticalLayout; /** * Upload files layout. - * - * */ @ViewScope @SpringComponent @@ -102,7 +101,7 @@ public class UploadLayout extends VerticalLayout { private final AtomicInteger numberOfFilesActuallyUpload = new AtomicInteger(); - private final List duplicateFileNamesList = new ArrayList(); + private final List duplicateFileNamesList = new ArrayList<>(); private Button processBtn; @@ -118,6 +117,8 @@ public class UploadLayout extends VerticalLayout { private DragAndDropWrapper dropAreaWrapper; + private Boolean hasDirectory = Boolean.FALSE; + /** * Initialize the upload layout. */ @@ -184,27 +185,60 @@ public class UploadLayout extends VerticalLayout { @Override public void drop(final DragAndDropEvent event) { - if (validate()) { - + if (validate(event)) { final Html5File[] files = ((WrapperTransferable) event.getTransferable()).getFiles(); - if (files != null) { - for (final Html5File file : files) { - if (!checkForDuplicate(file.getFileName())) { - numberOfFileUploadsExpected.incrementAndGet(); - file.setStreamVariable(createStreamVariable(file)); - } - } - if (numberOfFileUploadsExpected.get() > 0) { - processBtn.setEnabled(false); - // reset before we start - uploadInfoWindow.uploadSessionStarted(); - } - // in case if all selected files are duplicate ,then display - // message - displayDuplicateMessageAfterStreamingAll(); + // reset the flag + hasDirectory = Boolean.FALSE; + for (final Html5File file : files) { + processFile(file); + } + if (numberOfFileUploadsExpected.get() > 0) { + processBtn.setEnabled(false); + // reset before we start + uploadInfoWindow.uploadSessionStarted(); + } else { + // If the upload is not started, it signifies all + // dropped files as either duplicate or directory.So + // display message accordingly + displayCompositeMessage(); } } } + + private void processFile(final Html5File file) { + if (!isDirectory(file)) { + if (!checkForDuplicate(file.getFileName())) { + numberOfFileUploadsExpected.incrementAndGet(); + file.setStreamVariable(createStreamVariable(file)); + } + } else { + hasDirectory = Boolean.TRUE; + } + } + } + + private static boolean isDirectory(final Html5File file) { + if (Strings.isNullOrEmpty(file.getType()) && file.getFileSize() % 4096 == 0) { + return true; + } + return false; + } + + private void displayCompositeMessage() { + final String duplicateMessage = getDuplicateFileValidationMessage(); + final StringBuilder compositeMessage = new StringBuilder(); + if (!Strings.isNullOrEmpty(duplicateMessage)) { + compositeMessage.append(duplicateMessage); + } + if (hasDirectory) { + if (compositeMessage.length() > 0) { + compositeMessage.append("
"); + } + compositeMessage.append(i18n.get("message.no.directory.upload")); + } + if (!compositeMessage.toString().isEmpty()) { + uiNotification.displayValidationError(compositeMessage.toString()); + } } private VerticalLayout createDropAreaLayout() { @@ -315,10 +349,33 @@ public class UploadLayout extends VerticalLayout { } } - Boolean validate() { + Boolean validate(DragAndDropEvent event) { + // check if drop is valid.If valid ,check if software module is + // selected. + if(!isFilesDropped(event)){ + uiNotification.displayValidationError(i18n.get("message.action.not.allowed")); + return false; + } + return checkIfSoftwareModuleIsSelected(); + } + + private boolean isFilesDropped(DragAndDropEvent event) { + if (event.getTransferable() instanceof WrapperTransferable) { + final Html5File[] files = ((WrapperTransferable) event.getTransferable()).getFiles(); + // other components can also be wrapped in WrapperTransferable , so + // additional check on files + if (files == null) { + return false; + } + return true; + } else { + return false; + } + } + + Boolean checkIfSoftwareModuleIsSelected() { if (!isSoftwareModuleSelected()) { uiNotification.displayValidationError(i18n.get("message.error.noSwModuleSelected")); - return false; } return true; @@ -383,27 +440,30 @@ public class UploadLayout extends VerticalLayout { } } - void displayDuplicateMessageAfterStreamingAll() { + void displayDuplicateValidationMessage() { // check if streaming of all dropped files are completed if (numberOfFilesActuallyUpload.intValue() == numberOfFileUploadsExpected.intValue()) { - showDuplicateMessage(); + displayCompositeMessage(); } } - /** - * Show duplicate file selected message. - */ - public void showDuplicateMessage() { + private String getDuplicateFileValidationMessage() { + StringBuilder message = new StringBuilder(); if (!duplicateFileNamesList.isEmpty()) { final String fileNames = StringUtils.collectionToCommaDelimitedString(duplicateFileNamesList); if (duplicateFileNamesList.size() == 1) { - uiNotification.displayValidationError(i18n.get("message.no.duplicateFile") + fileNames); + message.append(i18n.get("message.no.duplicateFile") + fileNames); } else if (duplicateFileNamesList.size() > 1) { - uiNotification.displayValidationError(i18n.get("message.no.duplicateFiles")); + message.append(i18n.get("message.no.duplicateFiles")); } duplicateFileNamesList.clear(); } + return message.toString(); + } + + public void showDuplicateMessage() { + uiNotification.displayValidationError(getDuplicateFileValidationMessage()); } void increaseNumberOfFileUploadsExpected() { @@ -595,4 +655,8 @@ public class UploadLayout extends VerticalLayout { return uiNotification; } + + public void setHasDirectory(Boolean hasDirectory) { + this.hasDirectory = hasDirectory; + } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadResultWindow.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadResultWindow.java index a4470b8e1..c5319a221 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadResultWindow.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadResultWindow.java @@ -44,7 +44,7 @@ public class UploadResultWindow implements Button.ClickListener { private static final long serialVersionUID = 5205927189362269027L; - private List uploadResultList = new ArrayList(); + private List uploadResultList = new ArrayList<>(); private Button closeBtn; diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/ConfirmationDialog.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/ConfirmationDialog.java index 7ee6de97c..c0f42909d 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/ConfirmationDialog.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/ConfirmationDialog.java @@ -29,9 +29,6 @@ import com.vaadin.ui.themes.ValoTheme; * * module. * - * - * - * */ public class ConfirmationDialog implements Button.ClickListener { /** Serial version UID. */ @@ -127,6 +124,7 @@ public class ConfirmationDialog implements Button.ClickListener { * @param event * the click event. */ + @Override public void buttonClick(final ClickEvent event) { if (window.getParent() != null) { UI.getCurrent().removeWindow(window); @@ -146,6 +144,7 @@ public class ConfirmationDialog implements Button.ClickListener { /** * Interface for confirmation dialog callbacks. */ + @FunctionalInterface public interface ConfirmationDialogCallback { /** * The user response. diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/DistributionSetTypeBeanQuery.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/DistributionSetTypeBeanQuery.java index 787e51d83..b09255bc7 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/DistributionSetTypeBeanQuery.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/DistributionSetTypeBeanQuery.java @@ -83,7 +83,7 @@ public class DistributionSetTypeBeanQuery extends AbstractBeanQuery loadBeans(final int startIndex, final int count) { Page typeBeans; - final List distSetTypeList = new ArrayList(); + final List distSetTypeList = new ArrayList<>(); if (startIndex == 0 && firstPageDistSetType != null) { typeBeans = firstPageDistSetType; } else { diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/filterlayout/AbstractFilterSingleButtonClick.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/filterlayout/AbstractFilterSingleButtonClick.java index ccfbc922c..03b74b08d 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/filterlayout/AbstractFilterSingleButtonClick.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/filterlayout/AbstractFilterSingleButtonClick.java @@ -8,8 +8,6 @@ */ package org.eclipse.hawkbit.ui.common.filterlayout; -import java.util.Optional; - import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; import com.vaadin.ui.Button; @@ -17,20 +15,20 @@ import com.vaadin.ui.Button.ClickEvent; /** * Abstract Single button click behaviour of filter buttons layout. - * * * - * + * + * */ public abstract class AbstractFilterSingleButtonClick extends AbstractFilterButtonClickBehaviour { private static final long serialVersionUID = 478874092615793581L; - private Optional