Unify target attributes and metadata (#2408)

* Unify target attributes and metadata

Currently, the target attributes are Map while the metadata,
which has the same concept is List.
This PR unifies them making the metadata also a Map

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-05-21 11:26:02 +03:00
committed by GitHub
parent 424520bb72
commit ceba4f5cfb
29 changed files with 490 additions and 1107 deletions

View File

@@ -9,7 +9,6 @@
*/ */
package org.eclipse.hawkbit.repository; package org.eclipse.hawkbit.repository;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -41,7 +40,7 @@ public enum TargetFields implements RsqlQueryField {
DistributionSetFields.NAME.getJpaEntityFieldName(), DistributionSetFields.VERSION.getJpaEntityFieldName()), DistributionSetFields.NAME.getJpaEntityFieldName(), DistributionSetFields.VERSION.getJpaEntityFieldName()),
TAG("tags", TagFields.NAME.getJpaEntityFieldName()), TAG("tags", TagFields.NAME.getJpaEntityFieldName()),
LASTCONTROLLERREQUESTAT("lastTargetQuery"), LASTCONTROLLERREQUESTAT("lastTargetQuery"),
METADATA("metadata", new SimpleImmutableEntry<>("key", "value")), METADATA("metadata"),
TARGETTYPE("targetType", TargetTypeFields.KEY.getJpaEntityFieldName(), TargetTypeFields.NAME.getJpaEntityFieldName()); TARGETTYPE("targetType", TargetTypeFields.KEY.getJpaEntityFieldName(), TargetTypeFields.NAME.getJpaEntityFieldName());
private final String jpaEntityFieldName; private final String jpaEntityFieldName;
@@ -56,10 +55,6 @@ public enum TargetFields implements RsqlQueryField {
this(jpaEntityFieldName, List.of(subEntityAttributes), null); this(jpaEntityFieldName, List.of(subEntityAttributes), null);
} }
TargetFields(final String jpaEntityFieldName, final Entry<String, String> subEntityMapTuple) {
this(jpaEntityFieldName, Collections.emptyList(), subEntityMapTuple);
}
TargetFields(final String jpaEntityFieldName, final List<String> subEntityAttributes, final Entry<String, String> subEntityMapTuple) { TargetFields(final String jpaEntityFieldName, final List<String> subEntityAttributes, final Entry<String, String> subEntityMapTuple) {
this.jpaEntityFieldName = jpaEntityFieldName; this.jpaEntityFieldName = jpaEntityFieldName;
this.subEntityAttributes = subEntityAttributes; this.subEntityAttributes = subEntityAttributes;
@@ -73,6 +68,6 @@ public enum TargetFields implements RsqlQueryField {
@Override @Override
public boolean isMap() { public boolean isMap() {
return this == ATTRIBUTE || getSubEntityMapTuple().isPresent(); return this == ATTRIBUTE || this == METADATA;
} }
} }

View File

@@ -1,33 +0,0 @@
/**
* Copyright (c) 2018 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository;
import lombok.Getter;
/**
* Sort fields for TargetMetadata.
*/
@Getter
public enum TargetMetadataFields implements RsqlQueryField {
KEY("key"),
VALUE("value");
private final String jpaEntityFieldName;
TargetMetadataFields(final String jpaEntityFieldName) {
this.jpaEntityFieldName = jpaEntityFieldName;
}
@Override
public String identifierFieldName() {
return KEY.getJpaEntityFieldName();
}
}

View File

@@ -19,7 +19,7 @@ import lombok.ToString;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
/** /**
* The representation of an meta data in the REST API for POST/Create. * The representation of a meta-data in the REST API for POST/Create.
*/ */
@Data @Data
@Accessors(chain = true) @Accessors(chain = true)
@@ -29,9 +29,9 @@ import lombok.experimental.Accessors;
public class MgmtMetadata { public class MgmtMetadata {
@JsonProperty(required = true) @JsonProperty(required = true)
@Schema(description = "Metadata property key", example = "someKnownKey") @Schema(description = "Metadata property key", example = "country")
private String key; private String key;
@Schema(description = "Metadata property value", example = "someKnownKeyValue") @Schema(description = "Metadata property value", example = "DE")
private String value; private String value;
} }

View File

@@ -38,6 +38,7 @@ import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetAutoConfirmUpdate;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetRequestBody; import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTargetRequestBody;
import org.eclipse.hawkbit.rest.json.model.ExceptionInfo; import org.eclipse.hawkbit.rest.json.model.ExceptionInfo;
import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.MediaTypes;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
@@ -47,6 +48,7 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
/** /**
* API for handling target operations. * API for handling target operations.
@@ -727,15 +729,47 @@ public interface MgmtTargetRestApi {
produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<List<MgmtTag>> getTags(@PathVariable("targetId") String targetId); ResponseEntity<List<MgmtTag>> getTags(@PathVariable("targetId") String targetId);
/**
* Creates a list of metadata for a specific target.
*
* @param targetId the ID of the targetId to create metadata for
* @param metadataRest the list of metadata entries to create
*/
@Operation(summary = "Create a list of metadata for a specific target", description = "Create a list of metadata entries Required permissions: READ_REPOSITORY and UPDATE_TARGET")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "Successfully created"),
@ApiResponse(responseCode = "400", description = "Bad Request - e.g. invalid parameters",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ExceptionInfo.class))),
@ApiResponse(responseCode = "401", description = "The request requires user authentication.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "403",
description = "Insufficient permissions, entity is not allowed to be changed (i.e. read-only) or " +
"data volume restriction applies.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "404", description = "Target not found", content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "405", description = "The http request method is not allowed on the resource.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "406", description = "In case accept header is specified and not application/json.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "409", description = "E.g. in case an entity is created or modified by another " +
"user in another request at the same time. You may retry your modification request.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "415", description = "The request was attempt with a media-type which is not " +
"supported by the server for this resource.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "429", description = "Too many requests. The server will refuse further attempts " +
"and the client has to wait another second.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true)))
})
@PostMapping(value = MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/metadata",
consumes = { MediaType.APPLICATION_JSON_VALUE, MediaTypes.HAL_JSON_VALUE })
@ResponseStatus(HttpStatus.CREATED)
void createMetadata(@PathVariable("targetId") String targetId, @RequestBody List<MgmtMetadata> metadataRest);
/** /**
* Gets a paged list of metadata for a target. * Gets a paged list of metadata for a target.
* *
* @param targetId the ID of the target for the metadata * @param targetId the ID of the target for the metadata
* @param pagingOffsetParam the offset of list of targets for pagination, might not be present in the rest request then default value will
* be applied
* @param pagingLimitParam the limit of the paged request, might not be present in the rest request then default value will be applied
* @param sortParam the sorting parameter in the request URL, syntax {@code field:direction, field:direction}
* @param rsqlParam the search parameter in the request URL, syntax {@code q=key==abc}
* @return status OK if get request is successful with the paged list of metadata * @return status OK if get request is successful with the paged list of metadata
*/ */
@Operation(summary = "Return metadata for specific target", description = "Get a paged list of metadata for a target. Required permission: READ_REPOSITORY") @Operation(summary = "Return metadata for specific target", description = "Get a paged list of metadata for a target. Required permission: READ_REPOSITORY")
@@ -758,32 +792,9 @@ public interface MgmtTargetRestApi {
"and the client has to wait another second.", "and the client has to wait another second.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))) content = @Content(mediaType = "application/json", schema = @Schema(hidden = true)))
}) })
@GetMapping(value = MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/metadata", produces = { @GetMapping(value = MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/metadata",
MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<PagedList<MgmtMetadata>> getMetadata( ResponseEntity<PagedList<MgmtMetadata>> getMetadata(@PathVariable("targetId") String targetId);
@PathVariable("targetId") String targetId,
@RequestParam(
value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_OFFSET,
defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET)
@Schema(description = "The paging offset (default is 0)")
int pagingOffsetParam,
@RequestParam(
value = MgmtRestConstants.REQUEST_PARAMETER_PAGING_LIMIT,
defaultValue = MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT)
@Schema(description = "The maximum number of entries in a page (default is 50)")
int pagingLimitParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SORTING, required = false)
@Schema(description = """
The query parameter sort allows to define the sort order for the result of a query. A sort criteria
consists of the name of a field and the sort direction (ASC for ascending and DESC descending).
The sequence of the sort criteria (multiple can be used) defines the sort order of the entities
in the result.""")
String sortParam,
@RequestParam(value = MgmtRestConstants.REQUEST_PARAMETER_SEARCH, required = false)
@Schema(description = """
Query fields based on the Feed Item Query Language (FIQL). See Entity Definitions for
available fields.""")
String rsqlParam);
/** /**
* Gets a single metadata value for a specific key of a target. * Gets a single metadata value for a specific key of a target.
@@ -824,7 +835,6 @@ public interface MgmtTargetRestApi {
* @param targetId the ID of the target to update the metadata entry * @param targetId the ID of the target to update the metadata entry
* @param metadataKey the key of the metadata to update the value * @param metadataKey the key of the metadata to update the value
* @param metadata update body * @param metadata update body
* @return status OK if the update request is successful and the updated metadata result
*/ */
@Operation(summary = "Updates a single metadata value of a target", description = "Update a single metadata value for speficic key. Required permission: UPDATE_REPOSITORY") @Operation(summary = "Updates a single metadata value of a target", description = "Update a single metadata value for speficic key. Required permission: UPDATE_REPOSITORY")
@ApiResponses(value = { @ApiResponses(value = {
@@ -834,8 +844,7 @@ public interface MgmtTargetRestApi {
@ApiResponse(responseCode = "401", description = "The request requires user authentication.", @ApiResponse(responseCode = "401", description = "The request requires user authentication.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))), content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "403", @ApiResponse(responseCode = "403",
description = "Insufficient permissions, entity is not allowed to be changed (i.e. read-only) or " + description = "Insufficient permissions, entity is not allowed to be changed (i.e. read-only) or data volume restriction applies.",
"data volume restriction applies.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))), content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "404", description = "Target not found.", content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))), @ApiResponse(responseCode = "404", description = "Target not found.", content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "405", description = "The http request method is not allowed on the resource.", @ApiResponse(responseCode = "405", description = "The http request method is not allowed on the resource.",
@@ -852,9 +861,9 @@ public interface MgmtTargetRestApi {
"and the client has to wait another second.", "and the client has to wait another second.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))) content = @Content(mediaType = "application/json", schema = @Schema(hidden = true)))
}) })
@PutMapping(value = MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/metadata/{metadataKey}", @PutMapping(value = MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/metadata/{metadataKey}")
produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) @ResponseStatus(HttpStatus.OK)
ResponseEntity<MgmtMetadata> updateMetadata( void updateMetadata(
@PathVariable("targetId") String targetId, @PathVariable("targetId") String targetId,
@PathVariable("metadataKey") String metadataKey, @PathVariable("metadataKey") String metadataKey,
@RequestBody MgmtMetadataBodyPut metadata); @RequestBody MgmtMetadataBodyPut metadata);
@@ -887,49 +896,8 @@ public interface MgmtTargetRestApi {
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))) content = @Content(mediaType = "application/json", schema = @Schema(hidden = true)))
}) })
@DeleteMapping(value = MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/metadata/{metadataKey}") @DeleteMapping(value = MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/metadata/{metadataKey}")
ResponseEntity<Void> deleteMetadata( @ResponseStatus(HttpStatus.OK)
@PathVariable("targetId") String targetId, void deleteMetadata(@PathVariable("targetId") String targetId, @PathVariable("metadataKey") String metadataKey);
@PathVariable("metadataKey") String metadataKey);
/**
* Creates a list of metadata for a specific target.
*
* @param targetId the ID of the targetId to create metadata for
* @param metadataRest the list of metadata entries to create
* @return status created if post request is successful with the value of the created metadata
*/
@Operation(summary = "Create a list of metadata for a specific target", description = "Create a list of metadata entries Required permissions: READ_REPOSITORY and UPDATE_TARGET")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "Successfully created"),
@ApiResponse(responseCode = "400", description = "Bad Request - e.g. invalid parameters",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ExceptionInfo.class))),
@ApiResponse(responseCode = "401", description = "The request requires user authentication.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "403",
description = "Insufficient permissions, entity is not allowed to be changed (i.e. read-only) or " +
"data volume restriction applies.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "404", description = "Target not found", content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "405", description = "The http request method is not allowed on the resource.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "406", description = "In case accept header is specified and not application/json.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "409", description = "E.g. in case an entity is created or modified by another " +
"user in another request at the same time. You may retry your modification request.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "415", description = "The request was attempt with a media-type which is not " +
"supported by the server for this resource.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "429", description = "Too many requests. The server will refuse further attempts " +
"and the client has to wait another second.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true)))
})
@PostMapping(value = MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/metadata",
consumes = { MediaType.APPLICATION_JSON_VALUE, MediaTypes.HAL_JSON_VALUE },
produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE })
ResponseEntity<List<MgmtMetadata>> createMetadata(
@PathVariable("targetId") String targetId,
@RequestBody List<MgmtMetadata> metadataRest);
/** /**
* Get the current auto-confirm state for a specific target. * Get the current auto-confirm state for a specific target.
@@ -945,8 +913,7 @@ public interface MgmtTargetRestApi {
@ApiResponse(responseCode = "401", description = "The request requires user authentication.", @ApiResponse(responseCode = "401", description = "The request requires user authentication.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))), content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "403", @ApiResponse(responseCode = "403",
description = "Insufficient permissions, entity is not allowed to be changed (i.e. read-only) or " + description = "Insufficient permissions, entity is not allowed to be changed (i.e. read-only) or data volume restriction applies.",
"data volume restriction applies.",
content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))), content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "404", description = "Target not found.", content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))), @ApiResponse(responseCode = "404", description = "Target not found.", content = @Content(mediaType = "application/json", schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "405", description = "The http request method is not allowed on the resource.", @ApiResponse(responseCode = "405", description = "The http request method is not allowed on the resource.",

View File

@@ -18,7 +18,9 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@@ -46,11 +48,9 @@ import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.AutoConfirmationStatus; import org.eclipse.hawkbit.repository.model.AutoConfirmationStatus;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.MetaData;
import org.eclipse.hawkbit.repository.model.PollStatus; import org.eclipse.hawkbit.repository.model.PollStatus;
import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.eclipse.hawkbit.rest.json.model.ResponseList; import org.eclipse.hawkbit.rest.json.model.ResponseList;
import org.eclipse.hawkbit.util.IpUtil; import org.eclipse.hawkbit.util.IpUtil;
import org.eclipse.hawkbit.utils.TenantConfigHelper; import org.eclipse.hawkbit.utils.TenantConfigHelper;
@@ -79,10 +79,8 @@ public final class MgmtTargetMapper {
MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE, MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE,
ActionFields.ID.getJpaEntityFieldName() + ":" + SortDirection.DESC, null)) ActionFields.ID.getJpaEntityFieldName() + ":" + SortDirection.DESC, null))
.withRel(MgmtRestConstants.TARGET_V1_ACTIONS).expand()); .withRel(MgmtRestConstants.TARGET_V1_ACTIONS).expand());
response.add(linkTo(methodOn(MgmtTargetRestApi.class).getMetadata(response.getControllerId(), response.add(linkTo(methodOn(MgmtTargetRestApi.class).getMetadata(response.getControllerId()))
MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET_VALUE, .withRel("metadata").expand());
MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE, null, null)).withRel("metadata")
.expand());
if (response.getTargetType() != null) { if (response.getTargetType() != null) {
response.add(linkTo(methodOn(MgmtTargetTypeRestApi.class).getTargetType(response.getTargetType())) response.add(linkTo(methodOn(MgmtTargetTypeRestApi.class).getTargetType(response.getTargetType()))
.withRel(MgmtRestConstants.TARGET_V1_ASSIGNED_TARGET_TYPE).expand()); .withRel(MgmtRestConstants.TARGET_V1_ASSIGNED_TARGET_TYPE).expand());
@@ -196,19 +194,14 @@ public final class MgmtTargetMapper {
.toList(); .toList();
} }
static List<MetaData> fromRequestTargetMetadata(final List<MgmtMetadata> metadata, static Map<String, String> fromRequestMetadata(final List<MgmtMetadata> metadata) {
final EntityFactory entityFactory) { return metadata == null
if (metadata == null) { ? Collections.emptyMap()
return Collections.emptyList(); : metadata.stream().collect(Collectors.toMap(MgmtMetadata::getKey, MgmtMetadata::getValue));
}
return metadata.stream().map(
metadataRest -> entityFactory.generateTargetMetadata(metadataRest.getKey(), metadataRest.getValue()))
.toList();
} }
static List<MgmtActionStatus> toActionStatusRestResponse(final Collection<ActionStatus> actionStatus, static List<MgmtActionStatus> toActionStatusRestResponse(
final DeploymentManagement deploymentManagement) { final Collection<ActionStatus> actionStatus, final DeploymentManagement deploymentManagement) {
if (actionStatus == null) { if (actionStatus == null) {
return Collections.emptyList(); return Collections.emptyList();
} }
@@ -308,15 +301,15 @@ public final class MgmtTargetMapper {
return actions.stream().map(action -> toResponse(targetId, action)).toList(); return actions.stream().map(action -> toResponse(targetId, action)).toList();
} }
static MgmtMetadata toResponseTargetMetadata(final TargetMetadata metadata) { static MgmtMetadata toResponseMetadata(final String key, final String value) {
final MgmtMetadata metadataRest = new MgmtMetadata(); final MgmtMetadata metadataRest = new MgmtMetadata();
metadataRest.setKey(metadata.getKey()); metadataRest.setKey(key);
metadataRest.setValue(metadata.getValue()); metadataRest.setValue(value);
return metadataRest; return metadataRest;
} }
static List<MgmtMetadata> toResponseTargetMetadata(final List<TargetMetadata> metadata) { static List<MgmtMetadata> toResponseMetadata(final Map<String, String> metadata) {
return metadata.stream().map(MgmtTargetMapper::toResponseTargetMetadata).toList(); return metadata.entrySet().stream().map(e -> toResponseMetadata(e.getKey(), e.getValue())).toList();
} }
private static void addPollStatus(final Target target, final MgmtTarget targetRest, final Function<Target, PollStatus> pollStatusResolver) { private static void addPollStatus(final Target target, final MgmtTarget targetRest, final Function<Target, PollStatus> pollStatusResolver) {

View File

@@ -57,7 +57,6 @@ import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.DeploymentRequest; import org.eclipse.hawkbit.repository.model.DeploymentRequest;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult; import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.utils.TenantConfigHelper; import org.eclipse.hawkbit.utils.TenantConfigHelper;
@@ -421,57 +420,39 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
@Override @Override
public ResponseEntity<List<MgmtTag>> getTags(final String targetId) { public ResponseEntity<List<MgmtTag>> getTags(final String targetId) {
final Set<TargetTag> tags = targetManagement.getTagsByControllerId(targetId); final Set<TargetTag> tags = targetManagement.getTags(targetId);
return ResponseEntity.ok(MgmtTagMapper.toResponse(tags == null ? Collections.emptyList() : tags.stream().toList())); return ResponseEntity.ok(MgmtTagMapper.toResponse(tags == null ? Collections.emptyList() : tags.stream().toList()));
} }
@Override @Override
public ResponseEntity<PagedList<MgmtMetadata>> getMetadata( public void createMetadata(final String targetId, final List<MgmtMetadata> metadataRest) {
final String targetId, targetManagement.createMetadata(targetId, MgmtTargetMapper.fromRequestMetadata(metadataRest));
final int pagingOffsetParam, final int pagingLimitParam, final String sortParam, final String rsqlParam) { }
final int sanitizedOffsetParam = PagingUtility.sanitizeOffsetParam(pagingOffsetParam);
final int sanitizedLimitParam = PagingUtility.sanitizePageLimitParam(pagingLimitParam);
final Sort sorting = PagingUtility.sanitizeDistributionSetMetadataSortParam(sortParam);
final Pageable pageable = new OffsetBasedPageRequest(sanitizedOffsetParam, sanitizedLimitParam, sorting); @Override
final Page<TargetMetadata> metaDataPage; public ResponseEntity<PagedList<MgmtMetadata>> getMetadata(final String targetId) {
final Map<String, String> metadata = targetManagement.getMetadata(targetId);
if (rsqlParam != null) { return ResponseEntity.ok(new PagedList<>(MgmtTargetMapper.toResponseMetadata(metadata), metadata.size()));
metaDataPage = targetManagement.findMetaDataByControllerIdAndRsql(pageable, targetId, rsqlParam);
} else {
metaDataPage = targetManagement.findMetaDataByControllerId(pageable, targetId);
}
return ResponseEntity.ok(new PagedList<>(MgmtTargetMapper.toResponseTargetMetadata(
metaDataPage.getContent()), metaDataPage.getTotalElements()));
} }
@Override @Override
public ResponseEntity<MgmtMetadata> getMetadataValue(final String targetId, final String metadataKey) { public ResponseEntity<MgmtMetadata> getMetadataValue(final String targetId, final String metadataKey) {
final TargetMetadata findOne = targetManagement.getMetaDataByControllerId(targetId, metadataKey) final String metadataValue = targetManagement.getMetadata(targetId).get(metadataKey);
.orElseThrow(() -> new EntityNotFoundException(TargetMetadata.class, targetId, metadataKey)); if (metadataValue == null) {
return ResponseEntity.ok(MgmtTargetMapper.toResponseTargetMetadata(findOne)); throw new EntityNotFoundException("Target metadata", targetId + ":" + metadataKey);
}
return ResponseEntity.ok(MgmtTargetMapper.toResponseMetadata(metadataKey, metadataValue));
} }
@Override @Override
public ResponseEntity<MgmtMetadata> updateMetadata(final String targetId, final String metadataKey, final MgmtMetadataBodyPut metadata) { public void updateMetadata(final String targetId, final String metadataKey, final MgmtMetadataBodyPut metadata) {
final TargetMetadata updated = targetManagement.updateMetadata(targetId, targetManagement.updateMetadata(targetId, metadataKey, metadata.getValue());
entityFactory.generateTargetMetadata(metadataKey, metadata.getValue()));
return ResponseEntity.ok(MgmtTargetMapper.toResponseTargetMetadata(updated));
} }
@Override @Override
@AuditLog(entity = "Target", type = AuditLog.Type.DELETE, description = "Delete Target Metadata") @AuditLog(entity = "Target", type = AuditLog.Type.DELETE, description = "Delete Target Metadata")
public ResponseEntity<Void> deleteMetadata(final String targetId, final String metadataKey) { public void deleteMetadata(final String targetId, final String metadataKey) {
targetManagement.deleteMetaData(targetId, metadataKey); targetManagement.deleteMetadata(targetId, metadataKey);
return ResponseEntity.ok().build();
}
@Override
public ResponseEntity<List<MgmtMetadata>> createMetadata(final String targetId, final List<MgmtMetadata> metadataRest) {
final List<TargetMetadata> created = targetManagement.createMetaData(targetId,
MgmtTargetMapper.fromRequestTargetMetadata(metadataRest, entityFactory));
return new ResponseEntity<>(MgmtTargetMapper.toResponseTargetMetadata(created), HttpStatus.CREATED);
} }
@Override @Override

View File

@@ -37,7 +37,6 @@ import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@@ -74,12 +73,10 @@ import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.ActionStatus;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.MetaData;
import org.eclipse.hawkbit.repository.model.NamedEntity; import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetType; import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
@@ -114,8 +111,6 @@ import org.springframework.test.web.servlet.ResultActions;
@Story("Target Resource") @Story("Target Resource")
class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest { class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
@Autowired
ActionRepository actionRepository;
private static final String TARGET_DESCRIPTION_TEST = "created in test"; private static final String TARGET_DESCRIPTION_TEST = "created in test";
private static final String JSON_PATH_ROOT = "$"; private static final String JSON_PATH_ROOT = "$";
// fields, attributes // fields, attributes
@@ -139,23 +134,26 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
private static final String JSON_PATH_DESCRIPTION = JSON_PATH_ROOT + JSON_PATH_FIELD_DESCRIPTION; private static final String JSON_PATH_DESCRIPTION = JSON_PATH_ROOT + JSON_PATH_FIELD_DESCRIPTION;
private static final String JSON_PATH_LAST_REQUEST_AT = JSON_PATH_ROOT + JSON_PATH_FIELD_LAST_REQUEST_AT; private static final String JSON_PATH_LAST_REQUEST_AT = JSON_PATH_ROOT + JSON_PATH_FIELD_LAST_REQUEST_AT;
private static final String JSON_PATH_TYPE = JSON_PATH_ROOT + JSON_PATH_FIELD_TARGET_TYPE; private static final String JSON_PATH_TYPE = JSON_PATH_ROOT + JSON_PATH_FIELD_TARGET_TYPE;
@Autowired @Autowired
private ObjectMapper objectMapper; ActionRepository actionRepository;
@Autowired @Autowired
private JpaProperties jpaProperties; private JpaProperties jpaProperties;
@Autowired
private ObjectMapper objectMapper;
@Test @Test
@Description("Ensures that when targetType value of -1 is provided the target type is unassigned from the target.") @Description("Ensures that when targetType value of -1 is provided the target type is unassigned from the target.")
public void updateTargetAndUnnasignTargetType() throws Exception { void updateTargetAndUnassignTargetType() throws Exception {
final String knownControllerId = "123"; final String knownControllerId = "123";
final String knownNewAddress = "amqp://test123/foobar"; final String knownNewAddress = "amqp://test123/foobar";
final String knownNameNotModify = "controllerName"; final String knownNameNotModify = "controllerName";
final Long unnasignTargetTypeValue = -1L; final Long unassignTargetTypeValue = -1L;
final TargetType targetType = targetTypeManagement.create( final TargetType targetType = targetTypeManagement.create(
entityFactory.targetType().create().name("targettype1").description("targettypedes1")); entityFactory.targetType().create().name("targettype1").description("targettypedes1"));
final String body = new JSONObject().put("targetType", unnasignTargetTypeValue).toString(); final String body = new JSONObject().put("targetType", unassignTargetTypeValue).toString();
// create a target with the created TargetType // create a target with the created TargetType
targetManagement.create(entityFactory.target().create().controllerId(knownControllerId).name(knownNameNotModify) targetManagement.create(entityFactory.target().create().controllerId(knownControllerId).name(knownNameNotModify)
@@ -183,18 +181,18 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
@Test @Test
@Description("Ensures that when targetType value of -1 is provided the target type is unassigned from the target when updating multiple fields in target object.") @Description("Ensures that when targetType value of -1 is provided the target type is unassigned from the target when updating multiple fields in target object.")
public void updateTargetNameAndUnnasignTargetType() throws Exception { void updateTargetNameAndUnassignTargetType() throws Exception {
final String knownControllerId = "123"; final String knownControllerId = "123";
final String knownNewAddress = "amqp://test123/foobar"; final String knownNewAddress = "amqp://test123/foobar";
final String knownNameNotModify = "controllerName"; final String knownNameNotModify = "controllerName";
final Long unnasignTargetTypeValue = -1L; final Long unassignTargetTypeValue = -1L;
final String controllerNewName = "controllerNewName"; final String controllerNewName = "controllerNewName";
final TargetType targetType = targetTypeManagement.create( final TargetType targetType = targetTypeManagement.create(
entityFactory.targetType().create().name("targettype1").description("targettypedes1")); entityFactory.targetType().create().name("targettype1").description("targettypedes1"));
final String body = new JSONObject() final String body = new JSONObject()
.put("targetType", unnasignTargetTypeValue).put("name", "controllerNewName") .put("targetType", unassignTargetTypeValue).put("name", "controllerNewName")
.toString(); .toString();
// create a target with the created TargetType // create a target with the created TargetType
@@ -223,7 +221,7 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
@Test @Test
@Description("Handles the GET request of retrieving all targets within SP..") @Description("Handles the GET request of retrieving all targets within SP..")
public void getTargets() throws Exception { void getTargets() throws Exception {
enableConfirmationFlow(); enableConfirmationFlow();
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING)) mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING))
.andExpect(status().isOk()) .andExpect(status().isOk())
@@ -232,33 +230,15 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
@Test @Test
@Description("Handles the GET request of retrieving all targets within SP based by parameter.") @Description("Handles the GET request of retrieving all targets within SP based by parameter.")
public void getTargetsWithParameters() throws Exception { void getTargetsWithParameters() throws Exception {
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "?limit=10&sort=name:ASC&offset=0&q=name==a")) mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "?limit=10&sort=name:ASC&offset=0&q=name==a"))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andDo(MockMvcResultPrinter.print()); .andDo(MockMvcResultPrinter.print());
} }
@Test
@Description("Get a paged list of meta data for a target with standard page size.")
public void getMetadata() throws Exception {
final int totalMetadata = 4;
final String knownKeyPrefix = "knownKey";
final String knownValuePrefix = "knownValue";
final Target testTarget = testdataFactory.createTarget("targetId");
for (int index = 0; index < totalMetadata; index++) {
targetManagement.createMetaData(testTarget.getControllerId(), List.of(
entityFactory.generateTargetMetadata(knownKeyPrefix + index, knownValuePrefix + index)));
}
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/metadata", testTarget.getControllerId()))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk())
.andExpect(content().contentType(MediaTypes.HAL_JSON));
}
@Test @Test
@Description("Handles the POST request to activate auto-confirm on a target. Payload can be provided to specify more details about the operation.") @Description("Handles the POST request to activate auto-confirm on a target. Payload can be provided to specify more details about the operation.")
public void postActivateAutoConfirm() throws Exception { void postActivateAutoConfirm() throws Exception {
final Target testTarget = testdataFactory.createTarget("targetId"); final Target testTarget = testdataFactory.createTarget("targetId");
final MgmtTargetAutoConfirmUpdate body = new MgmtTargetAutoConfirmUpdate("custom_initiator_value", final MgmtTargetAutoConfirmUpdate body = new MgmtTargetAutoConfirmUpdate("custom_initiator_value",
@@ -273,7 +253,7 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
@Test @Test
@Description("Handles the POST request to deactivate auto-confirm on a target.") @Description("Handles the POST request to deactivate auto-confirm on a target.")
public void postDeactivateAutoConfirm() throws Exception { void postDeactivateAutoConfirm() throws Exception {
final Target testTarget = testdataFactory.createTarget("targetId"); final Target testTarget = testdataFactory.createTarget("targetId");
confirmationManagement.activateAutoConfirmation(testTarget.getControllerId(), null, null); confirmationManagement.activateAutoConfirmation(testTarget.getControllerId(), null, null);
@@ -285,7 +265,7 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
@Test @Test
@Description("Test confirmation of single Action with confirm status. Check that Action goes into Running status with appropriate messages and status code") @Description("Test confirmation of single Action with confirm status. Check that Action goes into Running status with appropriate messages and status code")
public void updateActionConfirmationWithConfirm() throws Exception { void updateActionConfirmationWithConfirm() throws Exception {
final int expectedStatusCode = 210; final int expectedStatusCode = 210;
final String expectedStatusMessage1 = "some-custom-message1"; final String expectedStatusMessage1 = "some-custom-message1";
final String expectedStatusMessage2 = "some-custom-message2"; final String expectedStatusMessage2 = "some-custom-message2";
@@ -296,7 +276,7 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
@Test @Test
@Description("Test confirmation of single Action with deny status. Check that Action stays in WAIT_FOR_CONFIRMATION status with appropriate messages and status code") @Description("Test confirmation of single Action with deny status. Check that Action stays in WAIT_FOR_CONFIRMATION status with appropriate messages and status code")
public void updateActionConfirmationWithDeny() throws Exception { void updateActionConfirmationWithDeny() throws Exception {
final int expectedStatusCode = 410; final int expectedStatusCode = 410;
final String expectedStatusMessage1 = "some-error-custom-message1"; final String expectedStatusMessage1 = "some-error-custom-message1";
final String expectedStatusMessage2 = "some-error-custom-message2"; final String expectedStatusMessage2 = "some-error-custom-message2";
@@ -307,7 +287,7 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
@Test @Test
@Description("Test confirmation of single Action with wrong ControllerId - e.g. the given Action is not assigned to the given Target - confirmation call must fail.") @Description("Test confirmation of single Action with wrong ControllerId - e.g. the given Action is not assigned to the given Target - confirmation call must fail.")
public void updateActionConfirmationFailsIfActionNotAssignedToTarget() throws Exception { void updateActionConfirmationFailsIfActionNotAssignedToTarget() throws Exception {
final int payloadCallCode = 200; final int payloadCallCode = 200;
final String payloadCallMessage1 = "random1"; final String payloadCallMessage1 = "random1";
final String payloadCallMessage2 = "random2"; final String payloadCallMessage2 = "random2";
@@ -2005,17 +1985,10 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
.contentType(MediaType.APPLICATION_JSON).content(metaData1.toString())) .contentType(MediaType.APPLICATION_JSON).content(metaData1.toString()))
.andDo(MockMvcResultPrinter.print()) .andDo(MockMvcResultPrinter.print())
.andExpect(status().isCreated()) .andExpect(status().isCreated())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(content().string(""));
.andExpect(jsonPath("[0].key", equalTo(knownKey1)))
.andExpect(jsonPath("[0].value", equalTo(knownValue1)))
.andExpect(jsonPath("[1].key", equalTo(knownKey2)))
.andExpect(jsonPath("[1].value", equalTo(knownValue2)));
final TargetMetadata metaKey1 = targetManagement.getMetaDataByControllerId(knownControllerId, knownKey1).get(); assertThat(targetManagement.getMetadata(knownControllerId).get(knownKey1)).isEqualTo(knownValue1);
final TargetMetadata metaKey2 = targetManagement.getMetaDataByControllerId(knownControllerId, knownKey2).get(); assertThat(targetManagement.getMetadata(knownControllerId).get(knownKey2)).isEqualTo(knownValue2);
assertThat(metaKey1.getValue()).isEqualTo(knownValue1);
assertThat(metaKey2.getValue()).isEqualTo(knownValue2);
// verify quota enforcement // verify quota enforcement
final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerTarget(); final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerTarget();
@@ -2025,16 +1998,14 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
metaData2.put(new JSONObject().put("key", knownKey1 + i).put("value", knownValue1 + i)); metaData2.put(new JSONObject().put("key", knownKey1 + i).put("value", knownValue1 + i));
} }
mvc.perform(post("/rest/v1/targets/{targetId}/metadata", knownControllerId).accept(MediaType.APPLICATION_JSON) mvc.perform(post("/rest/v1/targets/{targetId}/metadata", knownControllerId)
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON).content(metaData2.toString())) .contentType(MediaType.APPLICATION_JSON).content(metaData2.toString()))
.andDo(MockMvcResultPrinter.print()) .andDo(MockMvcResultPrinter.print())
.andExpect(status().isForbidden()); .andExpect(status().isForbidden());
// verify that the number of meta data entries has not changed // verify that the number of meta-data entries has not changed (we cannot use the PAGE constant here as it tries to sort by ID)
// (we cannot use the PAGE constant here as it tries to sort by ID) assertThat(targetManagement.getMetadata(knownControllerId)).hasSize(metaData1.length());
assertThat(targetManagement.findMetaDataByControllerId(PageRequest.of(0, Integer.MAX_VALUE), knownControllerId)
.getTotalElements()).isEqualTo(metaData1.length());
} }
@Test @Test
@@ -2056,14 +2027,9 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
.content(jsonObject.toString())) .content(jsonObject.toString()))
.andDo(MockMvcResultPrinter.print()) .andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect(content().string(""));
.andExpect(jsonPath("key", equalTo(knownKey)))
.andExpect(jsonPath("value", equalTo(updateValue)));
final TargetMetadata updatedTargetMetadata = targetManagement
.getMetaDataByControllerId(knownControllerId, knownKey).get();
assertThat(updatedTargetMetadata.getValue()).isEqualTo(updateValue);
assertThat(targetManagement.getMetadata(knownControllerId).get(knownKey)).isEqualTo(updateValue);
} }
@Test @Test
@@ -2081,7 +2047,12 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
.andDo(MockMvcResultPrinter.print()) .andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()); .andExpect(status().isOk());
assertThat(targetManagement.getMetaDataByControllerId(knownControllerId, knownKey)).isNotPresent(); // already deleted
mvc.perform(delete("/rest/v1/targets/{targetId}/metadata/{key}", knownControllerId, knownKey))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound());
assertThat(targetManagement.getMetadata(knownControllerId).get(knownKey)).isNull();
} }
@Test @Test
@@ -2103,7 +2074,7 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
.andDo(MockMvcResultPrinter.print()) .andDo(MockMvcResultPrinter.print())
.andExpect(status().isNotFound()); .andExpect(status().isNotFound());
assertThat(targetManagement.getMetaDataByControllerId(knownControllerId, knownKey)).isPresent(); assertThat(targetManagement.getMetadata(knownControllerId).get(knownKey)).isNotNull();
} }
@Test @Test
@@ -2126,53 +2097,27 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
@Test @Test
@Description("Ensures that a metadata entry paged list selection through API reflectes the repository content.") @Description("Ensures that a metadata entry paged list selection through API reflectes the repository content.")
void getPagedListOfMetadata() throws Exception { void getMetadata() throws Exception {
final String knownControllerId = "targetIdWithMetadata"; final String knownControllerId = "targetIdWithMetadata";
final int totalMetadata = 10; final int totalMetadata = 10;
final int limitParam = 5;
final String offsetParam = "0";
final String knownKeyPrefix = "knownKey"; final String knownKeyPrefix = "knownKey";
final String knownValuePrefix = "knownValue"; final String knownValuePrefix = "knownValue";
setupTargetWithMetadata(knownControllerId, knownKeyPrefix, knownValuePrefix, totalMetadata); setupTargetWithMetadata(knownControllerId, knownKeyPrefix, knownValuePrefix, totalMetadata);
mvc.perform(get("/rest/v1/targets/{targetId}/metadata?offset=" + offsetParam + "&limit=" + limitParam, mvc.perform(get("/rest/v1/targets/{targetId}/metadata", knownControllerId))
knownControllerId))
.andDo(MockMvcResultPrinter.print()) .andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("size", equalTo(limitParam))) .andExpect(jsonPath("size", equalTo(totalMetadata)))
.andExpect(jsonPath("total", equalTo(totalMetadata))) .andExpect(jsonPath("total", equalTo(totalMetadata)))
.andExpect(jsonPath("content[0].key", equalTo("knownKey0"))) .andExpect(jsonPath("content[0].key", equalTo("knownKey0")))
.andExpect(jsonPath("content[0].value", equalTo("knownValue0"))); .andExpect(jsonPath("content[0].value", equalTo("knownValue0")));
}
@Test
@Description("Ensures that a target metadata filtered query with value==knownValue1 parameter returns only the metadata entries with that value.")
void searchDistributionSetMetadataRsql() throws Exception {
final String knownControllerId = "targetIdWithMetadata";
final int totalMetadata = 10;
final String knownKeyPrefix = "knownKey";
final String knownValuePrefix = "knownValue";
setupTargetWithMetadata(knownControllerId, knownKeyPrefix, knownValuePrefix, totalMetadata);
final String rsqlSearchValue1 = "value==knownValue1";
mvc.perform(get("/rest/v1/targets/{targetId}/metadata?q=" + rsqlSearchValue1, knownControllerId))
.andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk())
.andExpect(jsonPath("size", equalTo(1)))
.andExpect(jsonPath("total", equalTo(1)))
.andExpect(jsonPath("content[0].key", equalTo("knownKey1")))
.andExpect(jsonPath("content[0].value", equalTo("knownValue1")));
} }
@Test @Test
@Description("A request for assigning multiple DS to a target results in a Bad Request when multiassignment in disabled.") @Description("A request for assigning multiple DS to a target results in a Bad Request when multiassignment in disabled.")
void multiassignmentRequestNotAllowedIfDisabled() throws Exception { void multiAssignmentRequestNotAllowedIfDisabled() throws Exception {
final String targetId = testdataFactory.createTarget().getControllerId(); final String targetId = testdataFactory.createTarget().getControllerId();
final List<Long> dsIds = testdataFactory.createDistributionSets(2).stream().map(DistributionSet::getId) final List<Long> dsIds = testdataFactory.createDistributionSets(2).stream().map(DistributionSet::getId)
.toList(); .toList();
@@ -2953,23 +2898,20 @@ class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest {
return targetManagement.getByControllerID(tA.getControllerId()).get(); return targetManagement.getByControllerID(tA.getControllerId()).get();
} }
private void setupTargetWithMetadata(final String knownControllerId, final String knownKey, private void setupTargetWithMetadata(final String knownControllerId, final String knownKey, final String knownValue) {
final String knownValue) {
testdataFactory.createTarget(knownControllerId); testdataFactory.createTarget(knownControllerId);
targetManagement.createMetaData(knownControllerId, targetManagement.createMetadata(knownControllerId, Map.of(knownKey, knownValue));
Collections.singletonList(entityFactory.generateTargetMetadata(knownKey, knownValue)));
} }
private void setupTargetWithMetadata(final String knownControllerId, final String knownKeyPrefix, private void setupTargetWithMetadata(
final String knownValuePrefix, final int totalMetadata) { final String knownControllerId, final String knownKeyPrefix, final String knownValuePrefix, final int totalMetadata) {
testdataFactory.createTarget(knownControllerId); testdataFactory.createTarget(knownControllerId);
final List<MetaData> targetMetadataEntries = new LinkedList<>(); final Map<String, String> metadataEntries = new HashMap<>();
for (int index = 0; index < totalMetadata; index++) { for (int index = 0; index < totalMetadata; index++) {
targetMetadataEntries metadataEntries.put(knownKeyPrefix + index, knownValuePrefix + index);
.add(entityFactory.generateTargetMetadata(knownKeyPrefix + index, knownValuePrefix + index));
} }
targetManagement.createMetaData(knownControllerId, targetMetadataEntries); targetManagement.createMetadata(knownControllerId, metadataEntries);
} }
private Action updateActionStatus(final Action action, final Status status, final Integer statusCode) { private Action updateActionStatus(final Action action, final Status status, final Integer statusCode) {

View File

@@ -54,16 +54,6 @@ public interface EntityFactory {
MetaData generateDsMetadata(@Size(min = 1, max = MetaData.KEY_MAX_SIZE) @NotNull String key, MetaData generateDsMetadata(@Size(min = 1, max = MetaData.KEY_MAX_SIZE) @NotNull String key,
@Size(max = MetaData.VALUE_MAX_SIZE) String value); @Size(max = MetaData.VALUE_MAX_SIZE) String value);
/**
* Generates an {@link MetaData} element for target without persisting it.
*
* @param key {@link MetaData#getKey()}
* @param value {@link MetaData#getValue()}
* @return {@link MetaData} object
*/
MetaData generateTargetMetadata(@Size(min = 1, max = MetaData.KEY_MAX_SIZE) @NotNull String key,
@Size(max = MetaData.VALUE_MAX_SIZE) String value);
/** /**
* @return {@link SoftwareModuleMetadataBuilder} object * @return {@link SoftwareModuleMetadataBuilder} object
*/ */

View File

@@ -35,7 +35,6 @@ import org.eclipse.hawkbit.repository.model.MetaData;
import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetType; import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetTypeAssignmentResult; import org.eclipse.hawkbit.repository.model.TargetTypeAssignmentResult;
@@ -434,8 +433,7 @@ public interface TargetManagement {
* @throws EntityNotFoundException if distribution set with given ID does not exist * @throws EntityNotFoundException if distribution set with given ID does not exist
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_READ_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_READ_TARGET)
Page<Target> findByInstalledDistributionSetAndRsql(@NotNull Pageable pageReq, long distributionSetId, Page<Target> findByInstalledDistributionSetAndRsql(@NotNull Pageable pageReq, long distributionSetId, @NotNull String rsqlParam);
@NotNull String rsqlParam);
/** /**
* Retrieves the {@link Target} which have a certain {@link TargetUpdateStatus}. * Retrieves the {@link Target} which have a certain {@link TargetUpdateStatus}.
@@ -509,6 +507,19 @@ public interface TargetManagement {
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Page<Target> findByRsqlAndTag(@NotNull Pageable pageable, @NotNull String rsqlParam, long tagId); Page<Target> findByRsqlAndTag(@NotNull Pageable pageable, @NotNull String rsqlParam, long tagId);
/**
* Verify if a target matches a specific target filter query, does not have a
* specific DS already assigned and is compatible with it.
*
* @param controllerId of the {@link org.eclipse.hawkbit.repository.model.Target} to check
* @param distributionSetId of the {@link org.eclipse.hawkbit.repository.model.DistributionSet} to consider
* @param targetFilterQuery to execute
* @return true if it matches
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_READ_TARGET)
boolean isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(
@NotNull String controllerId, long distributionSetId, @NotNull String targetFilterQuery);
/** /**
* Initiates {@link TargetType} assignment to given {@link Target}s. If some * Initiates {@link TargetType} assignment to given {@link Target}s. If some
* targets in the list have the {@link TargetType} not yet assigned, they will * targets in the list have the {@link TargetType} not yet assigned, they will
@@ -525,12 +536,10 @@ public interface TargetManagement {
TargetTypeAssignmentResult assignType(@NotEmpty Collection<String> controllerIds, @NotNull Long typeId); TargetTypeAssignmentResult assignType(@NotEmpty Collection<String> controllerIds, @NotNull Long typeId);
/** /**
* Initiates {@link TargetType} un-assignment to given {@link Target}s. The type * Initiates {@link TargetType} un-assignment to given {@link Target}s. The type of the targets will be set to {@code null}
* of the targets will be set to {@code null}
* *
* @param controllerIds to remove the type from * @param controllerIds to remove the type from
* @return {@link TargetTypeAssignmentResult} with all metadata of the * @return {@link TargetTypeAssignmentResult} with all metadata of the assignment outcome.
* assignment outcome.
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
TargetTypeAssignmentResult unassignType(@NotEmpty Collection<String> controllerIds); TargetTypeAssignmentResult unassignType(@NotEmpty Collection<String> controllerIds);
@@ -631,6 +640,25 @@ public interface TargetManagement {
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
List<Target> get(@NotNull Collection<Long> ids); List<Target> get(@NotNull Collection<Long> ids);
/**
* Verifies that {@link Target} with given controller ID exists in the repository.
*
* @param controllerId of target
* @return {@code true} if target with given ID exists
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
boolean existsByControllerId(@NotEmpty String controllerId);
/**
* Finds a single target tags its id.
*
* @param controllerId of the {@link Target}
* @return the found Tag set
* @throws EntityNotFoundException if target with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Set<TargetTag> getTags(@NotEmpty String controllerId);
/** /**
* Get controller attributes of given {@link Target}. * Get controller attributes of given {@link Target}.
* *
@@ -671,55 +699,37 @@ public interface TargetManagement {
Page<Target> findByControllerAttributesRequested(@NotNull Pageable pageReq); Page<Target> findByControllerAttributesRequested(@NotNull Pageable pageReq);
/** /**
* Verifies that {@link Target} with given controller ID exists in the * Creates a list of target meta-data entries.
* repository.
*
* @param controllerId of target
* @return {@code true} if target with given ID exists
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
boolean existsByControllerId(@NotEmpty String controllerId);
/**
* Verify if a target matches a specific target filter query, does not have a
* specific DS already assigned and is compatible with it.
*
* @param controllerId of the {@link org.eclipse.hawkbit.repository.model.Target} to
* check
* @param distributionSetId of the
* {@link org.eclipse.hawkbit.repository.model.DistributionSet} to
* consider
* @param targetFilterQuery to execute
* @return true if it matches
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_READ_TARGET)
boolean isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(@NotNull String controllerId,
long distributionSetId, @NotNull String targetFilterQuery);
/**
* Finds a single target tags its id.
*
* @param controllerId of the {@link Target}
* @return the found Tag set
* @throws EntityNotFoundException if target with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Set<TargetTag> getTagsByControllerId(@NotEmpty String controllerId);
/**
* Creates a list of target meta data entries.
* *
* @param controllerId {@link Target} controller id the metadata has to be created for * @param controllerId {@link Target} controller id the metadata has to be created for
* @param metadata the meta data entries to create or update * @param metadata the meta-data entries to create or update
* @return the updated or created target metadata entries
* @throws EntityNotFoundException if given target does not exist * @throws EntityNotFoundException if given target does not exist
* @throws EntityAlreadyExistsException in case one of the metadata entry already exists for the specific * @throws EntityAlreadyExistsException in case one of the metadata entry already exists for the specific key
* key * @throws AssignmentQuotaExceededException if the maximum number of {@link MetaData} entries is exceeded for the addressed {@link Target}
* @throws AssignmentQuotaExceededException if the maximum number of {@link MetaData} entries is exceeded for
* the addressed {@link Target}
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
List<TargetMetadata> createMetaData(@NotEmpty String controllerId, @NotEmpty Collection<MetaData> metadata); void createMetadata(@NotEmpty String controllerId, @NotEmpty Map<String, String> metadata);
/**
* Finds a single target meta-data by its id.
*
* @param controllerId of the {@link Target}
* @return the found target metadata
* @throws EntityNotFoundException if target with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
Map<String, String> getMetadata(@NotEmpty String controllerId);
/**
* Updates a target meta-data value if corresponding entry exists.
*
* @param controllerId {@link Target} controller id of the metadata entry to be updated
* @param key meta data-entry key to be updated
* @param value meta data-entry to be new value
* @throws EntityNotFoundException in case the metadata entry does not exist and cannot be updated
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
void updateMetadata(@NotEmpty String controllerId, @NotNull String key, @NotNull String value);
/** /**
* Deletes a target meta data entry. * Deletes a target meta data entry.
@@ -729,64 +739,5 @@ public interface TargetManagement {
* @throws EntityNotFoundException if given target does not exist * @throws EntityNotFoundException if given target does not exist
*/ */
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
void deleteMetaData(@NotEmpty String controllerId, @NotEmpty String key); void deleteMetadata(@NotEmpty String controllerId, @NotEmpty String key);
/**
* Finds all meta data by the given target id.
*
* @param pageable the page request to page the result
* @param controllerId the controller id to retrieve the metadata from
* @return a paged result of all meta data entries for a given target id
* @throws EntityNotFoundException if target with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
Page<TargetMetadata> findMetaDataByControllerId(@NotNull Pageable pageable, @NotEmpty String controllerId);
/**
* Counts all meta data by the given target id.
*
* @param controllerId the controller id to retrieve the meta data from
* @return count of all meta data entries for a given target id
* @throws EntityNotFoundException if target with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
long countMetaDataByControllerId(@NotEmpty String controllerId);
/**
* Finds all metadata by the given target id and query.
*
* @param pageable the page request to page the result
* @param controllerId the controller id to retrieve the metadata from
* @param rsqlParam rsql query string
* @return a paged result of all meta data entries for a given target id
* @throws RSQLParameterUnsupportedFieldException if a field in the RSQL string is used but not provided by the
* given {@code fieldNameProvider}
* @throws RSQLParameterSyntaxException if the RSQL syntax is wrong
* @throws EntityNotFoundException if target with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
Page<TargetMetadata> findMetaDataByControllerIdAndRsql(@NotNull Pageable pageable, @NotEmpty String controllerId,
@NotNull String rsqlParam);
/**
* Finds a single target meta data by its id.
*
* @param controllerId of the {@link Target}
* @param key of the meta data element
* @return the found TargetMetadata
* @throws EntityNotFoundException if target with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
Optional<TargetMetadata> getMetaDataByControllerId(@NotEmpty String controllerId, @NotEmpty String key);
/**
* Updates a target meta data value if corresponding entry exists.
*
* @param controllerId {@link Target} controller id of the metadata entry to be updated
* @param metadata meta data entry to be updated
* @return the updated meta data entry
* @throws EntityNotFoundException in case the metadata entry does not exist and cannot be updated
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
TargetMetadata updateMetadata(@NotEmpty String controllerId, @NotNull MetaData metadata);
} }

View File

@@ -75,10 +75,20 @@ public class EntityNotFoundException extends AbstractServerRtException {
* @param type of the entity that was not found * @param type of the entity that was not found
* @param entityId of the {@link BaseEntity} * @param entityId of the {@link BaseEntity}
*/ */
public EntityNotFoundException(final Class<? extends BaseEntity> type, final Object entityId) { public EntityNotFoundException(final String type, final Object entityId) {
super(type.getSimpleName() + " with given identifier {" + entityId + "} does not exist.", super(type + " with given identifier {" + entityId + "} does not exist.",
THIS_ERROR, THIS_ERROR,
Map.of(TYPE, type.getSimpleName(), ENTITY_ID, entityId)); Map.of(TYPE, type, ENTITY_ID, entityId));
}
/**
* Parameterized constructor for {@link BaseEntity} not found.
*
* @param type of the entity that was not found
* @param entityId of the {@link BaseEntity}
*/
public EntityNotFoundException(final Class<? extends BaseEntity> type, final Object entityId) {
this(type.getSimpleName(), entityId);
} }
/** /**

View File

@@ -47,6 +47,16 @@ public interface Target extends NamedEntity {
*/ */
int CONTROLLER_ATTRIBUTE_VALUE_SIZE = 128; int CONTROLLER_ATTRIBUTE_VALUE_SIZE = 128;
/**
* Maximum length of metadata key.
*/
int CONTROLLER_METADATA_KEY_SIZE = 128;
/**
* Maximum length of metadata value.
*/
int CONTROLLER_METADATA_VALUE_SIZE = 4000;
/** /**
* @return business identifier of the {@link Target} * @return business identifier of the {@link Target}
*/ */

View File

@@ -1,26 +0,0 @@
/**
* Copyright (c) 2018 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.model;
/**
* {@link MetaData} of a {@link Target}.
*/
public interface TargetMetadata extends MetaData {
/**
* @return {@link Target} of this {@link MetaData} entry.
*/
Target getTarget();
@Override
default Long getEntityId() {
return getTarget().getId();
}
}

View File

@@ -27,7 +27,6 @@ import org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleTypeBuilder; import org.eclipse.hawkbit.repository.jpa.builder.JpaSoftwareModuleTypeBuilder;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTagBuilder; import org.eclipse.hawkbit.repository.jpa.builder.JpaTagBuilder;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetMetadata;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata;
import org.eclipse.hawkbit.repository.model.MetaData; import org.eclipse.hawkbit.repository.model.MetaData;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@@ -77,11 +76,6 @@ public class JpaEntityFactory implements EntityFactory {
return new JpaDistributionSetMetadata(key, value == null ? null : value.strip()); return new JpaDistributionSetMetadata(key, value == null ? null : value.strip());
} }
@Override
public MetaData generateTargetMetadata(final String key, final String value) {
return new JpaTargetMetadata(key, value == null ? null : value.strip());
}
@Override @Override
public SoftwareModuleMetadataBuilder softwareModuleMetadata() { public SoftwareModuleMetadataBuilder softwareModuleMetadata() {
return softwareModuleMetadataBuilder; return softwareModuleMetadataBuilder;

View File

@@ -132,7 +132,6 @@ import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleMetadataRepos
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository; import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository;
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleTypeRepository; import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleTypeRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetFilterQueryRepository; import org.eclipse.hawkbit.repository.jpa.repository.TargetFilterQueryRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetMetadataRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository; import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetTagRepository; import org.eclipse.hawkbit.repository.jpa.repository.TargetTagRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetTypeRepository; import org.eclipse.hawkbit.repository.jpa.repository.TargetTypeRepository;
@@ -622,7 +621,7 @@ public class RepositoryApplicationConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
TargetManagement targetManagement(final EntityManager entityManager, final QuotaManagement quotaManagement, TargetManagement targetManagement(final EntityManager entityManager, final QuotaManagement quotaManagement,
final TargetRepository targetRepository, final TargetMetadataRepository targetMetadataRepository, final TargetRepository targetRepository,
final RolloutGroupRepository rolloutGroupRepository, final RolloutGroupRepository rolloutGroupRepository,
final TargetFilterQueryRepository targetFilterQueryRepository, final TargetFilterQueryRepository targetFilterQueryRepository,
final TargetTypeRepository targetTypeRepository, final TargetTagRepository targetTagRepository, final TargetTypeRepository targetTypeRepository, final TargetTagRepository targetTagRepository,
@@ -630,7 +629,7 @@ public class RepositoryApplicationConfiguration {
final VirtualPropertyReplacer virtualPropertyReplacer, final VirtualPropertyReplacer virtualPropertyReplacer,
final JpaProperties properties, final DistributionSetManagement distributionSetManagement) { final JpaProperties properties, final DistributionSetManagement distributionSetManagement) {
return new JpaTargetManagement(entityManager, distributionSetManagement, quotaManagement, targetRepository, return new JpaTargetManagement(entityManager, distributionSetManagement, quotaManagement, targetRepository,
targetTypeRepository, targetMetadataRepository, rolloutGroupRepository, targetFilterQueryRepository, targetTypeRepository, rolloutGroupRepository, targetFilterQueryRepository,
targetTagRepository, eventPublisherHolder, tenantAware, virtualPropertyReplacer, targetTagRepository, eventPublisherHolder, tenantAware, virtualPropertyReplacer,
properties.getDatabase()); properties.getDatabase());
} }

View File

@@ -37,12 +37,7 @@ public class JpaTargetCreate extends AbstractTargetUpdateCreate<TargetCreate> im
@Override @Override
public JpaTarget build() { public JpaTarget build() {
final JpaTarget target; final JpaTarget target = new JpaTarget(controllerId, securityToken);
if (ObjectUtils.isEmpty(securityToken)) {
target = new JpaTarget(controllerId);
} else {
target = new JpaTarget(controllerId, securityToken);
}
if (!ObjectUtils.isEmpty(name)) { if (!ObjectUtils.isEmpty(name)) {
target.setName(name); target.setName(name);

View File

@@ -31,6 +31,7 @@ import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.MapJoin; import jakarta.persistence.criteria.MapJoin;
import jakarta.persistence.criteria.Root; import jakarta.persistence.criteria.Root;
import jakarta.persistence.metamodel.MapAttribute;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import org.apache.commons.collections4.ListUtils; import org.apache.commons.collections4.ListUtils;
@@ -39,12 +40,10 @@ import org.eclipse.hawkbit.repository.FilterParams;
import org.eclipse.hawkbit.repository.QuotaManagement; import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.TargetFields; import org.eclipse.hawkbit.repository.TargetFields;
import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TargetMetadataFields;
import org.eclipse.hawkbit.repository.TimestampCalculator; import org.eclipse.hawkbit.repository.TimestampCalculator;
import org.eclipse.hawkbit.repository.builder.TargetCreate; import org.eclipse.hawkbit.repository.builder.TargetCreate;
import org.eclipse.hawkbit.repository.builder.TargetUpdate; import org.eclipse.hawkbit.repository.builder.TargetUpdate;
import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent; import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException; import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper; import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
@@ -52,18 +51,13 @@ import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetCreate; import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetCreate;
import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetUpdate; import org.eclipse.hawkbit.repository.jpa.builder.JpaTargetUpdate;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants; import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata_;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType; import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.jpa.model.TargetMetadataCompositeKey;
import org.eclipse.hawkbit.repository.jpa.model.helper.AfterTransactionCommitExecutorHolder; import org.eclipse.hawkbit.repository.jpa.model.helper.AfterTransactionCommitExecutorHolder;
import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository; import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetFilterQueryRepository; import org.eclipse.hawkbit.repository.jpa.repository.TargetFilterQueryRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetMetadataRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository; import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetTagRepository; import org.eclipse.hawkbit.repository.jpa.repository.TargetTagRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetTypeRepository; import org.eclipse.hawkbit.repository.jpa.repository.TargetTypeRepository;
@@ -73,11 +67,9 @@ import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper; import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.MetaData;
import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetType; import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetTypeAssignmentResult; import org.eclipse.hawkbit.repository.model.TargetTypeAssignmentResult;
@@ -109,7 +101,6 @@ public class JpaTargetManagement implements TargetManagement {
private final QuotaManagement quotaManagement; private final QuotaManagement quotaManagement;
private final TargetRepository targetRepository; private final TargetRepository targetRepository;
private final TargetTypeRepository targetTypeRepository; private final TargetTypeRepository targetTypeRepository;
private final TargetMetadataRepository targetMetadataRepository;
private final RolloutGroupRepository rolloutGroupRepository; private final RolloutGroupRepository rolloutGroupRepository;
private final TargetFilterQueryRepository targetFilterQueryRepository; private final TargetFilterQueryRepository targetFilterQueryRepository;
private final TargetTagRepository targetTagRepository; private final TargetTagRepository targetTagRepository;
@@ -122,7 +113,6 @@ public class JpaTargetManagement implements TargetManagement {
public JpaTargetManagement(final EntityManager entityManager, public JpaTargetManagement(final EntityManager entityManager,
final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement, final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement,
final TargetRepository targetRepository, final TargetTypeRepository targetTypeRepository, final TargetRepository targetRepository, final TargetTypeRepository targetTypeRepository,
final TargetMetadataRepository targetMetadataRepository,
final RolloutGroupRepository rolloutGroupRepository, final RolloutGroupRepository rolloutGroupRepository,
final TargetFilterQueryRepository targetFilterQueryRepository, final TargetFilterQueryRepository targetFilterQueryRepository,
final TargetTagRepository targetTagRepository, final EventPublisherHolder eventPublisherHolder, final TargetTagRepository targetTagRepository, final EventPublisherHolder eventPublisherHolder,
@@ -133,7 +123,6 @@ public class JpaTargetManagement implements TargetManagement {
this.quotaManagement = quotaManagement; this.quotaManagement = quotaManagement;
this.targetRepository = targetRepository; this.targetRepository = targetRepository;
this.targetTypeRepository = targetTypeRepository; this.targetTypeRepository = targetTypeRepository;
this.targetMetadataRepository = targetMetadataRepository;
this.rolloutGroupRepository = rolloutGroupRepository; this.rolloutGroupRepository = rolloutGroupRepository;
this.targetFilterQueryRepository = targetFilterQueryRepository; this.targetFilterQueryRepository = targetFilterQueryRepository;
this.targetTagRepository = targetTagRepository; this.targetTagRepository = targetTagRepository;
@@ -597,7 +586,7 @@ public class JpaTargetManagement implements TargetManagement {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId); final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
targetRepository.getAccessController().ifPresent(acm -> targetRepository.getAccessController().ifPresent(acm ->
acm.assertOperationAllowed(AccessController.Operation.UPDATE, target)); acm.assertOperationAllowed(AccessController.Operation.UPDATE, target));
final JpaTargetType targetType = getTargetTypeByIdAndThrowIfNotFound(targetTypeId); final JpaTargetType targetType = getTargetTypeByIdAndThrowIfNotFound(targetTypeId);
target.setTargetType(targetType); target.setTargetType(targetType);
@@ -635,57 +624,6 @@ public class JpaTargetManagement implements TargetManagement {
return Collections.unmodifiableList(targetRepository.findAll(TargetSpecifications.hasIdIn(ids))); return Collections.unmodifiableList(targetRepository.findAll(TargetSpecifications.hasIdIn(ids)));
} }
@Override
public Map<String, String> getControllerAttributes(final String controllerId) {
getByControllerIdAndThrowIfNotFound(controllerId);
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
final Root<JpaTarget> targetRoot = query.from(JpaTarget.class);
query.where(cb.equal(targetRoot.get(JpaTarget_.controllerId), controllerId));
final MapJoin<JpaTarget, String, String> attributes = targetRoot.join(JpaTarget_.controllerAttributes);
query.multiselect(attributes.key(), attributes.value());
query.orderBy(cb.asc(attributes.key()));
final List<Object[]> attr = entityManager.createQuery(query).getResultList();
return attr.stream().collect(Collectors.toMap(entry -> (String) entry[0], entry -> (String) entry[1],
(v1, v2) -> v1, LinkedHashMap::new));
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void requestControllerAttributes(final String controllerId) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
targetRepository.getAccessController()
.ifPresent(acm -> acm.assertOperationAllowed(AccessController.Operation.UPDATE, target));
target.setRequestControllerAttributes(true);
AfterTransactionCommitExecutorHolder.getInstance().getAfterCommit().afterCommit(() ->
eventPublisherHolder.getEventPublisher()
.publishEvent(new TargetAttributesRequestedEvent(
tenantAware.getCurrentTenant(), target.getId(), target.getControllerId(),
target.getAddress() != null ? target.getAddress().toString() : null,
JpaTarget.class, eventPublisherHolder.getApplicationId())));
}
@Override
public boolean isControllerAttributesRequested(final String controllerId) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
return target.isRequestControllerAttributes();
}
@Override
public Page<Target> findByControllerAttributesRequested(final Pageable pageReq) {
return JpaManagementHelper.findAllWithCountBySpec(targetRepository, List.of(TargetSpecifications.hasRequestControllerAttributesTrue()),
pageReq
);
}
@Override @Override
public boolean existsByControllerId(final String controllerId) { public boolean existsByControllerId(final String controllerId) {
return targetRepository.exists(TargetSpecifications.hasControllerId(controllerId)); return targetRepository.exists(TargetSpecifications.hasControllerId(controllerId));
@@ -710,115 +648,120 @@ public class JpaTargetManagement implements TargetManagement {
} }
@Override @Override
public Set<TargetTag> getTagsByControllerId(@NotEmpty String controllerId) { public Set<TargetTag> getTags(@NotEmpty String controllerId) {
// the method has PreAuthorized by itself // the method has PreAuthorized by itself
return ((JpaTarget)getWithTags(controllerId)).getTags(); return ((JpaTarget) getWithTags(controllerId)).getTags();
}
@Override
public Map<String, String> getControllerAttributes(final String controllerId) {
return getMap(controllerId, JpaTarget_.controllerAttributes);
} }
@Override @Override
@Transactional @Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY)) backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public List<TargetMetadata> createMetaData(final String controllerId, final Collection<MetaData> md) { public void requestControllerAttributes(final String controllerId) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId); final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
md.forEach(meta -> checkAndThrowIfTargetMetadataAlreadyExists(
new TargetMetadataCompositeKey(target.getId(), meta.getKey())));
assertMetaDataQuota(target.getId(), md.size());
final JpaTarget updatedTarget = JpaManagementHelper.touch(entityManager, targetRepository, target);
final List<TargetMetadata> createdMetadata = md.stream()
.map(meta -> targetMetadataRepository.save(new JpaTargetMetadata(meta.getKey(), meta.getValue(), updatedTarget)))
.collect(Collectors.toUnmodifiableList());
// TargetUpdatedEvent is not sent within the touch() method due to the
// "lastModifiedAt" field being ignored in JpaTarget
eventPublisherHolder.getEventPublisher().publishEvent(new TargetUpdatedEvent(updatedTarget, eventPublisherHolder.getApplicationId()));
return createdMetadata;
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void deleteMetaData(final String controllerId, final String key) {
final JpaTargetMetadata metadata = (JpaTargetMetadata) getMetaDataByControllerId(controllerId, key)
.orElseThrow(() -> new EntityNotFoundException(TargetMetadata.class, controllerId, key));
final JpaTarget target = JpaManagementHelper.touch(
entityManager, targetRepository, getByControllerIdAndThrowIfNotFound(controllerId));
targetRepository.getAccessController() targetRepository.getAccessController()
.ifPresent(acm -> acm.assertOperationAllowed(AccessController.Operation.UPDATE, target)); .ifPresent(acm -> acm.assertOperationAllowed(AccessController.Operation.UPDATE, target));
target.setRequestControllerAttributes(true);
targetMetadataRepository.deleteById(metadata.getId()); AfterTransactionCommitExecutorHolder.getInstance().getAfterCommit().afterCommit(() ->
// target update event is set to ignore "lastModifiedAt" field, so it is eventPublisherHolder.getEventPublisher()
// not send automatically within the touch() method .publishEvent(new TargetAttributesRequestedEvent(
eventPublisherHolder.getEventPublisher() tenantAware.getCurrentTenant(), target.getId(), target.getControllerId(),
.publishEvent(new TargetUpdatedEvent(target, eventPublisherHolder.getApplicationId())); target.getAddress() != null ? target.getAddress().toString() : null,
JpaTarget.class, eventPublisherHolder.getApplicationId())));
} }
@Override @Override
public Page<TargetMetadata> findMetaDataByControllerId(final Pageable pageable, final String controllerId) { public boolean isControllerAttributesRequested(final String controllerId) {
final Long id = getByControllerIdAndThrowIfNotFound(controllerId).getId(); return getByControllerIdAndThrowIfNotFound(controllerId).isRequestControllerAttributes();
return JpaManagementHelper.findAllWithCountBySpec(targetMetadataRepository, Collections.singletonList(metadataByTargetIdSpec(id)),
pageable
);
} }
@Override @Override
public long countMetaDataByControllerId(@NotEmpty final String controllerId) { public Page<Target> findByControllerAttributesRequested(final Pageable pageReq) {
final Long targetId = getByControllerIdAndThrowIfNotFound(controllerId).getId(); return JpaManagementHelper.findAllWithCountBySpec(
targetRepository, List.of(TargetSpecifications.hasRequestControllerAttributesTrue()), pageReq);
return JpaManagementHelper.countBySpec(targetMetadataRepository,
Collections.singletonList(metadataByTargetIdSpec(targetId)));
}
@Override
public Page<TargetMetadata> findMetaDataByControllerIdAndRsql(final Pageable pageable, final String controllerId,
final String rsqlParam) {
final Long targetId = getByControllerIdAndThrowIfNotFound(controllerId).getId();
final List<Specification<JpaTargetMetadata>> specList = Arrays.asList(RSQLUtility
.buildRsqlSpecification(rsqlParam, TargetMetadataFields.class, virtualPropertyReplacer, database),
metadataByTargetIdSpec(targetId));
return JpaManagementHelper.findAllWithCountBySpec(targetMetadataRepository, specList, pageable);
}
@Override
public Optional<TargetMetadata> getMetaDataByControllerId(final String controllerId, final String key) {
final Long targetId = getByControllerIdAndThrowIfNotFound(controllerId).getId();
return targetMetadataRepository.findById(new TargetMetadataCompositeKey(targetId, key)).map(t -> t);
} }
@Override @Override
@Transactional @Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, @Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY)) backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public TargetMetadata updateMetadata(final String controllerId, final MetaData md) { public void createMetadata(final String controllerId, final Map<String, String> md) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
// check if exists otherwise throw entity not found exception // get the modifiable metadata map
final JpaTargetMetadata updatedMetadata = (JpaTargetMetadata) getMetaDataByControllerId(controllerId, final Map<String, String> metadata = target.getMetadata();
md.getKey()) md.keySet().forEach(key -> {
.orElseThrow(() -> new EntityNotFoundException(TargetMetadata.class, controllerId, md.getKey())); if (metadata.containsKey(key)) {
updatedMetadata.setValue(md.getValue()); throw new EntityAlreadyExistsException("Metadata entry with key '" + key + "' already exists");
// touch it to update the lock revision because we are modifying the }
// target indirectly });
final JpaTarget target = JpaManagementHelper.touch(entityManager, targetRepository, metadata.putAll(md);
getByControllerIdAndThrowIfNotFound(controllerId));
final JpaTargetMetadata metadata = targetMetadataRepository.save(updatedMetadata); assertMetadataQuota(target.getId(), metadata.size());
// target update event is set to ignore "lastModifiedAt" field, so it is targetRepository.save(target);
// not send automatically within the touch() method }
eventPublisherHolder.getEventPublisher()
.publishEvent(new TargetUpdatedEvent(target, eventPublisherHolder.getApplicationId())); @Override
return metadata; public Map<String, String> getMetadata(final String controllerId) {
return getMap(controllerId, JpaTarget_.metadata);
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void updateMetadata(final String controllerId, final String key, final String value) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
// get the modifiable metadata map
final Map<String, String> metadata = target.getMetadata();
if (!metadata.containsKey(key)) {
throw new EntityNotFoundException("Target metadata", controllerId + ":" + key);
}
metadata.put(key, value);
targetRepository.save(target);
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void deleteMetadata(final String controllerId, final String key) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
// get the modifiable metadata map
final Map<String, String> metadata = target.getMetadata();
if (metadata.remove(key) == null) {
throw new EntityNotFoundException("Target metadata", controllerId + ":" + key);
}
targetRepository.save(target);
}
private Map<String, String> getMap(final String controllerId, final MapAttribute<JpaTarget, String, String> mapAttribute) {
getByControllerIdAndThrowIfNotFound(controllerId);
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
final Root<JpaTarget> targetRoot = query.from(JpaTarget.class);
query.where(cb.equal(targetRoot.get(JpaTarget_.controllerId), controllerId));
final MapJoin<JpaTarget, String, String> mapJoin = targetRoot.join(mapAttribute);
query.multiselect(mapJoin.key(), mapJoin.value());
query.orderBy(cb.asc(mapJoin.key()));
return entityManager
.createQuery(query)
.getResultList()
.stream()
.collect(Collectors.toMap(entry -> (String) entry[0], entry -> (String) entry[1], (v1, v2) -> v1, LinkedHashMap::new));
} }
private static boolean hasTagsFilterActive(final FilterParams filterParams) { private static boolean hasTagsFilterActive(final FilterParams filterParams) {
@@ -851,20 +794,9 @@ public class JpaTargetManagement implements TargetManagement {
return targetTypeRepository.findById(id).orElseThrow(() -> new EntityNotFoundException(TargetType.class, id)); return targetTypeRepository.findById(id).orElseThrow(() -> new EntityNotFoundException(TargetType.class, id));
} }
private void checkAndThrowIfTargetMetadataAlreadyExists(final TargetMetadataCompositeKey metadataId) { private void assertMetadataQuota(final Long targetId, final int requested) {
if (targetMetadataRepository.existsById(metadataId)) { final int limit = quotaManagement.getMaxMetaDataEntriesPerTarget();
throw new EntityAlreadyExistsException( QuotaHelper.assertAssignmentQuota(targetId, requested, limit, "Metadata", Target.class.getSimpleName(), null);
"Metadata entry with key '" + metadataId.getKey() + "' already exists");
}
}
private void assertMetaDataQuota(final Long targetId, final int requested) {
QuotaHelper.assertAssignmentQuota(targetId, requested, quotaManagement.getMaxMetaDataEntriesPerTarget(),
TargetMetadata.class, Target.class, targetMetadataRepository::countByTargetId);
}
private Specification<JpaTargetMetadata> metadataByTargetIdSpec(final Long targetId) {
return (root, query, cb) -> cb.equal(root.get(JpaTargetMetadata_.target).get(AbstractJpaBaseEntity_.id), targetId);
} }
private List<Specification<JpaTarget>> buildSpecificationList(final FilterParams filterParams) { private List<Specification<JpaTarget>> buildSpecificationList(final FilterParams filterParams) {

View File

@@ -59,7 +59,6 @@ import org.eclipse.hawkbit.repository.jpa.utils.MapAttributeConverter;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.PollStatus; import org.eclipse.hawkbit.repository.model.PollStatus;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetType; import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
@@ -181,7 +180,7 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
}) })
private Set<TargetTag> tags; private Set<TargetTag> tags;
// no cascade option on an ElementCollection, the target objects are always persisted, merged, removed with their parent. // no cascade option on an ElementCollection, the target objects are always persisted, merged, removed with their parent
@Getter @Getter
@ElementCollection @ElementCollection
@CollectionTable( @CollectionTable(
@@ -192,8 +191,16 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
@Column(name = "attribute_value", length = Target.CONTROLLER_ATTRIBUTE_VALUE_SIZE) @Column(name = "attribute_value", length = Target.CONTROLLER_ATTRIBUTE_VALUE_SIZE)
private Map<String, String> controllerAttributes; private Map<String, String> controllerAttributes;
@OneToMany(mappedBy = "target", fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE }, targetEntity = JpaTargetMetadata.class) // no cascade option on an ElementCollection, the target objects are always persisted, merged, removed with their parent
private List<TargetMetadata> metadata; @Getter
@ElementCollection
@CollectionTable(
name = "sp_target_metadata",
joinColumns = { @JoinColumn(name = "target", nullable = false) },
foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_target_metadata_target"))
@MapKeyColumn(name = "meta_key", length = Target.CONTROLLER_METADATA_KEY_SIZE)
@Column(name = "meta_value", length = Target.CONTROLLER_METADATA_VALUE_SIZE)
private Map<String, String> metadata;
@OneToMany(mappedBy = "target", fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE }, targetEntity = JpaAction.class) @OneToMany(mappedBy = "target", fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE }, targetEntity = JpaAction.class)
private List<JpaAction> actions; private List<JpaAction> actions;
@@ -201,15 +208,11 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
@OneToMany(mappedBy = "target", fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.REMOVE }) @OneToMany(mappedBy = "target", fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.REMOVE })
private List<RolloutTargetGroup> rolloutTargetGroup; private List<RolloutTargetGroup> rolloutTargetGroup;
public JpaTarget(final String controllerId) {
this(controllerId, SecurityTokenGeneratorHolder.getInstance().generateToken());
}
public JpaTarget(final String controllerId, final String securityToken) { public JpaTarget(final String controllerId, final String securityToken) {
this.controllerId = controllerId; this.controllerId = controllerId;
// truncate controller ID to max name length (if needed) // truncate controller ID to max name length (if too big)
setName(controllerId != null && controllerId.length() > NAME_MAX_SIZE ? controllerId.substring(0, NAME_MAX_SIZE) : controllerId); setName(controllerId != null && controllerId.length() > NAME_MAX_SIZE ? controllerId.substring(0, NAME_MAX_SIZE) : controllerId);
this.securityToken = securityToken; this.securityToken = ObjectUtils.isEmpty(securityToken) ? SecurityTokenGeneratorHolder.getInstance().generateToken() : securityToken;
} }
@Override @Override
@@ -287,10 +290,6 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
return tags == null ? Collections.emptySet() : Collections.unmodifiableSet(tags); return tags == null ? Collections.emptySet() : Collections.unmodifiableSet(tags);
} }
public List<TargetMetadata> getMetadata() {
return metadata == null ? Collections.emptyList() : Collections.unmodifiableList(metadata);
}
public void addAction(final Action action) { public void addAction(final Action action) {
if (actions == null) { if (actions == null) {
actions = new ArrayList<>(); actions = new ArrayList<>();

View File

@@ -1,100 +0,0 @@
/**
* Copyright (c) 2018 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serial;
import jakarta.persistence.ConstraintMode;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.Id;
import jakarta.persistence.IdClass;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
/**
* Meta data for {@link Target}.
*/
@NoArgsConstructor // Default constructor for JPA
@Setter
@Getter
@IdClass(TargetMetadataCompositeKey.class)
@Entity
@Table(name = "sp_target_metadata")
public class JpaTargetMetadata extends AbstractJpaMetaData implements TargetMetadata {
@Serial
private static final long serialVersionUID = 1L;
@Id
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(
name = "target", nullable = false, updatable = false,
foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_target_metadata_target"))
private JpaTarget target;
/**
* Creates a single metadata entry with the given key and value.
*
* @param key of the meta-data entry
* @param value of the meta-data entry
*/
public JpaTargetMetadata(final String key, final String value) {
super(key, value);
}
/**
* Creates a single metadata entry with the given key and value for the
* given {@link Target}.
*
* @param key of the meta-data entry
* @param value of the meta-data entry
* @param target the meta-data entry is associated with
*/
public JpaTargetMetadata(final String key, final String value, final Target target) {
super(key, value);
this.target = (JpaTarget) target;
}
public TargetMetadataCompositeKey getId() {
return new TargetMetadataCompositeKey(target.getId(), getKey());
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((target == null) ? 0 : target.hashCode());
return result;
}
@Override
// exception squid:S2259 - obj is checked for null in super
@SuppressWarnings("squid:S2259")
public boolean equals(final Object obj) {
if (!super.equals(obj)) {
return false;
}
final JpaTargetMetadata other = (JpaTargetMetadata) obj;
if (target == null) {
return other.target == null;
} else {
return target.equals(other.target);
}
}
}

View File

@@ -1,39 +0,0 @@
/**
* Copyright (c) 2018 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serial;
import java.io.Serializable;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* The Target Metadata composite key which contains the meta-data key and the ID of the Target itself.
*/
@NoArgsConstructor // Default constructor for JPA
@Data
public final class TargetMetadataCompositeKey implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String key;
private Long target;
/**
* @param target the target Id for this meta-data
* @param key the key of the meta-data
*/
public TargetMetadataCompositeKey(final Long target, final String key) {
this.target = target;
this.key = key;
}
}

View File

@@ -1,39 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.repository;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata;
import org.eclipse.hawkbit.repository.jpa.model.TargetMetadataCompositeKey;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
/**
* {@link TargetMetadata} repository.
*/
@Transactional(readOnly = true)
public interface TargetMetadataRepository
extends PagingAndSortingRepository<JpaTargetMetadata, TargetMetadataCompositeKey>,
CrudRepository<JpaTargetMetadata, TargetMetadataCompositeKey>,
JpaSpecificationExecutor<JpaTargetMetadata> {
/**
* Counts the meta data entries that match the given target ID.
* <p/>
* No access control applied
*
* @param id of the target.
* @return The number of matching meta data entries.
*/
long countByTargetId(@Param("id") Long id);
}

View File

@@ -13,35 +13,31 @@ import java.util.function.ToLongFunction;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException; import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
/** /**
* Helper class to check quotas. * Helper class to check quotas.
*/ */
@NoArgsConstructor
@Slf4j @Slf4j
public final class QuotaHelper { public final class QuotaHelper {
private static final String MAX_ASSIGNMENT_QUOTA_EXCEEDED = "Quota exceeded: Cannot assign %s entities at once. The maximum is %s."; private static final String MAX_ASSIGNMENT_QUOTA_EXCEEDED = "Quota exceeded: Cannot assign %s entities at once. The maximum is %s.";
private QuotaHelper() {
// no need to instantiate this class
}
/** /**
* Asserts the specified assignment quota. * Asserts the specified assignment quota.
* *
* @param requested The number of entities that shall be assigned to the parent * @param requested The number of entities that shall be assigned to the parent entity.
* entity. * @param limit The maximum number of entities that may be assigned to the parent entity.
* @param limit The maximum number of entities that may be assigned to the
* parent entity.
* @param type The type of the entities that shall be assigned. * @param type The type of the entities that shall be assigned.
* @param parentType The type of the parent entity. * @param parentType The type of the parent entity.
* @throws AssignmentQuotaExceededException if the assignment operation would cause the quota to be * @throws AssignmentQuotaExceededException if the assignment operation would cause the quota to be exceeded
* exceeded
*/ */
public static void assertAssignmentQuota(final long requested, final long limit, @NotNull final Class<?> type, public static void assertAssignmentQuota(
@NotNull final Class<?> parentType) { final long requested, final long limit,
@NotNull final Class<?> type, @NotNull final Class<?> parentType) {
assertAssignmentQuota(null, requested, limit, type.getSimpleName(), parentType.getSimpleName(), null); assertAssignmentQuota(null, requested, limit, type.getSimpleName(), parentType.getSimpleName(), null);
} }
@@ -49,19 +45,18 @@ public final class QuotaHelper {
* Asserts the specified assignment quota. * Asserts the specified assignment quota.
* *
* @param parentId The ID of the parent entity. * @param parentId The ID of the parent entity.
* @param requested The number of entities that shall be assigned to the parent * @param requested The number of entities that shall be assigned to the parent entity.
* entity. * @param limit The maximum number of entities that may be assigned to the parent entity.
* @param limit The maximum number of entities that may be assigned to the
* parent entity.
* @param type The type of the entities that shall be assigned. * @param type The type of the entities that shall be assigned.
* @param parentType The type of the parent entity. * @param parentType The type of the parent entity.
* @param countFct Function to count the entities that are currently assigned to * @param countFct Function to count the entities that are currently assigned to the parent entity.
* the parent entity. * @throws AssignmentQuotaExceededException if the assignment operation would cause the quota to be exceeded
* @throws AssignmentQuotaExceededException if the assignment operation would cause the quota to be
* exceeded
*/ */
public static <T> void assertAssignmentQuota(final T parentId, final long requested, final long limit, public static <T> void assertAssignmentQuota(
@NotNull final Class<?> type, @NotNull final Class<?> parentType, final ToLongFunction<T> countFct) { final T parentId,
final long requested, final long limit,
@NotNull final Class<?> type, @NotNull final Class<?> parentType,
final ToLongFunction<T> countFct) {
assertAssignmentQuota(parentId, requested, limit, type.getSimpleName(), parentType.getSimpleName(), countFct); assertAssignmentQuota(parentId, requested, limit, type.getSimpleName(), parentType.getSimpleName(), countFct);
} }
@@ -69,20 +64,18 @@ public final class QuotaHelper {
* Asserts the specified assignment quota. * Asserts the specified assignment quota.
* *
* @param parentId The ID of the parent entity. * @param parentId The ID of the parent entity.
* @param requested The number of entities that shall be assigned to the parent * @param requested The number of entities that shall be assigned to the parent entity.
* entity. * @param limit The maximum number of entities that may be assigned to the parent entity.
* @param limit The maximum number of entities that may be assigned to the
* parent entity.
* @param type The type of the entities that shall be assigned. * @param type The type of the entities that shall be assigned.
* @param parentType The type of the parent entity. * @param parentType The type of the parent entity.
* @param countFct Function to count the entities that are currently assigned to * @param countFct Function to count the entities that are currently assigned to the parent entity.
* the parent entity. * @throws AssignmentQuotaExceededException if the assignment operation would cause the quota to be exceeded
* @throws AssignmentQuotaExceededException if the assignment operation would cause the quota to be
* exceeded
*/ */
public static <T> void assertAssignmentQuota(final T parentId, final long requested, final long limit, public static <T> void assertAssignmentQuota(
@NotNull final String type, @NotNull final String parentType, final ToLongFunction<T> countFct) { final T parentId,
final long requested, final long limit,
@NotNull final String type, @NotNull final String parentType,
final ToLongFunction<T> countFct) {
// check if the quota is unlimited // check if the quota is unlimited
if (limit <= 0) { if (limit <= 0) {
log.debug("Quota 'Max {} entities per {}' is unlimited.", type, parentType); log.debug("Quota 'Max {} entities per {}' is unlimited.", type, parentType);
@@ -91,8 +84,9 @@ public final class QuotaHelper {
if (requested > limit) { if (requested > limit) {
final String parentIdStr = parentId != null ? String.valueOf(parentId) : "<new>"; final String parentIdStr = parentId != null ? String.valueOf(parentId) : "<new>";
log.warn("Cannot assign {} {} entities to {} '{}' because of the configured quota limit {}.", requested, log.warn(
type, parentType, parentIdStr, limit); "Cannot assign {} {} entities to {} '{}' because of the configured quota limit {}.",
requested, type, parentType, parentIdStr, limit);
throw new AssignmentQuotaExceededException(type, parentType, parentId, requested, limit); throw new AssignmentQuotaExceededException(type, parentType, parentId, requested, limit);
} }
@@ -100,7 +94,8 @@ public final class QuotaHelper {
final long currentCount = countFct.applyAsLong(parentId); final long currentCount = countFct.applyAsLong(parentId);
if (currentCount + requested > limit) { if (currentCount + requested > limit) {
log.warn( log.warn(
"Cannot assign {} {} entities to {} '{}' because of the configured quota limit {}. Currently, there are {} {} entities assigned.", "Cannot assign {} {} entities to {} '{}' because of the configured quota limit {}. " +
"Currently, there are {} {} entities assigned.",
requested, type, parentType, parentId, limit, currentCount, type); requested, type, parentType, parentId, limit, currentCount, type);
throw new AssignmentQuotaExceededException(type, parentType, parentId, requested, limit); throw new AssignmentQuotaExceededException(type, parentType, parentId, requested, limit);
} }
@@ -108,8 +103,7 @@ public final class QuotaHelper {
} }
/** /**
* Assert that the number of assignments in a request does not exceed the * Assert that the number of assignments in a request does not exceed the limit.
* limit.
* *
* @param requested the number of assignments that are to be made * @param requested the number of assignments that are to be made
* @param limit the maximum number of assignments per request * @param limit the maximum number of assignments per request
@@ -121,4 +115,4 @@ public final class QuotaHelper {
throw new AssignmentQuotaExceededException(message); throw new AssignmentQuotaExceededException(message);
} }
} }
} }

View File

@@ -303,7 +303,7 @@ public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest
} }
protected Set<TargetTag> getTargetTags(final String controllerId) { protected Set<TargetTag> getTargetTags(final String controllerId) {
return targetManagement.getTagsByControllerId(controllerId); return targetManagement.getTags(controllerId);
} }
protected JpaRolloutGroup refresh(final RolloutGroup group) { protected JpaRolloutGroup refresh(final RolloutGroup group) {

View File

@@ -68,7 +68,7 @@ class DistributionSetManagementSecurityTest
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void createMetaDataPermissionsCheck() { void createMetaDataPermissionsCheck() {
assertPermissions( assertPermissions(
() -> distributionSetManagement.putMetaData(1L, List.of(entityFactory.generateTargetMetadata("key", "value"))), () -> distributionSetManagement.putMetaData(1L, List.of(entityFactory.generateDsMetadata("key", "value"))),
List.of(SpPermission.UPDATE_REPOSITORY)); List.of(SpPermission.UPDATE_REPOSITORY));
} }

View File

@@ -10,6 +10,7 @@
package org.eclipse.hawkbit.repository.jpa.management; package org.eclipse.hawkbit.repository.jpa.management;
import java.util.List; import java.util.List;
import java.util.Map;
import io.qameta.allure.Description; import io.qameta.allure.Description;
import io.qameta.allure.Feature; import io.qameta.allure.Feature;
@@ -339,6 +340,26 @@ class TargetManagementSecurityTest extends AbstractJpaIntegrationTest {
assertPermissions(() -> targetManagement.get(List.of(1L)), List.of(SpPermission.READ_TARGET)); assertPermissions(() -> targetManagement.get(List.of(1L)), List.of(SpPermission.READ_TARGET));
} }
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void existsByControllerIdPermissionsCheck() {
assertPermissions(() -> targetManagement.existsByControllerId("controllerId"), List.of(SpPermission.READ_TARGET));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatablePermissionsCheck() {
assertPermissions(
() -> targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable("controllerId", 1L, "controllerId==id"),
List.of(SpPermission.READ_TARGET, SpPermission.READ_REPOSITORY));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void getTagsPermissionsCheck() {
assertPermissions(() -> targetManagement.getTags("controllerId"), List.of(SpPermission.READ_TARGET));
}
@Test @Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void getControllerAttributesPermissionsCheck() { void getControllerAttributesPermissionsCheck() {
@@ -368,72 +389,38 @@ class TargetManagementSecurityTest extends AbstractJpaIntegrationTest {
@Test @Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void existsByControllerIdPermissionsCheck() { void createMetadataPermissionsCheck() {
assertPermissions(() -> targetManagement.existsByControllerId("controllerId"), List.of(SpPermission.READ_TARGET));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatablePermissionsCheck() {
assertPermissions( assertPermissions(
() -> targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable("controllerId", 1L, "controllerId==id"), () -> {
List.of(SpPermission.READ_TARGET, SpPermission.READ_REPOSITORY)); targetManagement.createMetadata("controllerId", Map.of("key", "value"));
} return null;
},
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void getTagsByControllerIdPermissionsCheck() {
assertPermissions(() -> targetManagement.getTagsByControllerId("controllerId"), List.of(SpPermission.READ_TARGET));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void createMetaDataPermissionsCheck() {
assertPermissions(
() -> targetManagement.createMetaData("controllerId", List.of(entityFactory.generateTargetMetadata("key", "value"))),
List.of(SpPermission.UPDATE_REPOSITORY)); List.of(SpPermission.UPDATE_REPOSITORY));
} }
@Test @Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void deleteMetaDataPermissionsCheck() { void getMetadataPermissionsCheck() {
assertPermissions(() -> { assertPermissions(() -> targetManagement.getMetadata("controllerId"), List.of(SpPermission.READ_REPOSITORY));
targetManagement.deleteMetaData("controllerId", "key");
return null;
}, List.of(SpPermission.UPDATE_REPOSITORY));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void countMetaDataByControllerIdPermissionsCheck() {
assertPermissions(() -> targetManagement.countMetaDataByControllerId("controllerId"), List.of(SpPermission.READ_REPOSITORY));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void findMetaDataByControllerIdAndRsqlPermissionsCheck() {
assertPermissions(() -> targetManagement.findMetaDataByControllerIdAndRsql(PAGE, "controllerId", "controllerId==id"),
List.of(SpPermission.READ_REPOSITORY));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void getMetaDataByControllerIdPermissionsCheck() {
assertPermissions(() -> targetManagement.getMetaDataByControllerId("controllerId", "key"), List.of(SpPermission.READ_REPOSITORY));
}
@Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void findMetaDataByControllerIdPermissionsCheck() {
assertPermissions(() -> targetManagement.findMetaDataByControllerId(PAGE, "controllerId"), List.of(SpPermission.READ_REPOSITORY));
} }
@Test @Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.") @Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
@WithUser(principal = "user", authorities = { SpPermission.UPDATE_REPOSITORY }) @WithUser(principal = "user", authorities = { SpPermission.UPDATE_REPOSITORY })
void updateMetadataPermissionsCheck() { void updateMetadataPermissionsCheck() {
assertPermissions(() -> targetManagement.updateMetadata("controllerId", entityFactory.generateTargetMetadata("key", "value")), assertPermissions(() -> {
targetManagement.updateMetadata("controllerId", "key", "value");
return null;
},
List.of(SpPermission.UPDATE_REPOSITORY)); List.of(SpPermission.UPDATE_REPOSITORY));
} }
} @Test
@Description("Tests ManagementAPI PreAuthorized method with correct and insufficient permissions.")
void deleteMetadataPermissionsCheck() {
assertPermissions(() -> {
targetManagement.deleteMetadata("controllerId", "key");
return null;
}, List.of(SpPermission.UPDATE_REPOSITORY));
}
}

View File

@@ -60,17 +60,14 @@ import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldExc
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction; import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget; import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult; import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.MetaData;
import org.eclipse.hawkbit.repository.model.NamedEntity; import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Tag; import org.eclipse.hawkbit.repository.model.Tag;
import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetType; import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetTypeAssignmentResult; import org.eclipse.hawkbit.repository.model.TargetTypeAssignmentResult;
@@ -81,7 +78,6 @@ import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch; import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch;
import org.eclipse.hawkbit.repository.test.util.WithUser; import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice; import org.springframework.data.domain.Slice;
@@ -99,7 +95,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
final Target target = testdataFactory.createTarget(); final Target target = testdataFactory.createTarget();
assertThat(targetManagement.getByControllerID(NOT_EXIST_ID)).isNotPresent(); assertThat(targetManagement.getByControllerID(NOT_EXIST_ID)).isNotPresent();
assertThat(targetManagement.get(NOT_EXIST_IDL)).isNotPresent(); assertThat(targetManagement.get(NOT_EXIST_IDL)).isNotPresent();
assertThat(targetManagement.getMetaDataByControllerId(target.getControllerId(), NOT_EXIST_ID)).isNotPresent(); assertThat(targetManagement.getMetadata(target.getControllerId()).get(NOT_EXIST_ID)).isNull();
} }
@Test @Test
@@ -113,20 +109,15 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
final Target target = testdataFactory.createTarget(); final Target target = testdataFactory.createTarget();
verifyThrownExceptionBy( verifyThrownExceptionBy(
() -> targetManagement.assignTag(Collections.singletonList(target.getControllerId()), NOT_EXIST_IDL), () -> targetManagement.assignTag(Collections.singletonList(target.getControllerId()), NOT_EXIST_IDL), "TargetTag");
"TargetTag"); verifyThrownExceptionBy(() -> targetManagement.assignTag(Collections.singletonList(NOT_EXIST_ID), tag.getId()), "Target");
verifyThrownExceptionBy(() -> targetManagement.assignTag(Collections.singletonList(NOT_EXIST_ID), tag.getId()),
"Target");
verifyThrownExceptionBy(() -> targetManagement.findByTag(PAGE, NOT_EXIST_IDL), "TargetTag"); verifyThrownExceptionBy(() -> targetManagement.findByTag(PAGE, NOT_EXIST_IDL), "TargetTag");
verifyThrownExceptionBy(() -> targetManagement.findByRsqlAndTag(PAGE, "name==*", NOT_EXIST_IDL), "TargetTag"); verifyThrownExceptionBy(() -> targetManagement.findByRsqlAndTag(PAGE, "name==*", NOT_EXIST_IDL), "TargetTag");
verifyThrownExceptionBy(() -> targetManagement.countByAssignedDistributionSet(NOT_EXIST_IDL), verifyThrownExceptionBy(() -> targetManagement.countByAssignedDistributionSet(NOT_EXIST_IDL), "DistributionSet");
"DistributionSet"); verifyThrownExceptionBy(() -> targetManagement.countByInstalledDistributionSet(NOT_EXIST_IDL), "DistributionSet");
verifyThrownExceptionBy(() -> targetManagement.countByInstalledDistributionSet(NOT_EXIST_IDL), verifyThrownExceptionBy(() -> targetManagement.existsByInstalledOrAssignedDistributionSet(NOT_EXIST_IDL), "DistributionSet");
"DistributionSet");
verifyThrownExceptionBy(() -> targetManagement.existsByInstalledOrAssignedDistributionSet(NOT_EXIST_IDL),
"DistributionSet");
verifyThrownExceptionBy(() -> targetManagement.countByTargetFilterQuery(NOT_EXIST_IDL), "TargetFilterQuery"); verifyThrownExceptionBy(() -> targetManagement.countByTargetFilterQuery(NOT_EXIST_IDL), "TargetFilterQuery");
verifyThrownExceptionBy(() -> targetManagement.countByRsqlAndNonDSAndCompatibleAndUpdatable(NOT_EXIST_IDL, "name==*"), verifyThrownExceptionBy(() -> targetManagement.countByRsqlAndNonDSAndCompatibleAndUpdatable(NOT_EXIST_IDL, "name==*"),
@@ -138,45 +129,29 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
verifyThrownExceptionBy( verifyThrownExceptionBy(
() -> targetManagement.findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(PAGE, NOT_EXIST_IDL, "name==*"), () -> targetManagement.findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(PAGE, NOT_EXIST_IDL, "name==*"),
"DistributionSet"); "DistributionSet");
verifyThrownExceptionBy(() -> targetManagement.findByInRolloutGroupWithoutAction(PAGE, NOT_EXIST_IDL), verifyThrownExceptionBy(() -> targetManagement.findByInRolloutGroupWithoutAction(PAGE, NOT_EXIST_IDL), "RolloutGroup");
"RolloutGroup"); verifyThrownExceptionBy(() -> targetManagement.findByAssignedDistributionSet(PAGE, NOT_EXIST_IDL), "DistributionSet");
verifyThrownExceptionBy(() -> targetManagement.findByAssignedDistributionSet(PAGE, NOT_EXIST_IDL),
"DistributionSet");
verifyThrownExceptionBy( verifyThrownExceptionBy(
() -> targetManagement.findByAssignedDistributionSetAndRsql(PAGE, NOT_EXIST_IDL, "name==*"), () -> targetManagement.findByAssignedDistributionSetAndRsql(PAGE, NOT_EXIST_IDL, "name==*"), "DistributionSet");
"DistributionSet");
verifyThrownExceptionBy(() -> targetManagement.findByInstalledDistributionSet(PAGE, NOT_EXIST_IDL), verifyThrownExceptionBy(() -> targetManagement.findByInstalledDistributionSet(PAGE, NOT_EXIST_IDL), "DistributionSet");
"DistributionSet");
verifyThrownExceptionBy( verifyThrownExceptionBy(
() -> targetManagement.findByInstalledDistributionSetAndRsql(PAGE, NOT_EXIST_IDL, "name==*"), () -> targetManagement.findByInstalledDistributionSetAndRsql(PAGE, NOT_EXIST_IDL, "name==*"), "DistributionSet");
"DistributionSet");
verifyThrownExceptionBy(() -> targetManagement verifyThrownExceptionBy(() -> targetManagement
.assignTag(Collections.singletonList(target.getControllerId()), Long.parseLong(NOT_EXIST_ID)), "TargetTag"); .assignTag(Collections.singletonList(target.getControllerId()), Long.parseLong(NOT_EXIST_ID)), "TargetTag");
verifyThrownExceptionBy( verifyThrownExceptionBy(
() -> targetManagement.assignTag(Collections.singletonList(NOT_EXIST_ID), tag.getId()), () -> targetManagement.assignTag(Collections.singletonList(NOT_EXIST_ID), tag.getId()), "Target");
"Target");
verifyThrownExceptionBy(() -> targetManagement.unassignTag(List.of(NOT_EXIST_ID), tag.getId()), "Target"); verifyThrownExceptionBy(() -> targetManagement.unassignTag(List.of(NOT_EXIST_ID), tag.getId()), "Target");
verifyThrownExceptionBy(() -> targetManagement.unassignTag(List.of(target.getControllerId()), NOT_EXIST_IDL), verifyThrownExceptionBy(() -> targetManagement.unassignTag(List.of(target.getControllerId()), NOT_EXIST_IDL), "TargetTag");
"TargetTag");
verifyThrownExceptionBy(() -> targetManagement.update(entityFactory.target().update(NOT_EXIST_ID)), "Target"); verifyThrownExceptionBy(() -> targetManagement.update(entityFactory.target().update(NOT_EXIST_ID)), "Target");
verifyThrownExceptionBy(() -> targetManagement.createMetaData(NOT_EXIST_ID, verifyThrownExceptionBy(() -> targetManagement.createMetadata(NOT_EXIST_ID, Map.of("123", "123")), "Target");
Collections.singletonList(entityFactory.generateTargetMetadata("123", "123"))), "Target"); verifyThrownExceptionBy(() -> targetManagement.deleteMetadata(NOT_EXIST_ID, "xxx"), "Target");
verifyThrownExceptionBy(() -> targetManagement.deleteMetaData(NOT_EXIST_ID, "xxx"), "Target"); verifyThrownExceptionBy(() -> targetManagement.deleteMetadata(target.getControllerId(), NOT_EXIST_ID), "Target");
verifyThrownExceptionBy(() -> targetManagement.deleteMetaData(target.getControllerId(), NOT_EXIST_ID), verifyThrownExceptionBy(() -> targetManagement.getMetadata(NOT_EXIST_ID).get("xxx"), "Target");
"TargetMetadata"); verifyThrownExceptionBy(() -> targetManagement.updateMetadata(NOT_EXIST_ID, "xxx", "xxx"), "Target");
verifyThrownExceptionBy(() -> targetManagement.getMetaDataByControllerId(NOT_EXIST_ID, "xxx"), "Target");
verifyThrownExceptionBy(() -> targetManagement.findMetaDataByControllerId(PAGE, NOT_EXIST_ID), "Target");
verifyThrownExceptionBy(() -> targetManagement.findMetaDataByControllerIdAndRsql(PAGE, NOT_EXIST_ID, "key==*"),
"Target");
verifyThrownExceptionBy(
() -> targetManagement.updateMetadata(NOT_EXIST_ID, entityFactory.generateTargetMetadata("xxx", "xxx")),
"Target");
verifyThrownExceptionBy(() -> targetManagement.updateMetadata(target.getControllerId(),
entityFactory.generateTargetMetadata(NOT_EXIST_ID, "xxx")), "TargetMetadata");
} }
@Test @Test
@@ -764,68 +739,75 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
@Test @Test
@Description("Checks that metadata for a target can be created.") @Description("Checks that metadata for a target can be created.")
void createTargetMetadata() { void createMetadata() {
final String knownKey = "targetMetaKnownKey"; insertMetadata(
final String knownValue = "targetMetaKnownValue"; "targetMetaKnownKey", "targetMetaKnownValue",
testdataFactory.createTarget("targetIdWithMetadata")); // and validate
final Target target = testdataFactory.createTarget("targetIdWithMetadata");
final JpaTargetMetadata createdMetadata = insertTargetMetadata(knownKey, knownValue, target);
assertThat(createdMetadata).isNotNull();
assertThat(createdMetadata.getId().getKey()).isEqualTo(knownKey);
assertThat(createdMetadata.getTarget().getControllerId()).isEqualTo(target.getControllerId());
assertThat(createdMetadata.getTarget().getId()).isEqualTo(target.getId());
assertThat(createdMetadata.getValue()).isEqualTo(knownValue);
} }
@Test @Test
@Description("Verifies the enforcement of the metadata quota per target.") @Description("Verifies the enforcement of the metadata quota per target.")
void createTargetMetadataUntilQuotaIsExceeded() { void createMetadataUntilQuotaIsExceeded() {
// add meta-data one by one // add meta-data one by one
final Target target1 = testdataFactory.createTarget("target1"); final Target target1 = testdataFactory.createTarget("target1");
final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerTarget(); final int maxMetaData = quotaManagement.getMaxMetaDataEntriesPerTarget();
for (int i = 0; i < maxMetaData; ++i) { for (int i = 0; i < maxMetaData; ++i) {
assertThat(insertTargetMetadata("k" + i, "v" + i, target1)).isNotNull(); insertMetadata("k" + i, "v" + i, target1);
} }
// quota exceeded // quota exceeded
assertThatExceptionOfType(AssignmentQuotaExceededException.class) assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.isThrownBy(() -> insertTargetMetadata("k" + maxMetaData, "v" + maxMetaData, target1)); .isThrownBy(() -> insertMetadata("k" + maxMetaData, "v" + maxMetaData, target1));
// add multiple meta data entries at once // add multiple meta data entries at once
final Target target2 = testdataFactory.createTarget("target2"); final Target target2 = testdataFactory.createTarget("target2");
final List<MetaData> metaData2 = new ArrayList<>(); final Map<String, String> metaData2 = new HashMap<>();
for (int i = 0; i < maxMetaData + 1; ++i) { for (int i = 0; i < maxMetaData + 1; i++) {
metaData2.add(new JpaTargetMetadata("k" + i, "v" + i, target2)); metaData2.put("k" + i, "v" + i);
} }
// verify quota is exceeded // verify quota is exceeded
final String target2ControllerId = target2.getControllerId(); final String target2ControllerId = target2.getControllerId();
assertThatExceptionOfType(AssignmentQuotaExceededException.class) assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.isThrownBy(() -> targetManagement.createMetaData(target2ControllerId, metaData2)); .isThrownBy(() -> targetManagement.createMetadata(target2ControllerId, metaData2));
// add some meta data entries // add some meta data entries
final Target target3 = testdataFactory.createTarget("target3"); final Target target3 = testdataFactory.createTarget("target3");
final int firstHalf = maxMetaData / 2; final int firstHalf = maxMetaData / 2;
for (int i = 0; i < firstHalf; ++i) { for (int i = 0; i < firstHalf; i++) {
insertTargetMetadata("k" + i, "v" + i, target3); insertMetadata("k" + i, "v" + i, target3);
} }
// add too many data entries // add too many data entries
final int secondHalf = maxMetaData - firstHalf; final int secondHalf = maxMetaData - firstHalf;
final List<MetaData> metaData3 = new ArrayList<>(); final Map<String, String> metaData3 = new HashMap<>();
for (int i = 0; i < secondHalf + 1; ++i) { for (int i = 0; i < secondHalf + 1; i++) {
metaData3.add(new JpaTargetMetadata("kk" + i, "vv" + i, target3)); metaData3.put("kk" + i, "vv" + i);
} }
// verify quota is exceeded // verify quota is exceeded
final String target3ControllerId = target3.getControllerId(); final String target3ControllerId = target3.getControllerId();
assertThatExceptionOfType(AssignmentQuotaExceededException.class) assertThatExceptionOfType(AssignmentQuotaExceededException.class)
.isThrownBy(() -> targetManagement.createMetaData(target3ControllerId, metaData3)); .isThrownBy(() -> targetManagement.createMetadata(target3ControllerId, metaData3));
}
@Test
@Description("Queries and loads the metadata related to a given target.")
void getMetadata() {
// create targets
final Target target1 = createTargetWithMetadata("target1", 10);
final Target target2 = createTargetWithMetadata("target2", 8);
final Map<String, String> metadataOfTarget1 = targetManagement.getMetadata(target1.getControllerId());
final Map<String, String> metadataOfTarget2 = targetManagement.getMetadata(target2.getControllerId());
assertThat(metadataOfTarget1).hasSize(10);
assertThat(metadataOfTarget2).hasSize(8);
} }
@Test @Test
@WithUser(allSpPermissions = true) @WithUser(allSpPermissions = true)
@Description("Checks that metadata for a target can be updated.") @Description("Checks that metadata for a target can be updated.")
void updateTargetMetadata() { void updateMetadata() {
final String knownKey = "myKnownKey"; final String knownKey = "myKnownKey";
final String knownValue = "myKnownValue"; final String knownValue = "myKnownValue";
final String knownUpdateValue = "myNewUpdatedValue"; final String knownUpdateValue = "myNewUpdatedValue";
@@ -836,26 +818,36 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
assertThat(target.getOptLockRevision()).isEqualTo(1); assertThat(target.getOptLockRevision()).isEqualTo(1);
// create target meta data entry // create target meta data entry
insertTargetMetadata(knownKey, knownValue, target); insertMetadata(knownKey, knownValue, target);
Target changedLockRevisionTarget = targetManagement.get(target.getId()).orElseThrow(NoSuchElementException::new); final Target changedLockRevisionTarget = targetManagement.get(target.getId()).orElseThrow(NoSuchElementException::new);
assertThat(changedLockRevisionTarget.getOptLockRevision()).isEqualTo(2); assertThat(changedLockRevisionTarget.getOptLockRevision()).isEqualTo(2);
// update the target metadata // update the target metadata
final JpaTargetMetadata updated = (JpaTargetMetadata) targetManagement.updateMetadata( targetManagement.updateMetadata(target.getControllerId(), knownKey, knownUpdateValue);
target.getControllerId(), entityFactory.generateTargetMetadata(knownKey, knownUpdateValue)); // we are updating the target meta-data so also modifying the target so opt lock revision must be three
// we are updating the target meta-data so also modifying the base final Target changedLockRevisionTarget2 = targetManagement.get(target.getId()).orElseThrow(NoSuchElementException::new);
// software module so opt lock revision must be three assertThat(changedLockRevisionTarget2.getOptLockRevision()).isEqualTo(3);
changedLockRevisionTarget = targetManagement.get(target.getId()).orElseThrow(NoSuchElementException::new); assertThat(changedLockRevisionTarget2.getLastModifiedAt()).isPositive();
assertThat(changedLockRevisionTarget.getOptLockRevision()).isEqualTo(3);
assertThat(changedLockRevisionTarget.getLastModifiedAt()).isPositive();
// verify updated meta data contains the updated value // verify updated meta data contains the updated value
assertThat(updated).isNotNull(); assertThat(targetManagement.getMetadata(target.getControllerId()).get(knownKey)).isEqualTo(knownUpdateValue);
assertThat(updated.getValue()).isEqualTo(knownUpdateValue); }
assertThat(updated.getId().getKey()).isEqualTo(knownKey);
assertThat(updated.getTarget().getControllerId()).isEqualTo(target.getControllerId()); @Test
assertThat(updated.getTarget().getId()).isEqualTo(target.getId()); @Description("Queries and loads the metadata related to a given target.")
void deleteMetadata() {
final String knownKey = "myKnownKey";
final String knownValue = "myKnownValue";
// create target
final String controllerId = createTargetWithMetadata("target1", 10).getControllerId();
targetManagement.createMetadata(controllerId, Map.of(knownKey, knownValue));
targetManagement.deleteMetadata(controllerId, knownKey);
// already removed
assertThatExceptionOfType(EntityNotFoundException.class)
.isThrownBy(() -> targetManagement.deleteMetadata(controllerId, knownKey));
} }
@Test @Test
@@ -963,26 +955,6 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
checkTargetsHaveType(typeATargets, typeB); checkTargetsHaveType(typeATargets, typeB);
} }
@Test
@Description("Queries and loads the metadata related to a given target.")
void findAllTargetMetadataByControllerId() {
// create targets
final Target target1 = createTargetWithMetadata("target1", 10);
final Target target2 = createTargetWithMetadata("target2", 8);
final Page<TargetMetadata> metadataOfTarget1 = targetManagement
.findMetaDataByControllerId(PageRequest.of(0, 100), target1.getControllerId());
final Page<TargetMetadata> metadataOfTarget2 = targetManagement
.findMetaDataByControllerId(PageRequest.of(0, 100), target2.getControllerId());
assertThat(metadataOfTarget1.getNumberOfElements()).isEqualTo(10);
assertThat(metadataOfTarget1.getTotalElements()).isEqualTo(10);
assertThat(metadataOfTarget2.getNumberOfElements()).isEqualTo(8);
assertThat(metadataOfTarget2.getTotalElements()).isEqualTo(8);
}
@Test @Test
@WithUser(allSpPermissions = true) @WithUser(allSpPermissions = true)
@Description("Checks that target type is not assigned to target if invalid.") @Description("Checks that target type is not assigned to target if invalid.")
@@ -1419,11 +1391,9 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
} }
} }
private JpaTargetMetadata insertTargetMetadata(final String knownKey, final String knownValue, private void insertMetadata(final String knownKey, final String knownValue, final Target target) {
final Target target) { targetManagement.createMetadata(target.getControllerId(), Map.of(knownKey, knownValue));
final JpaTargetMetadata metadata = new JpaTargetMetadata(knownKey, knownValue, target); assertThat(targetManagement.getMetadata(target.getControllerId()).get(knownKey)).isEqualTo(knownValue);
return (JpaTargetMetadata) targetManagement
.createMetaData(target.getControllerId(), Collections.singletonList(metadata)).get(0);
} }
private void checkTargetsHaveType(final List<Target> targets, final TargetType type) { private void checkTargetsHaveType(final List<Target> targets, final TargetType type) {
@@ -1440,7 +1410,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
final Target target = testdataFactory.createTarget(controllerId); final Target target = testdataFactory.createTarget(controllerId);
for (int index = 1; index <= count; index++) { for (int index = 1; index <= count; index++) {
insertTargetMetadata("key" + index, controllerId + "-value" + index, target); insertMetadata("key" + index, controllerId + "-value" + index, target);
} }
return target; return target;
@@ -1450,7 +1420,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
final Target target = testdataFactory.createTarget(controllerId, controllerId, targetTypeId); final Target target = testdataFactory.createTarget(controllerId, controllerId, targetTypeId);
for (int index = 1; index <= count; index++) { for (int index = 1; index <= count; index++) {
insertTargetMetadata("key" + index, controllerId + "-value" + index, target); insertMetadata("key" + index, controllerId + "-value" + index, target);
} }
return target; return target;

View File

@@ -79,7 +79,7 @@ class RSQLTargetFieldTest extends AbstractJpaIntegrationTest {
.description("targetDesc123")); .description("targetDesc123"));
target = controllerManagement.updateControllerAttributes(target.getControllerId(), Map.of("revision", "1.1"), null); target = controllerManagement.updateControllerAttributes(target.getControllerId(), Map.of("revision", "1.1"), null);
target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(target.getControllerId(), LOCALHOST); target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(target.getControllerId(), LOCALHOST);
createTargetMetadata(target.getControllerId(), entityFactory.generateTargetMetadata("metaKey", "metaValue")); targetManagement.createMetadata(target.getControllerId(), Map.of("metaKey", "metaValue"));
assignDistributionSet(ds.getId(), target.getControllerId()); assignDistributionSet(ds.getId(), target.getControllerId());
targetManagement.assignType(target.getControllerId(), targetType1.getId()); targetManagement.assignType(target.getControllerId(), targetType1.getId());
@@ -89,7 +89,7 @@ class RSQLTargetFieldTest extends AbstractJpaIntegrationTest {
.description("targetId1234")); .description("targetId1234"));
target2 = controllerManagement.updateControllerAttributes(target2.getControllerId(), Map.of("revision", "1.2"), null); target2 = controllerManagement.updateControllerAttributes(target2.getControllerId(), Map.of("revision", "1.2"), null);
targetManagement.assignType(target2.getControllerId(), targetType2.getId()); targetManagement.assignType(target2.getControllerId(), targetType2.getId());
createTargetMetadata(target2.getControllerId(), entityFactory.generateTargetMetadata("metaKey", "value")); targetManagement.createMetadata(target2.getControllerId(), Map.of("metaKey", "value"));
target2 = controllerManagement.findOrRegisterTargetIfItDoesNotExist(target2.getControllerId(), LOCALHOST); target2 = controllerManagement.findOrRegisterTargetIfItDoesNotExist(target2.getControllerId(), LOCALHOST);
targetManagement.assignTag(Arrays.asList(target.getControllerId(), target2.getControllerId()), targetTag.getId()); targetManagement.assignTag(Arrays.asList(target.getControllerId(), target2.getControllerId()), targetTag.getId());
@@ -270,7 +270,7 @@ class RSQLTargetFieldTest extends AbstractJpaIntegrationTest {
@Test @Test
@Description("Test filter target by metadata") @Description("Test filter target by metadata")
void testFilterByMetadata() { void testFilterByMetadata() {
createTargetMetadata(testdataFactory.createTarget().getControllerId(), entityFactory.generateTargetMetadata("key.dot", "value.dot")); targetManagement.createMetadata(testdataFactory.createTarget().getControllerId(), Map.of("key.dot", "value.dot"));
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey==metaValue", 1); assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey==metaValue", 1);
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey==null", 0); // "null" check assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey==null", 0); // "null" check

View File

@@ -1,81 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.rsql;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.qameta.allure.Story;
import org.eclipse.hawkbit.repository.TargetMetadataFields;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.model.MetaData;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
@Feature("Component Tests - Repository")
@Story("RSQL filter target metadata")
class RSQLTargetMetadataFieldsTest extends AbstractJpaIntegrationTest {
private String controllerId;
@BeforeEach
void setupBeforeTest() {
final Target target = testdataFactory.createTarget("target");
controllerId = target.getControllerId();
final List<MetaData> metadata = new ArrayList<>(5);
for (int i = 0; i < 5; i++) {
metadata.add(entityFactory.generateTargetMetadata("" + i, "" + i));
}
targetManagement.createMetaData(controllerId, metadata);
targetManagement.createMetaData(controllerId,
Arrays.asList(entityFactory.generateTargetMetadata("emptyValueTest", null)));
}
@Test
@Description("Test filter target metadata by key")
void testFilterByParameterKey() {
assertRSQLQuery(TargetMetadataFields.KEY.name() + "==1", 1);
assertRSQLQuery(TargetMetadataFields.KEY.name() + "!=1", 5);
assertRSQLQuery(TargetMetadataFields.KEY.name() + "=in=(1,2)", 2);
assertRSQLQuery(TargetMetadataFields.KEY.name() + "=out=(1,2)", 4);
}
@Test
@Description("Test filter target metadata by value")
void testFilterByParameterValue() {
assertRSQLQuery(TargetMetadataFields.VALUE.name() + "==''", 1);
assertRSQLQuery(TargetMetadataFields.VALUE.name() + "!=''", 5);
assertRSQLQuery(TargetMetadataFields.VALUE.name() + "==1", 1);
assertRSQLQuery(TargetMetadataFields.VALUE.name() + "!=1", 5);
assertRSQLQuery(TargetMetadataFields.VALUE.name() + "=in=(1,2)", 2);
assertRSQLQuery(TargetMetadataFields.VALUE.name() + "=out=(1,2)", 4);
}
private void assertRSQLQuery(final String rsqlParam, final long expectedEntities) {
final Page<TargetMetadata> findEnitity = targetManagement
.findMetaDataByControllerIdAndRsql(PageRequest.of(0, 100), controllerId, rsqlParam);
final long countAllEntities = findEnitity.getTotalElements();
assertThat(findEnitity).isNotNull();
assertThat(countAllEntities).isEqualTo(expectedEntities);
}
}

View File

@@ -12,10 +12,10 @@
logging.level.org.eclipse.persistence=ERROR logging.level.org.eclipse.persistence=ERROR
## Uncomment to see the debug of persistence, e.g. to see the generated SQLs ## Uncomment to see the debug of persistence, e.g. to see the generated SQLs
logging.level.org.eclipse.persistence=DEBUG #logging.level.org.eclipse.persistence=DEBUG
spring.jpa.properties.eclipselink.logging.level=FINE #spring.jpa.properties.eclipselink.logging.level=FINE
spring.jpa.properties.eclipselink.logging.level.sql=FINE #spring.jpa.properties.eclipselink.logging.level.sql=FINE
spring.jpa.properties.eclipselink.logging.parameters=true #spring.jpa.properties.eclipselink.logging.parameters=true
## Enable EclipseLink performance monitor (monitoring and profile) ## Enable EclipseLink performance monitor (monitoring and profile)
#spring.jpa.properties.eclipselink.profiler=PerformanceMonitor #spring.jpa.properties.eclipselink.profiler=PerformanceMonitor

View File

@@ -424,10 +424,6 @@ public abstract class AbstractIntegrationTest {
return distributionSetManagement.putMetaData(dsId, md); return distributionSetManagement.putMetaData(dsId, md);
} }
protected void createTargetMetadata(final String controllerId, final MetaData md) {
createTargetMetadata(controllerId, Collections.singletonList(md));
}
protected Long getOsModule(final DistributionSet ds) { protected Long getOsModule(final DistributionSet ds) {
return ds.findFirstModuleByType(osType).orElseThrow(NoSuchElementException::new).getId(); return ds.findFirstModuleByType(osType).orElseThrow(NoSuchElementException::new).getId();
} }
@@ -504,8 +500,4 @@ public abstract class AbstractIntegrationTest {
throw new ArtifactStoreException("Cannot create temp file", e); throw new ArtifactStoreException("Cannot create temp file", e);
} }
} }
}
private void createTargetMetadata(final String controllerId, final List<MetaData> md) {
targetManagement.createMetaData(controllerId, md);
}
}