Merge remote-tracking branch 'origin/master' into fix_migration_to_new_spring_boot_version_merge_master
Signed-off-by: Ammar Bikic <ammar.bikic@bosch.io>
This commit is contained in:
119
.azure-pipelines/integration-tests.yml
Normal file
119
.azure-pipelines/integration-tests.yml
Normal file
@@ -0,0 +1,119 @@
|
||||
# Build hawkBit and run tests with Apache Maven.
|
||||
# Runs a matrix of various DB,JDK,RabbitMQ versions that are supported by hawkBit
|
||||
#
|
||||
# Requires the SonarCloud plugin and connection setup
|
||||
# https://kaizimmerm.com/post/azure-pipelines-for-the-oss-developer/#analyze-the-code-with-sonarcloud
|
||||
#
|
||||
# Add steps that analyze code, save build artifacts, deploy, and more:
|
||||
# https://docs.microsoft.com/en-us/azure/devops/pipelines/ecosystems/java
|
||||
# https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema
|
||||
# https://docs.microsoft.com/en-us/azure/devops/pipelines/release/caching?view=azure-devops#maven
|
||||
|
||||
pool:
|
||||
vmImage: "ubuntu-18.04"
|
||||
|
||||
# Default values for the hawbit build. Can be overriden in the variable group 'hawkbit'
|
||||
# see https://docs.microsoft.com/en-us/azure/devops/pipelines/library/variable-groups
|
||||
variables:
|
||||
# Defines defaults
|
||||
- name: sonarCloudConnection
|
||||
value: 'hawkBitSonar'
|
||||
- name: sonarCloudOrganization
|
||||
value: 'hawkbit'
|
||||
- name: sonarProjectKey
|
||||
value: 'org.eclipse:hawkbit'
|
||||
# Override defaults
|
||||
- group: hawkbit
|
||||
|
||||
jobs:
|
||||
- job: JDK_11
|
||||
displayName: Verify with JDK-11 and SonarCloud analysis
|
||||
steps:
|
||||
- template: rabbitmq-template.yml
|
||||
- template: maven-template.yml
|
||||
parameters:
|
||||
mavenGoals: "verify -Dsonar.host.url=https://sonarcloud.io -Dsonar.organization=$(sonarCloudOrganization) -Dsonar.projectKey=$(sonarProjectKey)"
|
||||
jdkVersionOption: "1.11"
|
||||
sonarQubeRunAnalysis: true
|
||||
sonarCloudConnection: $(sonarCloudConnection)
|
||||
sonarCloudOrganization: $(sonarCloudOrganization)
|
||||
- job: JDK_8
|
||||
displayName: Build with JDK-8 (hawkBit default)
|
||||
steps:
|
||||
- template: maven-template.yml
|
||||
parameters:
|
||||
mavenGoals: "install license:check"
|
||||
- job:
|
||||
dependsOn: JDK_8
|
||||
condition: succeeded()
|
||||
displayName: RABBIT
|
||||
strategy:
|
||||
matrix:
|
||||
3.6:
|
||||
rabbitmqVersion: "3.6"
|
||||
3.7:
|
||||
rabbitmqVersion: "3.7"
|
||||
3.8:
|
||||
rabbitmqVersion: "3.8"
|
||||
steps:
|
||||
- template: rabbitmq-template.yml
|
||||
parameters:
|
||||
rabbitmqVersion: $(rabbitmqVersion)
|
||||
- template: maven-template.yml
|
||||
parameters:
|
||||
mavenGoals: "verify"
|
||||
- job:
|
||||
dependsOn: JDK_8
|
||||
condition: succeeded()
|
||||
displayName: MYSQL
|
||||
strategy:
|
||||
matrix:
|
||||
5.6:
|
||||
dbVersion: "5.6"
|
||||
5.7:
|
||||
dbVersion: "5.7"
|
||||
steps:
|
||||
- template: rabbitmq-template.yml
|
||||
- script: "docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=8236472364 -e MYSQL_DATABASE=hawkbit -d mysql:$(dbVersion)"
|
||||
displayName: "Setup MYSQL Database docker instance"
|
||||
- template: maven-template.yml
|
||||
parameters:
|
||||
mavenGoals: "verify -Dspring.jpa.database=MYSQL -Dspring.datasource.driverClassName=org.mariadb.jdbc.Driver -Dspring.datasource.url=jdbc:mysql://localhost:3306/hawkbit -Dspring.datasource.username=root -Dspring.datasource.password=8236472364"
|
||||
- job:
|
||||
dependsOn: JDK_8
|
||||
condition: succeeded()
|
||||
displayName: MSSQL
|
||||
strategy:
|
||||
matrix:
|
||||
2017:
|
||||
dbVersion: "2017-latest"
|
||||
2019:
|
||||
dbVersion: "2019-latest"
|
||||
steps:
|
||||
- template: rabbitmq-template.yml
|
||||
- script: |
|
||||
docker run --name mssql -p 1433:1433 -e ACCEPT_EULA=Y -e SA_PASSWORD=1234567890.Ab -d mcr.microsoft.com/mssql/server:$(dbVersion)
|
||||
until [ "`/usr/bin/docker inspect -f {{.State.Running}} mssql`" == "true" ]; do sleep 1; done
|
||||
sleep 120
|
||||
until docker exec mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "1234567890.Ab" -Q "CREATE DATABASE hawkbit"; do sleep 1; done
|
||||
displayName: "Setup MSSQL Database docker instance"
|
||||
- template: maven-template.yml
|
||||
parameters:
|
||||
mavenGoals: "verify -Dspring.jpa.database=SQL_SERVER -Dspring.datasource.url=jdbc:sqlserver://localhost:1433;database=hawkbit -Dspring.datasource.username=SA -Dspring.datasource.password=1234567890.Ab -Dspring.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver"
|
||||
- job:
|
||||
dependsOn: JDK_8
|
||||
condition: succeeded()
|
||||
displayName: POSTGRESQL
|
||||
strategy:
|
||||
matrix:
|
||||
12:
|
||||
dbVersion: "12"
|
||||
13:
|
||||
dbVersion: "13"
|
||||
steps:
|
||||
- template: rabbitmq-template.yml
|
||||
- script: "docker run --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=1234567890 -e POSTGRES_DB=hawkbit -d postgres:$(dbVersion)"
|
||||
displayName: "Setup POSTGRESQL Database docker instance"
|
||||
- template: maven-template.yml
|
||||
parameters:
|
||||
mavenGoals: "verify -Dspring.jpa.database=POSTGRESQL -Dspring.datasource.url=jdbc:postgresql://localhost:5432/hawkbit?currentSchema=hawkbit -Dspring.datasource.username=postgres -Dspring.datasource.password=1234567890 -Dspring.datasource.driverClassName=org.postgresql.Driver"
|
||||
50
.azure-pipelines/maven-template.yml
Normal file
50
.azure-pipelines/maven-template.yml
Normal file
@@ -0,0 +1,50 @@
|
||||
parameters:
|
||||
- name: mavenGoals
|
||||
displayName: Maven Goal
|
||||
type: string
|
||||
- name: jdkVersionOption
|
||||
displayName: JDK Version
|
||||
type: string
|
||||
default: "1.8"
|
||||
- name: sonarQubeRunAnalysis
|
||||
displayName: Enable SonarQube analysis
|
||||
type: boolean
|
||||
default: false
|
||||
- name: sonarCloudConnection
|
||||
displayName: Optional SonarCloud connection
|
||||
type: string
|
||||
default: ''
|
||||
- name: sonarCloudOrganization
|
||||
displayName: Optional SonarCloud organization
|
||||
type: string
|
||||
default: ''
|
||||
- name: mavenCacheFolder
|
||||
displayName: Maven Cache Folder
|
||||
type: string
|
||||
default: $(Pipeline.Workspace)/.m2/repository
|
||||
|
||||
steps:
|
||||
- task: SonarCloudPrepare@1
|
||||
condition: eq('${{ parameters.sonarQubeRunAnalysis }}', true)
|
||||
displayName: 'Prepare SonarCloud analysis configuration'
|
||||
inputs:
|
||||
SonarCloud: ${{ parameters.sonarCloudConnection }}
|
||||
organization: ${{ parameters.sonarCloudOrganization }}
|
||||
scannerMode: Other
|
||||
- task: Cache@2
|
||||
inputs:
|
||||
key: 'maven | "$(Agent.OS)" | "${{ parameters.jdkVersionOption }}" | **/pom.xml'
|
||||
path: ${{ parameters.mavenCacheFolder }}
|
||||
displayName: Cache Maven local repo
|
||||
- task: Maven@3
|
||||
inputs:
|
||||
mavenPomFile: "pom.xml"
|
||||
mavenOptions: "-Xmx3072m"
|
||||
javaHomeOption: "JDKVersion"
|
||||
jdkVersionOption: ${{ parameters.jdkVersionOption }}
|
||||
jdkArchitectureOption: "x64"
|
||||
publishJUnitResults: true
|
||||
sonarQubeRunAnalysis: ${{ parameters.sonarQubeRunAnalysis }}
|
||||
sqMavenPluginVersionChoice: 'latest'
|
||||
testResultsFiles: "**/surefire-reports/TEST-*.xml"
|
||||
goals: "${{ parameters.mavenGoals }} -Dmaven.repo.local=${{ parameters.mavenCacheFolder }}"
|
||||
9
.azure-pipelines/rabbitmq-template.yml
Normal file
9
.azure-pipelines/rabbitmq-template.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
parameters:
|
||||
- name: rabbitmqVersion
|
||||
displayName: RabbitMQ Version
|
||||
type: string
|
||||
default: "3.8"
|
||||
|
||||
steps:
|
||||
- script: docker run -d --name rabbit -p 15672:15672 -p 5672:5672 -e RABBITMQ_DEFAULT_VHOST=/ rabbitmq:${{ parameters.rabbitmqVersion }}-management
|
||||
displayName: "Setup RabbitMQ docker instance"
|
||||
9
.devcontainer/Dockerfile
Normal file
9
.devcontainer/Dockerfile
Normal file
@@ -0,0 +1,9 @@
|
||||
# [Choice] Java version: 8. 11, 15
|
||||
ARG VARIANT=8
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/java:${VARIANT}
|
||||
|
||||
RUN su vscode -c "source /usr/local/sdkman/bin/sdkman-init.sh && sdk install maven \"${MAVEN_VERSION}\""
|
||||
|
||||
# [Optional] Uncomment this section to install additional OS packages.
|
||||
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
||||
35
.devcontainer/devcontainer.json
Normal file
35
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "Java",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile",
|
||||
"args": {
|
||||
// Update the VARIANT arg to pick a Java version: 8, 11, 15
|
||||
"VARIANT": "8"
|
||||
}
|
||||
},
|
||||
// Set *default* container specific settings.json values on container create.
|
||||
"settings": {
|
||||
"terminal.integrated.shell.linux": "/bin/bash",
|
||||
"java.home": "/docker-java-home",
|
||||
"java.format.settings.url": "https://raw.githubusercontent.com/eclipse/hawkbit/master/eclipse_codeformatter.xml",
|
||||
"maven.executable.path": "/usr/local/sdkman/candidates/maven/current/bin/mvn"
|
||||
},
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [
|
||||
"vscjava.vscode-java-pack",
|
||||
"DavidAnson.vscode-markdownlint",
|
||||
"ms-azuretools.vscode-docker",
|
||||
"ms-azure-devops.azure-pipelines",
|
||||
"sonarsource.sonarlint-vscode",
|
||||
"pivotal.vscode-spring-boot",
|
||||
"vscjava.vscode-spring-boot-dashboard"
|
||||
],
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
"forwardPorts": [
|
||||
8080
|
||||
],
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
// "postCreateCommand": "java -version",
|
||||
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "vscode"
|
||||
}
|
||||
5
.github/config.yml
vendored
5
.github/config.yml
vendored
@@ -1,5 +0,0 @@
|
||||
newPRWelcomeComment: >
|
||||
Thanks for taking the time to contribute to hawkBit! We really appreciate this.
|
||||
Make yourself comfortable while I'm looking for a committer to help you with your contribution.
|
||||
|
||||
Please make sure you read the [contribution guide](https://github.com/eclipse/hawkbit/blob/master/CONTRIBUTING.md) and signed the Eclipse Contributor Agreement (ECA).
|
||||
8
.github/no-response.yml
vendored
8
.github/no-response.yml
vendored
@@ -1,8 +0,0 @@
|
||||
# Configuration for probot-no-response - https://github.com/probot/no-response
|
||||
|
||||
daysUntilClose: 15
|
||||
responseRequiredLabel: awaiting
|
||||
|
||||
closeComment: >
|
||||
There has been no response from the original author so I closed this issue.
|
||||
Please reach out if you have or find the answers we need so that we can investigate further.
|
||||
14
.github/workflows/first-interaction.yml
vendored
Normal file
14
.github/workflows/first-interaction.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: First User Interaction
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
greeting:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/first-interaction@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.PAT_SECRET }}
|
||||
pr-message: |-
|
||||
Thanks @${{ github.actor }} for taking the time to contribute to hawkBit! We really appreciate this. Make yourself comfortable while I'm looking for a committer to help you with your contribution.
|
||||
Please make sure you read the [contribution guide](https://github.com/eclipse/hawkbit/blob/master/CONTRIBUTING.md) and signed the Eclipse Contributor Agreement (ECA).
|
||||
23
.github/workflows/stale.yml
vendored
Normal file
23
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: Mark & close stale issues
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.PAT_SECRET }}
|
||||
days-before-stale: -1
|
||||
days-before-close: 15
|
||||
stale-issue-label: 'awaiting'
|
||||
close-issue-message: |-
|
||||
There has been no response from the original author so I closed this issue.
|
||||
Please reach out if you have or find the answers we need so that we can investigate further.
|
||||
only-labels: 'awaiting'
|
||||
skip-stale-issue-message: 'true'
|
||||
skip-stale-pr-message: 'true'
|
||||
@@ -48,7 +48,7 @@ There are clients outside of the Eclipse IoT eco system as well, e.g.:
|
||||
|
||||
# Runtime dependencies and support
|
||||
|
||||
## Java Runtime Environment: 1.8
|
||||
## Java Runtime Environment: 1.8,11
|
||||
|
||||
## SQL database
|
||||
|
||||
@@ -56,12 +56,12 @@ There are clients outside of the Eclipse IoT eco system as well, e.g.:
|
||||
| --------------------------------- | :----------------------------------------------------: | :-------------------------------------------------------------------------: | :--------------------------------------------------------------: | :----------------------------------------------------------------: | :----------------: |
|
||||
| DDLs maintained by project | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Test dependencies defined | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | |
|
||||
| Versions tested | 1.4 | MySQL 5.6/5.7, AWS Aurora | MS SQL Server 2017 | PostgreSQL 12.1 | DB2 Server v11.1 |
|
||||
| Versions tested | 1.4 | MySQL 5.6/5.7, AWS Aurora | MS SQL Server 2017/2019 | PostgreSQL 12/13 | DB2 Server v11.1 |
|
||||
| Docker image with driver provided | :white_check_mark: | :white_check_mark: (Tag: "-mysql") | :white_check_mark: | :white_check_mark: | |
|
||||
| JDBC driver | [H2 1.4.200](https://github.com/h2database/h2database) | [MariaDB Connector/J 2.6.2](https://github.com/MariaDB/mariadb-connector-j) | [MSSQL-JDBC 7.4.1.jre8](https://github.com/Microsoft/mssql-jdbc) | [PostgreSQL JDBC Driver 42.2.14](https://github.com/pgjdbc/pgjdbc) | |
|
||||
| Status | Test, Dev | Production grade | Production grade | Test, Dev | Test, Dev |
|
||||
|
||||
## (Optional) RabbitMQ: 3.6,3.7
|
||||
## (Optional) RabbitMQ: 3.6,3.7,3.8
|
||||
|
||||
# Getting Started
|
||||
|
||||
|
||||
@@ -125,6 +125,20 @@ public interface TargetManagement {
|
||||
+ SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
|
||||
long countByInstalledDistributionSet(long distId);
|
||||
|
||||
/**
|
||||
* Checks if there is already a {@link Target} that has the given distribution set Id assigned or installed.
|
||||
*
|
||||
* @param distId
|
||||
* to search for
|
||||
* @return <code>true</code> if a {@link Target} exists.
|
||||
*
|
||||
* @throws EntityNotFoundException
|
||||
* if distribution set with given ID does not exist
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET + SpringEvalExpressions.HAS_AUTH_OR
|
||||
+ SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
|
||||
boolean existsByInstalledOrAssignedDistributionSet(long distId);
|
||||
|
||||
/**
|
||||
* Count {@link TargetFilterQuery}s for given target filter query.
|
||||
*
|
||||
|
||||
@@ -123,6 +123,9 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED = new EnumMap<>(Database.class);
|
||||
QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED.put(Database.SQL_SERVER, "DELETE TOP (" + ACTION_PAGE_LIMIT
|
||||
+ ") FROM sp_action WHERE tenant=#tenant AND status IN (%s) AND last_modified_at<#last_modified_at ");
|
||||
QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED.put(Database.POSTGRESQL,
|
||||
"DELETE FROM sp_action WHERE id IN (SELECT id FROM sp_action WHERE tenant=#tenant AND status IN (%s) AND last_modified_at<#last_modified_at LIMIT "
|
||||
+ ACTION_PAGE_LIMIT + ")");
|
||||
}
|
||||
|
||||
private final EntityManager entityManager;
|
||||
@@ -209,8 +212,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
final Map<Long, List<TargetWithActionType>> assignmentsByDsIds = convertRequest(validatedRequests);
|
||||
|
||||
final List<DistributionSetAssignmentResult> results = assignmentsByDsIds.entrySet().stream()
|
||||
.map(entry -> assignDistributionSetToTargetsWithRetry(initiatedBy, entry.getKey(), entry.getValue(), actionMessage,
|
||||
strategy))
|
||||
.map(entry -> assignDistributionSetToTargetsWithRetry(initiatedBy, entry.getKey(), entry.getValue(),
|
||||
actionMessage, strategy))
|
||||
.collect(Collectors.toList());
|
||||
strategy.sendDeploymentEvents(results);
|
||||
return results;
|
||||
@@ -239,8 +242,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
}
|
||||
}
|
||||
|
||||
private DistributionSetAssignmentResult assignDistributionSetToTargetsWithRetry(final String initiatedBy, final Long dsID,
|
||||
final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
|
||||
private DistributionSetAssignmentResult assignDistributionSetToTargetsWithRetry(final String initiatedBy,
|
||||
final Long dsID, final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
|
||||
final AbstractDsAssignmentStrategy assignmentStrategy) {
|
||||
final RetryCallback<DistributionSetAssignmentResult, ConcurrencyFailureException> retryCallback = retryContext -> assignDistributionSetToTargets(
|
||||
initiatedBy, dsID, targetsWithActionType, actionMessage, assignmentStrategy);
|
||||
@@ -427,9 +430,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
|
||||
return targetsWithActionType.stream()
|
||||
.map(twt -> assignmentStrategy.createTargetAction(initiatedBy, twt, targets, set))
|
||||
.filter(Objects::nonNull)
|
||||
.map(actionRepository::save)
|
||||
.collect(Collectors.toList());
|
||||
.filter(Objects::nonNull).map(actionRepository::save).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void createActionsStatus(final Collection<JpaAction> actions,
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.eclipse.persistence.config.PersistenceUnitProperties;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.cache.interceptor.KeyGenerator;
|
||||
import org.springframework.dao.ConcurrencyFailureException;
|
||||
@@ -45,6 +46,7 @@ import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.orm.jpa.vendor.Database;
|
||||
import org.springframework.retry.annotation.Backoff;
|
||||
import org.springframework.retry.annotation.Retryable;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
@@ -59,6 +61,7 @@ import org.springframework.validation.annotation.Validated;
|
||||
@Transactional(readOnly = true)
|
||||
@Validated
|
||||
public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, SystemManagement {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(JpaSystemManagement.class);
|
||||
|
||||
private static final int MAX_TENANTS_QUERY = 1000;
|
||||
@@ -123,12 +126,28 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
|
||||
@Autowired
|
||||
private ArtifactRepository artifactRepository;
|
||||
|
||||
private final String countArtifactQuery;
|
||||
private final String countSoftwareModulesQuery;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param properties
|
||||
* properties to get the underlying database
|
||||
*/
|
||||
public JpaSystemManagement(final JpaProperties properties) {
|
||||
|
||||
final String isDeleted = isPostgreSql(properties) ? "false" : "0";
|
||||
countArtifactQuery = "SELECT COUNT(a.id) FROM sp_artifact a INNER JOIN sp_base_software_module sm ON a.software_module = sm.id WHERE sm.deleted = "
|
||||
+ isDeleted;
|
||||
countSoftwareModulesQuery = "select SUM(file_size) from sp_artifact a INNER JOIN sp_base_software_module sm ON a.software_module = sm.id WHERE sm.deleted = "
|
||||
+ isDeleted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SystemUsageReport getSystemUsageStatistics() {
|
||||
|
||||
final Number count = (Number) entityManager.createNativeQuery(
|
||||
"select SUM(file_size) from sp_artifact a INNER JOIN sp_base_software_module sm ON a.software_module = sm.id WHERE sm.deleted = 0")
|
||||
.getSingleResult();
|
||||
final Number count = (Number) entityManager.createNativeQuery(countSoftwareModulesQuery).getSingleResult();
|
||||
|
||||
long sumOfArtifacts = 0;
|
||||
if (count != null) {
|
||||
@@ -140,9 +159,8 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
|
||||
final long targets = ((Number) entityManager.createNativeQuery("SELECT COUNT(id) FROM sp_target")
|
||||
.getSingleResult()).longValue();
|
||||
|
||||
final long artifacts = ((Number) entityManager.createNativeQuery(
|
||||
"SELECT COUNT(a.id) FROM sp_artifact a INNER JOIN sp_base_software_module sm ON a.software_module = sm.id WHERE sm.deleted = 0")
|
||||
.getSingleResult()).longValue();
|
||||
final long artifacts = ((Number) entityManager.createNativeQuery(countArtifactQuery).getSingleResult())
|
||||
.longValue();
|
||||
|
||||
final long actions = ((Number) entityManager.createNativeQuery("SELECT COUNT(id) FROM sp_action")
|
||||
.getSingleResult()).longValue();
|
||||
@@ -151,6 +169,10 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
|
||||
tenantMetaDataRepository.count());
|
||||
}
|
||||
|
||||
private static boolean isPostgreSql(final JpaProperties properties) {
|
||||
return Database.POSTGRESQL == properties.getDatabase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SystemUsageReportWithTenants getSystemUsageStatisticsWithTenants() {
|
||||
final SystemUsageReportWithTenants result = (SystemUsageReportWithTenants) getSystemUsageStatistics();
|
||||
|
||||
@@ -646,6 +646,13 @@ public class JpaTargetManagement implements TargetManagement {
|
||||
return targetRepository.countByInstalledDistributionSetId(distId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsByInstalledOrAssignedDistributionSet(final long distId) {
|
||||
throwEntityNotFoundIfDsDoesNotExist(distId);
|
||||
|
||||
return targetRepository.existsByInstalledOrAssignedDistributionSet(distId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Target> findByTargetFilterQueryAndNonDS(final Pageable pageRequest, final long distributionSetId,
|
||||
final String targetFilterQuery) {
|
||||
|
||||
@@ -15,6 +15,8 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import org.eclipse.hawkbit.artifact.repository.ArtifactRepository;
|
||||
import org.eclipse.hawkbit.repository.ArtifactManagement;
|
||||
import org.eclipse.hawkbit.repository.ControllerManagement;
|
||||
@@ -124,8 +126,6 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
import org.springframework.transaction.jta.JtaTransactionManager;
|
||||
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
* General configuration for hawkBit's Repository.
|
||||
*
|
||||
@@ -425,8 +425,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
SystemManagement systemManagement() {
|
||||
return new JpaSystemManagement();
|
||||
SystemManagement systemManagement(final JpaProperties properties) {
|
||||
return new JpaSystemManagement(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -759,8 +759,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
AutoAssignExecutor autoAssignExecutor(final TargetFilterQueryManagement targetFilterQueryManagement,
|
||||
final TargetManagement targetManagement, final DeploymentManagement deploymentManagement,
|
||||
final PlatformTransactionManager transactionManager) {
|
||||
final TargetManagement targetManagement, final DeploymentManagement deploymentManagement,
|
||||
final PlatformTransactionManager transactionManager) {
|
||||
return new AutoAssignChecker(targetFilterQueryManagement, targetManagement, deploymentManagement,
|
||||
transactionManager);
|
||||
}
|
||||
|
||||
@@ -208,6 +208,16 @@ public interface TargetRepository extends BaseEntityRepository<JpaTarget, Long>,
|
||||
*/
|
||||
Long countByInstalledDistributionSetId(Long distId);
|
||||
|
||||
/**
|
||||
* Checks if there is already a {@link Target} that has the given distribution set Id assigned or installed.
|
||||
*
|
||||
* @param distId
|
||||
* to check
|
||||
* @return <code>true</code> if a {@link Target} exists.
|
||||
*/
|
||||
@Query("SELECT CASE WHEN COUNT(t)>0 THEN 'true' ELSE 'false' END FROM JpaTarget t WHERE t.installedDistributionSet.id=:distId OR t.assignedDistributionSet.id=:distId")
|
||||
boolean existsByInstalledOrAssignedDistributionSet(@Param("distId") Long distId);
|
||||
|
||||
/**
|
||||
* Finds all {@link Target}s in the repository.
|
||||
*
|
||||
|
||||
@@ -35,6 +35,7 @@ import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
import javax.persistence.criteria.Subquery;
|
||||
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.apache.commons.lang3.text.StrLookup;
|
||||
import org.eclipse.hawkbit.repository.FieldNameProvider;
|
||||
import org.eclipse.hawkbit.repository.FieldValueConverter;
|
||||
@@ -375,7 +376,8 @@ public final class RSQLUtility {
|
||||
*/
|
||||
private Path<Object> getFieldPath(final A enumField, final String finalProperty) {
|
||||
return (Path<Object>) getFieldPath(root, getSubAttributesFrom(finalProperty), enumField.isMap(),
|
||||
this::getJoinFieldPath).orElseThrow(() -> new RSQLParameterUnsupportedFieldException("RSQL field path cannot be empty", null));
|
||||
this::getJoinFieldPath).orElseThrow(
|
||||
() -> new RSQLParameterUnsupportedFieldException("RSQL field path cannot be empty", null));
|
||||
}
|
||||
|
||||
private Path<?> getJoinFieldPath(final Path<?> fieldPath, final String fieldNameSplit) {
|
||||
@@ -396,8 +398,8 @@ public final class RSQLUtility {
|
||||
return fieldPath;
|
||||
}
|
||||
|
||||
private static Optional<Path<?>> getFieldPath(final Root<?> root, final String[] split, final boolean isMapKeyField,
|
||||
final BiFunction<Path<?>, String, Path<?>> joinFieldPathProvider) {
|
||||
private static Optional<Path<?>> getFieldPath(final Root<?> root, final String[] split,
|
||||
final boolean isMapKeyField, final BiFunction<Path<?>, String, Path<?>> joinFieldPathProvider) {
|
||||
Path<?> fieldPath = null;
|
||||
for (int i = 0; i < split.length; i++) {
|
||||
if (!(isMapKeyField && i == (split.length - 1))) {
|
||||
@@ -685,7 +687,11 @@ public final class RSQLUtility {
|
||||
}
|
||||
|
||||
private Predicate getEqualToPredicate(final Object transformedValue, final Path<Object> fieldPath) {
|
||||
if (transformedValue instanceof String) {
|
||||
if (transformedValue == null) {
|
||||
return cb.isNull(pathOfString(fieldPath));
|
||||
}
|
||||
|
||||
if ((transformedValue instanceof String) && !NumberUtils.isCreatable((String) transformedValue)) {
|
||||
if (StringUtils.isEmpty(transformedValue)) {
|
||||
return cb.or(cb.isNull(pathOfString(fieldPath)), cb.equal(pathOfString(fieldPath), ""));
|
||||
}
|
||||
@@ -694,10 +700,6 @@ public final class RSQLUtility {
|
||||
return cb.like(cb.upper(pathOfString(fieldPath)), sqlValue, ESCAPE_CHAR);
|
||||
}
|
||||
|
||||
if (transformedValue == null) {
|
||||
return cb.isNull(pathOfString(fieldPath));
|
||||
}
|
||||
|
||||
return cb.equal(fieldPath, transformedValue);
|
||||
}
|
||||
|
||||
@@ -708,7 +710,7 @@ public final class RSQLUtility {
|
||||
return toNotNullPredicate(fieldPath);
|
||||
}
|
||||
|
||||
if (transformedValue instanceof String) {
|
||||
if ((transformedValue instanceof String) && !NumberUtils.isCreatable((String) transformedValue)) {
|
||||
if (StringUtils.isEmpty(transformedValue)) {
|
||||
return toNotNullAndNotEmptyPredicate(fieldPath);
|
||||
}
|
||||
@@ -725,7 +727,7 @@ public final class RSQLUtility {
|
||||
return toNotEqualWithSubQueryPredicate(enumField, sqlValue, fieldNames);
|
||||
}
|
||||
|
||||
return toNotEqualPredicate(fieldPath, transformedValue);
|
||||
return toNullOrNotEqualPredicate(fieldPath, transformedValue);
|
||||
}
|
||||
|
||||
private void clearOuterJoinsIfNotNeeded() {
|
||||
@@ -738,15 +740,15 @@ public final class RSQLUtility {
|
||||
return cb.isNotNull(pathOfString(fieldPath));
|
||||
}
|
||||
|
||||
private Predicate toNotEqualPredicate(final Path<Object> fieldPath, final Object transformedValue) {
|
||||
return cb.notEqual(fieldPath, transformedValue);
|
||||
}
|
||||
|
||||
private Predicate toNullOrNotLikePredicate(final Path<Object> fieldPath, final String sqlValue) {
|
||||
return cb.or(cb.isNull(pathOfString(fieldPath)),
|
||||
cb.notLike(cb.upper(pathOfString(fieldPath)), sqlValue, ESCAPE_CHAR));
|
||||
}
|
||||
|
||||
private Predicate toNullOrNotEqualPredicate(final Path<Object> fieldPath, final Object transformedValue) {
|
||||
return cb.or(cb.isNull(pathOfString(fieldPath)), cb.notEqual(fieldPath, transformedValue));
|
||||
}
|
||||
|
||||
private Predicate toNotNullAndNotEmptyPredicate(final Path<Object> fieldPath) {
|
||||
return cb.and(cb.isNotNull(pathOfString(fieldPath)), cb.notEqual(pathOfString(fieldPath), ""));
|
||||
}
|
||||
@@ -797,9 +799,11 @@ public final class RSQLUtility {
|
||||
}
|
||||
|
||||
private static Path<?> getInnerFieldPath(final Root<?> subqueryRoot, final String[] split,
|
||||
final boolean isMapKeyField) {
|
||||
final boolean isMapKeyField) {
|
||||
return getFieldPath(subqueryRoot, split, isMapKeyField,
|
||||
(fieldPath, fieldNameSplit) -> getInnerJoinFieldPath(subqueryRoot, fieldPath, fieldNameSplit)).orElseThrow(() -> new RSQLParameterUnsupportedFieldException("RSQL field path cannot be empty", null));
|
||||
(fieldPath, fieldNameSplit) -> getInnerJoinFieldPath(subqueryRoot, fieldPath, fieldNameSplit))
|
||||
.orElseThrow(() -> new RSQLParameterUnsupportedFieldException(
|
||||
"RSQL field path cannot be empty", null));
|
||||
}
|
||||
|
||||
private static Path<?> getInnerJoinFieldPath(final Root<?> subqueryRoot, final Path<?> fieldPath,
|
||||
|
||||
@@ -31,7 +31,9 @@ import org.eclipse.hawkbit.repository.test.util.AbstractIntegrationTest;
|
||||
import org.eclipse.hawkbit.repository.test.util.RolloutTestApprovalStrategy;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
|
||||
import org.springframework.cloud.stream.test.binder.TestSupportBinderAutoConfiguration;
|
||||
import org.springframework.orm.jpa.vendor.Database;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -101,6 +103,13 @@ public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest
|
||||
@Autowired
|
||||
protected RolloutTestApprovalStrategy approvalStrategy;
|
||||
|
||||
@Autowired
|
||||
private JpaProperties jpaProperties;
|
||||
|
||||
protected Database getDatabase() {
|
||||
return jpaProperties.getDatabase();
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
protected List<Action> findActionsByRolloutAndStatus(final Rollout rollout, final Action.Status actionStatus) {
|
||||
return Lists.newArrayList(actionRepository.findByRolloutIdAndStatus(PAGE, rollout.getId(), actionStatus));
|
||||
|
||||
@@ -39,9 +39,9 @@ import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedE
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.TargetTagCreatedEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
|
||||
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
|
||||
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
|
||||
import org.eclipse.hawkbit.repository.exception.InvalidTargetAddressException;
|
||||
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
|
||||
import org.eclipse.hawkbit.repository.exception.TenantNotExistException;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetMetadata;
|
||||
@@ -108,6 +108,8 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
|
||||
"DistributionSet");
|
||||
verifyThrownExceptionBy(() -> targetManagement.countByInstalledDistributionSet(NOT_EXIST_IDL),
|
||||
"DistributionSet");
|
||||
verifyThrownExceptionBy(() -> targetManagement.existsByInstalledOrAssignedDistributionSet(NOT_EXIST_IDL),
|
||||
"DistributionSet");
|
||||
|
||||
verifyThrownExceptionBy(() -> targetManagement.countByTargetFilterQuery(NOT_EXIST_IDL), "TargetFilterQuery");
|
||||
verifyThrownExceptionBy(() -> targetManagement.countByRsqlAndNonDS(NOT_EXIST_IDL, "name==*"),
|
||||
@@ -465,10 +467,14 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
|
||||
.isEqualTo(0);
|
||||
assertThat(targetManagement.countByInstalledDistributionSet(set.getId())).as("Target count is wrong")
|
||||
.isEqualTo(0);
|
||||
assertThat(targetManagement.existsByInstalledOrAssignedDistributionSet(set.getId())).as("Target count is wrong")
|
||||
.isFalse();
|
||||
assertThat(targetManagement.countByAssignedDistributionSet(set2.getId())).as("Target count is wrong")
|
||||
.isEqualTo(0);
|
||||
assertThat(targetManagement.countByInstalledDistributionSet(set2.getId())).as("Target count is wrong")
|
||||
.isEqualTo(0);
|
||||
assertThat(targetManagement.existsByInstalledOrAssignedDistributionSet(set2.getId())).as("Target count is wrong")
|
||||
.isFalse();
|
||||
|
||||
Target target = createTargetWithAttributes("4711");
|
||||
|
||||
@@ -488,10 +494,14 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
|
||||
.isEqualTo(0);
|
||||
assertThat(targetManagement.countByInstalledDistributionSet(set.getId())).as("Target count is wrong")
|
||||
.isEqualTo(1);
|
||||
assertThat(targetManagement.existsByInstalledOrAssignedDistributionSet(set.getId())).as("Target count is wrong")
|
||||
.isTrue();
|
||||
assertThat(targetManagement.countByAssignedDistributionSet(set2.getId())).as("Target count is wrong")
|
||||
.isEqualTo(1);
|
||||
assertThat(targetManagement.countByInstalledDistributionSet(set2.getId())).as("Target count is wrong")
|
||||
.isEqualTo(0);
|
||||
assertThat(targetManagement.existsByInstalledOrAssignedDistributionSet(set2.getId())).as("Target count is wrong")
|
||||
.isTrue();
|
||||
assertThat(target.getLastTargetQuery()).as("Target query is not work").isGreaterThanOrEqualTo(current);
|
||||
assertThat(deploymentManagement.getAssignedDistributionSet("4711").get()).as("Assigned ds size is wrong")
|
||||
.isEqualTo(set2);
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.orm.jpa.vendor.Database;
|
||||
|
||||
import io.qameta.allure.Description;
|
||||
import io.qameta.allure.Feature;
|
||||
@@ -55,7 +56,7 @@ public class RSQLActionFieldsTest extends AbstractJpaIntegrationTest {
|
||||
final JpaAction newAction = new JpaAction();
|
||||
newAction.setActionType(ActionType.SOFT);
|
||||
newAction.setDistributionSet(dsA);
|
||||
newAction.setActive(i % 2 == 0);
|
||||
newAction.setActive((i % 2) == 0);
|
||||
newAction.setStatus(Status.RUNNING);
|
||||
newAction.setTarget(target);
|
||||
newAction.setWeight(45);
|
||||
@@ -71,9 +72,18 @@ public class RSQLActionFieldsTest extends AbstractJpaIntegrationTest {
|
||||
public void testFilterByParameterId() {
|
||||
assertRSQLQuery(ActionFields.ID.name() + "==" + action.getId(), 1);
|
||||
assertRSQLQuery(ActionFields.ID.name() + "!=" + action.getId(), 10);
|
||||
assertRSQLQuery(ActionFields.ID.name() + "==noExist*", 0);
|
||||
assertRSQLQuery(ActionFields.ID.name() + "=in=(" + action.getId() + ",1000000)", 1);
|
||||
assertRSQLQuery(ActionFields.ID.name() + "=out=(" + action.getId() + ",1000000)", 10);
|
||||
assertRSQLQuery(ActionFields.ID.name() + "==" + -1, 0);
|
||||
assertRSQLQuery(ActionFields.ID.name() + "!=" + -1, 11);
|
||||
|
||||
// Not supported for numbers
|
||||
if (Database.POSTGRESQL.equals(getDatabase())) {
|
||||
return;
|
||||
}
|
||||
|
||||
assertRSQLQuery(ActionFields.ID.name() + "==*", 11);
|
||||
assertRSQLQuery(ActionFields.ID.name() + "==noexist*", 0);
|
||||
assertRSQLQuery(ActionFields.ID.name() + "=in=(" + action.getId() + ",10000000)", 1);
|
||||
assertRSQLQuery(ActionFields.ID.name() + "=out=(" + action.getId() + ",10000000)", 10);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.orm.jpa.vendor.Database;
|
||||
|
||||
import io.qameta.allure.Description;
|
||||
import io.qameta.allure.Feature;
|
||||
@@ -32,10 +33,12 @@ import io.qameta.allure.Story;
|
||||
@Story("RSQL filter distribution set")
|
||||
public class RSQLDistributionSetFieldTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
private DistributionSet ds;
|
||||
|
||||
@Before
|
||||
public void seuptBeforeTest() {
|
||||
|
||||
DistributionSet ds = testdataFactory.createDistributionSet("DS");
|
||||
ds = testdataFactory.createDistributionSet("DS");
|
||||
ds = distributionSetManagement.update(entityFactory.distributionSet().update(ds.getId()).description("DS"));
|
||||
createDistributionSetMetadata(ds.getId(), entityFactory.generateDsMetadata("metaKey", "metaValue"));
|
||||
|
||||
@@ -59,7 +62,20 @@ public class RSQLDistributionSetFieldTest extends AbstractJpaIntegrationTest {
|
||||
@Test
|
||||
@Description("Test filter distribution set by id")
|
||||
public void testFilterByParameterId() {
|
||||
assertRSQLQuery(DistributionSetFields.ID.name() + "==" + ds.getId(), 1);
|
||||
assertRSQLQuery(DistributionSetFields.ID.name() + "!=" + ds.getId(), 4);
|
||||
assertRSQLQuery(DistributionSetFields.ID.name() + "==" + -1, 0);
|
||||
assertRSQLQuery(DistributionSetFields.ID.name() + "!=" + -1, 5);
|
||||
|
||||
// Not supported for numbers
|
||||
if (Database.POSTGRESQL.equals(getDatabase())) {
|
||||
return;
|
||||
}
|
||||
|
||||
assertRSQLQuery(DistributionSetFields.ID.name() + "==*", 5);
|
||||
assertRSQLQuery(DistributionSetFields.ID.name() + "==noexist*", 0);
|
||||
assertRSQLQuery(DistributionSetFields.ID.name() + "=in=(" + ds.getId() + ",10000000)", 1);
|
||||
assertRSQLQuery(DistributionSetFields.ID.name() + "=out=(" + ds.getId() + ",10000000)", 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.orm.jpa.vendor.Database;
|
||||
|
||||
import io.qameta.allure.Description;
|
||||
import io.qameta.allure.Feature;
|
||||
@@ -47,11 +48,21 @@ public class RSQLRolloutGroupFields extends AbstractJpaIntegrationTest {
|
||||
@Test
|
||||
@Description("Test filter rollout group by id")
|
||||
public void testFilterByParameterId() {
|
||||
assertRSQLQuery(RolloutGroupFields.ID.name() + "==*", 3);
|
||||
assertRSQLQuery(RolloutGroupFields.ID.name() + "==" + rolloutGroupId, 1);
|
||||
assertRSQLQuery(RolloutGroupFields.ID.name() + "!=" + rolloutGroupId, 3);
|
||||
assertRSQLQuery(RolloutGroupFields.ID.name() + "==noExist*", 0);
|
||||
assertRSQLQuery(RolloutGroupFields.ID.name() + "=in=(" + rolloutGroupId + ")", 1);
|
||||
assertRSQLQuery(RolloutGroupFields.ID.name() + "=out=(" + rolloutGroupId + ")", 3);
|
||||
assertRSQLQuery(RolloutGroupFields.ID.name() + "==" + -1, 0);
|
||||
assertRSQLQuery(RolloutGroupFields.ID.name() + "!=" + -1, 4);
|
||||
|
||||
// Not supported for numbers
|
||||
if (Database.POSTGRESQL.equals(getDatabase())) {
|
||||
return;
|
||||
}
|
||||
|
||||
assertRSQLQuery(RolloutGroupFields.ID.name() + "==*", 4);
|
||||
assertRSQLQuery(RolloutGroupFields.ID.name() + "==noexist*", 0);
|
||||
assertRSQLQuery(RolloutGroupFields.ID.name() + "=in=(" + rolloutGroupId + ",10000000)", 1);
|
||||
assertRSQLQuery(RolloutGroupFields.ID.name() + "=out=(" + rolloutGroupId + ",10000000)", 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.orm.jpa.vendor.Database;
|
||||
|
||||
import io.qameta.allure.Description;
|
||||
import io.qameta.allure.Feature;
|
||||
@@ -29,10 +30,12 @@ import io.qameta.allure.Story;
|
||||
@Story("RSQL filter software module")
|
||||
public class RSQLSoftwareModuleFieldTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
private SoftwareModule ah;
|
||||
|
||||
@Before
|
||||
public void setupBeforeTest() {
|
||||
final SoftwareModule ah = softwareModuleManagement.create(entityFactory.softwareModule().create().type(appType)
|
||||
.name("agent-hub").version("1.0.1").description("agent-hub"));
|
||||
ah = softwareModuleManagement.create(entityFactory.softwareModule().create().type(appType).name("agent-hub")
|
||||
.version("1.0.1").description("agent-hub"));
|
||||
softwareModuleManagement.create(entityFactory.softwareModule().create().type(runtimeType).name("oracle-jre")
|
||||
.version("1.7.2").description("aa"));
|
||||
softwareModuleManagement.create(
|
||||
@@ -55,7 +58,20 @@ public class RSQLSoftwareModuleFieldTest extends AbstractJpaIntegrationTest {
|
||||
@Test
|
||||
@Description("Test filter software module by id")
|
||||
public void testFilterByParameterId() {
|
||||
assertRSQLQuery(SoftwareModuleFields.ID.name() + "==" + ah.getId(), 1);
|
||||
assertRSQLQuery(SoftwareModuleFields.ID.name() + "!=" + ah.getId(), 4);
|
||||
assertRSQLQuery(SoftwareModuleFields.ID.name() + "==" + -1, 0);
|
||||
assertRSQLQuery(SoftwareModuleFields.ID.name() + "!=" + -1, 5);
|
||||
|
||||
// Not supported for numbers
|
||||
if (Database.POSTGRESQL.equals(getDatabase())) {
|
||||
return;
|
||||
}
|
||||
|
||||
assertRSQLQuery(SoftwareModuleFields.ID.name() + "==*", 5);
|
||||
assertRSQLQuery(SoftwareModuleFields.ID.name() + "==noexist*", 0);
|
||||
assertRSQLQuery(SoftwareModuleFields.ID.name() + "=in=(" + ah.getId() + ",1000000)", 1);
|
||||
assertRSQLQuery(SoftwareModuleFields.ID.name() + "=out=(" + ah.getId() + ",1000000)", 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.orm.jpa.vendor.Database;
|
||||
|
||||
import io.qameta.allure.Description;
|
||||
import io.qameta.allure.Feature;
|
||||
@@ -29,7 +30,20 @@ public class RSQLSoftwareModuleTypeFieldsTest extends AbstractJpaIntegrationTest
|
||||
@Test
|
||||
@Description("Test filter software module test type by id")
|
||||
public void testFilterByParameterId() {
|
||||
assertRSQLQuery(SoftwareModuleTypeFields.ID.name() + "==" + osType.getId(), 1);
|
||||
assertRSQLQuery(SoftwareModuleTypeFields.ID.name() + "!=" + osType.getId(), 2);
|
||||
assertRSQLQuery(SoftwareModuleTypeFields.ID.name() + "==" + -1, 0);
|
||||
assertRSQLQuery(SoftwareModuleTypeFields.ID.name() + "!=" + -1, 3);
|
||||
|
||||
// Not supported for numbers
|
||||
if (Database.POSTGRESQL.equals(getDatabase())) {
|
||||
return;
|
||||
}
|
||||
|
||||
assertRSQLQuery(SoftwareModuleTypeFields.ID.name() + "==*", 3);
|
||||
assertRSQLQuery(SoftwareModuleTypeFields.ID.name() + "==noexist*", 0);
|
||||
assertRSQLQuery(SoftwareModuleTypeFields.ID.name() + "=in=(" + osType.getId() + ",1000000)", 1);
|
||||
assertRSQLQuery(SoftwareModuleTypeFields.ID.name() + "=out=(" + osType.getId() + ",1000000)", 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.eclipse.hawkbit.repository.test.util.TestdataFactory;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.orm.jpa.vendor.Database;
|
||||
|
||||
import io.qameta.allure.Description;
|
||||
import io.qameta.allure.Feature;
|
||||
@@ -54,7 +55,21 @@ public class RSQLTargetFilterQueryFieldsTest extends AbstractJpaIntegrationTest
|
||||
@Test
|
||||
@Description("Test filter target filter query by id")
|
||||
public void testFilterByParameterId() {
|
||||
assertRSQLQuery(TargetFilterQueryFields.ID.name() + "==" + filter1.getId(), 1);
|
||||
assertRSQLQuery(TargetFilterQueryFields.ID.name() + "!=" + filter1.getId(), 2);
|
||||
assertRSQLQuery(TargetFilterQueryFields.ID.name() + "==" + -1, 0);
|
||||
assertRSQLQuery(TargetFilterQueryFields.ID.name() + "!=" + -1, 3);
|
||||
|
||||
// Not supported for numbers
|
||||
if (Database.POSTGRESQL.equals(getDatabase())) {
|
||||
return;
|
||||
}
|
||||
|
||||
assertRSQLQuery(TargetFilterQueryFields.ID.name() + "==*", 3);
|
||||
assertRSQLQuery(TargetFilterQueryFields.ID.name() + "==noexist*", 0);
|
||||
assertRSQLQuery(TargetFilterQueryFields.ID.name() + "=in=(" + filter1.getId() + ",10000000)", 1);
|
||||
assertRSQLQuery(TargetFilterQueryFields.ID.name() + "=out=(" + filter1.getId() + ",10000000)", 2);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -196,8 +196,7 @@ public class RSQLUtilityTest {
|
||||
when(criteriaBuilderMock.upper(eq(pathOfString(baseSoftwareModuleRootMock))))
|
||||
.thenReturn(pathOfString(baseSoftwareModuleRootMock));
|
||||
when(criteriaBuilderMock.like(any(Expression.class), anyString(), eq('\\'))).thenReturn(mock(Predicate.class));
|
||||
when(criteriaBuilderMock.<String> greaterThanOrEqualTo(any(Expression.class), any(String.class)))
|
||||
.thenReturn(mock(Predicate.class));
|
||||
when(criteriaBuilderMock.equal(any(Expression.class), any(String.class))).thenReturn(mock(Predicate.class));
|
||||
|
||||
// test
|
||||
RSQLUtility.parse(correctRsql, SoftwareModuleFields.class, null, testDb).toPredicate(baseSoftwareModuleRootMock,
|
||||
|
||||
@@ -51,6 +51,10 @@
|
||||
<groupId>com.microsoft.sqlserver</groupId>
|
||||
<artifactId>mssql-jdbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
</dependency>
|
||||
<!-- Add a JDBC DB2 compatabile driver if you want to run tests against DB2, e.g. -->
|
||||
<!-- <dependency> -->
|
||||
<!-- <groupId>com.ibm.db2.jcc</groupId> -->
|
||||
|
||||
@@ -107,7 +107,8 @@ import com.google.common.io.Files;
|
||||
// test execution. So, the order execution between EventVerifier and Cleanup is
|
||||
// important!
|
||||
@TestExecutionListeners(inheritListeners = true, listeners = { EventVerifier.class, CleanupTestExecutionListener.class,
|
||||
MySqlTestDatabase.class, MsSqlTestDatabase.class }, mergeMode = MergeMode.MERGE_WITH_DEFAULTS)
|
||||
MySqlTestDatabase.class, MsSqlTestDatabase.class,
|
||||
PostgreSqlTestDatabase.class }, mergeMode = MergeMode.MERGE_WITH_DEFAULTS)
|
||||
@TestPropertySource(properties = "spring.main.allow-bean-definition-overriding=true")
|
||||
public abstract class AbstractIntegrationTest {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AbstractIntegrationTest.class);
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2020 Microsoft and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.test.util;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.TestExecutionListener;
|
||||
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
||||
|
||||
/**
|
||||
* A {@link TestExecutionListener} for creating and dropping MySql schemas if
|
||||
* tests are setup with MySql.
|
||||
*/
|
||||
public abstract class AbstractSqlTestDatabase extends AbstractTestExecutionListener {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AbstractSqlTestDatabase.class);
|
||||
protected String schemaName;
|
||||
protected String uri;
|
||||
protected String username;
|
||||
protected String password;
|
||||
|
||||
@Override
|
||||
public void beforeTestClass(final TestContext testContext) throws Exception {
|
||||
if (isRunningWithSql()) {
|
||||
LOG.info("Setting up database for test class {}", testContext.getTestClass().getName());
|
||||
this.username = System.getProperty("spring.datasource.username");
|
||||
this.password = System.getProperty("spring.datasource.password");
|
||||
this.uri = System.getProperty("spring.datasource.url");
|
||||
createSchemaUri();
|
||||
createSchema();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTestClass(final TestContext testContext) throws Exception {
|
||||
if (isRunningWithSql()) {
|
||||
dropSchema();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void createSchemaUri();
|
||||
|
||||
protected abstract boolean isRunningWithSql();
|
||||
|
||||
protected abstract void createSchema();
|
||||
|
||||
protected abstract void dropSchema();
|
||||
}
|
||||
@@ -16,53 +16,31 @@ import java.sql.SQLException;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.TestExecutionListener;
|
||||
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
||||
|
||||
/**
|
||||
* A {@link TestExecutionListener} for creating and dropping MS SQL Server
|
||||
* schemas if tests are setup with MS SQL Server.
|
||||
*/
|
||||
public class MsSqlTestDatabase extends AbstractTestExecutionListener {
|
||||
public class MsSqlTestDatabase extends AbstractSqlTestDatabase {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MsSqlTestDatabase.class);
|
||||
private String schemaName;
|
||||
private String uri;
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
@Override
|
||||
public void beforeTestClass(final TestContext testContext) throws Exception {
|
||||
if (isRunningWithMsSql()) {
|
||||
LOG.info("Setting up mysql schema for test class {}", testContext.getTestClass().getName());
|
||||
this.username = System.getProperty("spring.datasource.username");
|
||||
this.password = System.getProperty("spring.datasource.password");
|
||||
this.uri = System.getProperty("spring.datasource.url");
|
||||
createSchemaUri();
|
||||
createSchema();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTestClass(final TestContext testContext) throws Exception {
|
||||
if (isRunningWithMsSql()) {
|
||||
dropSchema();
|
||||
}
|
||||
}
|
||||
|
||||
private void createSchemaUri() {
|
||||
protected void createSchemaUri() {
|
||||
schemaName = "SP" + RandomStringUtils.randomAlphanumeric(10);
|
||||
this.uri = this.uri.substring(0, uri.indexOf(';'));
|
||||
|
||||
System.setProperty("spring.datasource.url", uri + ";database=" + schemaName);
|
||||
}
|
||||
|
||||
private static boolean isRunningWithMsSql() {
|
||||
@Override
|
||||
protected boolean isRunningWithSql() {
|
||||
return "SQL_SERVER".equals(System.getProperty("spring.jpa.database"));
|
||||
}
|
||||
|
||||
private void createSchema() {
|
||||
@Override
|
||||
protected void createSchema() {
|
||||
try (Connection connection = DriverManager.getConnection(uri, username, password)) {
|
||||
try (PreparedStatement statement = connection.prepareStatement("CREATE DATABASE " + schemaName + ";")) {
|
||||
LOG.info("Creating schema {} on uri {}", schemaName, uri);
|
||||
@@ -75,7 +53,8 @@ public class MsSqlTestDatabase extends AbstractTestExecutionListener {
|
||||
|
||||
}
|
||||
|
||||
private void dropSchema() {
|
||||
@Override
|
||||
protected void dropSchema() {
|
||||
try (Connection connection = DriverManager.getConnection(uri, username, password)) {
|
||||
// Needed to avoid the DROP is rejected with "database still in use"
|
||||
try (PreparedStatement statement = connection
|
||||
|
||||
@@ -16,53 +16,31 @@ import java.sql.SQLException;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.TestExecutionListener;
|
||||
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
||||
|
||||
/**
|
||||
* A {@link TestExecutionListener} for creating and dropping MySql schemas if
|
||||
* tests are setup with MySql.
|
||||
*/
|
||||
public class MySqlTestDatabase extends AbstractTestExecutionListener {
|
||||
public class MySqlTestDatabase extends AbstractSqlTestDatabase {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MySqlTestDatabase.class);
|
||||
private String schemaName;
|
||||
private String uri;
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
@Override
|
||||
public void beforeTestClass(final TestContext testContext) throws Exception {
|
||||
if (isRunningWithMySql()) {
|
||||
LOG.info("Setting up mysql schema for test class {}", testContext.getTestClass().getName());
|
||||
this.username = System.getProperty("spring.datasource.username");
|
||||
this.password = System.getProperty("spring.datasource.password");
|
||||
this.uri = System.getProperty("spring.datasource.url");
|
||||
createSchemaUri();
|
||||
createSchema();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTestClass(final TestContext testContext) throws Exception {
|
||||
if (isRunningWithMySql()) {
|
||||
dropSchema();
|
||||
}
|
||||
}
|
||||
|
||||
private void createSchemaUri() {
|
||||
protected void createSchemaUri() {
|
||||
schemaName = "SP" + RandomStringUtils.randomAlphanumeric(10);
|
||||
this.uri = this.uri.substring(0, uri.lastIndexOf('/') + 1);
|
||||
|
||||
System.setProperty("spring.datasource.url", uri + schemaName);
|
||||
}
|
||||
|
||||
private boolean isRunningWithMySql() {
|
||||
@Override
|
||||
protected boolean isRunningWithSql() {
|
||||
return "MYSQL".equals(System.getProperty("spring.jpa.database"));
|
||||
}
|
||||
|
||||
private void createSchema() {
|
||||
@Override
|
||||
protected void createSchema() {
|
||||
try (Connection connection = DriverManager.getConnection(uri, username, password)) {
|
||||
try (PreparedStatement statement = connection.prepareStatement("CREATE SCHEMA " + schemaName + ";")) {
|
||||
LOG.info("Creating schema {} on uri {}", schemaName, uri);
|
||||
@@ -75,7 +53,8 @@ public class MySqlTestDatabase extends AbstractTestExecutionListener {
|
||||
|
||||
}
|
||||
|
||||
private void dropSchema() {
|
||||
@Override
|
||||
protected void dropSchema() {
|
||||
try (Connection connection = DriverManager.getConnection(uri, username, password)) {
|
||||
try (PreparedStatement statement = connection.prepareStatement("DROP SCHEMA " + schemaName + ";")) {
|
||||
LOG.info("Dropping schema {} on uri {}", schemaName, uri);
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2020 Microsoft and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.test.util;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.test.context.TestExecutionListener;
|
||||
|
||||
/**
|
||||
* A {@link TestExecutionListener} for creating and dropping MySql schemas if
|
||||
* tests are setup with MySql.
|
||||
*/
|
||||
public class PostgreSqlTestDatabase extends AbstractSqlTestDatabase {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PostgreSqlTestDatabase.class);
|
||||
|
||||
@Override
|
||||
protected void createSchemaUri() {
|
||||
schemaName = "sp" + RandomStringUtils.randomAlphanumeric(10).toLowerCase();
|
||||
this.uri = this.uri.substring(0, uri.indexOf('?'));
|
||||
|
||||
System.setProperty("spring.datasource.url", uri + "?currentSchema=" + schemaName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isRunningWithSql() {
|
||||
return "POSTGRESQL".equals(System.getProperty("spring.jpa.database"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createSchema() {
|
||||
try (Connection connection = DriverManager.getConnection(uri, username, password)) {
|
||||
try (PreparedStatement statement = connection.prepareStatement("CREATE schema " + schemaName + ";")) {
|
||||
LOG.info("Creating schema {} on uri {}", schemaName, uri);
|
||||
statement.execute();
|
||||
LOG.info("Created schema {} on uri {}", schemaName, uri);
|
||||
}
|
||||
} catch (final SQLException e) {
|
||||
LOG.error("Schema creation failed!", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dropSchema() {
|
||||
try (Connection connection = DriverManager.getConnection(uri, username, password)) {
|
||||
try (PreparedStatement statement = connection.prepareStatement("DROP schema " + schemaName + " CASCADE;")) {
|
||||
LOG.info("Dropping schema {} on uri {}", schemaName, uri);
|
||||
statement.execute();
|
||||
LOG.info("Dropped schema {} on uri {}", schemaName, uri);
|
||||
}
|
||||
} catch (final SQLException e) {
|
||||
LOG.error("Schema drop failed!", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,12 +64,15 @@ import org.eclipse.hawkbit.util.IpUtil;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.orm.jpa.vendor.Database;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
import com.jayway.jsonpath.JsonPath;
|
||||
@@ -113,6 +116,9 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
|
||||
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;
|
||||
|
||||
@Autowired
|
||||
private JpaProperties jpaProperties;
|
||||
|
||||
@Test
|
||||
@Description("Ensures that actions list is in exptected order.")
|
||||
public void getActionStatusReturnsCorrectType() throws Exception {
|
||||
@@ -2015,11 +2021,22 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
|
||||
assignDistributionSet(dsId, targetId, customWeightHigh);
|
||||
assignDistributionSet(dsId, targetId, customWeightLow);
|
||||
|
||||
mvc.perform(get("/rest/v1/targets/{targetId}/actions", targetId)
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_SORTING, "WEIGHT:ASC")).andDo(MockMvcResultPrinter.print())
|
||||
.andExpect(status().isOk()).andExpect(jsonPath("content.[0].weight").doesNotExist())
|
||||
.andExpect(jsonPath("content.[1].weight", equalTo(customWeightLow)))
|
||||
.andExpect(jsonPath("content.[2].weight", equalTo(customWeightHigh)));
|
||||
// POSTGRESQL sets null values at the end, not the beginning
|
||||
if (Database.POSTGRESQL.equals(jpaProperties.getDatabase())) {
|
||||
mvc.perform(get("/rest/v1/targets/{targetId}/actions", targetId)
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_SORTING, "WEIGHT:ASC"))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("content.[0].weight", equalTo(customWeightLow)))
|
||||
.andExpect(jsonPath("content.[1].weight", equalTo(customWeightHigh)))
|
||||
.andExpect(jsonPath("content.[2].weight").doesNotExist());
|
||||
} else {
|
||||
mvc.perform(get("/rest/v1/targets/{targetId}/actions", targetId)
|
||||
.param(MgmtRestConstants.REQUEST_PARAMETER_SORTING, "WEIGHT:ASC"))
|
||||
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andExpect(jsonPath("content.[0].weight").doesNotExist())
|
||||
.andExpect(jsonPath("content.[1].weight", equalTo(customWeightLow)))
|
||||
.andExpect(jsonPath("content.[2].weight", equalTo(customWeightHigh)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -225,14 +225,11 @@ public abstract class JsonBuilder {
|
||||
public static String deploymentActionFeedback(final String id, final String execution, final String finished,
|
||||
final Collection<String> messages) throws JSONException {
|
||||
|
||||
return new JSONObject().put("id", id).put("time", "20140511T121314")
|
||||
.put("status",
|
||||
new JSONObject().put("execution", execution)
|
||||
.put("result",
|
||||
new JSONObject().put("finished", finished).put("progress",
|
||||
new JSONObject().put("cnt", 2).put("of", 5)))
|
||||
.put("details", new JSONArray(messages)))
|
||||
.toString();
|
||||
return new JSONObject().put("id", id).put("status", new JSONObject().put("execution", execution)
|
||||
.put("result",
|
||||
new JSONObject().put("finished", finished).put("progress",
|
||||
new JSONObject().put("cnt", 2).put("of", 5)))
|
||||
.put("details", new JSONArray(messages))).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -566,7 +563,7 @@ public abstract class JsonBuilder {
|
||||
|
||||
public static String cancelActionFeedback(final String id, final String execution, final String message)
|
||||
throws JSONException {
|
||||
return new JSONObject().put("id", id).put("time", "20140511T121314")
|
||||
return new JSONObject().put("id", id)
|
||||
.put("status",
|
||||
new JSONObject().put("execution", execution)
|
||||
.put("result", new JSONObject().put("finished", "success"))
|
||||
|
||||
@@ -373,11 +373,11 @@ include::{snippets}/targets/delete-action-from-target/path-parameters.adoc[]
|
||||
|
||||
==== Request query parameter
|
||||
|
||||
include::{snippets}/targets/delete-actions-from-target-with-parameters/request-parameters.adoc[]
|
||||
include::{snippets}/targets/delete-action-from-target-with-parameters/request-parameters.adoc[]
|
||||
|
||||
==== Request parameter example
|
||||
|
||||
include::{snippets}/targets/delete-actions-from-target-with-parameters/http-request.adoc[]
|
||||
include::{snippets}/targets/delete-action-from-target-with-parameters/http-request.adoc[]
|
||||
|
||||
=== Response (Status 204)
|
||||
|
||||
|
||||
@@ -16,8 +16,6 @@ final class DdiApiModelProperties {
|
||||
// Direct Device Integration API
|
||||
static final String CONTROLLER_ID = "id of the controller";
|
||||
|
||||
static final String TARGET_TIME = "time on the target device";
|
||||
|
||||
static final String TARGET_STATUS = "target action status";
|
||||
|
||||
static final String TARGET_EXEC_STATUS = "status of the action execution";
|
||||
|
||||
@@ -182,7 +182,6 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio
|
||||
parameterWithName("controllerId").description(DdiApiModelProperties.CONTROLLER_ID),
|
||||
parameterWithName("actionId").description(DdiApiModelProperties.ACTION_ID_CANCELED)),
|
||||
requestFields(optionalRequestFieldWithPath("id").description(DdiApiModelProperties.ACTION_ID),
|
||||
optionalRequestFieldWithPath("time").description(DdiApiModelProperties.TARGET_TIME),
|
||||
requestFieldWithPath("status").description(DdiApiModelProperties.TARGET_STATUS),
|
||||
requestFieldWithPath("status.execution")
|
||||
.description(DdiApiModelProperties.TARGET_EXEC_STATUS).type("enum")
|
||||
@@ -386,7 +385,6 @@ public class RootControllerDocumentationTest extends AbstractApiRestDocumentatio
|
||||
parameterWithName("actionId").description(DdiApiModelProperties.ACTION_ID)),
|
||||
|
||||
requestFields(optionalRequestFieldWithPath("id").description(DdiApiModelProperties.ACTION_ID),
|
||||
optionalRequestFieldWithPath("time").description(DdiApiModelProperties.TARGET_TIME),
|
||||
requestFieldWithPath("status").description(DdiApiModelProperties.TARGET_STATUS),
|
||||
requestFieldWithPath("status.execution")
|
||||
.description(DdiApiModelProperties.TARGET_EXEC_STATUS).type("enum")
|
||||
|
||||
@@ -308,13 +308,17 @@ public class TargetResourceDocumentationTest extends AbstractApiRestDocumentatio
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Handles the GET request of retrieving all targets within SP based by parameter. Required Permission: READ_TARGET.")
|
||||
public void deleteActionsFromTargetWithParameters() throws Exception {
|
||||
generateActionForTarget(targetId);
|
||||
|
||||
mockMvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + targetId + "/"
|
||||
+ MgmtRestConstants.TARGET_V1_ACTIONS + "?force=true")).andExpect(status().isOk())
|
||||
.andDo(MockMvcResultPrinter.print()).andDo(this.document.document(
|
||||
@Description("Optionally force quits an active action, only active actions can be deleted. Required Permission: UPDATE_TARGET.")
|
||||
public void deleteActionFromTargetWithParameters() throws Exception {
|
||||
final Action action = generateActionForTarget(targetId, false);
|
||||
deploymentManagement.cancelAction(action.getId());
|
||||
|
||||
mockMvc.perform(delete(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/{targetId}/"
|
||||
+ MgmtRestConstants.TARGET_V1_ACTIONS + "/{actionId}?force=true", targetId, action.getId()))
|
||||
.andExpect(status().isNoContent()).andDo(MockMvcResultPrinter.print())
|
||||
.andDo(this.document.document(
|
||||
pathParameters(parameterWithName("targetId").description(ApiModelPropertiesGeneric.ITEM_ID),
|
||||
parameterWithName("actionId").description(ApiModelPropertiesGeneric.ITEM_ID)),
|
||||
requestParameters(parameterWithName("force").description(MgmtApiModelProperties.FORCE))));
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,8 @@ services:
|
||||
- 'SPRING_RABBITMQ_HOST=rabbitmq'
|
||||
- 'SPRING_RABBITMQ_USERNAME=guest'
|
||||
- 'SPRING_RABBITMQ_PASSWORD=guest'
|
||||
- 'VIRTUAL_HOST=hawkbit.eclipse.org'
|
||||
- 'LETSENCRYPT_HOST=hawkbit.eclipse.org'
|
||||
- 'VIRTUAL_HOST=hawkbit.eclipseprojects.io'
|
||||
- 'LETSENCRYPT_HOST=hawkbit.eclipseprojects.io'
|
||||
|
||||
# ---------------------
|
||||
# hawkBit simulator
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.app;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@@ -23,22 +25,23 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
/**
|
||||
* Error page controller that ensures that ocet stream does not return text in
|
||||
* case of an error.
|
||||
*
|
||||
*/
|
||||
@Controller
|
||||
// Exception squid:S3752 - errors need handling for all methods
|
||||
@SuppressWarnings("squid:S3752")
|
||||
public class StreamAwareErrorController extends BasicErrorController {
|
||||
public class ErrorController extends BasicErrorController {
|
||||
|
||||
private static final String PATH = "path";
|
||||
|
||||
/**
|
||||
* A new {@link StreamAwareErrorController}.
|
||||
* A new {@link ErrorController}.
|
||||
*
|
||||
* @param errorAttributes
|
||||
* the error attributes
|
||||
* @param serverProperties
|
||||
* configuration properties
|
||||
*/
|
||||
public StreamAwareErrorController(final ErrorAttributes errorAttributes, final ServerProperties serverProperties) {
|
||||
public ErrorController(final ErrorAttributes errorAttributes, final ServerProperties serverProperties) {
|
||||
super(errorAttributes, serverProperties.getError());
|
||||
}
|
||||
|
||||
@@ -48,4 +51,19 @@ public class StreamAwareErrorController extends BasicErrorController {
|
||||
return new ResponseEntity<>(status);
|
||||
}
|
||||
|
||||
@Override
|
||||
@RequestMapping
|
||||
public ResponseEntity<Map<String, Object>> error(final HttpServletRequest request) {
|
||||
final HttpStatus status = getStatus(request);
|
||||
final Map<String, Object> body = getErrorAttributesWithoutPath(request);
|
||||
return new ResponseEntity<>(body, status);
|
||||
}
|
||||
|
||||
private Map<String, Object> getErrorAttributesWithoutPath(final HttpServletRequest request) {
|
||||
final Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
|
||||
if (body != null && body.containsKey(PATH)) {
|
||||
body.remove(PATH);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
|
||||
import org.eclipse.hawkbit.repository.test.util.MsSqlTestDatabase;
|
||||
import org.eclipse.hawkbit.repository.test.util.MySqlTestDatabase;
|
||||
import org.eclipse.hawkbit.repository.test.util.PostgreSqlTestDatabase;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -24,6 +25,7 @@ import org.springframework.security.test.context.support.WithUserDetails;
|
||||
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
import org.springframework.test.context.TestExecutionListeners.MergeMode;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
@@ -31,22 +33,22 @@ import org.springframework.test.web.servlet.ResultActions;
|
||||
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.test.context.TestExecutionListeners.MergeMode;
|
||||
|
||||
import io.qameta.allure.Description;
|
||||
import io.qameta.allure.Feature;
|
||||
import io.qameta.allure.Story;
|
||||
|
||||
@TestPropertySource(properties = { "hawkbit.server.security.cors.enabled=true",
|
||||
"hawkbit.server.security.cors.allowedOrigins=" + CorsTest.ALLOWED_ORIGIN_FIRST + ","
|
||||
+ CorsTest.ALLOWED_ORIGIN_SECOND })
|
||||
@SpringBootTest(properties = {"hawkbit.dmf.rabbitmq.enabled=false", "hawkbit.server.security.cors.enabled=true",
|
||||
"hawkbit.server.security.cors.allowedOrigins=" + CorsTest.ALLOWED_ORIGIN_FIRST + "," + CorsTest.ALLOWED_ORIGIN_SECOND})
|
||||
@TestExecutionListeners(listeners = { MySqlTestDatabase.class, MsSqlTestDatabase.class },
|
||||
mergeMode = MergeMode.MERGE_WITH_DEFAULTS)
|
||||
@Feature("Integration Test - Security")
|
||||
@Story("CORS")
|
||||
public class CorsTest extends AbstractSecurityTest {
|
||||
|
||||
final static String ALLOWED_ORIGIN_FIRST = "http://test.first.origin";
|
||||
final static String ALLOWED_ORIGIN_SECOND = "http://test.second.origin";
|
||||
|
||||
|
||||
private final static String INVALID_ORIGIN = "http://test.invalid.origin";
|
||||
private final static String INVALID_CORS_REQUEST = "Invalid CORS request";
|
||||
|
||||
@@ -61,15 +63,16 @@ public class CorsTest extends AbstractSecurityTest {
|
||||
.andExpect(status().isForbidden()).andReturn().getResponse().getContentAsString();
|
||||
assertThat(invalidOriginResponseBody).isEqualTo(INVALID_CORS_REQUEST);
|
||||
|
||||
final String invalidCorsUrlResponseBody = performOptionsRequestToUrlWithOrigin(MgmtRestConstants.BASE_SYSTEM_MAPPING, ALLOWED_ORIGIN_FIRST)
|
||||
.andExpect(status().isForbidden()).andReturn().getResponse().getContentAsString();
|
||||
final String invalidCorsUrlResponseBody = performOptionsRequestToUrlWithOrigin(
|
||||
MgmtRestConstants.BASE_SYSTEM_MAPPING, ALLOWED_ORIGIN_FIRST).andExpect(status().isForbidden())
|
||||
.andReturn().getResponse().getContentAsString();
|
||||
assertThat(invalidCorsUrlResponseBody).isEqualTo(INVALID_CORS_REQUEST);
|
||||
}
|
||||
|
||||
private ResultActions performOptionsRequestToRestWithOrigin(final String origin) throws Exception {
|
||||
return performOptionsRequestToUrlWithOrigin(MgmtRestConstants.BASE_V1_REQUEST_MAPPING, origin);
|
||||
}
|
||||
|
||||
|
||||
private ResultActions performOptionsRequestToUrlWithOrigin(final String url, final String origin) throws Exception {
|
||||
return mvc.perform(options(url).header("Access-Control-Request-Method", "GET").header("Origin", origin));
|
||||
}
|
||||
|
||||
@@ -114,8 +114,8 @@ public class SwModulesToDistributionSetAssignmentSupport
|
||||
return false;
|
||||
}
|
||||
|
||||
if (targetManagement.countByFilters(null, null, null, ds.getId(), Boolean.FALSE, "") > 0) {
|
||||
/* Distribution is already assigned */
|
||||
if (targetManagement.existsByInstalledOrAssignedDistributionSet(ds.getId())) {
|
||||
/* Distribution is already assigned/installed */
|
||||
notification.displayValidationError(i18n.getMessage("message.dist.inuse", ds.getNameVersion()));
|
||||
return false;
|
||||
}
|
||||
|
||||
6
licenses/LICENSE_HEADER_TEMPLATE_BOSCH_21.txt
Normal file
6
licenses/LICENSE_HEADER_TEMPLATE_BOSCH_21.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
Copyright (c) 2021 Bosch.IO GmbH and others.
|
||||
|
||||
All rights reserved. This program and the accompanying materials
|
||||
are made available under the terms of the Eclipse Public License v1.0
|
||||
which accompanies this distribution, and is available at
|
||||
http://www.eclipse.org/legal/epl-v10.html
|
||||
6
licenses/LICENSE_HEADER_TEMPLATE_MICROSOFT_20.txt
Normal file
6
licenses/LICENSE_HEADER_TEMPLATE_MICROSOFT_20.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
Copyright (c) 2020 Microsoft and others.
|
||||
|
||||
All rights reserved. This program and the accompanying materials
|
||||
are made available under the terms of the Eclipse Public License v1.0
|
||||
which accompanies this distribution, and is available at
|
||||
http://www.eclipse.org/legal/epl-v10.html
|
||||
7
pom.xml
7
pom.xml
@@ -167,7 +167,7 @@
|
||||
<cron-utils.version>9.1.3</cron-utils.version>
|
||||
<jsoup.version>1.11.2</jsoup.version>
|
||||
<allure.version>2.13.6</allure.version>
|
||||
<eclipselink.version>2.7.7</eclipselink.version>
|
||||
<eclipselink.version>2.7.3</eclipselink.version>
|
||||
<gwtmockito.version>1.1.8</gwtmockito.version>
|
||||
<guava.version>25.0-jre</guava.version>
|
||||
<javax.el-api.version>2.2.4</javax.el-api.version>
|
||||
@@ -329,12 +329,17 @@
|
||||
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_BOSCH.txt</validHeader>
|
||||
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_BOSCH_18.txt</validHeader>
|
||||
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_BOSCH_19.txt</validHeader>
|
||||
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_BOSCH_20.txt</validHeader>
|
||||
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_BOSCH_21.txt</validHeader>
|
||||
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_MICROSOFT_18.txt</validHeader>
|
||||
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_MICROSOFT_20.txt</validHeader>
|
||||
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_DEVOLO_19.txt</validHeader>
|
||||
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_KIWIGRID_19.txt</validHeader>
|
||||
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_ENAPTER.txt</validHeader>
|
||||
</validHeaders>
|
||||
<excludes>
|
||||
<exclude>.azure-pipelines/*</exclude>
|
||||
<exclude>.devcontainer/*</exclude>
|
||||
<exclude>**/banner.txt</exclude>
|
||||
<exclude>**/helm/**</exclude>
|
||||
<exclude>**/README</exclude>
|
||||
|
||||
Reference in New Issue
Block a user