diff --git a/.gitignore b/.gitignore index e49219859..9ccbafa9d 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,10 @@ local.properties .settings/ .loadpath +# IDEA +*.iml +.idea + # External tool builders .externalToolBuilders/ diff --git a/README.md b/README.md index 778391f32..b9e826898 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [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. Build: [![Circle CI](https://circleci.com/gh/eclipse/hawkbit.svg?style=shield)](https://circleci.com/gh/eclipse/hawkbit) -[![SonarQuality](https://sonar.eu-gb.mybluemix.net/api/badges/gate?key=org.eclipse.hawkbit:hawkbit-parent)](https://sonar.eu-gb.mybluemix.net) +[![SonarQuality](https://sonar.eu-gb.mybluemix.net/api/badges/gate?key=org.eclipse.hawkbit:hawkbit-parent)](https://sonar.eu-gb.mybluemix.net)[![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.eclipse.hawkbit/hawkbit-parent/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.eclipse.hawkbit/hawkbit-parent) # Documentation @@ -78,4 +78,4 @@ $ java -jar ./examples/hawkbit-example-mgmt-simulator/target/hawkbit-example-mgm * `hawkbit-security-core` : Core security elements. * `hawkbit-security-integration` : Security integration elements to integrate security into hawkBit. * `hawkbit-test-report` : Test reports -* `hawkbit-ui` : Vaadin UI. \ No newline at end of file +* `hawkbit-ui` : Vaadin UI. diff --git a/examples/pom.xml b/examples/pom.xml index 85dd70651..5f8718e0a 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -8,8 +8,7 @@ http://www.eclipse.org/legal/epl-v10.html --> - + 4.0.0 @@ -22,14 +21,31 @@ hawkBit-example :: Parent pom - - hawkbit-device-simulator - hawkbit-example-app - hawkbit-example-core-feign-client - hawkbit-example-ddi-feign-client - hawkbit-example-mgmt-feign-client - hawkbit-example-mgmt-simulator - + + + noExampleApp + + hawkbit-example-core-feign-client + hawkbit-example-ddi-feign-client + hawkbit-example-mgmt-feign-client + hawkbit-example-mgmt-simulator + + + + default + + true + + + hawkbit-device-simulator + hawkbit-example-app + hawkbit-example-core-feign-client + hawkbit-example-ddi-feign-client + hawkbit-example-mgmt-feign-client + hawkbit-example-mgmt-simulator + + + diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryFields.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryFields.java new file mode 100644 index 000000000..1cee4c369 --- /dev/null +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryFields.java @@ -0,0 +1,70 @@ +/** + * 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.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Describing the fields of the Target model which can be used in the REST API + * e.g. for sorting etc. + * + */ +public enum TargetFilterQueryFields implements FieldNameProvider { + + /** + * The id field. + */ + ID("id"), + + /** + * The name field. + */ + NAME("name"), + + /** + * distribution set which is set as auto assign distribution set + */ + AUTOASSIGNDISTRIBUTIONSET("autoAssignDistributionSet", "name", "version"); + + + private final String fieldName; + private List subEntityAttributes; + private boolean mapField; + + TargetFilterQueryFields(final String fieldName) { + this(fieldName, false, Collections.emptyList()); + } + + TargetFilterQueryFields(final String fieldName, final String... subEntityAttribues) { + this(fieldName, false, Arrays.asList(subEntityAttribues)); + } + + TargetFilterQueryFields(final String fieldName, final boolean mapField, final List subEntityAttribues) { + this.fieldName = fieldName; + this.mapField = mapField; + this.subEntityAttributes = subEntityAttribues; + } + + @Override + public List getSubEntityAttributes() { + return subEntityAttributes; + } + + @Override + public boolean isMap() { + return mapField; + } + + @Override + public String getFieldName() { + return fieldName; + } +} 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 eb4cacf33..28e9708ac 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 @@ -21,6 +21,8 @@ import org.springframework.security.authentication.InsufficientAuthenticationExc import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; +import com.google.common.collect.Lists; + import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Features; import ru.yandex.qatools.allure.annotations.Stories; @@ -45,8 +47,8 @@ public class PreAuthTokenSourceTrustAuthenticationProviderTest { public void principalAndCredentialsNotTheSameThrowsAuthenticationException() { final String principal = "controllerIdURL"; final String credentials = "controllerIdHeader"; - final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(principal, - credentials); + final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken( + principal, Lists.newArrayList(credentials)); token.setDetails(webAuthenticationDetailsMock); // test, should throw authentication exception @@ -64,11 +66,12 @@ public class PreAuthTokenSourceTrustAuthenticationProviderTest { public void principalAndCredentialsAreTheSameWithNoSourceIpCheckIsSuccessful() { final String principal = "controllerId"; final String credentials = "controllerId"; - final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(principal, - credentials); + final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken( + principal, Lists.newArrayList(credentials)); token.setDetails(webAuthenticationDetailsMock); - final Authentication authenticate = underTestWithoutSourceIpCheck.authenticate(token); + final Authentication authenticate = underTestWithoutSourceIpCheck + .authenticate(token); assertThat(authenticate.isAuthenticated()).isTrue(); } @@ -78,8 +81,8 @@ public class PreAuthTokenSourceTrustAuthenticationProviderTest { final String remoteAddress = "192.168.1.1"; final String principal = "controllerId"; final String credentials = "controllerId"; - final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(principal, - credentials); + final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken( + principal, Lists.newArrayList(credentials)); token.setDetails(webAuthenticationDetailsMock); when(webAuthenticationDetailsMock.getRemoteAddress()).thenReturn(remoteAddress); @@ -99,14 +102,15 @@ public class PreAuthTokenSourceTrustAuthenticationProviderTest { public void priniciapAndCredentialsAreTheSameAndSourceIpIsTrusted() { final String principal = "controllerId"; final String credentials = "controllerId"; - final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(principal, - credentials); + final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken( + principal, Lists.newArrayList(credentials)); token.setDetails(webAuthenticationDetailsMock); when(webAuthenticationDetailsMock.getRemoteAddress()).thenReturn(REQUEST_SOURCE_IP); // test, should throw authentication exception - final Authentication authenticate = underTestWithSourceIpCheck.authenticate(token); + final Authentication authenticate = underTestWithSourceIpCheck + .authenticate(token); assertThat(authenticate.isAuthenticated()).isTrue(); } @@ -116,8 +120,8 @@ public class PreAuthTokenSourceTrustAuthenticationProviderTest { "192.168.1.3" }; final String principal = "controllerId"; final String credentials = "controllerId"; - final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(principal, - credentials); + final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken( + principal, Lists.newArrayList(credentials)); token.setDetails(webAuthenticationDetailsMock); when(webAuthenticationDetailsMock.getRemoteAddress()).thenReturn(REQUEST_SOURCE_IP); @@ -135,8 +139,8 @@ public class PreAuthTokenSourceTrustAuthenticationProviderTest { final String[] trustedIPAddresses = new String[] { "192.168.1.1", "192.168.1.2", "192.168.1.3" }; final String principal = "controllerId"; final String credentials = "controllerId"; - final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(principal, - credentials); + final PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken( + principal, Lists.newArrayList(credentials)); token.setDetails(webAuthenticationDetailsMock); when(webAuthenticationDetailsMock.getRemoteAddress()).thenReturn(REQUEST_SOURCE_IP); diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targetfilter/MgmtTargetFilterQuery.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targetfilter/MgmtTargetFilterQuery.java new file mode 100644 index 000000000..2a3acbe53 --- /dev/null +++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targetfilter/MgmtTargetFilterQuery.java @@ -0,0 +1,71 @@ +/** + * 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.json.model.targetfilter; + +import org.eclipse.hawkbit.mgmt.json.model.MgmtBaseEntity; + +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; + +/** + * A json annotated rest model for Target Filter Queries to RESTful API + * representation. + * + */ +@JsonInclude(Include.ALWAYS) +@JsonIgnoreProperties(ignoreUnknown = true) +public class MgmtTargetFilterQuery extends MgmtBaseEntity { + + @JsonProperty(value = "id", required = true) + private Long filterId; + + @JsonProperty + private String name; + + @JsonProperty + private String query; + + @JsonProperty + private Long autoAssignDistributionSet; + + public Long getFilterId() { + return filterId; + } + + public void setFilterId(final Long filterId) { + this.filterId = filterId; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public String getQuery() { + return query; + } + + public void setQuery(final String query) { + this.query = query; + } + + public Long getAutoAssignDistributionSet() { + return autoAssignDistributionSet; + } + + public void setAutoAssignDistributionSet(final Long autoAssignDistributionSet) { + this.autoAssignDistributionSet = autoAssignDistributionSet; + } + +} diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targetfilter/MgmtTargetFilterQueryRequestBody.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targetfilter/MgmtTargetFilterQueryRequestBody.java new file mode 100644 index 000000000..ecfe1291b --- /dev/null +++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/targetfilter/MgmtTargetFilterQueryRequestBody.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.mgmt.json.model.targetfilter; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Request body for target PUT/POST commands. + * + */ +public class MgmtTargetFilterQueryRequestBody { + @JsonProperty(required = true) + private String name; + + @JsonProperty(required = true) + private String query; + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public String getQuery() { + return query; + } + + public void setQuery(String query) { + this.query = query; + } + +} diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtDistributionSetRestApi.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtDistributionSetRestApi.java index d3439adc6..13eff9831 100644 --- a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtDistributionSetRestApi.java +++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtDistributionSetRestApi.java @@ -20,6 +20,7 @@ import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentR import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule; import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModuleAssigment; import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget; +import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; @@ -179,6 +180,37 @@ public interface MgmtDistributionSetRestApi { @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam); + /** + * Handles the GET request to retrieve target filter queries that have the + * given distribution set as auto assign DS. + * + * @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 name parameter in the request URL, syntax + * {@code q=myFilter} + * @return status OK if get request is successful with the paged list of + * targets + */ + @RequestMapping(method = RequestMethod.GET, value = "/{distributionSetId}/autoAssignTargetFilters", produces = { + MediaType.APPLICATION_JSON_VALUE, "application/hal+json" }) + ResponseEntity> getAutoAssignTargetFilterQueries( + @PathVariable("distributionSetId") final Long distributionSetId, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam); + /** * Handles the POST request of assigning multiple targets to a single * distribution set. diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java index e8c25403f..a46fbf79a 100644 --- a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java +++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtRestConstants.java @@ -104,6 +104,11 @@ public final class MgmtRestConstants { public static final String DISTRIBUTIONSET_TAG_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/distributionsettags"; + /** + * The target URL mapping rest resource. + */ + public static final String TARGET_FILTER_V1_REQUEST_MAPPING = BASE_V1_REQUEST_MAPPING + "/targetfilters"; + /** * The tag URL mapping rest resource. */ diff --git a/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetFilterQueryRestApi.java b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetFilterQueryRestApi.java new file mode 100644 index 000000000..7b7d51353 --- /dev/null +++ b/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/rest/api/MgmtTargetFilterQueryRestApi.java @@ -0,0 +1,156 @@ +/** + * 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.rest.api; + +import org.eclipse.hawkbit.mgmt.json.model.MgmtId; +import org.eclipse.hawkbit.mgmt.json.model.PagedList; +import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet; +import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery; +import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQueryRequestBody; +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(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING) +public interface MgmtTargetFilterQueryRestApi { + + /** + * Handles the GET request of retrieving a single target filter. + * + * @param filterId + * the ID of the target filter to retrieve + * @return a single target with status OK. + */ + + @RequestMapping(method = RequestMethod.GET, value = "/{filterId}", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + ResponseEntity getFilter(@PathVariable("filterId") final Long filterId); + + /** + * Handles the GET request of retrieving all filters. + * + * @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 }) + ResponseEntity> getFilters( + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) final int pagingOffsetParam, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) final int pagingLimitParam, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false) final String sortParam, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH, required = false) final String rsqlParam); + + /** + * Handles the POST request of creating new target filters. The request body + * must always be a list of target filters. + * + * @param filter + * the filters to be created. + * @return In case all filters were successfully created the ResponseEntity + * with status code 201 with a list of successfully created entities + * is returned. 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 }) + ResponseEntity createFilter(@RequestBody final MgmtTargetFilterQueryRequestBody filter); + + /** + * Handles the PUT request of updating a target filter. 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 filterId + * the path parameter which contains the ID of the target filter + * @param targetFilterRest + * the request body which contains the fields which should be + * updated, fields which are not given are ignored for the + * update. + * @return the updated target filter response which contains all fields + * including fields which have not been updated + */ + @RequestMapping(method = RequestMethod.PUT, value = "/{filterId}", consumes = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + ResponseEntity updateFilter(@PathVariable("filterId") final Long filterId, + @RequestBody final MgmtTargetFilterQueryRequestBody targetFilterRest); + + /** + * Handles the DELETE request of deleting a target filter. + * + * @param filterId + * the ID of the target filter to be deleted + * @return If the given controllerId could exists and could be deleted Http + * OK. In any failure the JsonResponseExceptionHandler is handling + * the response. + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/{filterId}", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + ResponseEntity deleteFilter(@PathVariable("filterId") final Long filterId); + + /** + * Handles the GET request of retrieving the distribution set for auto + * assignment of an specific target filter. + * + * @param filterId + * 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. "{}") + */ + @RequestMapping(method = RequestMethod.GET, value = "/{filterId}/autoAssignDS", produces = { "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }) + ResponseEntity getAssignedDistributionSet(@PathVariable("filterId") final Long filterId); + + /** + * Handles the POST request for changing distribution set for auto + * assignment of a target filter. + * + * @param filterId + * of the target to change + * @param dsId + * of the Id of the auto assign distribution set + * @return http status + */ + @RequestMapping(method = RequestMethod.POST, value = "/{filterId}/autoAssignDS", consumes = { + "application/hal+json", + MediaType.APPLICATION_JSON_VALUE }, produces = { "application/hal+json", MediaType.APPLICATION_JSON_VALUE }) + ResponseEntity postAssignedDistributionSet(@PathVariable("filterId") final Long filterId, + @RequestBody final MgmtId dsId); + + /** + * Handles the DELETE request for removing the distribution set for auto + * assignment of a target filter. + * + * @param filterId + * of the target to change + * @return http status + */ + @RequestMapping(method = RequestMethod.DELETE, value = "/{filterId}/autoAssignDS") + ResponseEntity deleteAssignedDistributionSet(@PathVariable("filterId") final Long filterId); + +} diff --git a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java index b49654460..7fd534320 100644 --- a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java +++ b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResource.java @@ -24,6 +24,7 @@ import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtTargetAssignmentR import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule; import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModuleAssigment; import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget; +import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery; import org.eclipse.hawkbit.mgmt.rest.api.MgmtDistributionSetRestApi; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.repository.DeploymentManagement; @@ -33,12 +34,14 @@ import org.eclipse.hawkbit.repository.EntityFactory; import org.eclipse.hawkbit.repository.OffsetBasedPageRequest; import org.eclipse.hawkbit.repository.SoftwareManagement; import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.model.TargetWithActionType; import org.eclipse.hawkbit.security.SystemSecurityContext; import org.slf4j.Logger; @@ -67,6 +70,9 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi { @Autowired private TargetManagement targetManagement; + @Autowired + private TargetFilterQueryManagement targetFilterQueryManagement; + @Autowired private DeploymentManagement deployManagament; @@ -224,6 +230,35 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi { HttpStatus.OK); } + @Override + public ResponseEntity> getAutoAssignTargetFilterQueries( + @PathVariable("distributionSetId") Long distributionSetId, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) int pagingOffsetParam, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) int pagingLimitParam, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false) String sortParam, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH, required = false) String rsqlParam) { + DistributionSet distributionSet = findDistributionSetWithExceptionIfNotFound(distributionSetId); + + final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam); + final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam); + final Sort sorting = PagingUtility.sanitizeTargetFilterQuerySortParam(sortParam); + + final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting); + final Page targetFilterQueries; + if (rsqlParam != null) { + targetFilterQueries = targetFilterQueryManagement.findTargetFilterQueryByAutoAssignDS(pageable, + distributionSet, rsqlParam); + } else { + targetFilterQueries = targetFilterQueryManagement.findTargetFilterQueryByAutoAssignDS(pageable, + distributionSet); + } + + return new ResponseEntity<>( + new PagedList<>(MgmtTargetFilterQueryMapper.toResponse(targetFilterQueries.getContent()), + targetFilterQueries.getTotalElements()), + HttpStatus.OK); + } + @Override public ResponseEntity createAssignedTarget( @PathVariable("distributionSetId") final Long distributionSetId, diff --git a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryMapper.java b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryMapper.java new file mode 100644 index 000000000..b2a23e259 --- /dev/null +++ b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryMapper.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.mgmt.rest.resource; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery; +import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQueryRequestBody; +import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetFilterQueryRestApi; +import org.eclipse.hawkbit.repository.EntityFactory; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.TargetFilterQuery; +import org.springframework.util.CollectionUtils; + +/** + * A mapper which maps repository model to RESTful model representation and + * back. + * + */ +public final class MgmtTargetFilterQueryMapper { + + private MgmtTargetFilterQueryMapper() { + // Utility class + } + + /** + * Create a response for target filter queries. + * + * @param filters + * list of targets + * @return the response + */ + public static List toResponse(final List filters) { + if (CollectionUtils.isEmpty(filters)) { + return Collections.emptyList(); + } + return filters.stream().map(MgmtTargetFilterQueryMapper::toResponse).collect(Collectors.toList()); + } + + /** + * Create a response for target filter query. + * + * @param filter + * the target + * @return the response + */ + public static MgmtTargetFilterQuery toResponse(final TargetFilterQuery filter) { + final MgmtTargetFilterQuery targetRest = new MgmtTargetFilterQuery(); + targetRest.setFilterId(filter.getId()); + targetRest.setName(filter.getName()); + targetRest.setQuery(filter.getQuery()); + + targetRest.setCreatedBy(filter.getCreatedBy()); + targetRest.setLastModifiedBy(filter.getLastModifiedBy()); + + targetRest.setCreatedAt(filter.getCreatedAt()); + targetRest.setLastModifiedAt(filter.getLastModifiedAt()); + + DistributionSet distributionSet = filter.getAutoAssignDistributionSet(); + if (distributionSet != null) { + targetRest.setAutoAssignDistributionSet(distributionSet.getId()); + } + + targetRest.add(linkTo(methodOn(MgmtTargetFilterQueryRestApi.class).getFilter(filter.getId())).withRel("self")); + targetRest.add( + linkTo(methodOn(MgmtTargetFilterQueryRestApi.class).postAssignedDistributionSet(filter.getId(), null)) + .withRel("autoAssignDS")); + + return targetRest; + } + + static TargetFilterQuery fromRequest(final EntityFactory entityFactory, + final MgmtTargetFilterQueryRequestBody filterRest) { + final TargetFilterQuery filter = entityFactory.generateTargetFilterQuery(); + filter.setName(filterRest.getName()); + filter.setQuery(filterRest.getQuery()); + + return filter; + } + +} diff --git a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryResource.java b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryResource.java new file mode 100644 index 000000000..1fc7c6c85 --- /dev/null +++ b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryResource.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.mgmt.rest.resource; + +import java.util.List; + +import org.eclipse.hawkbit.mgmt.json.model.MgmtId; +import org.eclipse.hawkbit.mgmt.json.model.PagedList; +import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet; +import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQuery; +import org.eclipse.hawkbit.mgmt.json.model.targetfilter.MgmtTargetFilterQueryRequestBody; +import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; +import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetFilterQueryRestApi; +import org.eclipse.hawkbit.repository.DistributionSetManagement; +import org.eclipse.hawkbit.repository.EntityFactory; +import org.eclipse.hawkbit.repository.OffsetBasedPageRequest; +import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; +import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.TargetFilterQuery; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +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.http.HttpStatus; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * REST Resource handling target CRUD operations. + */ +@RestController +public class MgmtTargetFilterQueryResource implements MgmtTargetFilterQueryRestApi { + private static final Logger LOG = LoggerFactory.getLogger(MgmtTargetFilterQueryResource.class); + + @Autowired + private TargetFilterQueryManagement filterManagement; + + @Autowired + private DistributionSetManagement distributionSetManagement; + + @Autowired + private EntityFactory entityFactory; + + @Override + public ResponseEntity getFilter(@PathVariable("filterId") Long filterId) { + final TargetFilterQuery findTarget = findFilterWithExceptionIfNotFound(filterId); + // to single response include poll status + final MgmtTargetFilterQuery response = MgmtTargetFilterQueryMapper.toResponse(findTarget); + + return new ResponseEntity<>(response, HttpStatus.OK); + } + + @Override + public ResponseEntity> getFilters( + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET) int pagingOffsetParam, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT, defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT) int pagingLimitParam, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false) String sortParam, + @RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH, required = false) String rsqlParam) { + + final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam); + final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam); + final Sort sorting = PagingUtility.sanitizeTargetFilterQuerySortParam(sortParam); + + final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting); + final Slice findTargetFiltersAll; + final Long countTargetsAll; + if (rsqlParam != null) { + final Page findFilterPage = filterManagement + .findTargetFilterQueryByFilter(pageable, rsqlParam); + countTargetsAll = findFilterPage.getTotalElements(); + findTargetFiltersAll = findFilterPage; + } else { + findTargetFiltersAll = filterManagement.findAllTargetFilterQuery(pageable); + countTargetsAll = filterManagement.countAllTargetFilterQuery(); + } + + final List rest = MgmtTargetFilterQueryMapper + .toResponse(findTargetFiltersAll.getContent()); + return new ResponseEntity<>(new PagedList(rest, countTargetsAll), HttpStatus.OK); + } + + @Override + public ResponseEntity createFilter(@RequestBody MgmtTargetFilterQueryRequestBody filter) { + final TargetFilterQuery createdTarget = filterManagement + .createTargetFilterQuery(MgmtTargetFilterQueryMapper.fromRequest(entityFactory, filter)); + + return new ResponseEntity<>(MgmtTargetFilterQueryMapper.toResponse(createdTarget), HttpStatus.CREATED); + } + + @Override + public ResponseEntity updateFilter(@PathVariable("filterId") Long filterId, + @RequestBody MgmtTargetFilterQueryRequestBody targetFilterRest) { + + final TargetFilterQuery existingFilter = findFilterWithExceptionIfNotFound(filterId); + LOG.debug("updating target filter query {}", existingFilter.getId()); + if (targetFilterRest.getName() != null) { + existingFilter.setName(targetFilterRest.getName()); + } + if (targetFilterRest.getQuery() != null) { + existingFilter.setQuery(targetFilterRest.getQuery()); + } + + final TargetFilterQuery updateFilter = filterManagement.updateTargetFilterQuery(existingFilter); + + return new ResponseEntity<>(MgmtTargetFilterQueryMapper.toResponse(updateFilter), HttpStatus.OK); + } + + @Override + public ResponseEntity deleteFilter(@PathVariable("filterId") Long filterId) { + final TargetFilterQuery filter = findFilterWithExceptionIfNotFound(filterId); + filterManagement.deleteTargetFilterQuery(filter.getId()); + LOG.debug("{} target filter query deleted, return status {}", filterId, HttpStatus.OK); + return new ResponseEntity<>(HttpStatus.OK); + } + + @Override + public ResponseEntity postAssignedDistributionSet(@PathVariable("filterId") Long filterId, + @RequestBody MgmtId dsId) { + final TargetFilterQuery filter = findFilterWithExceptionIfNotFound(filterId); + + DistributionSet distributionSet; + distributionSet = distributionSetManagement.findDistributionSetById(dsId.getId()); + if (distributionSet == null) { + throw new EntityNotFoundException("DistributionSet with Id {" + dsId + "} does not exist"); + } + + filter.setAutoAssignDistributionSet(distributionSet); + + final TargetFilterQuery updateFilter = filterManagement.updateTargetFilterQuery(filter); + + return new ResponseEntity<>(MgmtTargetFilterQueryMapper.toResponse(updateFilter), HttpStatus.OK); + } + + @Override + public ResponseEntity getAssignedDistributionSet(@PathVariable("filterId") Long filterId) { + final TargetFilterQuery filter = findFilterWithExceptionIfNotFound(filterId); + DistributionSet autoAssignDistributionSet = filter.getAutoAssignDistributionSet(); + MgmtDistributionSet distributionSetRest = MgmtDistributionSetMapper.toResponse(autoAssignDistributionSet); + final HttpStatus retStatus = distributionSetRest == null ? HttpStatus.NO_CONTENT : HttpStatus.OK; + return new ResponseEntity<>(distributionSetRest, retStatus); + } + + @Override + public ResponseEntity deleteAssignedDistributionSet(@PathVariable("filterId") Long filterId) { + final TargetFilterQuery filter = findFilterWithExceptionIfNotFound(filterId); + + filter.setAutoAssignDistributionSet(null); + + filterManagement.updateTargetFilterQuery(filter); + + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + private TargetFilterQuery findFilterWithExceptionIfNotFound(final Long filterId) { + final TargetFilterQuery filter = filterManagement.findTargetFilterQueryById(filterId); + if (filter == null) { + throw new EntityNotFoundException("TargetFilterQuery with Id {" + filterId + "} does not exist"); + } + return filter; + } + +} diff --git a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/PagingUtility.java b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/PagingUtility.java index abd9e9ec4..d70109e3d 100644 --- a/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/PagingUtility.java +++ b/hawkbit-mgmt-resource/src/main/java/org/eclipse/hawkbit/mgmt/rest/resource/PagingUtility.java @@ -20,6 +20,7 @@ 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.eclipse.hawkbit.repository.TargetFilterQueryFields; import org.eclipse.hawkbit.rest.util.SortUtility; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; @@ -59,6 +60,14 @@ public final class PagingUtility { return new Sort(SortUtility.parse(TargetFields.class, sortParam)); } + static Sort sanitizeTargetFilterQuerySortParam(final String sortParam) { + if (sortParam == null) { + // default + return new Sort(Direction.ASC, TargetFilterQueryFields.NAME.getFieldName()); + } + return new Sort(SortUtility.parse(TargetFilterQueryFields.class, sortParam)); + } + static Sort sanitizeSoftwareModuleSortParam(final String sortParam) { if (sortParam == null) { // default diff --git a/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResourceTest.java b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResourceTest.java index 725a10538..62b8548f1 100644 --- a/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResourceTest.java +++ b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtDistributionSetResourceTest.java @@ -30,11 +30,8 @@ import java.util.Set; import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; +import org.eclipse.hawkbit.repository.model.*; import org.eclipse.hawkbit.repository.model.Action.Status; -import org.eclipse.hawkbit.repository.model.DistributionSet; -import org.eclipse.hawkbit.repository.model.DistributionSetMetadata; -import org.eclipse.hawkbit.repository.model.SoftwareModule; -import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.test.util.TestdataFactory; import org.eclipse.hawkbit.repository.test.util.WithUser; import org.eclipse.hawkbit.rest.AbstractRestIntegrationTest; @@ -297,6 +294,84 @@ public class MgmtDistributionSetResourceTest extends AbstractRestIntegrationTest .andExpect(jsonPath("$.content[0].controllerId", equalTo(knownTargetId))); } + @Test + @Description("Ensures that target filters with auto assign DS are returned as persisted in the repository.") + public void getAutoAssignTargetFiltersOfDistributionSet() throws Exception { + // prepare distribution set + final String knownFilterName = "a"; + final Set createDistributionSetsAlphabetical = createDistributionSetsAlphabetical(1); + final DistributionSet createdDs = createDistributionSetsAlphabetical.iterator().next(); + + targetFilterQueryManagement + .createTargetFilterQuery(entityFactory.generateTargetFilterQuery(knownFilterName, "x==y", createdDs)); + // create some dummy target filter queries + targetFilterQueryManagement.createTargetFilterQuery(entityFactory.generateTargetFilterQuery("b", "x==y")); + targetFilterQueryManagement.createTargetFilterQuery(entityFactory.generateTargetFilterQuery("c", "x==y")); + + mvc.perform(get(MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + createdDs.getId() + + "/autoAssignTargetFilters")).andExpect(status().isOk()).andExpect(jsonPath("$.size", equalTo(1))) + .andExpect(jsonPath("$.content[0].name", equalTo(knownFilterName))); + } + + @Test + @Description("Ensures that an error is returned when the query is invalid.") + public void getAutoAssignTargetFiltersOfDSWithInvalidFilter() throws Exception { + // prepare distribution set + final Set createDistributionSetsAlphabetical = createDistributionSetsAlphabetical(1); + final DistributionSet createdDs = createDistributionSetsAlphabetical.iterator().next(); + final String invalidQuery = "unknownField=le=42"; + + mvc.perform(get(MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + createdDs.getId() + + "/autoAssignTargetFilters").param(MgmtRestConstants.REQUEST_PARAMETER_SEARCH, invalidQuery)) + .andExpect(status().isBadRequest()); + } + + @Test + @Description("Ensures that target filters with auto assign DS are returned according to the query.") + public void getMultipleAutoAssignTargetFiltersOfDistributionSet() throws Exception { + final String filterNamePrefix = "filter-"; + final Set createDistributionSetsAlphabetical = createDistributionSetsAlphabetical(1); + final DistributionSet createdDs = createDistributionSetsAlphabetical.iterator().next(); + final String query = "name==" + filterNamePrefix + "*"; + + // create target filter queries that should be found + targetFilterQueryManagement.createTargetFilterQuery( + entityFactory.generateTargetFilterQuery(filterNamePrefix + "1", "x==y", createdDs)); + targetFilterQueryManagement.createTargetFilterQuery( + entityFactory.generateTargetFilterQuery(filterNamePrefix + "2", "x==z", createdDs)); + // create some dummy target filter queries + targetFilterQueryManagement.createTargetFilterQuery(entityFactory.generateTargetFilterQuery("b", "x==y")); + targetFilterQueryManagement.createTargetFilterQuery(entityFactory.generateTargetFilterQuery("c", "x==y")); + + mvc.perform(get(MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + createdDs.getId() + + "/autoAssignTargetFilters").param(MgmtRestConstants.REQUEST_PARAMETER_SEARCH, query)) + .andExpect(status().isOk()).andExpect(jsonPath("$.size", equalTo(2))) + .andExpect(jsonPath("$.content[0].name", equalTo(filterNamePrefix + "1"))) + .andExpect(jsonPath("$.content[1].name", equalTo(filterNamePrefix + "2"))); + } + + @Test + @Description("Ensures that no target filters are returned according to the non matching query.") + public void getEmptyAutoAssignTargetFiltersOfDistributionSet() throws Exception { + final String filterNamePrefix = "filter-"; + final Set createDistributionSetsAlphabetical = createDistributionSetsAlphabetical(1); + final DistributionSet createdDs = createDistributionSetsAlphabetical.iterator().next(); + final String query = "name==doesNotExist"; + + // create target filter queries that should be found + targetFilterQueryManagement.createTargetFilterQuery( + entityFactory.generateTargetFilterQuery(filterNamePrefix + "1", "x==y", createdDs)); + targetFilterQueryManagement.createTargetFilterQuery( + entityFactory.generateTargetFilterQuery(filterNamePrefix + "2", "x==z", createdDs)); + // create some dummy target filter queries + targetFilterQueryManagement.createTargetFilterQuery(entityFactory.generateTargetFilterQuery("b", "x==y")); + targetFilterQueryManagement.createTargetFilterQuery(entityFactory.generateTargetFilterQuery("c", "x==y")); + + mvc.perform(get(MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + createdDs.getId() + + "/autoAssignTargetFilters").param(MgmtRestConstants.REQUEST_PARAMETER_SEARCH, query)) + .andExpect(status().isOk()).andExpect(jsonPath("$.size", equalTo(0))); + } + @Test @Description("Ensures that DS in repository are listed with proper paging properties.") public void getDistributionSetsWithoutAddtionalRequestParameters() throws Exception { diff --git a/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryResourceTest.java b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryResourceTest.java new file mode 100644 index 000000000..5694c9bfb --- /dev/null +++ b/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtTargetFilterQueryResourceTest.java @@ -0,0 +1,362 @@ +/** + * 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.rest.resource; + +import static com.google.common.collect.Lists.newArrayList; +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.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.util.*; +import java.util.stream.Collectors; + +import org.eclipse.hawkbit.exception.SpServerError; +import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; +import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; +import org.eclipse.hawkbit.repository.model.*; +import org.eclipse.hawkbit.repository.model.Action.ActionType; +import org.eclipse.hawkbit.rest.AbstractRestIntegrationTest; +import org.eclipse.hawkbit.rest.exception.MessageNotReadableException; +import org.eclipse.hawkbit.rest.json.model.ExceptionInfo; +import org.eclipse.hawkbit.rest.util.JsonBuilder; +import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter; +import org.json.JSONObject; +import org.junit.Test; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MvcResult; + +import com.jayway.jsonpath.JsonPath; + +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +/** + * Spring MVC Tests against the MgmtTargetResource. + * + */ +@Features("Component Tests - Management API") +@Stories("Target Filter Query Resource") +public class MgmtTargetFilterQueryResourceTest extends AbstractRestIntegrationTest { + + private static final String TARGET_DESCRIPTION_TEST = "created in test"; + + private static final String JSON_PATH_ROOT = "$"; + + // fields, attributes + private static final String JSON_PATH_FIELD_ID = ".id"; + private static final String JSON_PATH_FIELD_NAME = ".name"; + private static final String JSON_PATH_FIELD_QUERY = ".query"; + private static final String JSON_PATH_FIELD_CONTENT = ".content"; + private static final String JSON_PATH_FIELD_SIZE = ".size"; + private static final String JSON_PATH_FIELD_TOTAL = ".total"; + private static final String JSON_PATH_FIELD_AUTO_ASSIGN_DS = ".autoAssignDistributionSet"; + + // target + // $.field + static final String JSON_PATH_PAGED_LIST_CONTENT = JSON_PATH_ROOT + JSON_PATH_FIELD_CONTENT; + static final String JSON_PATH_PAGED_LIST_SIZE = JSON_PATH_ROOT + JSON_PATH_FIELD_SIZE; + static final String JSON_PATH_PAGED_LIST_TOTAL = JSON_PATH_ROOT + JSON_PATH_FIELD_TOTAL; + + private static final String JSON_PATH_NAME = JSON_PATH_ROOT + JSON_PATH_FIELD_NAME; + private static final String JSON_PATH_ID = JSON_PATH_ROOT + JSON_PATH_FIELD_ID; + private static final String JSON_PATH_QUERY = JSON_PATH_ROOT + JSON_PATH_FIELD_QUERY; + private static final String JSON_PATH_AUTO_ASSIGN_DS = JSON_PATH_ROOT + JSON_PATH_FIELD_AUTO_ASSIGN_DS; + + + @Test + @Description("Ensures that deletion is executed if permitted.") + public void deleteTargetFilterQueryReturnsOK() throws Exception { + final String filterName = "filter_01"; + TargetFilterQuery filterQuery = createSingleTargetFilterQuery(filterName, "name=test_01"); + + mvc.perform(delete(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + filterQuery.getId())) + .andExpect(status().isOk()); + + TargetFilterQuery tfq = targetFilterQueryManagement.findTargetFilterQueryById(filterQuery.getId()); + assertThat(tfq).isNull(); + } + + @Test + @Description("Ensures that deletion is refused with not found if target does not exist.") + public void deleteTargetWhichDoesNotExistsLeadsToEntityNotFound() throws Exception { + final String notExistingId = "4395"; + + mvc.perform(delete(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + notExistingId)) + .andExpect(status().isNotFound()); + } + + @Test + @Description("Ensures that update is refused with not found if target does not exist.") + public void updateTargetWhichDoesNotExistsLeadsToEntityNotFound() throws Exception { + final String notExistingId = "4395"; + mvc.perform(put(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + notExistingId).content("{}") + .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()) + .andExpect(status().isNotFound()); + } + + @Test + @Description("Ensures that update request is reflected by repository.") + public void updateTargetFilterQueryQuery() throws Exception { + final String filterName = "filter_02"; + final String filterQuery = "name=test_02"; + final String filterQuery2 = "name=test_02_changed"; + final String body = new JSONObject().put("query", filterQuery2).toString(); + + // prepare + TargetFilterQuery tfq = createSingleTargetFilterQuery(filterName, filterQuery); + + mvc.perform(put(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId()).content(body) + .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(jsonPath("$.id", equalTo(tfq.getId().intValue()))) + .andExpect(jsonPath("$.query", equalTo(filterQuery2))) + .andExpect(jsonPath("$.name", equalTo(filterName))); + + TargetFilterQuery tfqCheck = targetFilterQueryManagement.findTargetFilterQueryById(tfq.getId()); + assertThat(tfqCheck.getQuery()).isEqualTo(filterQuery2); + assertThat(tfqCheck.getName()).isEqualTo(filterName); + } + + @Test + @Description("Ensures that update request is reflected by repository.") + public void updateTargetFilterQueryName() throws Exception { + final String filterName = "filter_03"; + final String filterName2 = "filter_03_changed"; + final String filterQuery = "name=test_03"; + final String body = new JSONObject().put("name", filterName2).toString(); + + // prepare + TargetFilterQuery tfq = entityFactory.generateTargetFilterQuery(); + tfq.setName(filterName); + tfq.setQuery(filterQuery); + tfq = targetFilterQueryManagement.createTargetFilterQuery(tfq); + + mvc.perform(put(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId()).content(body) + .contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(jsonPath("$.id", equalTo(tfq.getId().intValue()))) + .andExpect(jsonPath("$.query", equalTo(filterQuery))) + .andExpect(jsonPath("$.name", equalTo(filterName2))); + + TargetFilterQuery tfqCheck = targetFilterQueryManagement.findTargetFilterQueryById(tfq.getId()); + assertThat(tfqCheck.getQuery()).isEqualTo(filterQuery); + assertThat(tfqCheck.getName()).isEqualTo(filterName2); + } + + + @Test + @Description("Ensures that request returns list of filters in defined format.") + public void getTargetFilterQueryWithoutAdditionalRequestParameters() throws Exception { + final int knownTargetAmount = 3; + final String idA = "a"; + final String idB = "b"; + final String idC = "c"; + final String testQuery = "name=test"; + + createSingleTargetFilterQuery(idA, testQuery); + createSingleTargetFilterQuery(idB, testQuery); + createSingleTargetFilterQuery(idC, testQuery); + + mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING)).andExpect(status().isOk()) + .andDo(MockMvcResultPrinter.print()) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_TOTAL, equalTo(knownTargetAmount))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_SIZE, equalTo(knownTargetAmount))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_CONTENT, hasSize(knownTargetAmount))) + // idA + .andExpect(jsonPath("$.content.[?(@.name==" + idA + ")].name", contains(idA))) + .andExpect(jsonPath("$.content.[?(@.name==" + idA + ")].query", contains(testQuery))) + // idB + .andExpect(jsonPath("$.content.[?(@.name==" + idB + ")].name", contains(idB))) + .andExpect(jsonPath("$.content.[?(@.name==" + idB + ")].query", contains(testQuery))) + // idC + .andExpect(jsonPath("$.content.[?(@.name==" + idC + ")].name", contains(idC))) + .andExpect(jsonPath("$.content.[?(@.name==" + idC + ")].query", contains(testQuery))); + } + + @Test + @Description("Ensures that request returns list of filters in defined format in size reduced by given limit parameter.") + public void getTargetWithPagingLimitRequestParameter() throws Exception { + final int limitSize = 1; + final int knownTargetAmount = 3; + final String idA = "a"; + final String idB = "b"; + final String idC = "c"; + final String testQuery = "name=test"; + + createSingleTargetFilterQuery(idA, testQuery); + createSingleTargetFilterQuery(idB, testQuery); + createSingleTargetFilterQuery(idC, testQuery); + + mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING) + .param(MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT, String.valueOf(limitSize))) + .andExpect(status().isOk()).andDo(MockMvcResultPrinter.print()) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_TOTAL, equalTo(knownTargetAmount))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_SIZE, equalTo(limitSize))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_CONTENT, hasSize(limitSize))) + // idA + .andExpect(jsonPath("$.content.[?(@.name==" + idA + ")].name", contains(idA))) + .andExpect(jsonPath("$.content.[?(@.name==" + idA + ")].query", contains(testQuery))); + } + + @Test + @Description("Ensures that request returns list of filters 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; + final int expectedSize = knownTargetAmount - offsetParam; + final String idC = "c"; + final String idD = "d"; + final String idE = "e"; + final String testQuery = "name=test"; + + createSingleTargetFilterQuery("a", testQuery); + createSingleTargetFilterQuery("b", testQuery); + createSingleTargetFilterQuery(idC, testQuery); + createSingleTargetFilterQuery(idD, testQuery); + createSingleTargetFilterQuery(idE, testQuery); + + mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING) + .param(MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET, String.valueOf(offsetParam)) + .param(MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT, String.valueOf(knownTargetAmount))) + .andExpect(status().isOk()).andDo(MockMvcResultPrinter.print()) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_TOTAL, equalTo(knownTargetAmount))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_SIZE, equalTo(expectedSize))) + .andExpect(jsonPath(JSON_PATH_PAGED_LIST_CONTENT, hasSize(expectedSize))) + // idA + .andExpect(jsonPath("$.content.[?(@.name==" + idC + ")].name", contains(idC))) + .andExpect(jsonPath("$.content.[?(@.name==" + idC + ")].query", contains(testQuery))) + // idB + .andExpect(jsonPath("$.content.[?(@.name==" + idD + ")].name", contains(idD))) + .andExpect(jsonPath("$.content.[?(@.name==" + idD + ")].query", contains(testQuery))) + // idC + .andExpect(jsonPath("$.content.[?(@.name==" + idE + ")].name", contains(idE))) + .andExpect(jsonPath("$.content.[?(@.name==" + idE + ")].query", contains(testQuery))); + } + + @Test + public void getSingleTarget() throws Exception { + // create first a target which can be retrieved by rest interface + final String knownQuery = "name=test01"; + final String knownName = "someName"; + final TargetFilterQuery tfq = createSingleTargetFilterQuery(knownName, knownQuery); + final String hrefPrefix = "http://localhost"+MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING+"/" + tfq.getId(); + // test + mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId())) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(jsonPath(JSON_PATH_NAME, equalTo(knownName))) + .andExpect(jsonPath(JSON_PATH_QUERY, equalTo(knownQuery))) + .andExpect(jsonPath("$._links.self.href", equalTo(hrefPrefix))) + .andExpect(jsonPath("$._links.autoAssignDS.href", equalTo(hrefPrefix + "/autoAssignDS"))); + } + + @Test + public void getSingleTargetNoExistsResponseNotFound() throws Exception { + final String targetIdNotExists = "546546"; + // test + + final MvcResult mvcResult = mvc + .perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + targetIdNotExists)) + .andExpect(status().isNotFound()).andReturn(); + + // verify response json exception message + final ExceptionInfo exceptionInfo = ResourceUtility + .convertException(mvcResult.getResponse().getContentAsString()); + assertThat(exceptionInfo.getErrorCode()).isEqualTo(SpServerError.SP_REPO_ENTITY_NOT_EXISTS.getKey()); + } + + @Test + public void createTargetFilterQueryWithBadPayloadBadRequest() throws Exception { + final String notJson = "abc"; + + final MvcResult mvcResult = mvc + .perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING).content(notJson) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isBadRequest()).andReturn(); + + assertThat(targetFilterQueryManagement.countAllTargetFilterQuery()).isEqualTo(0); + + // verify response json exception message + final ExceptionInfo exceptionInfo = ResourceUtility + .convertException(mvcResult.getResponse().getContentAsString()); + assertThat(exceptionInfo.getExceptionClass()).isEqualTo(MessageNotReadableException.class.getName()); + assertThat(exceptionInfo.getErrorCode()).isEqualTo(SpServerError.SP_REST_BODY_NOT_READABLE.getKey()); + } + + @Test + public void setAutoAssignDistributionSetToTargetFilterQuery() throws Exception { + + final String knownQuery = "name=test05"; + final String knownName = "filter05"; + + final DistributionSet set = testdataFactory.createDistributionSet("one"); + final TargetFilterQuery tfq = createSingleTargetFilterQuery(knownName, knownQuery); + + mvc.perform(post(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/"+tfq.getId()+"/autoAssignDS") + .content("{\"id\":" + set.getId() + "}").contentType(MediaType.APPLICATION_JSON)) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()); + + assertThat(targetFilterQueryManagement.findTargetFilterQueryById(tfq.getId()).getAutoAssignDistributionSet()).isEqualTo(set); + + final String hrefPrefix = "http://localhost"+MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING+"/" + tfq.getId(); + + mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/" + tfq.getId())) + .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) + .andExpect(jsonPath(JSON_PATH_NAME, equalTo(knownName))) + .andExpect(jsonPath(JSON_PATH_QUERY, equalTo(knownQuery))) + .andExpect(jsonPath(JSON_PATH_AUTO_ASSIGN_DS, equalTo(set.getId().intValue()))) + .andExpect(jsonPath("$._links.self.href", equalTo(hrefPrefix))) + .andExpect(jsonPath("$._links.autoAssignDS.href", equalTo(hrefPrefix + "/autoAssignDS"))); + } + + @Test + public void deleteAutoAssignDistributionSetOfTargetFilterQuery() throws Exception { + + final String knownQuery = "name=test06"; + final String knownName = "filter06"; + final String dsName = "testDS"; + + final DistributionSet set = testdataFactory.createDistributionSet(dsName); + TargetFilterQuery tfq = entityFactory.generateTargetFilterQuery(); + tfq.setName(knownName); + tfq.setQuery(knownQuery); + tfq.setAutoAssignDistributionSet(set); + tfq = targetFilterQueryManagement.createTargetFilterQuery(tfq); + + assertThat(targetFilterQueryManagement.findTargetFilterQueryById(tfq.getId()).getAutoAssignDistributionSet()) + .isEqualTo(set); + + mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/"+tfq.getId()+"/autoAssignDS")) + .andExpect(status().isOk()) + .andExpect(jsonPath(JSON_PATH_NAME, equalTo(dsName))); + + mvc.perform(delete(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/"+tfq.getId()+"/autoAssignDS")) + .andExpect(status().isNoContent()); + + assertThat(targetFilterQueryManagement.findTargetFilterQueryById(tfq.getId()).getAutoAssignDistributionSet()) + .isNull(); + + mvc.perform(get(MgmtRestConstants.TARGET_FILTER_V1_REQUEST_MAPPING + "/"+tfq.getId()+"/autoAssignDS")) + .andExpect(status().isNoContent()); + + + } + + private TargetFilterQuery createSingleTargetFilterQuery(final String name, final String query) { + final TargetFilterQuery target = entityFactory.generateTargetFilterQuery(); + target.setName(name); + target.setQuery(query); + return targetFilterQueryManagement.createTargetFilterQuery(target); + } + +} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/AutoAssignProperties.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/AutoAssignProperties.java new file mode 100644 index 000000000..2c9bd7849 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/AutoAssignProperties.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.repository; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Rollout Management properties. + * + */ +@ConfigurationProperties("hawkbit.autoassign") +public class AutoAssignProperties { + /** + * Autoassign scheduler configuration. + */ + public static class Scheduler { + // used by @Scheduled annotation which needs constant + public static final String PROP_SCHEDULER_DELAY_PLACEHOLDER = "${hawkbit.autoassign.scheduler.fixedDelay:60000}"; + + /** + * Schedule where the autoassign scheduler looks necessary state changes in + * milliseconds. + */ + private long fixedDelay = 60000L; + + public long getFixedDelay() { + return fixedDelay; + } + + public void setFixedDelay(final long fixedDelay) { + this.fixedDelay = fixedDelay; + } + + } + + private final Scheduler scheduler = new Scheduler(); + + public Scheduler getScheduler() { + return scheduler; + } + +} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java index 270de3e74..ab1a680ba 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/DeploymentManagement.java @@ -104,6 +104,27 @@ public interface DeploymentManagement { DistributionSetAssignmentResult assignDistributionSet(@NotNull Long dsID, @NotEmpty Collection targets); + /** + * method assigns the {@link DistributionSet} to all {@link Target}s by + * their IDs with a specific {@link ActionType} and an action message. + * + * @param dsID + * the ID of the distribution set to assign + * @param targets + * a list of all targets and their action type + * @param actionMessage + * an optional message for the action status + * @return the assignment result + * + * @throw IncompleteDistributionSetException if mandatory + * {@link SoftwareModuleType} are not assigned as define by the + * {@link DistributionSetType}. + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET) + DistributionSetAssignmentResult assignDistributionSet(@NotNull Long dsID, + @NotEmpty Collection targets, + String actionMessage); + /** * method assigns the {@link DistributionSet} to all {@link Target}s by * their IDs with a specific {@link ActionType} and {@code forcetime}. @@ -364,9 +385,6 @@ public interface DeploymentManagement { * pagination parameter * @param action * to be filtered on - * @param withMessages - * to true if {@link ActionStatus#getMessages()} - * need to be fetched. * @return the corresponding {@link Page} of {@link ActionStatus} */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) @@ -463,8 +481,6 @@ public interface DeploymentManagement { * * @param action * to be canceled - * @param target - * for which the action needs cancellation * * @return generated {@link Action} or null if not active on * {@link Target}. @@ -478,8 +494,6 @@ public interface DeploymentManagement { * Updates a {@link Action} and forces the {@link Action} if it's not * already forced. * - * @param targetId - * the ID of the target * @param actionId * the ID of the action * @return the updated or the found {@link Action} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/EntityFactory.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/EntityFactory.java index 925351807..c54f00cc6 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/EntityFactory.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/EntityFactory.java @@ -322,6 +322,30 @@ public interface EntityFactory { */ TargetFilterQuery generateTargetFilterQuery(); + /** + * Generates an {@link TargetFilterQuery} without persisting it. + * + * @param name + * name for the filter + * @param query + * query of the filter + * @return {@link TargetFilterQuery} object + */ + TargetFilterQuery generateTargetFilterQuery(String name, String query); + + /** + * Generates an {@link TargetFilterQuery} without persisting it. + * + * @param name + * name for the filter + * @param query + * query of the filter + * @param autoAssignDS + * auto assign distribution set + * @return {@link TargetFilterQuery} object + */ + TargetFilterQuery generateTargetFilterQuery(String name, String query, DistributionSet autoAssignDS); + /** * Generates an empty {@link TargetTag} without persisting it. * diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryManagement.java index d748ed470..27bb32b25 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetFilterQueryManagement.java @@ -13,6 +13,7 @@ import javax.validation.constraints.NotNull; import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException; import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; +import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -70,6 +71,13 @@ public interface TargetFilterQueryManagement { @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) Page findAllTargetFilterQuery(@NotNull Pageable pageable); + /** + * Counts all target filter queries + * @return the number of all target filter queries + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) + Long countAllTargetFilterQuery(); + /** * Retrieves all target filter query which {@link TargetFilterQuery}. * @@ -77,11 +85,64 @@ public interface TargetFilterQueryManagement { * @param pageable * pagination parameter * @param name - * target filter query name + * name filter * @return the page with the found {@link TargetFilterQuery} */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) - Page findTargetFilterQueryByFilters(@NotNull Pageable pageable, String name); + Page findTargetFilterQueryByName(@NotNull Pageable pageable, String name); + + /** + * Retrieves all target filter query which {@link TargetFilterQuery}. + * + * + * @param pageable + * pagination parameter + * @param rsqlFilter + * RSQL filter string + * @return the page with the found {@link TargetFilterQuery} + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) + Page findTargetFilterQueryByFilter(@NotNull Pageable pageable, String rsqlFilter); + + /** + * Retrieves all target filter query which {@link TargetFilterQuery}. + * + * + * @param pageable + * pagination parameter + * @param distributionSet + * the auto assign distribution set + * @return the page with the found {@link TargetFilterQuery} + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) + Page findTargetFilterQueryByAutoAssignDS(@NotNull Pageable pageable, + DistributionSet distributionSet); + + /** + * Retrieves all target filter query which {@link TargetFilterQuery}. + * + * + * @param pageable + * pagination parameter + * @param distributionSet + * the auto assign distribution set + * @param rsqlParam + * RSQL filter + * @return the page with the found {@link TargetFilterQuery} + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) + Page findTargetFilterQueryByAutoAssignDS(@NotNull Pageable pageable, + DistributionSet distributionSet, String rsqlParam); + + /** + * Retrieves all target filter query with auto assign DS which {@link TargetFilterQuery}. + * + * + * @return the page with the found {@link TargetFilterQuery} + * @param pageable + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) + Page findTargetFilterQueryWithAutoAssignDS(@NotNull Pageable pageable); /** * Find target filter query by id. @@ -114,4 +175,4 @@ public interface TargetFilterQueryManagement { */ @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) TargetFilterQuery updateTargetFilterQuery(@NotNull TargetFilterQuery targetFilterQuery); -} \ No newline at end of file +} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java index 97cce6436..adaa81c34 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java @@ -248,6 +248,37 @@ public interface TargetManagement { List findAllTargetIdsByTargetFilterQuery(@NotNull Pageable pageRequest, @NotNull TargetFilterQuery targetFilterQuery); + /** + * Finds all targets for all the given parameter {@link TargetFilterQuery} + * and that don't have the specified distribution set in their action + * history. + * + * @param pageRequest + * the pageRequest to enhance the query for paging and sorting + * @param distributionSetId + * id of the {@link DistributionSet} + * @param targetFilterQuery + * {@link TargetFilterQuery} + * @return the found {@link TargetIdName}s + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) + Page findAllTargetsByTargetFilterQueryAndNonDS(@NotNull Pageable pageRequest, Long distributionSetId, + @NotNull TargetFilterQuery targetFilterQuery); + + /** + * Counts all targets for all the given parameter {@link TargetFilterQuery} + * and that don't have the specified distribution set in their action + * history. + * + * @param distributionSetId + * id of the {@link DistributionSet} + * @param targetFilterQuery + * {@link TargetFilterQuery} + * @return the found {@link TargetIdName}s + */ + @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) + Long countTargetsByTargetFilterQueryAndNonDS(Long distributionSetId, @NotNull TargetFilterQuery targetFilterQuery); + /** * retrieves {@link Target}s by the assigned {@link DistributionSet} without * details, i.e. NO {@link Target#getTags()} and {@link Target#getActions()} diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSet.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSet.java index b0bff56cb..8704e5e17 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSet.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/DistributionSet.java @@ -88,6 +88,11 @@ public interface DistributionSet extends NamedVersionedEntity { */ List getAssignedTargets(); + /** + * @return the auto assign target filters + */ + List getAutoAssignFilters(); + /** * @return the installedTargets */ diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetFilterQuery.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetFilterQuery.java index 5052a0060..5db7593a1 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetFilterQuery.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/model/TargetFilterQuery.java @@ -56,4 +56,16 @@ public interface TargetFilterQuery extends TenantAwareBaseEntity { */ void setQuery(String query); + /** + * @return the auto assign {@link DistributionSet} if given. + */ + DistributionSet getAutoAssignDistributionSet(); + + /** + * @param distributionSet + * the {@link DistributionSet} that should be assigned to a + * target when this filter matches. + */ + void setAutoAssignDistributionSet(DistributionSet distributionSet); + } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java index 6ce33b8a9..059b6851e 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/RepositoryApplicationConfiguration.java @@ -42,6 +42,8 @@ import org.eclipse.hawkbit.repository.jpa.JpaTargetManagement; import org.eclipse.hawkbit.repository.jpa.JpaTenantConfigurationManagement; import org.eclipse.hawkbit.repository.jpa.JpaTenantStatsManagement; import org.eclipse.hawkbit.repository.jpa.aspects.ExceptionMappingAspectHandler; +import org.eclipse.hawkbit.repository.jpa.autoassign.AutoAssignChecker; +import org.eclipse.hawkbit.repository.jpa.autoassign.AutoAssignScheduler; import org.eclipse.hawkbit.repository.jpa.configuration.MultiTenantJpaTransactionManager; import org.eclipse.hawkbit.repository.jpa.model.helper.AfterTransactionCommitExecutorHolder; import org.eclipse.hawkbit.repository.jpa.model.helper.CacheManagerHolder; @@ -399,4 +401,45 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration { return new JpaEntityFactory(); } + /** + * {@link AutoAssignChecker} bean. + * + * @param targetFilterQueryManagement + * to get all target filter queries + * @param targetManagement + * to get targets + * @param deploymentManagement + * to assign distribution sets to targets + * @param transactionManager + * to run transactions + * @return a new {@link AutoAssignChecker} + */ + @Bean + @ConditionalOnMissingBean + public AutoAssignChecker autoAssignChecker(TargetFilterQueryManagement targetFilterQueryManagement, + TargetManagement targetManagement, DeploymentManagement deploymentManagement, + PlatformTransactionManager transactionManager) { + return new AutoAssignChecker(targetFilterQueryManagement, targetManagement, deploymentManagement, + transactionManager); + } + + /** + * {@link AutoAssignScheduler} bean. + * + * @param tenantAware + * to run as specific tenant + * @param systemManagement + * to find all tenants + * @param systemSecurityContext + * to run as system + * @param autoAssignChecker + * to run a check as tenant + * @return a new {@link AutoAssignChecker} + */ + @Bean + @ConditionalOnMissingBean + public AutoAssignScheduler autoAssignScheduler(TenantAware tenantAware, SystemManagement systemManagement, + SystemSecurityContext systemSecurityContext, AutoAssignChecker autoAssignChecker) { + return new AutoAssignScheduler(tenantAware, systemManagement, systemSecurityContext, autoAssignChecker); + } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java index f4963046e..ee60b72e0 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDeploymentManagement.java @@ -179,13 +179,23 @@ public class JpaDeploymentManagement implements DeploymentManagement { @CacheEvict(value = { "distributionUsageAssigned" }, allEntries = true) public DistributionSetAssignmentResult assignDistributionSet(final Long dsID, final Collection targets) { + return assignDistributionSet(dsID, targets, null); + } + + @Override + @Modifying + @Transactional(isolation = Isolation.READ_COMMITTED) + @CacheEvict(value = { "distributionUsageAssigned" }, allEntries = true) + public DistributionSetAssignmentResult assignDistributionSet(final Long dsID, + final Collection targets, + final String actionMessage) { final JpaDistributionSet 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, null, null); + return assignDistributionSetToTargets(set, targets, null, null, actionMessage); } @Override @@ -200,21 +210,23 @@ public class JpaDeploymentManagement implements DeploymentManagement { String.format("no %s with id %d found", DistributionSet.class.getSimpleName(), dsID)); } - return assignDistributionSetToTargets(set, targets, (JpaRollout) rollout, (JpaRolloutGroup) rolloutGroup); + return assignDistributionSetToTargets(set, targets, (JpaRollout) rollout, (JpaRolloutGroup) rolloutGroup, null); } /** * method assigns the {@link DistributionSet} to all {@link Target}s by * their IDs with a specific {@link ActionType} and {@code forcetime}. * - * @param dsID + * @param set * the ID of the distribution set to assign - * @param targets + * @param targetsWithActionType * a list of all targets and their action type * @param rollout * the rollout for this assignment * @param rolloutGroup * the rollout group for this assignment + * @param actionMessage + * an optional message to be written into the action status * @return the assignment result * * @throw IncompleteDistributionSetException if mandatory @@ -223,7 +235,7 @@ public class JpaDeploymentManagement implements DeploymentManagement { */ private DistributionSetAssignmentResult assignDistributionSetToTargets(@NotNull final JpaDistributionSet set, final Collection targetsWithActionType, final JpaRollout rollout, - final JpaRolloutGroup rolloutGroup) { + final JpaRolloutGroup rolloutGroup, final String actionMessage) { if (!set.isComplete()) { throw new IncompleteDistributionSetException( @@ -296,6 +308,9 @@ public class JpaDeploymentManagement implements DeploymentManagement { actionStatus.setAction(action); actionStatus.setOccurredAt(action.getCreatedAt()); actionStatus.setStatus(Status.RUNNING); + if(actionMessage != null) { + actionStatus.addMessage(actionMessage); + } actionStatusRepository.save(actionStatus); }); @@ -350,7 +365,7 @@ public class JpaDeploymentManagement implements DeploymentManagement { * the Target which has been assigned to a distribution set * @param actionId * the action id of the assignment - * @param softwareModules + * @param modules * the software modules which have been assigned */ private void assignDistributionSetEvent(final JpaTarget target, final Long actionId, @@ -367,11 +382,11 @@ public class JpaDeploymentManagement implements DeploymentManagement { } /** - * Removes {@link UpdateAction}s that are no longer necessary and sends + * Removes {@link Action}s that are no longer necessary and sends * cancellations to the controller. * - * @param myTarget - * to override {@link UpdateAction}s + * @param targetsIds + * to override {@link Action}s */ private Set overrideObsoleteUpdateActions(final List targetsIds) { @@ -406,7 +421,7 @@ public class JpaDeploymentManagement implements DeploymentManagement { return assignDistributionSetToTargets(set, tIDs.stream() .map(t -> new TargetWithActionType(t, actionType, forcedTime)).collect(Collectors.toList()), null, - null); + null, null); } @Override diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetManagement.java index b9df4e1b0..c91da5f21 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaDistributionSetManagement.java @@ -96,6 +96,9 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { @Autowired private DistributionSetMetadataRepository distributionSetMetadataRepository; + @Autowired + private TargetFilterQueryRepository targetFilterQueryRepository; + @Autowired private ActionRepository actionRepository; @@ -182,7 +185,9 @@ public class JpaDistributionSetManagement implements DistributionSetManagement { // soft delete assigned if (!assigned.isEmpty()) { - distributionSetRepository.deleteDistributionSet(assigned.toArray(new Long[assigned.size()])); + Long[] dsIds = assigned.toArray(new Long[assigned.size()]); + distributionSetRepository.deleteDistributionSet(dsIds); + targetFilterQueryRepository.unsetAutoAssignDistributionSet(dsIds); } // mark the rest as hard delete diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaEntityFactory.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaEntityFactory.java index 1bc8063ec..d09606df2 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaEntityFactory.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaEntityFactory.java @@ -134,6 +134,16 @@ public class JpaEntityFactory implements EntityFactory { return new JpaTargetFilterQuery(); } + @Override + public TargetFilterQuery generateTargetFilterQuery(String name, String query) { + return new JpaTargetFilterQuery(name, query); + } + + @Override + public TargetFilterQuery generateTargetFilterQuery(String name, String query, DistributionSet autoAssignDS) { + return new JpaTargetFilterQuery(name, query, (JpaDistributionSet) autoAssignDS); + } + @Override public SoftwareModuleType generateSoftwareModuleType() { return new JpaSoftwareModuleType(); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSystemManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSystemManagement.java index ca7eb4b3c..a8c3f1860 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSystemManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaSystemManagement.java @@ -56,6 +56,9 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst @Autowired private TargetRepository targetRepository; + @Autowired + private TargetFilterQueryRepository targetFilterQueryRepository; + @Autowired private ActionRepository actionRepository; @@ -221,6 +224,7 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst tenantMetaDataRepository.deleteByTenantIgnoreCase(tenant); tenantConfigurationRepository.deleteByTenantIgnoreCase(tenant); targetRepository.deleteByTenantIgnoreCase(tenant); + targetFilterQueryRepository.deleteByTenantIgnoreCase(tenant); actionRepository.deleteByTenantIgnoreCase(tenant); rolloutGroupRepository.deleteByTenantIgnoreCase(tenant); rolloutRepository.deleteByTenantIgnoreCase(tenant); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetFilterQueryManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetFilterQueryManagement.java index ed9ba5977..58ea2bde3 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetFilterQueryManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetFilterQueryManagement.java @@ -9,9 +9,11 @@ package org.eclipse.hawkbit.repository.jpa; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.eclipse.hawkbit.repository.TargetFields; +import org.eclipse.hawkbit.repository.TargetFilterQueryFields; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery; @@ -19,6 +21,7 @@ import org.eclipse.hawkbit.repository.jpa.rsql.RSQLUtility; import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer; import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder; import org.eclipse.hawkbit.repository.jpa.specifications.TargetFilterQuerySpecification; +import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -34,6 +37,8 @@ import org.springframework.validation.annotation.Validated; import com.google.common.base.Strings; +import javax.validation.constraints.NotNull; + /** * JPA implementation of {@link TargetFilterQueryManagement}. * @@ -71,20 +76,61 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme return convertPage(targetFilterQueryRepository.findAll(pageable), pageable); } + @Override + public Long countAllTargetFilterQuery() { + return targetFilterQueryRepository.count(); + } + private static Page convertPage(final Page findAll, final Pageable pageable) { return new PageImpl<>(new ArrayList<>(findAll.getContent()), pageable, findAll.getTotalElements()); } @Override - public Page findTargetFilterQueryByFilters(final Pageable pageable, final String name) { - final List> specList = new ArrayList<>(); + public Page findTargetFilterQueryByName(final Pageable pageable, final String name) { + List> specList = Collections.emptyList(); if (!Strings.isNullOrEmpty(name)) { - specList.add(TargetFilterQuerySpecification.likeName(name)); + specList = Collections.singletonList(TargetFilterQuerySpecification.likeName(name)); } return convertPage(findTargetFilterQueryByCriteriaAPI(pageable, specList), pageable); } + @Override + public Page findTargetFilterQueryByFilter(@NotNull Pageable pageable, String rsqlFilter) { + List> specList = Collections.emptyList(); + if (!Strings.isNullOrEmpty(rsqlFilter)) { + specList = Collections.singletonList( + RSQLUtility.parse(rsqlFilter, TargetFilterQueryFields.class, virtualPropertyReplacer)); + } + return convertPage(findTargetFilterQueryByCriteriaAPI(pageable, specList), pageable); + } + + @Override + public Page findTargetFilterQueryByAutoAssignDS(@NotNull Pageable pageable, + DistributionSet distributionSet) { + return findTargetFilterQueryByAutoAssignDS(pageable, distributionSet, null); + } + + @Override + public Page findTargetFilterQueryByAutoAssignDS(@NotNull Pageable pageable, + DistributionSet distributionSet, String rsqlFilter) { + final List> specList = new ArrayList<>(2); + if (distributionSet != null) { + specList.add(TargetFilterQuerySpecification.byAutoAssignDS(distributionSet)); + } + if (!Strings.isNullOrEmpty(rsqlFilter)) { + specList.add(RSQLUtility.parse(rsqlFilter, TargetFilterQueryFields.class, virtualPropertyReplacer)); + } + return convertPage(findTargetFilterQueryByCriteriaAPI(pageable, specList), pageable); + } + + @Override + public Page findTargetFilterQueryWithAutoAssignDS(@NotNull Pageable pageable) { + final List> specList = Collections + .singletonList(TargetFilterQuerySpecification.withAutoAssignDS()); + return convertPage(findTargetFilterQueryByCriteriaAPI(pageable, specList), pageable); + } + private Page findTargetFilterQueryByCriteriaAPI(final Pageable pageable, final List> specList) { if (specList == null || specList.isEmpty()) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java index 5eddbee0d..9a412f9aa 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/JpaTargetManagement.java @@ -26,6 +26,7 @@ import javax.persistence.criteria.JoinType; import javax.persistence.criteria.Order; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; +import javax.validation.constraints.NotNull; import org.apache.commons.lang3.StringUtils; import org.eclipse.hawkbit.repository.FilterParams; @@ -570,7 +571,8 @@ public class JpaTargetManagement implements TargetManagement { final CriteriaQuery multiselect = query.multiselect(targetRoot.get(JpaTarget_.id), targetRoot.get(JpaTarget_.controllerId), targetRoot.get(JpaTarget_.name), targetRoot.get(sortProperty)); - final Specification spec = RSQLUtility.parse(targetFilterQuery.getQuery(), TargetFields.class, virtualPropertyReplacer); + final Specification spec = RSQLUtility.parse(targetFilterQuery.getQuery(), TargetFields.class, + virtualPropertyReplacer); final Predicate[] specificationsForMultiSelect = specificationsToPredicate(Lists.newArrayList(spec), targetRoot, multiselect, cb); @@ -584,6 +586,33 @@ public class JpaTargetManagement implements TargetManagement { .collect(Collectors.toList()); } + @Override + public Page findAllTargetsByTargetFilterQueryAndNonDS(@NotNull Pageable pageRequest, Long distributionSetId, + @NotNull TargetFilterQuery targetFilterQuery) { + + final Specification spec = RSQLUtility.parse(targetFilterQuery.getQuery(), TargetFields.class, + virtualPropertyReplacer); + + return findTargetsBySpec( + (root, cq, + cb) -> cb.and(spec.toPredicate(root, cq, cb), TargetSpecifications + .hasNotDistributionSetInActions(distributionSetId).toPredicate(root, cq, cb)), + pageRequest); + + } + + @Override + public Long countTargetsByTargetFilterQueryAndNonDS(Long distributionSetId, + @NotNull TargetFilterQuery targetFilterQuery) { + final Specification spec = RSQLUtility.parse(targetFilterQuery.getQuery(), TargetFields.class, + virtualPropertyReplacer); + final List> specList = new ArrayList<>(2); + specList.add(spec); + specList.add(TargetSpecifications.hasNotDistributionSetInActions(distributionSetId)); + + return countByCriteriaAPI(specList); + } + @PreDestroy void destroy() { eventBus.unregister(this); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryRepository.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryRepository.java index 1f2fec8d8..5ebee7619 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryRepository.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryRepository.java @@ -13,6 +13,8 @@ import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.springframework.data.domain.Page; 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.Isolation; import org.springframework.transaction.annotation.Transactional; @@ -43,4 +45,15 @@ public interface TargetFilterQueryRepository @Transactional S save(S entity); + /** + * Sets the auto assign distribution sets to null which match the ds ids. + * + * @param dsIds + * distribution set ids to be set to null + */ + @Modifying + @Transactional(isolation = Isolation.READ_UNCOMMITTED) + @Query("update JpaTargetFilterQuery d set d.autoAssignDistributionSet = NULL where d.autoAssignDistributionSet in :ids") + void unsetAutoAssignDistributionSet(@Param("ids") Long... dsIds); + } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignChecker.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignChecker.java new file mode 100644 index 000000000..1b78ee7ee --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignChecker.java @@ -0,0 +1,179 @@ +/** + * 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.jpa.autoassign; + +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.hawkbit.exception.AbstractServerRtException; +import org.eclipse.hawkbit.repository.DeploymentManagement; +import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; +import org.eclipse.hawkbit.repository.TargetManagement; +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.RepositoryModelConstants; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetFilterQuery; +import org.eclipse.hawkbit.repository.model.TargetWithActionType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.DefaultTransactionDefinition; +import org.springframework.transaction.support.TransactionTemplate; + +import javax.persistence.PersistenceException; + +/** + * Checks if targets need a new distribution set (DS) based on the target filter + * queries and assigns the new DS when necessary. First all target filter + * queries are listed. For every target filter query (TFQ) the auto assign DS is + * retrieved. All targets get listed per target filter query, that match the TFQ + * and that don't have the auto assign DS in their action history. + */ +public class AutoAssignChecker { + + private static final Logger LOGGER = LoggerFactory.getLogger(AutoAssignChecker.class); + + private final TargetFilterQueryManagement targetFilterQueryManagement; + + private final TargetManagement targetManagement; + + private final DeploymentManagement deploymentManagement; + + private final TransactionTemplate transactionTemplate; + + /** + * Maximum for target filter queries with auto assign DS Maximum for targets + * that are fetched in one turn + */ + private static final int PAGE_SIZE = 1000; + + /** + * The message which is added to the action status when a distribution set + * is assigned to an target. First %s is the name of the target filter. + */ + private static final String ACTION_MESSAGE = "Auto assignment by target filter: %s"; + + /** + * Instantiates a new auto assign checker + * + * @param targetFilterQueryManagement + * to get all target filter queries + * @param targetManagement + * to get targets + * @param deploymentManagement + * to assign distribution sets to targets + * @param transactionManager + * to run transactions + */ + public AutoAssignChecker(TargetFilterQueryManagement targetFilterQueryManagement, TargetManagement targetManagement, + DeploymentManagement deploymentManagement, PlatformTransactionManager transactionManager) { + this.targetFilterQueryManagement = targetFilterQueryManagement; + this.targetManagement = targetManagement; + this.deploymentManagement = deploymentManagement; + + final DefaultTransactionDefinition def = new DefaultTransactionDefinition(); + def.setName("autoAssignDSToTargets"); + def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + transactionTemplate = new TransactionTemplate(transactionManager, def); + } + + /** + * Checks all target filter queries with an auto assign distribution set and + * triggers the check and assignment to targets that don't have the design + * DS yet + */ + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void check() { + + PageRequest pageRequest = new PageRequest(0, PAGE_SIZE); + + Page filterQueries = targetFilterQueryManagement + .findTargetFilterQueryWithAutoAssignDS(pageRequest); + + for (TargetFilterQuery filterQuery : filterQueries) { + checkByTargetFilterQueryAndAssignDS(filterQuery); + } + + } + + /** + * Fetches the distribution set, gets all controllerIds and assigns the DS + * to them. Catches PersistenceException and own exceptions derived from + * AbstractServerRtException + * + * @param targetFilterQuery + * the target filter query + */ + private void checkByTargetFilterQueryAndAssignDS(TargetFilterQuery targetFilterQuery) { + try { + DistributionSet distributionSet = targetFilterQuery.getAutoAssignDistributionSet(); + + int count; + do { + + count = runTransactionalAssignment(targetFilterQuery, distributionSet.getId()); + + } while (count == PAGE_SIZE); + + } catch (PersistenceException | AbstractServerRtException e) { + LOGGER.error("Error during auto assign check of target filter query " + targetFilterQuery.getId(), e); + } + + } + + /** + * Runs one page of target assignments within a dedicated transaction + * + * @param targetFilterQuery + * the target filter query + * @param dsId + * distribution set id to assign + * @return count of targets + */ + private int runTransactionalAssignment(TargetFilterQuery targetFilterQuery, Long dsId) { + final String actionMessage = String.format(ACTION_MESSAGE, targetFilterQuery.getName()); + return transactionTemplate.execute(status -> { + List targets = getTargetsWithActionType(targetFilterQuery, dsId, PAGE_SIZE); + int count = targets.size(); + if (count > 0) { + deploymentManagement.assignDistributionSet(dsId, targets, actionMessage); + } + return count; + }); + } + + /** + * Gets all matching targets with the designated action from the target + * management + * + * @param targetFilterQuery + * the query the targets have to match + * @param dsId + * dsId the targets are not allowed to have in their action + * history + * @param count + * maximum amount of targets to retrieve + * @return list of targets with action type + */ + private List getTargetsWithActionType(TargetFilterQuery targetFilterQuery, Long dsId, + int count) { + Page targets = targetManagement.findAllTargetsByTargetFilterQueryAndNonDS(new PageRequest(0, count), + dsId, targetFilterQuery); + + return targets.getContent().stream().map(t -> new TargetWithActionType(t.getControllerId(), + Action.ActionType.FORCED, RepositoryModelConstants.NO_FORCE_TIME)).collect(Collectors.toList()); + } + +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignScheduler.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignScheduler.java new file mode 100644 index 000000000..e677f889b --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignScheduler.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.repository.jpa.autoassign; + +import java.util.List; + +import org.eclipse.hawkbit.repository.AutoAssignProperties; +import org.eclipse.hawkbit.repository.SystemManagement; +import org.eclipse.hawkbit.security.SystemSecurityContext; +import org.eclipse.hawkbit.tenancy.TenantAware; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Profile; +import org.springframework.scheduling.annotation.Scheduled; + +/** + * Scheduler to check target filters for auto assignment of distribution sets + */ +// don't active the auto assign scheduler in test, otherwise it is hard to test +@Profile("!test") +@EnableConfigurationProperties(AutoAssignProperties.class) +public class AutoAssignScheduler { + + private static final Logger LOGGER = LoggerFactory.getLogger(AutoAssignScheduler.class); + + private final TenantAware tenantAware; + + private final SystemManagement systemManagement; + + private final SystemSecurityContext systemSecurityContext; + + private final AutoAssignChecker autoAssignChecker; + + /** + * Instantiates a new AutoAssignScheduler + * + * @param tenantAware + * to run as specific tenant + * @param systemManagement + * to find all tenants + * @param systemSecurityContext + * to run as system + * @param autoAssignChecker + * to run a check as tenant + */ + public AutoAssignScheduler(TenantAware tenantAware, SystemManagement systemManagement, + SystemSecurityContext systemSecurityContext, AutoAssignChecker autoAssignChecker) { + this.tenantAware = tenantAware; + this.systemManagement = systemManagement; + this.systemSecurityContext = systemSecurityContext; + this.autoAssignChecker = autoAssignChecker; + } + + /** + * Scheduler method called by the spring-async mechanism. Retrieves all + * tenants from the {@link SystemManagement#findTenants()} and runs for each + * tenant the auto assignments defined in the target filter queries + * {@link SystemSecurityContext}. + */ + @Scheduled(initialDelayString = AutoAssignProperties.Scheduler.PROP_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = AutoAssignProperties.Scheduler.PROP_SCHEDULER_DELAY_PLACEHOLDER) + public void autoAssignScheduler() { + LOGGER.debug("auto assign 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 separately. + final List tenants = systemManagement.findTenants(); + LOGGER.info("Checking target filter queries for tenants: {}", tenants.size()); + for (final String tenant : tenants) { + tenantAware.runAsTenant(tenant, () -> { + + autoAssignChecker.check(); + + return null; + }); + } + return null; + }); + } +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSet.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSet.java index 9e6efe826..cbb7444d0 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSet.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaDistributionSet.java @@ -50,6 +50,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.model.Target; +import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.model.TargetInfo; import org.eclipse.persistence.annotations.CascadeOnDelete; import org.eclipse.persistence.descriptors.DescriptorEvent; @@ -95,6 +96,9 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen @OneToMany(mappedBy = "assignedDistributionSet", targetEntity = JpaTarget.class, fetch = FetchType.LAZY) private List assignedToTargets; + @OneToMany(mappedBy = "autoAssignDistributionSet", targetEntity = JpaTargetFilterQuery.class, fetch = FetchType.LAZY) + private List autoAssignFilters; + @OneToMany(mappedBy = "installedDistributionSet", targetEntity = JpaTargetInfo.class, fetch = FetchType.LAZY) private List installedAtTargets; @@ -224,6 +228,11 @@ public class JpaDistributionSet extends AbstractJpaNamedVersionedEntity implemen return Collections.unmodifiableList(assignedToTargets); } + @Override + public List getAutoAssignFilters() { + return autoAssignFilters; + } + @Override public List getInstalledTargets() { if (installedAtTargets == null) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetFilterQuery.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetFilterQuery.java index 2a40619b2..b7e80cbb3 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetFilterQuery.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/model/JpaTargetFilterQuery.java @@ -9,13 +9,19 @@ package org.eclipse.hawkbit.repository.jpa.model; 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.Table; import javax.persistence.UniqueConstraint; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; +import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; /** @@ -42,6 +48,10 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity imple @NotNull private String query; + @ManyToOne(optional = true, fetch = FetchType.LAZY, targetEntity = JpaDistributionSet.class) + @JoinColumn(name = "auto_assign_distribution_set", nullable = true, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_filter_auto_assign_ds")) + private JpaDistributionSet autoAssignDistributionSet; + public JpaTargetFilterQuery() { // Default constructor for JPA. } @@ -59,6 +69,22 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity imple this.query = query; } + /** + * Construct a Target filter query with auto assign distribution set + * + * @param name + * of the {@link TargetFilterQuery}. + * @param query + * of the {@link TargetFilterQuery}. + * @param autoAssignDistributionSet + * of the {@link TargetFilterQuery}. + */ + public JpaTargetFilterQuery(String name, String query, JpaDistributionSet autoAssignDistributionSet) { + this.name = name; + this.query = query; + this.autoAssignDistributionSet = autoAssignDistributionSet; + } + @Override public String getName() { return name; @@ -78,4 +104,14 @@ public class JpaTargetFilterQuery extends AbstractJpaTenantAwareBaseEntity imple public void setQuery(final String query) { this.query = query; } + + @Override + public DistributionSet getAutoAssignDistributionSet() { + return autoAssignDistributionSet; + } + + @Override + public void setAutoAssignDistributionSet(DistributionSet distributionSet) { + autoAssignDistributionSet = (JpaDistributionSet)distributionSet; + } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/SpecificationsBuilder.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/SpecificationsBuilder.java index 005942719..095269622 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/SpecificationsBuilder.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/SpecificationsBuilder.java @@ -28,7 +28,7 @@ public final class SpecificationsBuilder { * where clause. * * @param specList - * all specification wich will combine + * all specification which will combine * @return if the given specification list is empty */ public static Specifications combineWithAnd(final List> specList) { @@ -36,8 +36,7 @@ public final class SpecificationsBuilder { return null; } Specifications specs = Specifications.where(specList.get(0)); - specList.remove(0); - for (final Specification specification : specList) { + for (final Specification specification : specList.subList(1, specList.size())) { specs = specs.and(specification); } return specs; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetFilterQuerySpecification.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetFilterQuerySpecification.java index 4b27c7fc7..d783738ae 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetFilterQuerySpecification.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetFilterQuerySpecification.java @@ -10,6 +10,7 @@ package org.eclipse.hawkbit.repository.jpa.specifications; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery_; +import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.springframework.data.jpa.domain.Specification; @@ -37,4 +38,28 @@ public final class TargetFilterQuerySpecification { return cb.like(cb.lower(targetFilterQueryRoot.get(JpaTargetFilterQuery_.name)), searchTextToLower); }; } + + /** + * {@link Specification} for retrieving {@link JpaTargetFilterQuery}s based + * on is {@link JpaTargetFilterQuery#getName()}. + * + * @param distributionSet + * of the filter + * @return the {@link JpaTargetFilterQuery} {@link Specification} + */ + public static Specification byAutoAssignDS(final DistributionSet distributionSet) { + return (targetFilterQueryRoot, query, cb) -> cb + .equal(targetFilterQueryRoot.get(JpaTargetFilterQuery_.autoAssignDistributionSet), distributionSet); + } + + /** + * {@link Specification} for retrieving {@link JpaTargetFilterQuery}s based + * on is {@link JpaTargetFilterQuery#getName()}. + * + * @return the {@link JpaTargetFilterQuery} {@link Specification} + */ + public static Specification withAutoAssignDS() { + return (targetFilterQueryRoot, query, cb) -> cb + .isNotNull(targetFilterQueryRoot.get(JpaTargetFilterQuery_.autoAssignDistributionSet)); + } } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java index 5503ae36e..99112cd38 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/specifications/TargetSpecifications.java @@ -14,12 +14,15 @@ import java.util.List; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.Join; import javax.persistence.criteria.JoinType; +import javax.persistence.criteria.ListJoin; import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import javax.persistence.criteria.SetJoin; import javax.validation.constraints.NotNull; +import org.eclipse.hawkbit.repository.jpa.model.JpaAction; +import org.eclipse.hawkbit.repository.jpa.model.JpaAction_; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; @@ -234,6 +237,24 @@ public final class TargetSpecifications { distributionSetId); } + /** + * {@link Specification} for retrieving {@link Target}s that don't have the given + * distribution set in their action history + * + * @param distributionSetId + * the ID of the distribution set which must not be assigned + * @return the {@link Target} {@link Specification} + */ + public static Specification hasNotDistributionSetInActions(final Long distributionSetId) { + return (targetRoot, query, cb) -> { + final ListJoin actionsJoin = targetRoot.join(JpaTarget_.actions, JoinType.LEFT); + actionsJoin.on(cb.equal(actionsJoin.get(JpaAction_.distributionSet).get(JpaDistributionSet_.id), + distributionSetId)); + + return cb.isNull(actionsJoin.get(JpaAction_.id)); + }; + } + /** * {@link Specification} for retrieving {@link Target}s by assigned * distribution set. diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/H2/V1_8_0__auto_assign_ds_filter__H2.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/H2/V1_8_0__auto_assign_ds_filter__H2.sql new file mode 100644 index 000000000..2d56f27f2 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/H2/V1_8_0__auto_assign_ds_filter__H2.sql @@ -0,0 +1,8 @@ +ALTER TABLE sp_target_filter_query + ADD column auto_assign_distribution_set BIGINT; + +ALTER TABLE sp_target_filter_query + ADD CONSTRAINT fk_filter_auto_assign_ds +FOREIGN KEY (auto_assign_distribution_set) +REFERENCES sp_distribution_set +ON DELETE SET NULL; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/MYSQL/V1_8_0__auto_assign_ds_filter__MYSQL.sql b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/MYSQL/V1_8_0__auto_assign_ds_filter__MYSQL.sql new file mode 100644 index 000000000..e56a6d242 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/resources/db/migration/MYSQL/V1_8_0__auto_assign_ds_filter__MYSQL.sql @@ -0,0 +1,8 @@ +ALTER TABLE sp_target_filter_query + ADD COLUMN auto_assign_distribution_set BIGINT; + +ALTER TABLE sp_target_filter_query + ADD CONSTRAINT fk_filter_auto_assign_ds +FOREIGN KEY (auto_assign_distribution_set) +REFERENCES sp_distribution_set (id) +ON DELETE SET NULL; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryManagementTest.java new file mode 100644 index 000000000..efde4e493 --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryManagementTest.java @@ -0,0 +1,257 @@ +/** + * 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.jpa; + +import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; +import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; +import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException; +import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; +import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetFilterQuery; +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; + +import java.util.Iterator; +import java.util.List; + +import static org.fest.assertions.api.Assertions.assertThat; +import static org.junit.Assert.*; + +/** + * Test class for {@link TargetFilterQueryManagement}. + * + */ +@Features("Component Tests - Repository") +@Stories("Target Filter Query Management") +public class TargetFilterQueryManagementTest extends AbstractJpaIntegrationTest { + + @Test + @Description("Test creation of target filter query.") + public void createTargetFilterQuery() { + final String filterName = "new target filter"; + final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement + .createTargetFilterQuery(new JpaTargetFilterQuery(filterName, "name==PendingTargets001")); + assertEquals("Retrieved newly created custom target filter", targetFilterQuery, + targetFilterQueryManagement.findTargetFilterQueryByName(filterName)); + } + + @Test + @Description("Test searching a target filter query.") + public void searchTargetFilterQuery() { + final String filterName = "targetFilterQueryName"; + final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement + .createTargetFilterQuery(new JpaTargetFilterQuery(filterName, "name==PendingTargets001")); + + targetFilterQueryManagement + .createTargetFilterQuery(new JpaTargetFilterQuery("someOtherFilter", "name==PendingTargets002")); + + List results = targetFilterQueryManagement + .findTargetFilterQueryByFilter(new PageRequest(0, 10), "name==" + filterName).getContent(); + assertEquals("Search result should have 1 result", 1, results.size()); + assertEquals("Retrieved newly created custom target filter", targetFilterQuery, results.get(0)); + } + + @Test(expected = RSQLParameterUnsupportedFieldException.class) + @Description("Test searching a target filter query with an invalid filter.") + public void searchTargetFilterQueryInvalidField() { + // Should throw an exception + targetFilterQueryManagement.findTargetFilterQueryByFilter(new PageRequest(0, 10), "unknownField==testValue") + .getContent(); + + } + + @Test + @Description("Checks if the EntityAlreadyExistsException is thrown if a targetfilterquery with the same name are created more than once.") + public void createDuplicateTargetFilterQuery() { + final String filterName = "new target filter duplicate"; + targetFilterQueryManagement + .createTargetFilterQuery(new JpaTargetFilterQuery(filterName, "name==PendingTargets001")); + + try { + targetFilterQueryManagement + .createTargetFilterQuery(new JpaTargetFilterQuery(filterName, "name==PendingTargets001")); + fail("should not have worked as query already exists"); + } catch (final EntityAlreadyExistsException e) { + + } + } + + @Test + @Description("Test deletion of target filter query.") + public void deleteTargetFilterQuery() { + final String filterName = "delete_target_filter_query"; + final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement + .createTargetFilterQuery(new JpaTargetFilterQuery(filterName, "name==PendingTargets001")); + targetFilterQueryManagement.deleteTargetFilterQuery(targetFilterQuery.getId()); + assertEquals("Returns null as the target filter is deleted", null, + targetFilterQueryManagement.findTargetFilterQueryById(targetFilterQuery.getId())); + + } + + @Test + @Description("Test updation of target filter query.") + public void updateTargetFilterQuery() { + final String filterName = "target_filter_01"; + final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement + .createTargetFilterQuery(new JpaTargetFilterQuery(filterName, "name==PendingTargets001")); + + final String newQuery = "status==UNKNOWN"; + targetFilterQuery.setQuery(newQuery); + targetFilterQueryManagement.updateTargetFilterQuery(targetFilterQuery); + assertEquals("Returns updated target filter query", newQuery, + targetFilterQueryManagement.findTargetFilterQueryByName(filterName).getQuery()); + + } + + @Test + @Description("Test assigning a distribution set") + public void assignDistributionSet() { + final String filterName = "target_filter_02"; + final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement + .createTargetFilterQuery(new JpaTargetFilterQuery(filterName, "name==PendingTargets001")); + + final DistributionSet distributionSet = distributionSetManagement.createDistributionSet(new JpaDistributionSet( + "dist_Set_01", "0.1", "", null, null + )); + + targetFilterQuery.setAutoAssignDistributionSet(distributionSet); + targetFilterQueryManagement.updateTargetFilterQuery(targetFilterQuery); + + TargetFilterQuery tfq = targetFilterQueryManagement.findTargetFilterQueryByName(filterName); + + assertEquals("Returns correct distribution set", distributionSet, + tfq.getAutoAssignDistributionSet()); + + } + + @Test + @Description("Test removing distribution set while it has a relation to a target filter query") + public void removeAssignDistributionSet() { + final String filterName = "target_filter_03"; + final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement + .createTargetFilterQuery(new JpaTargetFilterQuery(filterName, "name==PendingTargets001")); + + final DistributionSet distributionSet = distributionSetManagement.createDistributionSet(new JpaDistributionSet( + "dist_Set_02", "0.1", "", null, null + )); + + targetFilterQuery.setAutoAssignDistributionSet(distributionSet); + targetFilterQueryManagement.updateTargetFilterQuery(targetFilterQuery); + + // Check if target filter query is there + TargetFilterQuery tfq = targetFilterQueryManagement.findTargetFilterQueryByName(filterName); + assertEquals("Returns correct distribution set", distributionSet, + tfq.getAutoAssignDistributionSet()); + + distributionSetManagement.deleteDistributionSet(distributionSet); + + // Check if auto assign distribution set is null + tfq = targetFilterQueryManagement.findTargetFilterQueryByName(filterName); + assertNotNull("Returns target filter query", tfq); + assertNull("Returns distribution set as null", tfq.getAutoAssignDistributionSet()); + + } + + @Test + @Description("Test to implicitly remove the auto assign distribution set when the ds is soft deleted") + public void implicitlyRemoveAssignDistributionSet() { + final String filterName = "target_filter_03"; + DistributionSet distributionSet = testdataFactory.createDistributionSet("dist_set"); + Target target = testdataFactory.createTarget(); + + // Assign the distribution set to an target, to force a soft delete in a + // later step + deploymentManagement.assignDistributionSet(distributionSet.getId(), target.getControllerId()); + + targetFilterQueryManagement.createTargetFilterQuery( + new JpaTargetFilterQuery(filterName, "name==PendingTargets001", (JpaDistributionSet) distributionSet)); + + // Check if target filter query is there with the distribution set + TargetFilterQuery tfq = targetFilterQueryManagement.findTargetFilterQueryByName(filterName); + assertEquals("Returns correct distribution set", distributionSet, tfq.getAutoAssignDistributionSet()); + + distributionSetManagement.deleteDistributionSet(distributionSet); + + // Check if distribution set is still in the database with deleted flag + assertTrue("Distribution set should be deleted", + distributionSetManagement.findDistributionSetById(distributionSet.getId()).isDeleted()); + + // Check if auto assign distribution set is null + tfq = targetFilterQueryManagement.findTargetFilterQueryByName(filterName); + assertNotNull("Returns target filter query", tfq); + assertNull("Returns distribution set as null", tfq.getAutoAssignDistributionSet()); + + } + + @Test + @Description("Test finding and auto assign distribution set") + public void findFiltersWithDistributionSet() { + + final String filterName = "d"; + + assertEquals(0L, targetFilterQueryManagement.countAllTargetFilterQuery().longValue()); + + targetFilterQueryManagement.createTargetFilterQuery(new JpaTargetFilterQuery("a", "name==*")); + targetFilterQueryManagement.createTargetFilterQuery(new JpaTargetFilterQuery("b", "name==*")); + + final DistributionSet distributionSet = distributionSetManagement + .createDistributionSet(new JpaDistributionSet("dist_Set_01", "0.1", "", null, null)); + final DistributionSet distributionSet2 = distributionSetManagement + .createDistributionSet(new JpaDistributionSet("dist_Set_02", "0.1", "", null, null)); + + final TargetFilterQuery tfq = targetFilterQueryManagement.createTargetFilterQuery( + new JpaTargetFilterQuery("c", "name==x", (JpaDistributionSet) distributionSet)); + + final TargetFilterQuery tfq2 = targetFilterQueryManagement.createTargetFilterQuery( + new JpaTargetFilterQuery(filterName, "name==z*", (JpaDistributionSet) distributionSet2)); + + assertEquals(4L, targetFilterQueryManagement.countAllTargetFilterQuery().longValue()); + + // check if find works + Page tfqList = targetFilterQueryManagement + .findTargetFilterQueryByAutoAssignDS(new PageRequest(0, 500), distributionSet); + assertThat(1L).as("Target filter query").isEqualTo(tfqList.getTotalElements()); + + assertEquals("Returns correct target filter query", tfq.getId(), tfqList.iterator().next().getId()); + + tfq2.setAutoAssignDistributionSet(distributionSet); + targetFilterQueryManagement.updateTargetFilterQuery(tfq2); + + // check if find works for two + tfqList = targetFilterQueryManagement.findTargetFilterQueryByAutoAssignDS(new PageRequest(0, 500), + distributionSet); + assertThat(2L).as("Target filter query count").isEqualTo(tfqList.getTotalElements()); + Iterator iterator = tfqList.iterator(); + assertEquals("Returns correct target filter query 1", tfq.getId(), iterator.next().getId()); + assertEquals("Returns correct target filter query 2", tfq2.getId(), iterator.next().getId()); + + // check if find works with name filter + tfqList = targetFilterQueryManagement.findTargetFilterQueryByAutoAssignDS(new PageRequest(0, 500), + distributionSet, "name==" + filterName); + assertThat(1L).as("Target filter query count").isEqualTo(tfqList.getTotalElements()); + + assertEquals("Returns correct target filter query", tfq2.getId(), tfqList.iterator().next().getId()); + + // check if find works for all with auto assign DS + tfqList = targetFilterQueryManagement.findTargetFilterQueryWithAutoAssignDS(new PageRequest(0, 500)); + assertThat(2L).as("Target filter query count").isEqualTo(tfqList.getTotalElements()); + iterator = tfqList.iterator(); + assertEquals("Returns correct target filter query 1", tfq.getId(), iterator.next().getId()); + assertEquals("Returns correct target filter query 2", tfq2.getId(), iterator.next().getId()); + + } + +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryManagenmentTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryManagenmentTest.java deleted file mode 100644 index 07b92ebeb..000000000 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetFilterQueryManagenmentTest.java +++ /dev/null @@ -1,85 +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.jpa; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; -import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; -import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery; -import org.eclipse.hawkbit.repository.model.TargetFilterQuery; -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; - -/** - * Test class for {@link TargetFilterQueryManagement}. - * - */ -@Features("Component Tests - Repository") -@Stories("Target Filter Query Management") -public class TargetFilterQueryManagenmentTest extends AbstractJpaIntegrationTest { - - @Test - @Description("Test creation of target filter query.") - public void createTargetFilterQuery() { - final String filterName = "new target filter"; - final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement - .createTargetFilterQuery(new JpaTargetFilterQuery(filterName, "name==PendingTargets001")); - assertEquals("Retrieved newly created custom target filter", targetFilterQuery, - targetFilterQueryManagement.findTargetFilterQueryByName(filterName)); - } - - @Test - @Description("Checks if the EntityAlreadyExistsException is thrown if a targetfilterquery with the same name are created more than once.") - public void createDuplicateTargetFilterQuery() { - final String filterName = "new target filter duplicate"; - targetFilterQueryManagement - .createTargetFilterQuery(new JpaTargetFilterQuery(filterName, "name==PendingTargets001")); - - try { - targetFilterQueryManagement - .createTargetFilterQuery(new JpaTargetFilterQuery(filterName, "name==PendingTargets001")); - fail("should not have worked as query already exists"); - } catch (final EntityAlreadyExistsException e) { - - } - } - - @Test - @Description("Test deletion of target filter query.") - public void deleteTargetFilterQuery() { - final String filterName = "delete_target_filter_query"; - final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement - .createTargetFilterQuery(new JpaTargetFilterQuery(filterName, "name==PendingTargets001")); - targetFilterQueryManagement.deleteTargetFilterQuery(targetFilterQuery.getId()); - assertEquals("Returns null as the target filter is deleted", null, - targetFilterQueryManagement.findTargetFilterQueryById(targetFilterQuery.getId())); - - } - - @Test - @Description("Test updation of target filter query.") - public void updateTargetFilterQuery() { - final String filterName = "target_filter_01"; - final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement - .createTargetFilterQuery(new JpaTargetFilterQuery(filterName, "name==PendingTargets001")); - - final String newQuery = "status==UNKNOWN"; - targetFilterQuery.setQuery(newQuery); - targetFilterQueryManagement.updateTargetFilterQuery(targetFilterQuery); - assertEquals("Returns updated target filter query", newQuery, - targetFilterQueryManagement.findTargetFilterQueryByName(filterName).getQuery()); - - } - -} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java index 05f276a31..7c0cc775b 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/TargetManagementSearchTest.java @@ -24,15 +24,8 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag; -import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.*; 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.Target; -import org.eclipse.hawkbit.repository.model.TargetIdName; -import org.eclipse.hawkbit.repository.model.TargetTag; -import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; -import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity; import org.junit.Test; import org.springframework.data.domain.Slice; @@ -835,6 +828,26 @@ public class TargetManagementSearchTest extends AbstractJpaIntegrationTest { } + @Test + @Description("Verifies that targets without given assigned DS are returned from repository.") + public void findTargetWithoutAssignedDistributionSet() { + final DistributionSet assignedSet = testdataFactory.createDistributionSet(""); + final TargetFilterQuery tfq = targetFilterQueryManagement + .createTargetFilterQuery(entityFactory.generateTargetFilterQuery("tfq", "name==*")); + List unassignedTargets = targetManagement + .createTargets(testdataFactory.generateTargets(12, "unassigned")); + List assignedTargets = targetManagement.createTargets(testdataFactory.generateTargets(10, "assigned")); + + deploymentManagement.assignDistributionSet(assignedSet, assignedTargets); + + List result = targetManagement.findAllTargetsByTargetFilterQueryAndNonDS(pageReq, + assignedSet.getId(), tfq).getContent(); + assertThat(result) + .as("count of targets").hasSize(unassignedTargets.size()) + .as("contains all targets").containsAll(unassignedTargets); + + } + @Test @Description("Verfies that targets with given installed DS are returned from repository.") public void findTargetByInstalledDistributionSet() { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerTest.java new file mode 100644 index 000000000..47fab431e --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/autoassign/AutoAssignCheckerTest.java @@ -0,0 +1,154 @@ +/** + * 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.jpa.autoassign; + +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; +import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; +import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetFilterQuery; +import org.junit.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Step; +import ru.yandex.qatools.allure.annotations.Stories; + +import static org.fest.assertions.api.Assertions.assertThat; + +/** + * Test class for {@link AutoAssignChecker}. + * + */ +@Features("Component Tests - Repository") +@Stories("Auto assign checker") +public class AutoAssignCheckerTest extends AbstractJpaIntegrationTest { + + @Autowired + private AutoAssignChecker autoAssignChecker; + + @Test + @Description("Test auto assignment of a DS to filtered targets") + public void checkAutoAssign() { + + final DistributionSet setA = testdataFactory.createDistributionSet("dsA"); // will be auto assigned + final DistributionSet setB = testdataFactory.createDistributionSet("dsB"); + + // target filter query that matches all targets + TargetFilterQuery targetFilterQuery = targetFilterQueryManagement + .createTargetFilterQuery(new JpaTargetFilterQuery("filterA", "name==*")); + targetFilterQuery.setAutoAssignDistributionSet(setA); + targetFilterQueryManagement.updateTargetFilterQuery(targetFilterQuery); + + + final String targetDsAIdPref = "targ"; + List targets = targetManagement.createTargets( + testdataFactory.generateTargets(100, targetDsAIdPref, targetDsAIdPref.concat(" description"))); + int targetsCount = targets.size(); + + // assign set A to first 10 targets + deploymentManagement.assignDistributionSet(setA, targets.subList(0, 10)); + verifyThatTargetsHaveDistributionSetAssignment(setA, targets.subList(0, 10), targetsCount); + + // assign set B to first 5 targets + // they have now 2 DS in their action history and should not get updated with dsA + deploymentManagement.assignDistributionSet(setB, targets.subList(0, 5)); + verifyThatTargetsHaveDistributionSetAssignment(setB, targets.subList(0, 5), targetsCount); + + // assign set B to next 10 targets + deploymentManagement.assignDistributionSet(setB, targets.subList(10, 20)); + verifyThatTargetsHaveDistributionSetAssignment(setB, targets.subList(10, 20), targetsCount); + + // Count the number of targets that will be assigned with setA + assertThat(targetManagement.countTargetsByTargetFilterQueryAndNonDS(setA.getId(), targetFilterQuery)) + .isEqualTo(90); + + // Run the check + autoAssignChecker.check(); + + verifyThatTargetsHaveDistributionSetAssignment(setA, targets.subList(5, 100), targetsCount); + + // first 5 should keep their dsB, because they already had the dsA once + verifyThatTargetsHaveDistributionSetAssignment(setB, targets.subList(0, 5), targetsCount); + + } + + @Test + @Description("Test auto assignment of an incomplete DS to filtered targets, that causes failures") + public void checkAutoAssignWithFailures() { + + // incomplete distribution set that will be assigned + final DistributionSet setF = distributionSetManagement + .createDistributionSet(entityFactory.generateDistributionSet("dsA", "1", "incomplete ds", + testdataFactory.findOrCreateDefaultTestDsType(), null)); + final DistributionSet setA = testdataFactory.createDistributionSet("dsA"); + final DistributionSet setB = testdataFactory.createDistributionSet("dsB"); + + final String targetDsAIdPref = "targA"; + final String targetDsFIdPref = "targB"; + + // target filter query that matches first bunch of targets, that should + // fail + targetFilterQueryManagement.createTargetFilterQuery( + new JpaTargetFilterQuery("filterA", "id==" + targetDsFIdPref + "*", (JpaDistributionSet) setF)); + + // target filter query that matches failed bunch of targets + targetFilterQueryManagement.createTargetFilterQuery( + new JpaTargetFilterQuery("filterB", "id==" + targetDsAIdPref + "*", (JpaDistributionSet) setA)); + + List targetsF = targetManagement.createTargets( + testdataFactory.generateTargets(10, targetDsFIdPref, targetDsFIdPref.concat(" description"))); + + List targetsA = targetManagement.createTargets( + testdataFactory.generateTargets(10, targetDsAIdPref, targetDsAIdPref.concat(" description"))); + + int targetsCount = targetsA.size() + targetsF.size(); + + // assign set B to first 5 targets of fail group + deploymentManagement.assignDistributionSet(setB, targetsF.subList(0, 5)); + verifyThatTargetsHaveDistributionSetAssignment(setB, targetsF.subList(0, 5), targetsCount); + + // Run the check + autoAssignChecker.check(); + + // first 5 targets of the fail group should still have setB + verifyThatTargetsHaveDistributionSetAssignment(setB, targetsF.subList(0, 5), targetsCount); + + // all targets of A group should have received setA + verifyThatTargetsHaveDistributionSetAssignment(setA, targetsA, targetsCount); + + } + + /** + * @param set the expected distribution set + * @param targets the targets that should have it + */ + @Step + private void verifyThatTargetsHaveDistributionSetAssignment(final DistributionSet set, List targets, int count) { + List targetIds = targets.stream().map(Target::getId).collect(Collectors.toList()); + + Slice targetsAll = targetManagement.findTargetsAll(new PageRequest(0, 1000)); + assertThat(targetsAll).as("Count of targets").hasSize(count); + + for (Target target : targetsAll) { + if(targetIds.contains(target.getId())) { + assertThat(target.getAssignedDistributionSet()).as("assigned DS").isEqualTo(set); + } + } + + } + +} diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/specifications/SpecificationsBuilderTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/specifications/SpecificationsBuilderTest.java new file mode 100644 index 000000000..aeb28fcae --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/specifications/SpecificationsBuilderTest.java @@ -0,0 +1,106 @@ +/** + * 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.jpa.specifications; + +import org.junit.Test; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.jpa.domain.Specifications; +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Expression; +import javax.persistence.criteria.Path; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@Features("Unit Tests - Repository") +@Stories("Specifications builder") +public class SpecificationsBuilderTest { + + @Test + @Description("Test the combination of specs on an empty list which returns null") + public void combineWithAndEmptyList() { + List> specList = Collections.emptyList(); + assertThat(SpecificationsBuilder.combineWithAnd(specList)).isNull(); + } + + @Test + @Description("Test the combination of specs on an immutable list with one entry") + public void combineWithAndSingleImmutableList() { + Specification spec = (root, query, cb) -> cb.equal(root.get("field1"), "testValue"); + List> specList = Collections.singletonList(spec); + Specifications specifications = SpecificationsBuilder.combineWithAnd(specList); + assertThat(specifications).as("Specifications").isNotNull(); + + // mocks to call toPredicate on specifications + CriteriaBuilder criteriaBuilder = mock(CriteriaBuilder.class); + final Path field1 = mock(Path.class); + final Predicate equalPredicate = mock(Predicate.class); + final CriteriaQuery query = mock(CriteriaQuery.class); + final Root root = mock(Root.class); + + when(criteriaBuilder.equal(any(Expression.class), anyString())).thenReturn(equalPredicate); + when(root.get("field1")).thenReturn(field1); + + Predicate predicate = specifications.toPredicate(root, query, criteriaBuilder); + + assertThat(predicate).isEqualTo(equalPredicate); + + } + + + @Test + @Description("Test the combination of specs on a list with multiple entries") + public void combineWithAndList() { + Specification spec1 = (root, query, cb) -> cb.equal(root.get("field1"), "testValue1"); + Specification spec2 = (root, query, cb) -> cb.equal(root.get("field2"), "testValue2"); + + List> specList = new ArrayList<>(2); + specList.add(spec1); + specList.add(spec2); + + Specifications specifications = SpecificationsBuilder.combineWithAnd(specList); + assertThat(specifications).as("Specifications").isNotNull(); + + // mocks to call toPredicate on specifications + CriteriaBuilder criteriaBuilder = mock(CriteriaBuilder.class); + final Path field1 = mock(Path.class); + final Path field2 = mock(Path.class); + final Predicate equalPredicate1 = mock(Predicate.class); + final Predicate equalPredicate2 = mock(Predicate.class); + final Predicate combinedPredicate = mock(Predicate.class); + final CriteriaQuery query = mock(CriteriaQuery.class); + final Root root = mock(Root.class); + + when(criteriaBuilder.equal(any(Path.class), eq("testValue1"))).thenReturn(equalPredicate1); + when(criteriaBuilder.equal(any(Path.class), eq("testValue2"))).thenReturn(equalPredicate2); + when(criteriaBuilder.and(eq(equalPredicate1), eq(equalPredicate2))).thenReturn(combinedPredicate); + when(root.get("field1")).thenReturn(field1); + when(root.get("field2")).thenReturn(field2); + + Predicate predicate = specifications.toPredicate(root, query, criteriaBuilder); + + assertThat(predicate).as("Combined predicate").isEqualTo(combinedPredicate); + + } + +} \ No newline at end of file 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 edf2ef034..56baf3bfc 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 @@ -18,7 +18,7 @@ import org.slf4j.LoggerFactory; /** * An abstraction for all controller based security. Check if the tenant * configuration is enabled. - * + * * * */ @@ -46,12 +46,6 @@ public abstract class AbstractControllerAuthenticationFilter implements PreAuthe return tenantAware.runAsTenant(secruityToken.getTenant(), configurationKeyTenantRunner); } - @Override - public abstract HeaderAuthentication getPreAuthenticatedPrincipal(TenantSecurityToken secruityToken); - - @Override - public abstract HeaderAuthentication getPreAuthenticatedCredentials(TenantSecurityToken secruityToken); - private final class SecurityConfigurationKeyTenantRunner implements TenantAware.TenantRunner { @Override public Boolean run() { 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 6836b8a31..fae0b53ad 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 @@ -8,6 +8,10 @@ */ package org.eclipse.hawkbit.security; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken; import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.tenancy.TenantAware; @@ -20,8 +24,6 @@ import org.slf4j.LoggerFactory; * request URI and the credential from a request header in a the * {@link TenantSecurityToken}. * - * - * */ public class ControllerPreAuthenticatedSecurityHeaderFilter extends AbstractControllerAuthenticationFilter { @@ -77,8 +79,7 @@ public class ControllerPreAuthenticatedSecurityHeaderFilter extends AbstractCont @Override public HeaderAuthentication getPreAuthenticatedPrincipal(final TenantSecurityToken secruityToken) { // retrieve the common name header and the authority name header from - // the http request and - // combine them together + // the http request and combine them together final String commonNameValue = secruityToken.getHeader(caCommonNameHeader); final String knownSslIssuerConfigurationValue = tenantAware.runAsTenant(secruityToken.getTenant(), sslIssuerNameConfigTenantRunner); @@ -97,18 +98,20 @@ public class ControllerPreAuthenticatedSecurityHeaderFilter extends AbstractCont } @Override - public HeaderAuthentication getPreAuthenticatedCredentials(final TenantSecurityToken secruityToken) { + public Object getPreAuthenticatedCredentials(final TenantSecurityToken secruityToken) { final String authorityNameConfigurationValue = tenantAware.runAsTenant(secruityToken.getTenant(), sslIssuerNameConfigTenantRunner); String controllerId = secruityToken.getControllerId(); // in case of legacy download artifact, the controller ID is not in the - // URL path, so then - // we just use the common name header + // URL path, so then we just use the common name header if (controllerId == null || "anonymous".equals(controllerId)) { controllerId = secruityToken.getHeader(caCommonNameHeader); } - return new HeaderAuthentication(controllerId, authorityNameConfigurationValue); + List knownHashes = splitMultiHashBySemicolon(authorityNameConfigurationValue); + + final String cntlId = controllerId; + return knownHashes.stream().map(hashItem -> new HeaderAuthentication(cntlId, hashItem)).collect(Collectors.toSet()); } /** @@ -117,12 +120,15 @@ public class ControllerPreAuthenticatedSecurityHeaderFilter extends AbstractCont * It's ok if we find the the hash in any the trusted CA chain to accept * this request for this tenant. */ - private String getIssuerHashHeader(final TenantSecurityToken secruityToken, final String knownIssuerHash) { + private String getIssuerHashHeader(final TenantSecurityToken secruityToken, final String knownIssuerHashes) { + // there may be several knownIssuerHashes configured for the tenant + List knownHashes = splitMultiHashBySemicolon(knownIssuerHashes); + // iterate over the headers until we get a null header. int iHeader = 1; String foundHash; while ((foundHash = secruityToken.getHeader(String.format(sslIssuerHashBasicHeader, iHeader))) != null) { - if (foundHash.equals(knownIssuerHash)) { + if (knownHashes.contains(foundHash)) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Found matching ssl issuer hash at position {}", iHeader); } @@ -148,4 +154,8 @@ public class ControllerPreAuthenticatedSecurityHeaderFilter extends AbstractCont TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME, String.class).getValue()); } } + + private static List splitMultiHashBySemicolon(String knownIssuerHashes) { + return Arrays.asList(knownIssuerHashes.split(";")); + } } diff --git a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthTokenSourceTrustAuthenticationProvider.java b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthTokenSourceTrustAuthenticationProvider.java index b4960737d..ffe1b7377 100644 --- a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthTokenSourceTrustAuthenticationProvider.java +++ b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthTokenSourceTrustAuthenticationProvider.java @@ -27,17 +27,17 @@ import org.springframework.security.web.authentication.preauth.PreAuthenticatedA * An spring authentication provider which supports authentication tokens of * type {@link PreAuthenticatedAuthenticationToken} created by the * {@link ControllerPreAuthenticatedSecurityHeaderFilter}. - * + * * Additionally to the authentication token providing the principal and the * credentials which must be match, this authentication provider can also check * the remote IP address of the request. - * + * * E.g. The request path is /controller/v1/{controllerId} then the controllerId * in the path is the principal. The credentials are the extracted information * from e.g. a certificate provided by an reverse proxy. Due this request is * only allowed from a specific source address this authentication manager can * also check the remote IP address of the request. - * + * * * */ @@ -58,7 +58,7 @@ public class PreAuthTokenSourceTrustAuthenticationProvider implements Authentica * Creates a new PreAuthTokenSourceTrustAuthenticationProvider with given * source IP addresses which are trusted and should be checked against the * request remote IP address. - * + * * @param authorizedSourceIps * a list of IP addresses. */ @@ -70,7 +70,7 @@ public class PreAuthTokenSourceTrustAuthenticationProvider implements Authentica * Creates a new PreAuthTokenSourceTrustAuthenticationProvider with given * source IP addresses which are trusted and should be checked against the * request remote IP address. - * + * * @param authorizedSourceIps * a list of IP addresses. */ @@ -87,7 +87,6 @@ public class PreAuthTokenSourceTrustAuthenticationProvider implements Authentica return null; } - boolean successAuthentication = false; final PreAuthenticatedAuthenticationToken token = (PreAuthenticatedAuthenticationToken) authentication; final Object credentials = token.getCredentials(); final Object principal = token.getPrincipal(); @@ -97,14 +96,7 @@ public class PreAuthTokenSourceTrustAuthenticationProvider implements Authentica throw new BadCredentialsException("The provided principal and credentials are not match"); } - // check if principal equals credentials because we want to check if - // e.g. controllerId - // containing in the URL equals the controllerId in the special header - // set by the reverse - // proxy which extracted the CN from the certificate - if (principal.equals(credentials)) { - successAuthentication = checkSourceIPAddressIfNeccessary(tokenDetails); - } + boolean successAuthentication = calculateAuthenticationSuccess(principal, credentials, tokenDetails); if (successAuthentication) { final Collection controllerAuthorities = new ArrayList<>(); @@ -119,6 +111,41 @@ public class PreAuthTokenSourceTrustAuthenticationProvider implements Authentica throw new BadCredentialsException("The provided principal and credentials are not match"); } + /** + * + * The credentials may either be of type HeaderAuthentication or of type + * Collection depending on the authentication mode in + * use (the latter is used in case of trusted reverse-proxy). It is checked + * whether principal equals credentials (respectively if credentials + * contains principal in case of collection) because we want to check if + * e.g. controllerId containing in the URL equals the controllerId in the + * special header set by the reverse-proxy which extracted the CN from the + * certificate. + * + * @param principal + * the {@link HeaderAuthentication} from the header + * @param credentials + * a single {@link HeaderAuthentication} or a Collection of + * HeaderAuthentication + * @param tokenDetails + * authentication details + * @return true if authentication succeeded, otherwise + * false + */ + private boolean calculateAuthenticationSuccess(Object principal, Object credentials, Object tokenDetails) { + boolean successAuthentication = false; + if (credentials instanceof Collection) { + final Collection multiValueCredentials = (Collection) credentials; + if (multiValueCredentials.contains(principal)) { + successAuthentication = checkSourceIPAddressIfNeccessary(tokenDetails); + } + } else if (principal.equals(credentials)) { + successAuthentication = checkSourceIPAddressIfNeccessary(tokenDetails); + } + + return successAuthentication; + } + private boolean checkSourceIPAddressIfNeccessary(final Object tokenDetails) { boolean success = authorizedSourceIps == null; String remoteAddress = null; diff --git a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthentificationFilter.java b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthentificationFilter.java index 90f2acd50..f0801ff4d 100644 --- a/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthentificationFilter.java +++ b/hawkbit-security-integration/src/main/java/org/eclipse/hawkbit/security/PreAuthentificationFilter.java @@ -22,7 +22,7 @@ public interface PreAuthentificationFilter { /** * Check if the filter is enabled. - * + * * @param secruityToken * the secruity info * @return is enabled diabled @@ -31,7 +31,7 @@ public interface PreAuthentificationFilter { /** * Extract the principal information from the current secruityToken. - * + * * @param secruityToken * the secruityToken * @return the extracted tenant and controller id @@ -40,17 +40,17 @@ public interface PreAuthentificationFilter { /** * Extract the principal credentials from the current secruityToken. - * + * * @param secruityToken * the secruityToken * @return the extracted tenant and controller id */ - HeaderAuthentication getPreAuthenticatedCredentials(TenantSecurityToken secruityToken); + Object getPreAuthenticatedCredentials(TenantSecurityToken secruityToken); /** * Allows to add additional authorities to the successful authenticated * token. - * + * * @return the authorities granted to the principal, or an empty collection * if the token has not been authenticated. Never null. * @see Authentication#getAuthorities() diff --git a/hawkbit-security-integration/src/test/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedSecurityHeaderFilterTest.java b/hawkbit-security-integration/src/test/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedSecurityHeaderFilterTest.java new file mode 100644 index 000000000..bfa13f71a --- /dev/null +++ b/hawkbit-security-integration/src/test/java/org/eclipse/hawkbit/security/ControllerPreAuthenticatedSecurityHeaderFilterTest.java @@ -0,0 +1,141 @@ +/** + * 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 static org.fest.assertions.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.when; + +import java.util.Collection; + +import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken; +import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken.FileResource; +import org.eclipse.hawkbit.repository.TenantConfigurationManagement; +import org.eclipse.hawkbit.repository.model.TenantConfigurationValue; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationKey; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Unit Tests - Security") +@Stories("Issuer hash based authentication") +@RunWith(MockitoJUnitRunner.class) +public class ControllerPreAuthenticatedSecurityHeaderFilterTest { + + private ControllerPreAuthenticatedSecurityHeaderFilter underTest; + + @Mock + private TenantConfigurationManagement tenantConfigurationManagementMock; + + @Mock + private TenantSecurityToken tenantSecurityTokenMock; + + private final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(); + + private static final String CA_COMMON_NAME = "ca-cn"; + private static final String CA_COMMON_NAME_VALUE = "box1"; + + private static final String X_SSL_ISSUER_HASH_1 = "X-Ssl-Issuer-Hash-1"; + + private static final String SINGLE_HASH = "hash1"; + private static final String SECOND_HASH = "hash2"; + private static final String UNKNOWN_HASH = "unknown"; + + private static final String MULTI_HASH = "hash1;hash2;hash3"; + + private static final TenantConfigurationValue CONFIG_VALUE_SINGLE_HASH = TenantConfigurationValue + . builder().value(SINGLE_HASH).build(); + + private static final TenantConfigurationValue CONFIG_VALUE_MULTI_HASH = TenantConfigurationValue + . builder().value(MULTI_HASH).build(); + + @Before + public void before() { + underTest = new ControllerPreAuthenticatedSecurityHeaderFilter(CA_COMMON_NAME, "X-Ssl-Issuer-Hash-%d", + tenantConfigurationManagementMock, tenantAware, new SystemSecurityContext(tenantAware)); + } + + @Test + @Description("Tests the filter for issuer hash based authentication with a single known hash") + public void testIssuerHashBasedAuthenticationWithSingleKnownHash() { + final TenantSecurityToken securityToken = prepareSecurityToken(SINGLE_HASH); + // use single known hash + when(tenantConfigurationManagementMock.getConfigurationValue( + eq(TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME), eq(String.class))) + .thenReturn(CONFIG_VALUE_SINGLE_HASH); + assertThat(underTest.getPreAuthenticatedPrincipal(securityToken)).isNotNull(); + } + + @Test + @Description("Tests the filter for issuer hash based authentication with multiple known hashes") + public void testIssuerHashBasedAuthenticationWithMultipleKnownHashes() { + final TenantSecurityToken securityToken = prepareSecurityToken(SINGLE_HASH); + // use multiple known hashes + when(tenantConfigurationManagementMock.getConfigurationValue( + eq(TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME), eq(String.class))) + .thenReturn(CONFIG_VALUE_MULTI_HASH); + assertThat(underTest.getPreAuthenticatedPrincipal(securityToken)).isNotNull(); + } + + @Test + @Description("Tests the filter for issuer hash based authentication with unknown hash") + public void testIssuerHashBasedAuthenticationWithUnknownHash() { + final TenantSecurityToken securityToken = prepareSecurityToken(UNKNOWN_HASH); + // use single known hash + when(tenantConfigurationManagementMock.getConfigurationValue( + eq(TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME), eq(String.class))) + .thenReturn(CONFIG_VALUE_MULTI_HASH); + assertThat(underTest.getPreAuthenticatedPrincipal(securityToken)).isNull(); + } + + @Test + @Description("Tests different values for issuer hash header and inspects the credentials") + public void useDifferentValuesForIssuerHashHeader() { + final TenantSecurityToken securityToken1 = prepareSecurityToken(SINGLE_HASH); + final TenantSecurityToken securityToken2 = prepareSecurityToken(SECOND_HASH); + + final HeaderAuthentication expected1 = new HeaderAuthentication(CA_COMMON_NAME_VALUE, SINGLE_HASH); + final HeaderAuthentication expected2 = new HeaderAuthentication(CA_COMMON_NAME_VALUE, SECOND_HASH); + + when(tenantConfigurationManagementMock.getConfigurationValue( + eq(TenantConfigurationKey.AUTHENTICATION_MODE_HEADER_AUTHORITY_NAME), eq(String.class))) + .thenReturn(CONFIG_VALUE_MULTI_HASH); + + final Collection credentials1 = (Collection) underTest + .getPreAuthenticatedCredentials(securityToken1); + final Collection credentials2 = (Collection) underTest + .getPreAuthenticatedCredentials(securityToken2); + + final Object principal1 = underTest.getPreAuthenticatedPrincipal(securityToken1); + final Object principal2 = underTest.getPreAuthenticatedPrincipal(securityToken2); + + assertThat(credentials1.contains(expected1)).isTrue(); + assertThat(credentials2.contains(expected2)).isTrue(); + + assertEquals("hash1 expected in principal!", expected1, principal1); + assertEquals("hash2 expected in principal!", expected2, principal2); + + } + + private static TenantSecurityToken prepareSecurityToken(final String issuerHashHeaderValue) { + final TenantSecurityToken securityToken = new TenantSecurityToken("DEFAULT", CA_COMMON_NAME_VALUE, + FileResource.createFileResourceBySha1("12345")); + securityToken.putHeader(CA_COMMON_NAME, CA_COMMON_NAME_VALUE); + securityToken.putHeader(X_SSL_ISSUER_HASH_1, issuerHashHeaderValue); + return securityToken; + } + +} diff --git a/hawkbit-test-report/pom.xml b/hawkbit-test-report/pom.xml index 3e9182f0a..77e416d2c 100644 --- a/hawkbit-test-report/pom.xml +++ b/hawkbit-test-report/pom.xml @@ -8,9 +8,7 @@ http://www.eclipse.org/legal/epl-v10.html --> - - + 4.0.0 org.eclipse.hawkbit @@ -22,60 +20,63 @@ pom - - - - maven-resources-plugin - 2.7 - - - copy-resources - verify - - copy-resources - + + + generateTestReport + + + + maven-resources-plugin + + + copy-resources + verify + + copy-resources + + + ${basedir}/target/allure-results + + + ${basedir}/.. + false + + **/target/allure-results/*.xml + + + + ${basedir} + false + + placeholder.txt + + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin - ${basedir}/target/allure-results - - - ${basedir}/.. - false - - **/target/allure-results/*.xml - - - - ${basedir} - false - - placeholder.txt - - - + + src/main/assembly/test-report.xml + + false - - - - - org.apache.maven.plugins - maven-assembly-plugin - 2.5.3 - - - src/main/assembly/test-report.xml - - false - - - - create-report-zip-assembly - verify - - single - - - - - - + + + create-report-zip-assembly + verify + + single + + + + + + + + \ No newline at end of file diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/CommonDialogWindow.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/CommonDialogWindow.java index 3cc00e016..86841f08e 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/CommonDialogWindow.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/CommonDialogWindow.java @@ -263,9 +263,9 @@ public class CommonDialogWindow extends Window { if (field instanceof Table) { ((Table) field).addItemSetChangeListener(new ChangeListener(field)); - } else { - field.addValueChangeListener(new ChangeListener(field)); } + field.addValueChangeListener(new ChangeListener(field)); + } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/detailslayout/TargetFilterQueryDetailsTable.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/detailslayout/TargetFilterQueryDetailsTable.java new file mode 100644 index 000000000..98c91eb54 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/detailslayout/TargetFilterQueryDetailsTable.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.ui.common.detailslayout; + +import java.util.List; + +import com.vaadin.data.Container; +import com.vaadin.data.Item; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.TargetFilterQuery; + +import com.vaadin.data.util.IndexedContainer; +import com.vaadin.spring.annotation.SpringComponent; +import com.vaadin.spring.annotation.VaadinSessionScope; +import com.vaadin.ui.Table; +import com.vaadin.ui.themes.ValoTheme; +import org.eclipse.hawkbit.ui.utils.I18N; +import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; + +/** + * + * DistributionSet TargetFilterQuery table + * + */ + +@SpringComponent +@VaadinSessionScope +public class TargetFilterQueryDetailsTable extends Table { + + private static final long serialVersionUID = 2913758299611837718L; + + private static final String TFQ_NAME = "name"; + private static final String TFQ_QUERY = "query"; + + private I18N i18n; + + /** + * + * @param i18n + */ + public void init(final I18N i18n) { + this.i18n = i18n; + createTable(); + } + + /** + * Populate software module metadata. + * + * @param distributionSet the selected distribution set + */ + public void populateTableByDistributionSet(final DistributionSet distributionSet) { + removeAllItems(); + if (distributionSet == null) { + return; + } + + Container dataSource = getContainerDataSource(); + List filters = distributionSet.getAutoAssignFilters(); + filters.forEach(query -> { + Object itemId = dataSource.addItem(); + Item item = dataSource.getItem(itemId); + item.getItemProperty(TFQ_NAME).setValue(query.getName()); + item.getItemProperty(TFQ_QUERY).setValue(query.getQuery()); + }); + + } + + private void createTable() { + addStyleName(ValoTheme.TABLE_NO_HORIZONTAL_LINES); + addStyleName(ValoTheme.TABLE_NO_STRIPES); + addStyleName(SPUIStyleDefinitions.SW_MODULE_TABLE); + addStyleName("details-layout"); + setSelectable(false); + setImmediate(true); + setContainerDataSource(getDistSetContainer()); + setColumnHeaderMode(ColumnHeaderMode.EXPLICIT); + addTableHeader(); + setSizeFull(); + // same as height of other tabs in details tabsheet + setHeight(116, Unit.PIXELS); + } + + private IndexedContainer getDistSetContainer() { + final IndexedContainer container = new IndexedContainer(); + container.addContainerProperty(TFQ_NAME, String.class, ""); + container.addContainerProperty(TFQ_QUERY, String.class, ""); + setColumnExpandRatio(TFQ_NAME, 0.4F); + setColumnAlignment(TFQ_NAME, Align.LEFT); + setColumnExpandRatio(TFQ_QUERY, 0.6F); + setColumnAlignment(TFQ_QUERY, Align.LEFT); + + return container; + } + + private void addTableHeader() { + setColumnHeader(TFQ_NAME, i18n.get("header.target.filter.name")); + setColumnHeader(TFQ_QUERY, i18n.get("header.target.filter.query")); + } + + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/ProxyDistribution.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/ProxyDistribution.java index 0420f9e4b..fccf7d6a7 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/ProxyDistribution.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/ProxyDistribution.java @@ -10,6 +10,9 @@ package org.eclipse.hawkbit.ui.components; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.ui.common.DistributionSetIdName; +import org.eclipse.hawkbit.ui.common.UserDetailsFormatter; +import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; +import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil; /** * Proxy for {@link DistributionSet}. @@ -39,6 +42,33 @@ public class ProxyDistribution { private String version; private String description; + /** + * Creates an empty proxy distribution set + */ + public ProxyDistribution() { + // Default constructor + } + + /** + * Creates a new proxy distribution set by using the values from a distribution set + * @param distributionSet the source distribution set + */ + public ProxyDistribution(DistributionSet distributionSet) { + setName(distributionSet.getName()); + setDescription(distributionSet.getDescription()); + setDistId(distributionSet.getId()); + setId(distributionSet.getId()); + setVersion(distributionSet.getVersion()); + setCreatedDate(SPDateTimeUtil.getFormattedDate(distributionSet.getCreatedAt())); + setLastModifiedDate(SPDateTimeUtil.getFormattedDate(distributionSet.getLastModifiedAt())); + setCreatedByUser(UserDetailsFormatter.loadAndFormatCreatedBy(distributionSet)); + setModifiedByUser(UserDetailsFormatter.loadAndFormatLastModifiedBy(distributionSet)); + setNameVersion( + HawkbitCommonUtil.getFormattedNameVersion(distributionSet.getName(), distributionSet.getVersion())); + setIsComplete(distributionSet.isComplete()); + } + + /** * @return the nameVersion */ diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/ProxyTargetFilter.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/ProxyTargetFilter.java index 9faf501e0..75d8d343c 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/ProxyTargetFilter.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/components/ProxyTargetFilter.java @@ -26,6 +26,7 @@ public class ProxyTargetFilter { private String createdBy; private String lastModifiedBy; private String query; + private ProxyDistribution autoAssignDistributionSet; public String getCreatedDate() { return createdDate; @@ -90,4 +91,11 @@ public class ProxyTargetFilter { this.createdBy = createdBy; } + public ProxyDistribution getAutoAssignDistributionSet() { + return autoAssignDistributionSet; + } + + public void setAutoAssignDistributionSet(ProxyDistribution autoAssignDistributionSet) { + this.autoAssignDistributionSet = autoAssignDistributionSet; + } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/DistributionSetDetails.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/DistributionSetDetails.java index b6f90e7ac..d9026d64b 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/DistributionSetDetails.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/DistributionSetDetails.java @@ -25,6 +25,7 @@ import org.eclipse.hawkbit.ui.common.DistributionSetIdName; import org.eclipse.hawkbit.ui.common.detailslayout.AbstractNamedVersionedEntityTableDetailsLayout; import org.eclipse.hawkbit.ui.common.detailslayout.DistributionSetMetadatadetailslayout; import org.eclipse.hawkbit.ui.common.detailslayout.SoftwareModuleDetailsTable; +import org.eclipse.hawkbit.ui.common.detailslayout.TargetFilterQueryDetailsTable; import org.eclipse.hawkbit.ui.common.tagdetails.DistributionTagToken; import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleSmallNoBorder; @@ -91,6 +92,8 @@ public class DistributionSetDetails extends AbstractNamedVersionedEntityTableDet private DistributionSetMetadatadetailslayout dsMetadataTable; + private TargetFilterQueryDetailsTable tfqDetailsTable; + private VerticalLayout tagsLayout; private Map assignedSWModule; @@ -103,9 +106,14 @@ public class DistributionSetDetails extends AbstractNamedVersionedEntityTableDet softwareModuleTable = new SoftwareModuleDetailsTable(); softwareModuleTable.init(getI18n(), true, getPermissionChecker(), distributionSetManagement, getEventBus(), manageDistUIState); + dsMetadataTable = new DistributionSetMetadatadetailslayout(); dsMetadataTable.init(getI18n(), getPermissionChecker(), distributionSetManagement, dsMetadataPopupLayout, entityFactory); + + tfqDetailsTable = new TargetFilterQueryDetailsTable(); + tfqDetailsTable.init(getI18n()); + super.init(); } @@ -120,6 +128,7 @@ public class DistributionSetDetails extends AbstractNamedVersionedEntityTableDet populateModule(); populateTags(); populateMetadataDetails(); + populateTargetFilterQueries(); } private void populateModule() { @@ -277,6 +286,10 @@ public class DistributionSetDetails extends AbstractNamedVersionedEntityTableDet dsMetadataTable.populateDSMetadata(getSelectedBaseEntity()); } + protected void populateTargetFilterQueries() { + tfqDetailsTable.populateTableByDistributionSet(getSelectedBaseEntity()); + } + private void updateDistributionSetDetailsLayout(final String type, final Boolean isMigrationRequired) { final VerticalLayout detailsTabLayout = getDetailsLayout(); detailsTabLayout.removeAllComponents(); @@ -332,6 +345,7 @@ public class DistributionSetDetails extends AbstractNamedVersionedEntityTableDet detailsTab.addTab(createTagsLayout(), getI18n().get("caption.tags.tab"), null); detailsTab.addTab(createLogLayout(), getI18n().get("caption.logs.tab"), null); detailsTab.addTab(dsMetadataTable, getI18n().get("caption.metadata"), null); + detailsTab.addTab(tfqDetailsTable, getI18n().get("caption.auto.assignment.ds"), null); } @Override diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/ManageDistBeanQuery.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/ManageDistBeanQuery.java index a3a08f2b7..81ece7340 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/ManageDistBeanQuery.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/distributions/dstable/ManageDistBeanQuery.java @@ -20,10 +20,8 @@ import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetFilter; import org.eclipse.hawkbit.repository.model.DistributionSetFilter.DistributionSetFilterBuilder; import org.eclipse.hawkbit.repository.model.DistributionSetType; -import org.eclipse.hawkbit.ui.common.UserDetailsFormatter; import org.eclipse.hawkbit.ui.components.ProxyDistribution; import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; -import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil; import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; import org.eclipse.hawkbit.ui.utils.SpringContextHelper; import org.springframework.data.domain.Page; @@ -43,11 +41,12 @@ public class ManageDistBeanQuery extends AbstractBeanQuery { private static final long serialVersionUID = 5176481314404662215L; private Sort sort = new Sort(Direction.ASC, "createdAt"); - private String searchText = null; + private String searchText; private transient DistributionSetManagement distributionSetManagement; - private transient Page firstPageDistributionSets = null; + private transient Page firstPageDistributionSets; - private DistributionSetType distributionSetType = null; + private DistributionSetType distributionSetType; + private Boolean dsComplete; /** * @@ -65,14 +64,17 @@ public class ManageDistBeanQuery extends AbstractBeanQuery { if (!Strings.isNullOrEmpty(searchText)) { searchText = String.format("%%%s%%", searchText); } - if (null != queryConfig.get(SPUIDefinitions.FILTER_BY_DISTRIBUTION_SET_TYPE)) { + if (queryConfig.get(SPUIDefinitions.FILTER_BY_DISTRIBUTION_SET_TYPE) != null) { distributionSetType = (DistributionSetType) queryConfig .get(SPUIDefinitions.FILTER_BY_DISTRIBUTION_SET_TYPE); } + if(queryConfig.get(SPUIDefinitions.FILTER_BY_DS_COMPLETE) != null) { + dsComplete = (Boolean)queryConfig.get(SPUIDefinitions.FILTER_BY_DS_COMPLETE); + } } if (sortStates.length > 0) { - // Initalize sort + // Initialize sort sort = new Sort(sortStates[0] ? Direction.ASC : Direction.DESC, (String) sortPropertyIds[0]); // Add sort for (int distId = 1; distId < sortPropertyIds.length; distId++) { @@ -96,27 +98,17 @@ public class ManageDistBeanQuery extends AbstractBeanQuery { } else if (Strings.isNullOrEmpty(searchText)) { // if no search filters available distBeans = getDistributionSetManagement().findDistributionSetsByDeletedAndOrCompleted( - new OffsetBasedPageRequest(startIndex, count, sort), false, null); + new OffsetBasedPageRequest(startIndex, count, sort), false, dsComplete); } else { final DistributionSetFilter distributionSetFilter = new DistributionSetFilterBuilder().setIsDeleted(false) + .setIsComplete(dsComplete) .setSearchText(searchText).setSelectDSWithNoTag(Boolean.FALSE).setType(distributionSetType).build(); distBeans = getDistributionSetManagement().findDistributionSetsByFilters( new PageRequest(startIndex / count, count, sort), distributionSetFilter); } for (final DistributionSet distributionSet : distBeans) { - final ProxyDistribution proxyDistribution = new ProxyDistribution(); - proxyDistribution.setName(distributionSet.getName()); - proxyDistribution.setDescription(distributionSet.getDescription()); - proxyDistribution.setDistId(distributionSet.getId()); - proxyDistribution.setId(distributionSet.getId()); - proxyDistribution.setVersion(distributionSet.getVersion()); - proxyDistribution.setCreatedDate(SPDateTimeUtil.getFormattedDate(distributionSet.getCreatedAt())); - proxyDistribution.setLastModifiedDate(SPDateTimeUtil.getFormattedDate(distributionSet.getLastModifiedAt())); - proxyDistribution.setCreatedByUser(UserDetailsFormatter.loadAndFormatCreatedBy(distributionSet)); - proxyDistribution.setModifiedByUser(UserDetailsFormatter.loadAndFormatLastModifiedBy(distributionSet)); - proxyDistribution.setIsComplete(distributionSet.isComplete()); - proxyDistributions.add(proxyDistribution); + proxyDistributions.add(new ProxyDistribution(distributionSet)); } return proxyDistributions; } @@ -132,9 +124,10 @@ public class ManageDistBeanQuery extends AbstractBeanQuery { if (Strings.isNullOrEmpty(searchText) && null == distributionSetType) { // if no search filters available firstPageDistributionSets = getDistributionSetManagement().findDistributionSetsByDeletedAndOrCompleted( - new PageRequest(0, SPUIDefinitions.PAGE_SIZE, sort), false, null); + new PageRequest(0, SPUIDefinitions.PAGE_SIZE, sort), false, dsComplete); } else { final DistributionSetFilter distributionSetFilter = new DistributionSetFilterBuilder().setIsDeleted(false) + .setIsComplete(dsComplete) .setSearchText(searchText).setSelectDSWithNoTag(Boolean.FALSE).setType(distributionSetType).build(); firstPageDistributionSets = getDistributionSetManagement().findDistributionSetsByFilters( new PageRequest(0, SPUIDefinitions.PAGE_SIZE, sort), distributionSetFilter); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/DistributionSetSelectTable.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/DistributionSetSelectTable.java new file mode 100644 index 000000000..82d41a616 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/DistributionSetSelectTable.java @@ -0,0 +1,179 @@ +/** + * 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.filtermanagement; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +import org.eclipse.hawkbit.repository.eventbus.event.DistributionCreatedEvent; +import org.eclipse.hawkbit.repository.eventbus.event.DistributionDeletedEvent; +import org.eclipse.hawkbit.ui.distributions.dstable.ManageDistBeanQuery; +import org.eclipse.hawkbit.ui.distributions.state.ManageDistUIState; +import org.eclipse.hawkbit.ui.utils.I18N; +import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; +import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions; +import org.eclipse.hawkbit.ui.utils.TableColumn; +import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.vaadin.addons.lazyquerycontainer.BeanQueryFactory; +import org.vaadin.addons.lazyquerycontainer.LazyQueryContainer; +import org.vaadin.addons.lazyquerycontainer.LazyQueryDefinition; +import org.vaadin.spring.events.EventBus; +import org.vaadin.spring.events.EventScope; +import org.vaadin.spring.events.annotation.EventBusListenerMethod; + +import com.vaadin.data.Container; +import com.vaadin.spring.annotation.SpringComponent; +import com.vaadin.spring.annotation.ViewScope; +import com.vaadin.ui.Table; +import com.vaadin.ui.themes.ValoTheme; + +/** + * Table for selecting a distribution set. + */ +@SpringComponent +@ViewScope +public class DistributionSetSelectTable extends Table { + + private static final long serialVersionUID = -4307487829435471759L; + + @Autowired + private I18N i18n; + + @Autowired + private transient EventBus.SessionEventBus eventBus; + + @Autowired + private ManageDistUIState manageDistUIState; + + private Container container; + + /** + * Initialize the component. + */ + @PostConstruct + protected void init() { + setStyleName("sp-table"); + setSizeFull(); + setSelectable(true); + setMultiSelect(false); + setImmediate(true); + addStyleName(ValoTheme.TABLE_NO_VERTICAL_LINES); + addStyleName(ValoTheme.TABLE_SMALL); + populateTableData(); + setColumnCollapsingAllowed(false); + setColumnProperties(); + setId(UIComponentIdProvider.DIST_SET_SELECT_TABLE_ID); + eventBus.subscribe(this); + } + + @PreDestroy + void destroy() { + eventBus.unsubscribe(this); + } + + @EventBusListenerMethod(scope = EventScope.SESSION) + void onEvents(final List events) { + final Object firstEvent = events.get(0); + if (DistributionCreatedEvent.class.isInstance(firstEvent) + || DistributionDeletedEvent.class.isInstance(firstEvent)) { + refreshDistributions(); + } + } + + private void populateTableData() { + container = createContainer(); + addContainerproperties(); + setContainerDataSource(container); + setColumnProperties(); + + } + + protected Container createContainer() { + + final Map queryConfiguration = prepareQueryConfigFilters(); + final BeanQueryFactory distributionQF = new BeanQueryFactory<>(ManageDistBeanQuery.class); + + distributionQF.setQueryConfiguration(queryConfiguration); + return new LazyQueryContainer( + new LazyQueryDefinition(true, SPUIDefinitions.PAGE_SIZE, SPUILabelDefinitions.VAR_DIST_ID_NAME), + distributionQF); + } + + private void addContainerproperties() { + /* Create HierarchicalContainer container */ + container.addContainerProperty(SPUILabelDefinitions.NAME, String.class, null); + container.addContainerProperty(SPUILabelDefinitions.VAR_VERSION, String.class, null); + } + + private List getVisbleColumns() { + final List columnList = new ArrayList<>(2); + columnList.add(new TableColumn(SPUILabelDefinitions.NAME, i18n.get("header.name"), 0.6F)); + columnList.add(new TableColumn(SPUILabelDefinitions.VAR_VERSION, i18n.get("header.version"), 0.4F)); + return columnList; + + } + + private void setColumnProperties() { + setVisibleColumns(getVisbleColumns().stream().map(column -> { + setColumnHeader(column.getColumnPropertyId(), column.getColumnHeader()); + setColumnExpandRatio(column.getColumnPropertyId(), column.getExpandRatio()); + return column.getColumnPropertyId(); + }).toArray()); + } + + private Map prepareQueryConfigFilters() { + final Map queryConfig = new HashMap<>(); + manageDistUIState.getManageDistFilters().getSearchText() + .ifPresent(value -> queryConfig.put(SPUIDefinitions.FILTER_BY_TEXT, value)); + + if (null != manageDistUIState.getManageDistFilters().getClickedDistSetType()) { + queryConfig.put(SPUIDefinitions.FILTER_BY_DISTRIBUTION_SET_TYPE, + manageDistUIState.getManageDistFilters().getClickedDistSetType()); + } + + queryConfig.put(SPUIDefinitions.FILTER_BY_DS_COMPLETE, Boolean.TRUE); + + return queryConfig; + } + + private void refreshDistributions() { + final LazyQueryContainer dsContainer = (LazyQueryContainer) getContainerDataSource(); + final int size = dsContainer.size(); + if (size < SPUIDefinitions.MAX_TABLE_ENTRIES) { + refreshTablecontainer(); + } + if (size != 0) { + setData(SPUIDefinitions.DATA_AVAILABLE); + } + } + + private Object getItemIdToSelect() { + if (manageDistUIState.getSelectedDistributions().isPresent()) { + return manageDistUIState.getSelectedDistributions().get(); + } + return null; + } + + private void selectRow() { + setValue(getItemIdToSelect()); + } + + private void refreshTablecontainer() { + final LazyQueryContainer dsContainer = (LazyQueryContainer) getContainerDataSource(); + dsContainer.refresh(); + selectRow(); + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/DistributionSetSelectWindow.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/DistributionSetSelectWindow.java new file mode 100644 index 000000000..f0f484526 --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/DistributionSetSelectWindow.java @@ -0,0 +1,307 @@ +/** + * 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.filtermanagement; + +import com.vaadin.server.FontAwesome; +import com.vaadin.ui.Alignment; +import com.vaadin.ui.Button; +import com.vaadin.ui.CheckBox; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.UI; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.Window; +import org.eclipse.hawkbit.repository.DistributionSetManagement; +import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; +import org.eclipse.hawkbit.repository.TargetManagement; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.TargetFilterQuery; +import org.eclipse.hawkbit.ui.common.CommonDialogWindow; +import org.eclipse.hawkbit.ui.common.DistributionSetIdName; +import org.eclipse.hawkbit.ui.common.builder.WindowBuilder; +import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; +import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleNoBorderWithIcon; +import org.eclipse.hawkbit.ui.filtermanagement.event.CustomFilterUIEvent; +import org.eclipse.hawkbit.ui.utils.I18N; +import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; +import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; +import org.springframework.beans.factory.annotation.Autowired; +import org.vaadin.spring.events.EventBus; + +import com.vaadin.data.Property; +import com.vaadin.server.Sizeable; +import com.vaadin.spring.annotation.SpringComponent; +import com.vaadin.spring.annotation.ViewScope; + +import java.io.Serializable; + +/** + * Creates a dialog window to select the distribution set for a target filter query. + */ +@SpringComponent +@ViewScope +public class DistributionSetSelectWindow + implements CommonDialogWindow.SaveDialogCloseListener, Property.ValueChangeListener { + + private static final long serialVersionUID = 4752345414134989396L; + + @Autowired + private I18N i18n; + + @Autowired + private DistributionSetSelectTable dsTable; + + @Autowired + private transient EventBus.SessionEventBus eventBus; + + @Autowired + private transient DistributionSetManagement distributionSetManagement; + + @Autowired + private transient TargetManagement targetManagement; + + @Autowired + private transient TargetFilterQueryManagement targetFilterQueryManagement; + + private CommonDialogWindow window; + private CheckBox checkBox; + private VerticalLayout verticalLayout; + private Long tfqId; + + private void init() { + Label label = new Label(i18n.get("label.auto.assign.description")); + + checkBox = new CheckBox(i18n.get("label.auto.assign.enable")); + checkBox.setId(UIComponentIdProvider.DIST_SET_SELECT_ENABLE_ID); + checkBox.setImmediate(true); + checkBox.addValueChangeListener(this); + + verticalLayout = new VerticalLayout(); + verticalLayout.addComponent(label); + verticalLayout.addComponent(checkBox); + verticalLayout.addComponent(dsTable); + + window = new WindowBuilder(SPUIDefinitions.CREATE_UPDATE_WINDOW) + .caption(i18n.get("caption.select.auto.assign.dist")).content(verticalLayout).layout(verticalLayout) + .i18n(i18n).saveDialogCloseListener(this).buildCommonDialogWindow(); + window.setId(UIComponentIdProvider.DIST_SET_SELECT_WINDOW_ID); + } + + public void setValue(DistributionSetIdName distSet) { + dsTable.setVisible(distSet != null); + checkBox.setValue(distSet != null); + dsTable.setValue(distSet); + dsTable.setCurrentPageFirstItemId(distSet); + } + + public DistributionSetIdName getValue() { + if (checkBox.getValue()) { + return (DistributionSetIdName) dsTable.getValue(); + } + return null; + } + + /** + * Shows a distribution set select window for the given target filter query + * + * @param tfqId + * target filter query id + */ + public void showForTargetFilter(Long tfqId) { + this.tfqId = tfqId; + TargetFilterQuery tfq = targetFilterQueryManagement.findTargetFilterQueryById(tfqId); + if(tfq == null) { + throw new IllegalStateException("TargetFilterQuery does not exist for the given id"); + } + + init(); + + DistributionSet distributionSet = tfq.getAutoAssignDistributionSet(); + if(distributionSet != null) { + setValue(DistributionSetIdName.generate(distributionSet)); + } else { + setValue(null); + } + + window.setWidth(40.0F, Sizeable.Unit.PERCENTAGE); + UI.getCurrent().addWindow(window); + window.setVisible(true); + } + + /** + * Is triggered when the checkbox value changes + * + * @param event + * change event + */ + @Override + public void valueChange(Property.ValueChangeEvent event) { + dsTable.setVisible(checkBox.getValue()); + if (window != null) { + window.center(); + + } + } + + /** + * Is triggered when the save button is clicked + * + * @return whether the click should be allowed + */ + @Override + public boolean canWindowSaveOrUpdate() { + return !checkBox.getValue() || dsTable.getValue() != null; + } + + /** + * Is called when the new value should be saved after the save button has + * been clicked + */ + @Override + public void saveOrUpdate() { + if(checkBox.getValue() && dsTable.getValue() != null) { + DistributionSetIdName ds = (DistributionSetIdName) dsTable.getValue(); + updateTargetFilterQueryDS(tfqId, ds.getId()); + + } else if(!checkBox.getValue()) { + updateTargetFilterQueryDS(tfqId, null); + + } + + } + + private void updateTargetFilterQueryDS(final Long targetFilterQueryId, final Long dsId) { + TargetFilterQuery tfq = targetFilterQueryManagement.findTargetFilterQueryById(targetFilterQueryId); + + + + if(dsId != null) { + confirmWithConsequencesDialog(tfq, dsId); + } else { + tfq.setAutoAssignDistributionSet(null); + targetFilterQueryManagement.updateTargetFilterQuery(tfq); + eventBus.publish(this, CustomFilterUIEvent.UPDATED_TARGET_FILTER_QUERY); + } + + + } + + private void confirmWithConsequencesDialog(TargetFilterQuery tfq, final Long dsId) { + + ConfirmConsequencesDialog dialog = new ConfirmConsequencesDialog(tfq, dsId, new ConfirmCallback() { + @Override + public void onConfirmResult(boolean accepted) { + if(accepted) { + tfq.setAutoAssignDistributionSet(distributionSetManagement.findDistributionSetById(dsId)); + targetFilterQueryManagement.updateTargetFilterQuery(tfq); + eventBus.publish(this, CustomFilterUIEvent.UPDATED_TARGET_FILTER_QUERY); + } + } + }); + + dialog.setWidth(40.0F, Sizeable.Unit.PERCENTAGE); + + UI.getCurrent().addWindow(dialog); + dialog.setVisible(true); + + } + + /** + * A dialog that displays how many targets will be assigned immediately with the + */ + private class ConfirmConsequencesDialog extends Window implements Button.ClickListener { + + private static final long serialVersionUID = 7738545414137389326L; + + private TargetFilterQuery targetFilterQuery; + private Long distributionSetId; + + private Button okButton; + private Button cancelButton; + + private ConfirmCallback callback; + + public ConfirmConsequencesDialog(TargetFilterQuery targetFilterQuery, final Long dsId, ConfirmCallback callback) { + super(i18n.get("caption.confirm.assign.consequences")); + + this.callback = callback; + this.targetFilterQuery = targetFilterQuery; + this.distributionSetId = dsId; + + init(); + + } + + private void init() { + setId(UIComponentIdProvider.DIST_SET_SELECT_CONS_WINDOW_ID); + setModal(true); + setResizable(false); + + VerticalLayout layout = new VerticalLayout(); + layout.setSpacing(true); + layout.setMargin(true); + setContent(layout); + + Long targetsCount = targetManagement.countTargetsByTargetFilterQueryAndNonDS(distributionSetId, targetFilterQuery); + Label mainTextLabel; + if(targetsCount == 0) { + mainTextLabel = new Label(i18n.get("message.confirm.assign.consequences.none")); + } else { + mainTextLabel = new Label(i18n.get("message.confirm.assign.consequences.text", new Object[]{targetsCount})); + } + + layout.addComponent(mainTextLabel); + + HorizontalLayout buttonsLayout = new HorizontalLayout(); + buttonsLayout.setSizeFull(); + buttonsLayout.setSpacing(true); + buttonsLayout.addStyleName("actionButtonsMargin"); + layout.addComponent(buttonsLayout); + + + okButton = SPUIComponentProvider.getButton(UIComponentIdProvider.SAVE_BUTTON, i18n.get("button.ok"), "", "", true, + FontAwesome.SAVE, SPUIButtonStyleNoBorderWithIcon.class); + okButton.setSizeUndefined(); + okButton.addStyleName("default-color"); + okButton.addClickListener(this); + buttonsLayout.addComponent(okButton); + buttonsLayout.setComponentAlignment(okButton, Alignment.MIDDLE_RIGHT); + buttonsLayout.setExpandRatio(okButton, 1.0F); + + + cancelButton = SPUIComponentProvider.getButton(UIComponentIdProvider.CANCEL_BUTTON, i18n.get("button.cancel"), "", "", true, + FontAwesome.TIMES, SPUIButtonStyleNoBorderWithIcon.class); + cancelButton.setSizeUndefined(); + cancelButton.addStyleName("default-color"); + cancelButton.addClickListener(this); + buttonsLayout.addComponent(cancelButton); + buttonsLayout.setComponentAlignment(cancelButton, Alignment.MIDDLE_LEFT); + buttonsLayout.setExpandRatio(cancelButton, 1.0F); + + + } + + @Override + public void buttonClick(Button.ClickEvent event) { + if(event.getButton().getId().equals(okButton.getId())) { + callback.onConfirmResult(true); + } else { + callback.onConfirmResult(false); + } + + close(); + + } + } + + @FunctionalInterface + private interface ConfirmCallback extends Serializable { + void onConfirmResult(boolean accepted); + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterBeanQuery.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterBeanQuery.java index 9231b1658..c38dd694b 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterBeanQuery.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterBeanQuery.java @@ -13,8 +13,10 @@ import java.util.List; import java.util.Map; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; +import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.ui.common.UserDetailsFormatter; +import org.eclipse.hawkbit.ui.components.ProxyDistribution; import org.eclipse.hawkbit.ui.components.ProxyTargetFilter; import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil; @@ -85,7 +87,7 @@ public class TargetFilterBeanQuery extends AbstractBeanQuery targetFilterQuery = getTargetFilterQueryManagement().findAllTargetFilterQuery( new PageRequest(startIndex / SPUIDefinitions.PAGE_SIZE, SPUIDefinitions.PAGE_SIZE, sort)); } else { - targetFilterQuery = getTargetFilterQueryManagement().findTargetFilterQueryByFilters( + targetFilterQuery = getTargetFilterQueryManagement().findTargetFilterQueryByName( new PageRequest(startIndex / SPUIDefinitions.PAGE_SIZE, SPUIDefinitions.PAGE_SIZE, sort), searchText); } @@ -98,6 +100,11 @@ public class TargetFilterBeanQuery extends AbstractBeanQuery proxyTarFilter.setModifiedDate(SPDateTimeUtil.getFormattedDate(tarFilterQuery.getLastModifiedAt())); proxyTarFilter.setLastModifiedBy(UserDetailsFormatter.loadAndFormatLastModifiedBy(tarFilterQuery)); proxyTarFilter.setQuery(tarFilterQuery.getQuery()); + + final DistributionSet distributionSet = tarFilterQuery.getAutoAssignDistributionSet(); + if (distributionSet != null) { + proxyTarFilter.setAutoAssignDistributionSet(new ProxyDistribution(distributionSet)); + } proxyTargetFilter.add(proxyTarFilter); } return proxyTargetFilter; @@ -117,7 +124,7 @@ public class TargetFilterBeanQuery extends AbstractBeanQuery .findAllTargetFilterQuery(new PageRequest(0, SPUIDefinitions.PAGE_SIZE, sort)); } else { firstPageTargetFilter = getTargetFilterQueryManagement() - .findTargetFilterQueryByFilters(new PageRequest(0, SPUIDefinitions.PAGE_SIZE, sort), searchText); + .findTargetFilterQueryByName(new PageRequest(0, SPUIDefinitions.PAGE_SIZE, sort), searchText); } final long size = firstPageTargetFilter.getTotalElements(); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterTable.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterTable.java index a7b1c9929..6aefa0b75 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterTable.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterTable.java @@ -10,16 +10,21 @@ package org.eclipse.hawkbit.ui.filtermanagement; import java.util.ArrayList; import java.util.Date; -import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; +import com.vaadin.ui.Button; +import com.vaadin.ui.Link; +import com.vaadin.ui.Table; +import com.vaadin.ui.UI; +import org.eclipse.hawkbit.repository.DistributionSetManagement; import org.eclipse.hawkbit.repository.TargetFilterQueryManagement; import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.ui.common.ConfirmationDialog; +import org.eclipse.hawkbit.ui.components.ProxyDistribution; import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleSmallNoBorder; import org.eclipse.hawkbit.ui.filtermanagement.event.CustomFilterUIEvent; @@ -44,15 +49,11 @@ import com.vaadin.data.Item; import com.vaadin.server.FontAwesome; import com.vaadin.spring.annotation.SpringComponent; import com.vaadin.spring.annotation.ViewScope; -import com.vaadin.ui.Button; import com.vaadin.ui.Button.ClickEvent; -import com.vaadin.ui.Link; -import com.vaadin.ui.Table; -import com.vaadin.ui.UI; import com.vaadin.ui.themes.ValoTheme; /** - * + * Displays list of target filter queries * */ @SpringComponent @@ -76,6 +77,12 @@ public class TargetFilterTable extends Table { @Autowired private transient TargetFilterQueryManagement targetFilterQueryManagement; + @Autowired + private transient DistributionSetManagement distributionSetManagement; + + @Autowired + private DistributionSetSelectWindow dsSelectWindow; + private Container container; private static final int PROPERTY_DEPT = 3; @@ -142,15 +149,17 @@ public class TargetFilterTable extends Table { container.addContainerProperty(SPUILabelDefinitions.VAR_CREATED_DATE, Date.class, null); container.addContainerProperty(SPUILabelDefinitions.VAR_MODIFIED_DATE, Date.class, null); container.addContainerProperty(SPUILabelDefinitions.VAR_MODIFIED_BY, String.class, null); + container.addContainerProperty(SPUILabelDefinitions.AUTO_ASSIGN_DISTRIBUTION_SET, String.class, null); } private List getVisbleColumns() { - final List columnList = new ArrayList<>(); + final List columnList = new ArrayList<>(7); columnList.add(new TableColumn(SPUILabelDefinitions.NAME, i18n.get("header.name"), 0.2F)); - columnList.add(new TableColumn(SPUILabelDefinitions.VAR_CREATED_USER, i18n.get("header.createdBy"), 0.15F)); + columnList.add(new TableColumn(SPUILabelDefinitions.VAR_CREATED_USER, i18n.get("header.createdBy"), 0.1F)); columnList.add(new TableColumn(SPUILabelDefinitions.VAR_CREATED_DATE, i18n.get("header.createdDate"), 0.2F)); - columnList.add(new TableColumn(SPUILabelDefinitions.VAR_MODIFIED_BY, i18n.get("header.modifiedBy"), 0.15F)); + columnList.add(new TableColumn(SPUILabelDefinitions.VAR_MODIFIED_BY, i18n.get("header.modifiedBy"), 0.1F)); columnList.add(new TableColumn(SPUILabelDefinitions.VAR_MODIFIED_DATE, i18n.get("header.modifiedDate"), 0.2F)); + columnList.add(new TableColumn(SPUILabelDefinitions.AUTO_ASSIGN_DISTRIBUTION_SET, i18n.get("header.auto.assignment.ds"), 0.1F)); columnList.add(new TableColumn(SPUIDefinitions.CUSTOM_FILTER_DELETE, i18n.get("header.delete"), 0.1F)); return columnList; @@ -172,7 +181,7 @@ public class TargetFilterTable extends Table { return deleteIcon; } - private String getDeleteIconId(final String targetFilterName) { + private static String getDeleteIconId(final String targetFilterName) { return new StringBuilder(UIComponentIdProvider.CUSTOM_FILTER_DELETE_ICON).append('.').append(targetFilterName) .toString(); } @@ -208,6 +217,9 @@ public class TargetFilterTable extends Table { addGeneratedColumn(SPUILabelDefinitions.NAME, (source, itemId, columnId) -> customFilterDetailButton((Long) itemId)); + addGeneratedColumn(SPUILabelDefinitions.AUTO_ASSIGN_DISTRIBUTION_SET, + (source, itemId, columnId) -> customFilterDistributionSetButton((Long) itemId)); + } private Button customFilterDetailButton(final Long itemId) { @@ -222,6 +234,34 @@ public class TargetFilterTable extends Table { return updateIcon; } + private Button customFilterDistributionSetButton(final Long itemId) { + final Item row1 = getItem(itemId); + final ProxyDistribution distSet = (ProxyDistribution) row1.getItemProperty(SPUILabelDefinitions.AUTO_ASSIGN_DISTRIBUTION_SET).getValue(); + final String buttonId = "distSetButton"; + Button updateIcon; + if(distSet == null) { + updateIcon = SPUIComponentProvider.getButton(buttonId, i18n.get("button.no.auto.assignment"), + i18n.get("button.auto.assignment.desc"), null, false, null, SPUIButtonStyleSmallNoBorder.class); + } else { + updateIcon = SPUIComponentProvider.getButton(buttonId, distSet.getNameVersion(), + i18n.get("button.auto.assignment.desc"), null, false, null, SPUIButtonStyleSmallNoBorder.class); + } + + updateIcon.addClickListener(this::onClickOfDistributionSetButton); + updateIcon.setData(row1); + updateIcon.addStyleName(ValoTheme.LINK_SMALL + " " + "on-focus-no-border link"); + + return updateIcon; + } + + private void onClickOfDistributionSetButton(final ClickEvent event) { + final Item item = (Item) ((Button) event.getComponent()).getData(); + final Long tfqId = (Long)item.getItemProperty(SPUILabelDefinitions.VAR_ID).getValue(); + + dsSelectWindow.showForTargetFilter(tfqId); + + } + private void onClickOfDetailButton(final ClickEvent event) { final String targetFilterName = (String) ((Button) event.getComponent()).getData(); final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement @@ -246,14 +286,11 @@ public class TargetFilterTable extends Table { } private void setColumnProperties() { - final List columnList = getVisbleColumns(); - final List swColumnIds = new ArrayList<>(); - for (final TableColumn column : columnList) { + setVisibleColumns(getVisbleColumns().stream().map(column -> { setColumnHeader(column.getColumnPropertyId(), column.getColumnHeader()); setColumnExpandRatio(column.getColumnPropertyId(), column.getExpandRatio()); - swColumnIds.add(column.getColumnPropertyId()); - } - setVisibleColumns(swColumnIds.toArray()); + return column.getColumnPropertyId(); + }).toArray()); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/CertificateAuthenticationConfigurationItem.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/CertificateAuthenticationConfigurationItem.java index c50b9f8fb..e041f5564 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/CertificateAuthenticationConfigurationItem.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/tenantconfiguration/authentication/CertificateAuthenticationConfigurationItem.java @@ -63,13 +63,17 @@ public class CertificateAuthenticationConfigurationItem extends AbstractAuthenti final Label caRootAuthorityLabel = new LabelBuilder().name("SSL Issuer Hash:").buildLabel(); caRootAuthorityLabel.setDescription( "The SSL Issuer iRules.X509 hash, to validate against the controller request certifcate."); + caRootAuthorityLabel.setWidthUndefined(); - caRootAuthorityTextField = new TextFieldBuilder().immediate(true).maxLengthAllowed(128).buildTextComponent(); - caRootAuthorityTextField.setWidth("500px"); + caRootAuthorityTextField = new TextFieldBuilder().immediate(true).maxLengthAllowed(160).buildTextComponent(); + caRootAuthorityTextField.setWidth("100%"); caRootAuthorityTextField.addTextChangeListener(event -> caRootAuthorityChanged()); caRootAuthorityLayout.addComponent(caRootAuthorityLabel); + caRootAuthorityLayout.setExpandRatio(caRootAuthorityLabel, 0); caRootAuthorityLayout.addComponent(caRootAuthorityTextField); + caRootAuthorityLayout.setExpandRatio(caRootAuthorityTextField, 1); + caRootAuthorityLayout.setWidth("100%"); detailLayout.addComponent(caRootAuthorityLayout); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIDefinitions.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIDefinitions.java index 78f1ce8ee..db2fb12e6 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIDefinitions.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIDefinitions.java @@ -487,6 +487,11 @@ public final class SPUIDefinitions { */ public static final String FILTER_BY_INVALID_QUERY = "FilterByInvalidFilterQueryText"; + /** + * Filter by distribution set complete. + */ + public static final String FILTER_BY_DS_COMPLETE = "FilterByDistributionSetComplete"; + /** * Sort order of column - created at in target table. */ diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUILabelDefinitions.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUILabelDefinitions.java index a7b8716cc..120c9fc58 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUILabelDefinitions.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUILabelDefinitions.java @@ -203,6 +203,10 @@ public final class SPUILabelDefinitions { * ASSIGNED DISTRIBUTION ID. */ public static final String ASSIGNED_DISTRIBUTION_ID = "assignedDistributionSet.id"; + /** + * AUTO ASSIGN DISTRIBUTION SET ID + */ + public static final String AUTO_ASSIGN_DISTRIBUTION_SET = "autoAssignDistributionSet"; /** * ASSIGNED DISTRIBUTION Name & Version. */ diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIComponentIdProvider.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIComponentIdProvider.java index 1b97e5589..b3c1199a8 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIComponentIdProvider.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIComponentIdProvider.java @@ -885,6 +885,26 @@ public final class UIComponentIdProvider { */ public static final String FILTER_SEARCH_ICON_ID = "filter.search.icon"; + /** + * Distribution set select table id + */ + public static final String DIST_SET_SELECT_TABLE_ID = "distribution.set.select.table"; + + /** + * Distribution set select window id + */ + public static final String DIST_SET_SELECT_WINDOW_ID = "distribution.set.select.window"; + + /** + * Distribution set select consequences window id + */ + public static final String DIST_SET_SELECT_CONS_WINDOW_ID = "distribution.set.select.consequences.window"; + + /** + * Distribution set select enable checkbox id + */ + public static final String DIST_SET_SELECT_ENABLE_ID = "distribution.set.select.enable"; + /** * /* Private Constructor. */ diff --git a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/table-common.scss b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/table-common.scss index 2be45904a..ea5d5fbde 100644 --- a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/table-common.scss +++ b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/table-common.scss @@ -168,6 +168,7 @@ .v-table-cell-content { height: 27px; + font-size: 12px; } } diff --git a/hawkbit-ui/src/main/resources/messages.properties b/hawkbit-ui/src/main/resources/messages.properties index 487665dcb..43fafbf14 100644 --- a/hawkbit-ui/src/main/resources/messages.properties +++ b/hawkbit-ui/src/main/resources/messages.properties @@ -22,8 +22,11 @@ button.no.actions = No actions button.ok = OK button.cancel = Cancel button.upload.file = Upload File +button.no.auto.assignment = none +button.auto.assignment.desc = Select auto assign distribution set bulk.targets.upload = Please upload csv file. bulkupload.ds.name = DS Name +button.discard=Discard # Headers prefix with - header header.target.table=Targets @@ -48,6 +51,14 @@ header.dist.twintable.selected=Selected header.dist.twintable.available=Available header.target.installed = Installed header.target.assigned = Assigned +header.type=Type +header.swmodules=SwModules +header.migrations.step=IsRequiredMigrationStep +header.action=Actions +header.action.run=Run +header.action.pause=Pause +header.action.update=Edit +header.status=Status # Captions prefix with - caption caption.action.history = Action history for {0} @@ -59,6 +70,7 @@ caption.filter.simple = Simple Filter caption.filter.custom = Custom Filter caption.metadata = Metadata +caption.select.auto.assign.dist = Select auto assignment distribution set caption.add.softwaremodule = Configure Software Module caption.add.new.dist = Configure New Distribution caption.update.dist = Configure Update Distribution @@ -92,7 +104,8 @@ caption.confirm.abort.action = Confirm Abort Action caption.filter.delete.confirmbox = Confirm Filter Delete Action caption.metadata.popup = Metadata of caption.metadata.delete.action.confirmbox = Confirm Metadata Delete Action - +caption.confirm.assign.consequences = Auto assign consequences +caption.auto.assignment.ds = Auto assignment # Labels prefix with - label label.dist.details.type = Type : @@ -129,8 +142,6 @@ label.combobox.tag = Select Tag label.choose.tag = Choose Tag to update label.choose.tag.color = Choose Tag Color label.target.filtered.total = Total filtered targets : -label.filter.selected = Selected: -label.filter.shown = Shown: label.filter = Filter : label.target.filter.count = Total Targets : label.filter.selected = Selected : @@ -161,13 +172,15 @@ label.filter.by.status = Filter by Status label.filter.by.overdue = Filter by Overdue label.target.controller.attrs = Controller attributes label.target.lastpolldate = Last poll : -label.no.tag.assigned = NO TAG label.tag.name = Tag name label.configuration.auth.header = Allow targets to authenticate via a certificate authenticated by an reverse proxy label.configuration.auth.gatewaytoken = Allow a gateway to authenticate and manage multiple targets through a gateway security token label.configuration.auth.targettoken = Allow targets to authenticate directly with their target security token label.configuration.anonymous.download = Allow targets to download artifacts without security credentials label.unsupported.browser.ie=Sorry! current browser is not supported. Please use Internet Explorer 11 and above +label.auto.assign.description=When an auto assign distribution set is selected, it will be automatically assigned to all targets that match the target filter. +label.auto.assign.enable=Enable auto assignment +label.scheduled=Scheduled # Checkbox label prefix with - checkbox checkbox.dist.migration.required = Required Migration Step : @@ -233,7 +246,6 @@ message.dists.already.deleted = Few distribution(s) are already deleted.Pending message.target.deleted.pending = Target(s) already deleted.Pending for action message.dist.deleted.pending = Distribution(s) already deleted.Pending for action message.dist.delete.success = All selected distribution sets are deleted successfully ! -message.dist.discard.success = All distributions selected for delete are discarded successfully ! message.target.delete.success = All selected targets are deleted successfully ! message.target.discard.success = All targets selected for delete are discarded successfully ! message.software.discard.success = All software modules selected for delete are discarded successfully ! @@ -298,7 +310,6 @@ message.no.targets.assiged.fortag = No targets are assigned to tag {0} message.error.missing.tagname = Please select tag name message.type.delete = Please unclick the distribution type {0}, then try to delete message.error.dist.set.type.update= Distribution Set Type is already assigned to targets and cannot be changed -message.target.ds.assign.success = Assignments saved successfully ! message.no.directory.upload = Directory upload is not supported message.delete.filter.confirm = Are you sure that you want to delete custom filter? message.delete.filter.success = Custom filter {0} deleted Successfully! @@ -309,6 +320,8 @@ message.target.filter.duplicate = {0} already exists, please enter another valu message.tag.use.bulk.upload = {0} cannot be deleted .It is in use in targets bulk upload message.bulk.upload.tag.assignment.failed = Tag {0} assignment failed as tag no longer exists message.bulk.upload.tag.assignments.failed= Few tag assignments failed as tags no longer exists +message.confirm.assign.consequences.none = This auto assignment will not have any effect on the currently available targets. In future added targets might match the filter and will receive the selected distribution set automatically. +message.confirm.assign.consequences.text = When you confirm this auto assignment, {0} targets which match the filter will immediately get assigned with the selected distribution set. # action info action.target.table.selectall = Select all (Ctrl+A) @@ -450,12 +463,12 @@ header.assigned.ds = Assigned DS header.installed.ds = Installed DS header.target.status = Status header.target.tags = Tags -header.distributionset = Distribution set -header.numberofgroups = No. of groups -header.detail.status = Detail status header.total.targets = Total targets header.key = Key header.value = Value +header.auto.assignment.ds = Auto assignment +header.target.filter.name = Target filter name +header.target.filter.query = Target filter query distribution.details.header = Distribution set target.details.header = Target @@ -512,3 +525,9 @@ menu.title = Software Provisioning #Target Filter Management breadcrumb.target.filter.custom.filters = Custom Filters + +notification.configuration.save=Saved changes +controller.polling.title=Polling Configuration +controller.polling.time=Polling Time +controller.polling.overduetime=Polling Overdue Time + diff --git a/hawkbit-ui/src/main/resources/messages_de.properties b/hawkbit-ui/src/main/resources/messages_de.properties deleted file mode 100644 index b77ed427b..000000000 --- a/hawkbit-ui/src/main/resources/messages_de.properties +++ /dev/null @@ -1,506 +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 -# - -######################################################################################### -# This is the messages_en.properties file -######################################################################################### - -# Button names prefix with - button -button.save = Save -button.delete = Delete -button.discard = Discard -button.discard.all = Discard All -button.delete.all = Delete All -button.assign.all = Save Assign -button.actions = You have actions -button.no.actions = No actions -button.ok = OK -button.cancel = Cancel -button.upload.file = Upload File -bulk.targets.upload = Please upload csv file. -bulkupload.ds.name = DS Name - -# Headers prefix with - header -header.target.table=Targets -header.dist.table=Distributions -header.filter.tag=Filter by Tag -header.target.filter.tag=Filters -header.first.assignment.table = Targets -header.second.assignment.table = Distributions -header.dist.first.assignment.table = Distributions -header.dist.second.assignment.table = Software Modules -header.third.assignment.table = Discard -header.one.deletedist.table = Distribution Name -header.second.deletedist.table = Discard Changes -header.first.deletetarget.table = Target Name -header.second.deletetarget.table = Discard Changes -header.first.deleteswmodule.table = Delete software -header.first.delete.dist.type.table = DistributionSetType -header.second.delete.dist.type.table = Discard -header.first.delete.swmodule.type.table = Software Module Type -header.second.delete.swmodule.type.table = Discard -header.dist.twintable.selected=Selected -header.dist.twintable.available=Available -header.target.installed = Installed -header.target.assigned = Assigned - -# Captions prefix with - caption -caption.action.history = Action history for {0} -caption.error = Error -caption.new.softwaremodule.application = Configure New Application -caption.new.softwaremodule.jvm = Configure New Runtime -caption.new.softwaremodule.os = Configure New OS -caption.metadata = Metadata - - -caption.add.softwaremodule = Configure Software Module -caption.add.new.dist = Configure New Distribution -caption.update.dist = Configure Update Distribution -caption.add.tag = Configure Tag -caption.add.type = Configure Type -caption.add.new.target = Configure New Target -caption.update.target = Configure Update Target -caption.bulk.upload.targets = Bulk Upload -caption.softwares.distdetail.tab = Modules -caption.tags.tab = Tags -caption.logs.tab = Logs -caption.attributes.tab = Attributes -caption.types.tab = Types -caption.save.window = Action Details -caption.assign.dist.accordion.tab = Assign Distribution Set -caption.delete.dist.accordion.tab = Delete Distributions -caption.delete.target.accordion.tab = Delete Targets -caption.delete.swmodule.accordion.tab = Delete SW Modules -caption.delete.dist.set.type.accordion.tab = Delete Distribution Set Type -caption.delete.sw.module.type.accordion.tab = Delete Software Module Type -caption.attributes = Attributes -caption.panel.dist.installed = Installed Distribution Set -caption.panel.dist.assigned = Assigned Distribution Set -caption.soft.delete.confirmbox = Confirm Software Module Delete Action -caption.cancel.action.confirmbox = Confirm Action Cancellation -caption.forced.datefield = Force update at time -caption.force.action.confirmbox = Confirm Force Active Action -caption.filter.simple = Simple Filter -caption.filter.custom = Custom Filter -caption.filter.delete.confirmbox = Confirm Filter Delete Action -caption.confirm.abort.action = Confirm Abort Action - -caption.metadata.popup = Metadata of -caption.metadata.delete.action.confirmbox = Confirm Metadata Delete Action - -# Labels prefix with - label -label.dist.details.type = Type : -label.dist.details.name = Name : -label.dist.details.version = Version : -label.dist.details.vendor = Vendor : -label.dist.details.jvm = Runtime : -label.dist.details.ah = Application : -label.dist.details.os = OS : -label.modified.date = Last modified at : -label.modified.by = Last modified by : -label.created.at = Created at : -label.created.by = Created by : -label.target.count = Targets : -label.description = Description : -label.ip = Address : -label.type = Type : -label.assigned.type = Assignment type : -label.assigned.count = {0} Assigned -label.installed.count = {0} Installed -label.mandatory.field = * Mandatory Field -label.components.drop.area = Drop here to delete -label.software.module.drop.area = Delete Software -label.create.tag = Create Tag -label.update.tag = Update Tag -label.create.type = Create Type -label.update.type = Update Type -label.singleAssign.type = Firmware (FW) -label.multiAssign.type = Software (SW) -label.choose.type = Choose Type -label.choose.type.color = Choose Type Color -label.combobox.type = Select Type -label.combobox.tag = Select Tag -label.choose.tag = Choose Tag to update -label.choose.tag.color = Choose Tag Color -label.target.filtered.total = Total filtered targets : -label.filter.selected = Selected: -label.filter.shown = Shown: -label.filter = Filter : -label.target.filter.count = Total Targets : -label.filter.selected = Selected : -label.filter.shown = Shown : -label.filter.status = Status, -label.filter.overdue = Overdue, -label.filter.tags = Tags, -label.filter.text = Search Text -label.filter.dist = Distribution, -label.filter.custom = Custom -label.target.filter.truncated={0} targets has been truncated in the list due the target size limit of {1}, use filters to reduce the targets to be shown -label.active =Active -label.inactive = In-active -label.finished = Finished -label.error = Error -label.warning = Warning -label.running = Running -label.cancelled = Cancelled -label.cancelling = Canceling -label.retrieved = Retrieved -label.download = Downloading -label.scheduled = Scheduled -label.target.id = Controller Id : -label.target.ip = Controller IP : -label.target.security.token = Security token : -label.filter.by.status = Filter by Status -label.filter.by.overdue = Filter by Overdue -label.target.controller.attrs = Controller attributes -label.target.lastpolldate = Last poll : -label.no.tag.assigned = NO TAG -label.tag.name = Tag name -label.configuration.auth.header = Allow targets to authenticate via a certificate authenticated by an reverse proxy -label.configuration.auth.gatewaytoken = Allow a gateway to authenticate and manage multiple targets through a gateway security token -label.configuration.auth.targettoken = Allow targets to authenticate directly with their target security token -label.configuration.anonymous.download = Allow targets to download artifacts without security credentials -label.unsupported.browser.ie=Sorry! current browser is not supported. Please use Internet Explorer 11 and above - -# Checkbox label prefix with - checkbox -checkbox.dist.migration.required = Required Migration Step : -checkbox.dist.required.migration.step = Required Migration Step - -# TextFields prefix with - textfield -textfield.name = Name -textfield.key = Key -textfield.version = Version -textfield.vendor = Vendor -textfield.description = Description -textfield.customfiltername = Filter name -textfield.value = Value -ui.version = Powered by Bosch IoT Software Provisioning -prompt.target.id = Controller ID - - -#Tooltips prefix with - tooltip -tooltip.add.module = Add Software Module -tooltip.status.unknown = Unknown -tooltip.status.registered = Registered -tooltip.status.pending = Pending -tooltip.status.error = Error -tooltip.status.insync = In-sync -tooltip.status.overdue = Overdue -tooltip.delete.module = Select and delete Software Module -tooltip.forced.item=Forced update action -tooltip.soft.item=Soft update action which interacts with an user as a attempt update action for example -tooltip.timeforced.item=Soft update until a specific time and then the action will be forced -tooltip.check.for.mandatory=Check to make Mandatory -tooltip.artifact.icon=Show Artifact Details -tooltip.click.to.edit = Click to edit -tooltip.metadata.icon = Manage Metadata - - -# Notification messages prefix with - message -message.save.success = {0} saved successfully -message.update.success = {0} updated successfully -message.delete.success = {0} deleted successfully -message.dist.installedorassigned = Target {targId} is already assigned/installed with distribution -message.dist.pending.action = Target {0} is already assigned with distribution {1} . Pending for action -message.empty.target.tags= No Tags Created -message.empty.disttype.tags = No Distribution type tags created -message.select.row = Please select a row to drag -message.error = Unknown error occured during the operation. Please contact administrator -message.dist.assigned.one = {0} is assigned to {1} -message.dist.assigned.many = {0} DistributionSets are assigned to {1} -message.dist.unassigned.one = {0} is unassigned from {1} -message.dist.unassigned.many = {0} DistributionSets are unassigned from {1} -message.target.assigned.one = {0} is assigned to {1} -message.target.assigned.many = {0} Targets are assigned to {1} -message.target.unassigned.one = {0} is unassigned from {1} -message.target.unassigned.many = {0} Targets are unassigned from {1} -message.target.assigned.pending = Some target(s) are already assigned.Pending for action -message.cannot.delete = Cannot be deleted -message.check.softwaremodule = Please provide both name and version! -message.cannot.delete.default.dstype = Default distribution set type cannot be deleted -message.duplicate.softwaremodule = {0} : {1} already exists! -message.cannot.delete.default.dstype = Default distribution set type cannot be deleted -message.tag.delete = Please unclick the tag {0}, then try to delete -message.dist.type.check.delete = Please unclick the distribution type {0}, then try to delete -message.swmodule.type.check.delete = Please unclick the Software Module type {0}, then try to delete -message.targets.already.deleted = Few Target(s) are already deleted.Pending for action -message.dists.already.deleted = Few distribution(s) are already deleted.Pending for action -message.target.deleted.pending = Target(s) already deleted.Pending for action -message.dist.deleted.pending = Distribution(s) already deleted.Pending for action -message.dist.delete.success = All selected distribution sets are deleted successfully ! -message.dist.discard.success = All distributions selected for delete are discarded successfully ! -message.target.delete.success = All selected targets are deleted successfully ! -message.target.discard.success = All targets selected for delete are discarded successfully ! -message.software.discard.success = All software modules selected for delete are discarded successfully ! -message.software.type.discard.success = All software moduleTypes selected for delete are discarded successfully ! -message.assign.software.discard.success = All software moduleTypes selected for assign are discarded successfully ! -message.software.delete.success = All software modules selected for delete are deleted successfully ! -message.software.type.delete.success = All software modules types selected for delete are deleted successfully ! -message.dist.set.type.deleted.success = {0} DistributionSetType deleted successfully ! -message.new.dist.save.success = {0} - {1} saved successfully -message.dist.update.success = {0} - {1} updated successfully -message.duplicate.dist = Distribution set [{0}] or version [{1}] must be unique, entered value already exists. -message.error.view = No such view: {0} -message.accessdenied.view = No access to view: {0} -message.no.data = No data available -message.target.assignment = {0} Assignment(s) done -message.target.deleted = {0} Target(s) deleted -message.dist.deleted = {0} Distribution Set(s) deleted -message.tag.update.mandatory = Please select the Tag to update -message.tag.duplicate.check = {0} already exists, please enter another value -message.type.key.duplicate.check = Distribution type with key {0} already exists, please give another value -message.type.key.swmodule.duplicate.check = Software Module type with key {0} already exists, please give another value -message.no.action.history = No action history is available for the target : {0} -message.no.available = --No messages available-- -message.no.actionupdateds.available = No other updates available for this action -message.mandatory.check = Mandatory details are missing -message.target.duplicate.check = Target [ {0} ] must be unique, entered value already exists. -message.permission.insufficient = Insufficient permissions to perform this action. -message.error.temp = The operation cannot be fulfilled due to {0}. Please contact administrator -message.dist.alreadyassigned = {0} : {1} is already assigned/installed, cannot be updated -message.dist.tag.alreadyassigned = {0} : {1} is already assigned/installed, cannot assign/un-assign to tag -message.dists.unassign.tag.alreadyassigned = Few of the DistributionSet's are already assigned to Target, those cannot be unassigned from Tag" -message.dists.assign.tag.alreadyassigned = Few of the DistributionSet's are already assigned to Target, those cannot be assigned to Tag" -message.dists.tag.assigned = {0} DistributionSet's assigned to Tag {1} -message.dists.tag.unassigned = {0} DistributionSet's un-assigned from Tag {1} -message.dist.no.operation = {0} - already assigned/installed, No operation -message.sm.delete.confirm = Are you sure that you want to delete the selected {0} Software Module? -message.error.os.softmodule = Please select the OS to delete -message.error.ah.softmodule = Please select the Application to delete -message.error.softmodule.deleted = The selected Software Module is already deleted -message.cancel.action = Cancel -message.cancel.action.success = Action cancelled successfully ! -message.cancel.action.failed = Unable to cancel the action ! -message.cancel.action.confirm = Are you sure that you want to cancel this action? -message.target.alreadyAssigned = {0} Target(s) were already assigned -message.dist.alreadyAssigned = {0} Distribution Set(s) were already assigned -message.force.action = Force -message.force.action.confirm = Are you sure that you want to force this action? -message.force.action.success = Action forced successfully ! -message.distribution.no.update = distribution {0} set is already assigned to targets and cannot be changed -message.action.not.allowed = Action not allowed -message.action.did.not.work = Action did not work. Please try again. -message.onlyone.distribution.assigned = Only one distribution set can be assigned -message.onlyone.distribution.dropallowed = Only one distribution set can be dropped -message.error.missing.typename = Missing Type Name -message.error.missing.typenameorkey = Missing Type Name or Key -message.tag.cannot.be.assigned = Target/DS cannot be assigned to {0} -message.no.targets.assiged.fortag = No targets are assigned to tag {0} -message.error.missing.tagname = Please select tag name -message.type.delete = Please unclick the distribution type {0}, then try to delete -message.error.dist.set.type.update= Distribution Set Type is already assigned to targets and cannot be changed -message.target.ds.assign.success = Assignments saved successfully ! -message.no.directory.upload = Directory upload is not supported - -message.delete.filter.confirm = Are you sure that you want to delete custom filter? -message.delete.filter.success = Custom filter {0} deleted Successfully! -message.create.filter.success = Custom filter {0} created Successfully! -message.update.filter.success = Custom filter updated Successfully! -message.target.filter.validation = Please enter name and query -message.target.filter.duplicate = {0} already exists, please enter another value -message.tag.use.bulk.upload = {0} cannot be deleted .It is in use in targets bulk upload -message.bulk.upload.tag.assignment.failed = Tag {0} assignment failed as tag no longer exists -message.bulk.upload.tag.assignments.failed= Few tag assignments failed as tags no longer exists - -#reused messages -soft.module.jvm =Runtime -soft.module.application =Application -soft.module.os =OS - -#Artifact upload -message.error.noFileSelected = No file selected for upload -message.error.noProvidedName = Please provide custom file name -message.error.noSwModuleSelected = Please select a Software Module -message.no.duplicateFiles = Duplicate files selected -message.no.duplicateFile = Duplicate file selected : -message.delete.artifact = Are you sure that you want to delete artifact {0} ? -message.duplicate.filename = Duplicate file name -message.swModule.deleted = {0} Software Module(s) deleted -message.error.missing.tagname = Please select tag name -message.upload.failed = Streaming Failed -message.uploadedfile.size.exceeded = File size exceeded .Allowed size {0} bytes -message.uploadedfile.aborted = File upload aborted -message.file.not.found = File not found -message.artifact.deleted =Artifact with file {0} deleted successfully -message.abort.upload = Are you sure that you want to abort the upload? - - - -upload.swModuleTable.header = Software Module -upload.selectedfile.name = file selected for upload -upload.file.name = File name -upload.sha1 = SHA1 checksum -upload.md5 = MD5 checksum -upload.last.modified.date=Last modified date -upload.failed = Failed -upload.success = Success -upload.caption.add.new.swmodule = Configure New Software Module -upload.caption.delete.swmodule = Configure Delete Software Module -upload.swmodule.type = Type -upload.artifact.alreadyExists = Artifact will be overridden as the given name already exists -upload.size = Size(B) -upload.validation = Validation -upload.reason = Reason -upload.action = Action -upload.result.status = Upload status -upload.file = Upload File -upload.caption.update.swmodule = Update Software Module -caption.tab.details = Details -caption.tab.description = Description - -caption.delete.artifact.confirmbox = Confirm Artifact Delete Action - -#Manage distributions view -label.drop.dist.delete.area = Drop here
to delete -caption.assign.software.dist.accordion.tab = Assign Software Modules -message.software.assignment = {0} Assignment(s) done -message.dist.inuse = {0} Distribution is already assigned to target -message.software.dist.already.assigned = {0} Distribution already has Software Module {1} -message.software.dist.type.notallowed = {0} Software Module type can not assign to Distribution {1} -message.target.assigned = {0} is assigned to {1} -message.dist.type.delete = {0} DistributionType(s) Deleted successfully. -message.sw.module.type.delete = {0} Software Module Type(s) deleted successfully. -message.dist.type.discard.success = All Distribution Types are discarded successfully ! -message.dist.discard.success = All Distributions are discarded successfully ! -message.assign.discard.success = All assignments are discarded successfully ! -message.bulk.upload.assignment.failed = Distribution set assignment failed as distribution set no longer exists! -message.key.missing = Key is missing ! -message.value.missing = Value is missing ! -message.metadata.saved = Metadata with key {0} successfully saved ! -message.metadata.updated = Metadata with key {0} successfully updated ! -message.metadata.duplicate.check = Metadata with key {0} already exists, please enter another value -message.metadata.deleted.successfully = Metadata with key {0} successfully deleted ! -message.confirm.delete.metadata = Are you sure that you want to delete metadata with key {0} ? -message.error.notification.ds.target.assigned = Distribution set {0}:{1} is already assigned to targets and cannot be changed - - -# Login view -notification.login.title=Welcome to Bosch IoT Software Provisioning. -notification.login.description=Please login with your Bosch Identity Management credentials. -notification.login.failed.title=Login failed! -notification.login.failed.description=Login with the given credentials failed. -notification.login.failed.credentialsexpired.title=Passwort Abgelaufen! -notification.login.failed.credentialsexpired.description=Passwort ist entweder abgelaufen or muss initial gesetzt werden, bitte im Benutzer Verwaltungssystem �ndern. -label.login.tenant=Tenant -label.login.username=Username -label.login.password=Password -button.login.signin=Sign in -checkbox.login.rememberme=Remember me - -# Links -link.documentation.name=Dokumentation -link.demo.name=Demo -link.requestaccount.name=Beantragung eines Accounts -link.support.name=Support -link.usermanagement.name=Benutzerverwaltung - - -# System Configuration View -notification.configuration.save=Ver�nderungen gespeichert -configuration.defaultdistributionset.title=Distribution Set Typ -configuration.defaultdistributionset.select.label=Wahl des default Distribution Set typs: -configuration.savebutton.tooltip=Konfigurationen speichern -configuration.cancellbutton.tooltip=Konfigurationen zur�cksetzen -configuration.authentication.title=Authentifikationseinstellungen - -#Calendar -calendar.year=Jahr -calendar.years=Jahre -calendar.month=Monat -calendar.months=Monate -calendar.day=Tag -calendar.days=Tage -calendar.hour=Stunde -calendar.hours=Stunden -calendar.minute=Minute -calendar.minutes=Minuten -calendar.second=Sekunde -calendar.seconds=Sekunden - -header.name = Name -header.vendor = Vendor -header.version = Version -header.description = Description -header.createdBy = Created By -header.createdDate = Created Date -header.modifiedBy = Modified By -header.modifiedDate = Modified Date -header.delete = Delete -header.assigned.ds = Assigned DS -header.installed.ds = Installed DS -header.status = Status -header.target.tags = Tags -header.distributionset = Distribution set -header.numberofgroups = No. of groups -header.detail.status = Detail status -header.total.targets = Total targets -header.type = Type -header.swmodules = SwModules -header.migrations.step=IsRequiredMigrationStep - -header.action=Actions -header.action.run=Run -header.action.pause=Pause -header.action.update=Edit - -header.rolloutgroup.installed.percentage = % Finished -header.rolloutgroup.threshold.error = Error threshold -header.rolloutgroup.threshold = Trigger threshold - -header.rolloutgroup.target.date = Date and time -header.rolloutgroup.target.message = Messages - -rollout.group.label.target.truncated = {0} targets has been truncated in the list due the target size limit of {1} - -distribution.details.header = Distribution Set -target.details.header = Target -header.caption.mandatory = Mandatory -header.caption.typename = SoftwareModuleType -header.caption.softwaremodule = SoftwareModule -header.caption.unassign = Unassign -message.sw.unassigned = Software Module {0} successfully unassigned -header.caption.upload.details = Upload details -header.key = Key -header.value = Value -combo.type.tag.name = Type tag name - -label.yes = Yes -label.no =No - - -#Menu -menu.title = Software Provisioning - - -#Rollout management -prompt.number.of.groups = Number of groups -prompt.tigger.threshold = Trigger threshold -prompt.error.threshold = Error threshold -prompt.distribution.set = Distribution Set -caption.configure.rollout = Configure Rollout -caption.update.rollout = Update Rollout -prompt.target.filter = Custom Target Filter -message.rollout.nonzero.group.number = Number of groups must be greater than zero -message.rollout.max.group.number = Number of groups must not be greater than 500 -message.rollout.duplicate.check = Rollout [ {0} ] must be unique, entered value already exists. -message.rollout.field.value.range = Value should be in range {0} to {1} -message.correct.invalid.value = Please correct invalid values -message.enter.number = Please enter number -message.rollout.started = Rollout {0} started successfully -message.rollout.paused = Rollout {0} paused successfully -message.rollout.resumed = Rollout {0} resumed successfully -message.rollout.noofgroups.or.targetfilter.missing = Please enter number of groups and select target filter -message.rollouts = Rollouts -label.target.per.group = Targets per group : -message.dist.already.assigned = Distribution {0} is already assigned to target -message.error.creating.rollout = Server error. Error creating Rollout. Please contact the administrator -message.error.starting.rollout = Server error. Error starting Rollout. Please contact the administrator - -#Target Filter Management -breadcrumb.target.filter.custom.filters = Custom Filters diff --git a/hawkbit-ui/src/main/resources/messages_en.properties b/hawkbit-ui/src/main/resources/messages_en.properties deleted file mode 100644 index f6089364a..000000000 --- a/hawkbit-ui/src/main/resources/messages_en.properties +++ /dev/null @@ -1,502 +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 -# - -######################################################################################### -# This is the messages_en.properties file -######################################################################################### - -# Button names prefix with - button -button.save = Save -button.delete = Delete -button.discard = Discard -button.discard.all = Discard All -button.delete.all = Delete All -button.assign.all = Save Assign -button.actions = You have actions -button.no.actions = No actions -button.ok = OK -button.cancel = Cancel -button.upload.file = Upload File -bulk.targets.upload = Please upload csv file. -bulkupload.ds.name = DS Name - -# Headers prefix with - header -header.target.table=Targets -header.dist.table=Distributions -header.filter.tag=Filter by Tag -header.target.filter.tag=Filters -header.first.assignment.table = Targets -header.second.assignment.table = Distributions -header.dist.first.assignment.table = Distributions -header.dist.second.assignment.table = Software Modules -header.third.assignment.table = Discard -header.one.deletedist.table = Distribution Name -header.second.deletedist.table = Discard Changes -header.first.deletetarget.table = Target Name -header.second.deletetarget.table = Discard Changes -header.first.deleteswmodule.table = Delete software -header.first.delete.dist.type.table = DistributionSetType -header.second.delete.dist.type.table = Discard -header.first.delete.swmodule.type.table = Software Module Type -header.second.delete.swmodule.type.table = Discard -header.dist.twintable.selected=Selected -header.dist.twintable.available=Available -header.target.installed = Installed -header.target.assigned = Assigned - -# Captions prefix with - caption -caption.action.history = Action history for {0} -caption.error = Error -caption.new.softwaremodule.application = Configure New Application -caption.new.softwaremodule.jvm = Configure New Runtime -caption.new.softwaremodule.os = Configure New OS -caption.filter.simple = Simple Filter -caption.filter.custom = Custom Filter -caption.metadata = Metadata - -caption.add.softwaremodule = Configure Software Module -caption.add.new.dist = Configure New Distribution -caption.update.dist = Configure Update Distribution -caption.add.tag = Configure Tag -caption.add.type = Configure Type -caption.add.new.target = Configure New Target -caption.update.target = Configure Update Target -caption.bulk.upload.targets = Bulk Upload -caption.softwares.distdetail.tab = Modules -caption.tags.tab = Tags -caption.logs.tab = Logs -caption.attributes.tab = Attributes -caption.types.tab = Types -caption.save.window = Action Details -caption.assign.dist.accordion.tab = Assign Software Module -caption.delete.dist.accordion.tab = Delete Distributions -caption.delete.target.accordion.tab = Delete Targets -caption.delete.swmodule.accordion.tab = Delete SW Modules -caption.delete.dist.set.type.accordion.tab = Delete Distribution Set Type -caption.delete.sw.module.type.accordion.tab = Delete Software Module Type -caption.attributes = Attributes -caption.panel.dist.installed = Installed Distribution Set -caption.panel.dist.assigned = Assigned Distribution Set -caption.soft.delete.confirmbox = Confirm Software Module Delete Action -caption.cancel.action.confirmbox = Confirm Action Cancellation -caption.forced.datefield = Force update at time -caption.force.action.confirmbox = Confirm Force Active Action -caption.filter.delete.confirmbox = Confirm Filter Delete Action -caption.metadata.popup = Metadata of -caption.metadata.delete.action.confirmbox = Confirm Metadata Delete Action - -caption.confirm.abort.action = Confirm Abort Action - -# Labels prefix with - label -label.dist.details.type = Type : -label.dist.details.name = Name : -label.dist.details.version = Version : -label.dist.details.vendor = Vendor : -label.dist.details.jvm = Runtime : -label.dist.details.ah = Application : -label.dist.details.os = OS : -label.modified.date = Last modified at : -label.modified.by = Last modified by : -label.created.at = Created at : -label.created.by = Created by : -label.target.count = Targets : -label.description = Description : -label.ip = Address : -label.type = Type : -label.assigned.type = Assignment type : -label.assigned.count = {0} Assigned -label.installed.count = {0} Installed -label.mandatory.field = * Mandatory Field -label.components.drop.area = Drop here to delete -label.software.module.drop.area = Delete Software -label.create.tag = Create Tag -label.update.tag = Update Tag -label.create.type = Create Type -label.update.type = Update Type -label.singleAssign.type = Firmware (FW) -label.multiAssign.type = Software (SW) -label.choose.type = Choose Type to Update -label.choose.type.color = Choose Type Color -label.combobox.type = Select Type -label.combobox.tag = Select Tag -label.choose.tag = Choose Tag to update -label.choose.tag.color = Choose Tag Color -label.target.filtered.total = Total filtered targets : -label.filter.selected = Selected: -label.filter.shown = Shown: -label.filter.targets = Filtered targets : -label.filter = Filter : -label.target.filter.count = Total Targets : -label.filter.selected = Selected : -label.filter.shown = Shown : -label.filter.status = Status, -label.filter.overdue = Overdue, -label.filter.tags = Tags, -label.filter.text = Search Text -label.filter.dist = Distribution, -label.filter.custom = Custom -label.target.filter.truncated={0} targets has been truncated in the list due the target size limit of {1}, use filters to reduce the targets to be shown -label.active =Active -label.inactive = In-active -label.finished = Finished -label.error = Error -label.warning = Warning -label.running = Running -label.cancelled = Cancelled -label.cancelling = Canceling -label.retrieved = Retrieved -label.download = Downloading -label.scheduled = Scheduled -label.target.id = Controller Id : -label.target.ip = Controller IP : -label.target.security.token = Security token : -label.filter.by.status = Filter by Status -label.filter.by.overdue = Filter by Overdue -label.target.controller.attrs = Controller attributes -label.target.lastpolldate = Last poll : -label.no.tag.assigned = NO TAG -label.tag.name = Tag name -label.configuration.auth.header = Allow targets to authenticate via a certificate authenticated by an reverse proxy -label.configuration.auth.gatewaytoken = Allow a gateway to authenticate and manage multiple targets through a gateway security token -label.configuration.auth.targettoken = Allow targets to authenticate directly with their target security token -label.configuration.anonymous.download = Allow targets to download artifacts without security credentials -label.unsupported.browser.ie=Sorry! current browser is not supported. Please use Internet Explorer 11 and above - -# Checkbox label prefix with - checkbox -checkbox.dist.migration.required = Required Migration Step : -checkbox.dist.required.migration.step = Required Migration Step - -# TextFields prefix with - textfield -textfield.name = Name -textfield.key = Key -textfield.version = Version -textfield.vendor = Vendor -textfield.description = Description -textfield.customfiltername = Filter name -textfield.value = Value -ui.version = Powered by Bosch IoT Software Provisioning -prompt.target.id = Controller ID - - -#Tooltips prefix with - tooltip -tooltip.add.module = Add Software Module -tooltip.status.unknown = Unknown -tooltip.status.registered = Registered -tooltip.status.pending = Pending -tooltip.status.error = Error -tooltip.status.insync = In-sync -tooltip.status.overdue = Overdue -tooltip.delete.module = Select and delete Software Module -tooltip.forced.item=Forced update action -tooltip.soft.item=Soft update action which interacts with an user as a attempt update action for example -tooltip.timeforced.item=Soft update until a specific time and then the action will be forced -tooltip.check.for.mandatory=Check to make Mandatory -tooltip.artifact.icon=Show Artifact Details -tooltip.click.to.edit = Click to edit -tooltip.metadata.icon = Manage Metadata - - -# Notification messages prefix with - message -message.save.success = {0} saved successfully -message.update.success = {0} updated successfully -message.delete.success = {0} deleted successfully -message.dist.installedorassigned = Target {targId} is already assigned/installed with distribution -message.dist.pending.action = Target {0} is already assigned with distribution {1} . Pending for action -message.empty.target.tags= No Tags Created -message.empty.disttype.tags = No Distribution type tags created -message.select.row = Please select a row to drag -message.error = Unknown error occured during the operation. Please contact administrator -message.dist.assigned.one = {0} is assigned to {1} -message.dist.assigned.many = {0} DistributionSets are assigned to {1} -message.dist.unassigned.one = {0} is unassigned from {1} -message.dist.unassigned.many = {0} DistributionSets are unassigned from {1} -message.target.assigned.one = {0} is assigned to {1} -message.target.assigned.many = {0} Targets are assigned to {1} -message.target.unassigned.one = {0} is unassigned from {1} -message.target.unassigned.many = {0} Targets are unassigned from {1} -message.target.assigned.pending = Some target(s) are already assigned.Pending for action -message.cannot.delete = Cannot be deleted -message.check.softwaremodule = Please provide both name and version! -message.duplicate.softwaremodule = {0} : {1} already exists! -message.tag.delete = Please unclick the tag {0}, then try to delete -message.dist.type.check.delete = Please unclick the distribution type {0}, then try to delete -message.swmodule.type.check.delete = Please unclick the Software Module type {0}, then try to delete -message.targets.already.deleted = Few Target(s) are already deleted.Pending for action -message.dists.already.deleted = Few distribution(s) are already deleted.Pending for action -message.target.deleted.pending = Target(s) already deleted.Pending for action -message.dist.deleted.pending = Distribution(s) already deleted.Pending for action -message.dist.delete.success = All selected distribution sets are deleted successfully ! -message.dist.discard.success = All distributions selected for delete are discarded successfully ! -message.target.delete.success = All selected targets are deleted successfully ! -message.target.discard.success = All targets selected for delete are discarded successfully ! -message.software.discard.success = All software modules selected for delete are discarded successfully ! -message.software.type.discard.success = All software moduleTypes selected for delete are discarded successfully ! -message.assign.software.discard.success = All software moduleTypes selected for assign are discarded successfully ! -message.software.delete.success = All software modules selected for delete are deleted successfully ! -message.software.type.delete.success = All software modules types selected for delete are deleted successfully ! -message.dist.set.type.deleted.success = {0} DistributionSetType deleted successfully ! -message.new.dist.save.success = {0} - {1} saved successfully -message.dist.update.success = {0} - {1} updated successfully -message.duplicate.dist = Distribution set [{0}] or version [{1}] must be unique, entered value already exists. -message.error.view = No such view: {0} -message.accessdenied.view = No access to view: {0} -message.no.data = No data available -message.target.assignment = {0} Assignment(s) done -message.target.deleted = {0} Target(s) deleted -message.dist.deleted = {0} Distribution set(s) deleted -message.tag.update.mandatory = Please select the Tag to update -message.tag.duplicate.check = {0} already exists, please enter another value -message.type.key.duplicate.check = Distribution type with key {0} already exists, please give another value -message.type.key.swmodule.duplicate.check = Software Module type with key {0} already exists, please give another value -message.no.action.history = No action history is available for the target : {0} -message.no.available = --No messages available-- -message.no.actionupdateds.available = No other updates available for this action -message.mandatory.check = Mandatory details are missing -message.target.duplicate.check = Target [ {0} ] must be unique, entered value already exists. -message.permission.insufficient = Insufficient permissions to perform this action. -message.error.temp = The operation cannot be fulfilled due to {0}. Please contact administrator -message.dist.alreadyassigned = {0} : {1} is already assigned/installed, cannot be updated -message.dist.tag.alreadyassigned = {0} : {1} is already assigned/installed, cannot assign/un-assign to tag -message.dists.unassign.tag.alreadyassigned = Few of the DistributionSet's are already assigned to Target, those cannot be unassigned from Tag" -message.dists.assign.tag.alreadyassigned = Few of the DistributionSet's are already assigned to Target, those cannot be assigned to Tag" -message.dists.tag.assigned = {0} DistributionSet's assigned to Tag {1} -message.dists.tag.unassigned = {0} DistributionSet's un-assigned from Tag {1} -message.dist.no.operation = {0} - already assigned/installed, No operation -message.sm.delete.confirm = Are you sure that you want to delete the selected {0} Software Module? -message.error.os.softmodule = Please select the OS to delete -message.error.ah.softmodule = Please select the Application to delete -message.error.softmodule.deleted = The selected Software Module is already deleted -message.cancel.action = Cancel -message.cancel.action.success = Action cancelled successfully ! -message.cancel.action.failed = Unable to cancel the action ! -message.cancel.action.confirm = Are you sure that you want to cancel this action? -message.target.alreadyAssigned = {0} Target(s) were already assigned -message.dist.alreadyAssigned = {0} Distribution Set(s) were already assigned -message.force.action = Force -message.force.action.confirm = Are you sure that you want to force this action? -message.force.action.success = Action forced successfully ! -message.distribution.no.update = distribution {0} set is already assigned to targets and cannot be changed -message.action.not.allowed = Action not allowed -message.action.did.not.work = Action did not work. Please try again. -message.onlyone.distribution.assigned = Only one distribution set can be assigned -message.onlyone.distribution.dropallowed = Only one distribution set can be dropped -message.error.missing.typename = Missing Type Name -message.error.missing.typenameorkey = Missing Type Name or Key or Software Module type -message.tag.cannot.be.assigned = Target/DS cannot be assigned to {0} -message.no.targets.assiged.fortag = No targets are assigned to tag {0} -message.error.missing.tagname = Please select tag name -message.type.delete = Please unclick the distribution type {0}, then try to delete -message.error.dist.set.type.update= Distribution Set Type is already assigned to targets and cannot be changed -message.target.ds.assign.success = Assignments saved successfully ! -message.no.directory.upload = Directory upload is not supported - -message.delete.filter.confirm = Are you sure that you want to delete custom filter? -message.delete.filter.success = Custom filter {0} deleted Successfully! -message.create.filter.success = Custom filter {0} created Successfully! -message.update.filter.success = Custom filter updated Successfully! -message.target.filter.validation = Please enter name and query -message.target.filter.duplicate = {0} already exists, please enter another value - -# action info -action.target.table.selectall = Select all (Ctrl+A) -action.target.table.clear = Clear selections - -#reused messages -soft.module.jvm =Runtime -soft.module.application =Application -soft.module.os =OS - -#Artifact upload -message.error.noFileSelected = No file selected for upload -message.error.noProvidedName = Please provide custom file name -message.error.noSwModuleSelected = Please select a Software Module -message.no.duplicateFiles = Duplicate files selected -message.no.duplicateFile = Duplicate file selected : -message.delete.artifact = Are you sure that you want to delete artifact {0} ? -message.duplicate.filename = Duplicate file name -message.swModule.deleted = {0} Software Module(s) deleted -message.upload.failed = Streaming Failed -message.uploadedfile.size.exceeded = File size exceeded .Allowed size {0} bytes -message.uploadedfile.aborted = File upload aborted -message.file.not.found = File not found -message.abort.upload = Are you sure that you want to abort the upload? - - -upload.swModuleTable.header = Software Module -upload.selectedfile.name = file selected for upload -upload.file.name = File name -upload.sha1 = SHA1 checksum -upload.md5 = MD5 checksum -upload.last.modified.date=Last modified date -upload.failed = Failed -upload.success = Success -upload.caption.add.new.swmodule = Configure New Software Module -upload.caption.delete.swmodule = Configure Delete Software Module -upload.swmodule.type = Type -upload.artifact.alreadyExists = Artifact will be overridden as the given name already exists -upload.size = Size(B) -upload.validation = Validation -upload.reason = Reason -upload.action = Action -upload.result.status = Upload status -upload.file = Upload File -upload.caption.update.swmodule = Update Software Module -caption.tab.details = Details -caption.tab.description = Description - -caption.delete.artifact.confirmbox = Confirm Artifact Delete Action - - -#Manage distributions view -label.drop.dist.delete.area = Drop here
to delete -caption.assign.software.dist.accordion.tab = Assign Software Modules -message.software.assignment = {0} Software Module(s) Assignment(s) done -message.dist.inuse = {0} Distribution is already assigned to target -message.software.dist.already.assigned = {0} Distribution already has Software Module {1} -message.software.dist.type.notallowed = {0} Software Module type can not assign to Distribution {1} -message.target.assigned = {0} is assigned to {1} -message.dist.type.delete = {0} DistributionType(s) Deleted successfully. -message.sw.module.type.delete = {0} Software Module Type(s) deleted successfully. -message.dist.type.discard.success = All Distribution Types are discarded successfully ! -message.dist.discard.success = All Distributions are discarded successfully ! -message.assign.discard.success = All assignments are discarded successfully ! -message.key.missing = Key is missing ! -message.value.missing = Value is missing ! -message.metadata.saved = Metadata with key {0} successfully saved ! -message.metadata.updated = Metadata with key {0} successfully updated ! -message.metadata.duplicate.check = Metadata with key {0} already exists, please enter another value -message.metadata.deleted.successfully = Metadata with key {0} successfully deleted ! -message.confirm.delete.metadata = Are you sure that you want to delete metadata with key {0} ? -message.error.notification.ds.target.assigned = Distribution set {0}:{1} is already assigned to targets and cannot be changed - -# Login view -notification.login.title=Welcome to Bosch IoT Software Provisioning. -notification.login.description=Please login with your Bosch Identity Management credentials. -notification.login.failed.title=Login failed! -notification.login.failed.description=Login with the given credentials failed. -notification.login.failed.credentialsexpired.title=Password Expired! -notification.login.failed.credentialsexpired.description=Password has been expired or needs to be set initially, please visit the User Management to change or set your password. -label.login.tenant=Tenant -label.login.username=Username -label.login.password=Password -button.login.signin=Sign in -checkbox.login.rememberme=Remember me - -# Links -link.documentation.name=Documentation -link.demo.name=Demo -link.requestaccount.name=Request Account -link.support.name=Support -link.usermanagement.name=User Management - -# System Configuration View -notification.configuration.save=Saved changes -configuration.defaultdistributionset.title=Distribution Set Type -configuration.defaultdistributionset.select.label=Select the default Distribution Set type: -configuration.savebutton.tooltip=Save Configurations -configuration.cancellbutton.tooltip=Cancel Configurations -configuration.authentication.title=Authentication Configuration -controller.polling.title=Polling Configuration -controller.polling.time=Polling Time -controller.polling.overduetime=Polling Overdue Time - -#Calendar -calendar.year=year -calendar.years=years -calendar.month=month -calendar.months=months -calendar.day=day -calendar.days=days -calendar.hour=hour -calendar.hours=hours -calendar.minute=minute -calendar.minutes=minutes -calendar.second=second -calendar.seconds=seconds - -header.name = Name -header.vendor = Vendor -header.version = Version -header.description = Description -header.createdBy = Created By -header.createdDate = Created Date -header.modifiedBy = Modified By -header.modifiedDate = Modified Date -header.delete = Delete -header.assigned.ds = Assigned DS -header.installed.ds = Installed DS -header.status = Status -header.target.tags = Tags -header.distributionset = Distribution Set -header.numberofgroups = No. of groups -header.detail.status = Detail status -header.total.targets = Total targets -header.type = Type -header.swmodules = SwModules -header.migrations.step=IsRequiredMigrationStep - -header.action=Actions -header.action.run=Run -header.action.pause=Pause -header.action.update=Edit - -header.rolloutgroup.installed.percentage = % Finished -header.rolloutgroup.threshold.error = Error threshold -header.rolloutgroup.threshold = Trigger threshold - - - -header.rolloutgroup.target.date = Date and time -header.rolloutgroup.target.message = Messages - -rollout.group.label.target.truncated = {0} targets has been truncated in the list due the target size limit of {1} - -distribution.details.header = Distribution Set -target.details.header = Target -header.caption.mandatory = Mandatory -header.caption.typename = SoftwareModuleType -header.caption.softwaremodule = SoftwareModule -header.caption.upload.details = Upload details -header.key = Key -header.value = Value -combo.type.tag.name = Type tag name - -label.yes = Yes -label.no =No - -#Menu -menu.title = Software Provisioning - - -#Rollout management -prompt.number.of.groups = Number of groups -prompt.tigger.threshold = Trigger threshold -prompt.error.threshold = Error threshold -prompt.distribution.set = Distribution Set -caption.configure.rollout = Configure Rollout -caption.update.rollout = Update Rollout -prompt.target.filter = Custom Target Filter -message.rollout.nonzero.group.number = Number of groups must be greater than zero -message.rollout.max.group.number = Number of groups must not be greater than 500 -message.rollout.duplicate.check = Rollout [ {0} ] must be unique, entered value already exists. -message.correct.invalid.value = Please correct invalid values -message.enter.number = Please enter number -message.rollout.field.value.range = Value should be in range {0} to {1} -message.rollout.started = Rollout {0} started successfully -message.rollout.paused = Rollout {0} paused successfully -message.rollout.resumed = Rollout {0} resumed successfully -message.rollout.noofgroups.or.targetfilter.missing = Please enter number of groups and select target filter -message.rollouts = Rollouts -label.target.per.group = Targets per group : -message.dist.already.assigned = Distribution {0} is already assigned to target -message.error.creating.rollout = Server error. Error creating Rollout. Please contact the administrator -message.error.starting.rollout = Server error. Error starting Rollout. Please contact the administrator - -#Target Filter Management -breadcrumb.target.filter.custom.filters = Custom Filters diff --git a/pom.xml b/pom.xml index 2084b92f6..f83b97624 100644 --- a/pom.xml +++ b/pom.xml @@ -149,7 +149,7 @@ - scm:git:https://github.com/eclipse/hawkbit.git + scm:git:git@github.com:eclipse/hawkbit.git scm:git:https://github.com/eclipse/hawkbit.git https://github.com/eclipse/hawkbit.git