Code format hawkbit (#1948)
Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -10,35 +10,34 @@
|
||||
|
||||
-->
|
||||
<project
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-sdk</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-sdk</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hawkbit-sdk-commons</artifactId>
|
||||
<name>hawkBit :: SDK :: Commons</name>
|
||||
<description>SDK commons</description>
|
||||
<artifactId>hawkbit-sdk-commons</artifactId>
|
||||
<name>hawkBit :: SDK :: Commons</name>
|
||||
<description>SDK commons</description>
|
||||
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
<version>${spring-cloud-starter-openfeign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-hc5</artifactId>
|
||||
<version>${openfeign-hc5.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-hateoas</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
<version>${spring-cloud-starter-openfeign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-hc5</artifactId>
|
||||
<version>${openfeign-hc5.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-hateoas</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -11,8 +11,6 @@ package org.eclipse.hawkbit.sdk;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.sdk;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import feign.Client;
|
||||
import feign.Contract;
|
||||
import feign.Feign;
|
||||
@@ -20,49 +25,42 @@ import lombok.Builder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
@Slf4j
|
||||
@Builder
|
||||
public class HawkbitClient {
|
||||
|
||||
private static final String AUTHORIZATION = "Authorization";
|
||||
private static final ErrorDecoder DEFAULT_ERROR_DECODER_0 = new ErrorDecoder.Default();
|
||||
public static final BiFunction<Tenant, Controller, RequestInterceptor> DEFAULT_REQUEST_INTERCEPTOR_FN =
|
||||
(tenant, controller) ->
|
||||
controller == null ?
|
||||
template -> {
|
||||
template.header(
|
||||
AUTHORIZATION,
|
||||
|
||||
"Basic " +
|
||||
Base64.getEncoder()
|
||||
.encodeToString(
|
||||
(Objects.requireNonNull(tenant.getUsername(), "User is null!") +
|
||||
":" +
|
||||
Objects.requireNonNull(tenant.getPassword(),
|
||||
"Password is not available!"))
|
||||
.getBytes(StandardCharsets.ISO_8859_1)));
|
||||
} :
|
||||
template -> {
|
||||
if (ObjectUtils.isEmpty(tenant.getGatewayToken())) {
|
||||
if (!ObjectUtils.isEmpty(controller.getSecurityToken())) {
|
||||
template.header(AUTHORIZATION, "TargetToken " + controller.getSecurityToken());
|
||||
} // else do not sent authentication
|
||||
} else {
|
||||
template.header(AUTHORIZATION, "GatewayToken " + tenant.getGatewayToken());
|
||||
}
|
||||
};
|
||||
private static final ErrorDecoder DEFAULT_ERROR_DECODER_0 = new ErrorDecoder.Default();
|
||||
public static final ErrorDecoder DEFAULT_ERROR_DECODER = (methodKey, response) -> {
|
||||
final Exception e = DEFAULT_ERROR_DECODER_0.decode(methodKey, response);
|
||||
log.trace("REST API call failed!", e);
|
||||
return e;
|
||||
};
|
||||
|
||||
public static final BiFunction<Tenant, Controller, RequestInterceptor> DEFAULT_REQUEST_INTERCEPTOR_FN =
|
||||
(tenant, controller) ->
|
||||
controller == null ?
|
||||
template -> {
|
||||
template.header(
|
||||
AUTHORIZATION,
|
||||
|
||||
"Basic " +
|
||||
Base64.getEncoder()
|
||||
.encodeToString(
|
||||
(Objects.requireNonNull(tenant.getUsername(), "User is null!") +
|
||||
":" +
|
||||
Objects.requireNonNull(tenant.getPassword(),"Password is not available!"))
|
||||
.getBytes(StandardCharsets.ISO_8859_1)));
|
||||
} :
|
||||
template -> {
|
||||
if (ObjectUtils.isEmpty(tenant.getGatewayToken())) {
|
||||
if (!ObjectUtils.isEmpty(controller.getSecurityToken())) {
|
||||
template.header(AUTHORIZATION, "TargetToken " + controller.getSecurityToken());
|
||||
} // else do not sent authentication
|
||||
} else {
|
||||
template.header(AUTHORIZATION, "GatewayToken " + tenant.getGatewayToken());
|
||||
}
|
||||
};
|
||||
|
||||
private final HawkbitServer hawkBitServerProperties;
|
||||
|
||||
private final Client client;
|
||||
@@ -100,6 +98,7 @@ public class HawkbitClient {
|
||||
public <T> T mgmtService(final Class<T> serviceType, final Tenant tenantProperties) {
|
||||
return service(serviceType, tenantProperties, null);
|
||||
}
|
||||
|
||||
public <T> T ddiService(final Class<T> serviceType, final Tenant tenantProperties, final Controller controller) {
|
||||
return service(serviceType, tenantProperties, controller);
|
||||
}
|
||||
@@ -113,7 +112,7 @@ public class HawkbitClient {
|
||||
.requestInterceptor(requestInterceptorFn.apply(tenant, controller))
|
||||
.target(serviceType,
|
||||
controller == null ?
|
||||
hawkBitServerProperties.getMgmtUrl() :
|
||||
hawkBitServerProperties.getDdiUrl());
|
||||
hawkBitServerProperties.getMgmtUrl() :
|
||||
hawkBitServerProperties.getDdiUrl());
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,10 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.sdk;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
import feign.Contract;
|
||||
import feign.MethodMetadata;
|
||||
import feign.RequestInterceptor;
|
||||
@@ -30,13 +34,9 @@ import org.springframework.hateoas.config.EnableHypermediaSupport;
|
||||
import org.springframework.hateoas.config.WebConverters;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@EnableConfigurationProperties({ HawkbitServer.class, Tenant.class})
|
||||
@EnableConfigurationProperties({ HawkbitServer.class, Tenant.class })
|
||||
@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
|
||||
@Import(FeignClientsConfiguration.class)
|
||||
@PropertySource("classpath:/hawkbit-sdk-defaults.properties")
|
||||
@@ -62,7 +62,7 @@ public class HawkbitSDKConfigurtion {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnNotWebApplication
|
||||
@ConditionalOnClass({ WebConverters.class})
|
||||
@ConditionalOnClass({ WebConverters.class })
|
||||
public HttpMessageConverterCustomizer webConvertersCustomizerOverrider(WebConverters webConverters) {
|
||||
return new WebConvertersCustomizer(webConverters);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
@ConfigurationProperties(prefix="hawkbit.server")
|
||||
@ConfigurationProperties(prefix = "hawkbit.server")
|
||||
@Data
|
||||
public class HawkbitServer {
|
||||
|
||||
|
||||
@@ -25,11 +25,8 @@ public interface ArtifactHandler {
|
||||
|
||||
interface DownloadHandler {
|
||||
|
||||
enum Status {
|
||||
SUCCESS, ERROR
|
||||
}
|
||||
|
||||
DownloadHandler SKIP = new DownloadHandler() {
|
||||
|
||||
@Override
|
||||
public void read(byte[] buff, int off, int len) {
|
||||
// skip
|
||||
@@ -69,5 +66,9 @@ public interface ArtifactHandler {
|
||||
* @return the path to the download
|
||||
*/
|
||||
Optional<Path> download();
|
||||
|
||||
enum Status {
|
||||
SUCCESS, ERROR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,72 +10,72 @@
|
||||
|
||||
-->
|
||||
<project
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-sdk</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-sdk</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hawkbit-sdk-demo</artifactId>
|
||||
<name>hawkBit :: SDK :: Test / Example</name>
|
||||
<description>Test / Example of how SDK could be used to for devices and for Mgmt API access</description>
|
||||
<artifactId>hawkbit-sdk-demo</artifactId>
|
||||
<name>hawkBit :: SDK :: Test / Example</name>
|
||||
<description>Test / Example of how SDK could be used to for devices and for Mgmt API access</description>
|
||||
|
||||
<properties>
|
||||
<spring-shell.version>3.1.5</spring-shell.version>
|
||||
<spring.app.class>org.eclipse.hawkbit.sdk.demo.multidevice.MultiDeviceApp</spring.app.class>
|
||||
<start-class>${spring.app.class}</start-class>
|
||||
</properties>
|
||||
<properties>
|
||||
<spring-shell.version>3.1.5</spring-shell.version>
|
||||
<spring.app.class>org.eclipse.hawkbit.sdk.demo.multidevice.MultiDeviceApp</spring.app.class>
|
||||
<start-class>${spring.app.class}</start-class>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-sdk-device</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-sdk-dmf</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-sdk-mgmt</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-sdk-device</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-sdk-dmf</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-sdk-mgmt</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.shell</groupId>
|
||||
<artifactId>spring-shell-starter</artifactId>
|
||||
<version>${spring-shell.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.shell</groupId>
|
||||
<artifactId>spring-shell-starter</artifactId>
|
||||
<version>${spring-shell.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${baseDir}</outputDirectory>
|
||||
<layout>JAR</layout>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${baseDir}</outputDirectory>
|
||||
<layout>JAR</layout>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.sdk.demo.device;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import feign.Client;
|
||||
import feign.Contract;
|
||||
import feign.codec.Decoder;
|
||||
@@ -29,9 +32,6 @@ import org.springframework.shell.standard.ShellComponent;
|
||||
import org.springframework.shell.standard.ShellMethod;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Abstract class representing DDI device connecting directly to hawkVit.
|
||||
*/
|
||||
@@ -52,7 +52,7 @@ public class DeviceApp {
|
||||
|
||||
@Bean
|
||||
DdiTenant ddiTenant(final Tenant defaultTenant,
|
||||
final HawkbitClient hawkbitClient) {
|
||||
final HawkbitClient hawkbitClient) {
|
||||
return new DdiTenant(defaultTenant, hawkbitClient);
|
||||
}
|
||||
|
||||
@@ -69,7 +69,6 @@ public class DeviceApp {
|
||||
private final DdiController device;
|
||||
private final MgmtApi mgmtApi;
|
||||
|
||||
|
||||
Shell(final DdiTenant ddiTenant, final MgmtApi mgmtApi, final Optional<UpdateHandler> updateHandler) {
|
||||
this.ddiTenant = ddiTenant;
|
||||
this.mgmtApi = mgmtApi;
|
||||
@@ -88,7 +87,7 @@ public class DeviceApp {
|
||||
@ShellMethod(key = "setup")
|
||||
public void setup() {
|
||||
mgmtApi.setupTargetAuthentication();
|
||||
mgmtApi.setupTargetToken(device.getControllerId(),device.getTargetSecurityToken());
|
||||
mgmtApi.setupTargetToken(device.getControllerId(), device.getTargetSecurityToken());
|
||||
}
|
||||
|
||||
@ShellMethod(key = "start")
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.sdk.demo.dmf;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.hawkbit.sdk.Controller;
|
||||
import org.eclipse.hawkbit.sdk.Tenant;
|
||||
@@ -26,9 +29,6 @@ import org.springframework.shell.standard.ShellComponent;
|
||||
import org.springframework.shell.standard.ShellMethod;
|
||||
import org.springframework.shell.standard.ShellOption;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Abstract class representing DDI device connecting directly to hawkVit.
|
||||
*/
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.sdk.demo.multidevice;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import feign.Client;
|
||||
import feign.Contract;
|
||||
import feign.codec.Decoder;
|
||||
@@ -29,9 +32,6 @@ import org.springframework.shell.standard.ShellComponent;
|
||||
import org.springframework.shell.standard.ShellMethod;
|
||||
import org.springframework.shell.standard.ShellOption;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Abstract class representing DDI device connecting directly to hawkVit.
|
||||
*/
|
||||
@@ -52,7 +52,7 @@ public class MultiDeviceApp {
|
||||
|
||||
@Bean
|
||||
DdiTenant ddiTenant(final Tenant defaultTenant,
|
||||
final HawkbitClient hawkbitClient) {
|
||||
final HawkbitClient hawkbitClient) {
|
||||
return new DdiTenant(defaultTenant, hawkbitClient);
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ public class MultiDeviceApp {
|
||||
public void startOne(@ShellOption("--id") final String controllerId) {
|
||||
final String securityTargetToken;
|
||||
if (setup) {
|
||||
securityTargetToken = mgmtApi.setupTargetToken(controllerId,null);
|
||||
securityTargetToken = mgmtApi.setupTargetToken(controllerId, null);
|
||||
} else {
|
||||
securityTargetToken = null;
|
||||
}
|
||||
@@ -96,12 +96,12 @@ public class MultiDeviceApp {
|
||||
ddiTenant.getController(controllerId).ifPresentOrElse(
|
||||
ddiController -> ddiController.start(Executors.newSingleThreadScheduledExecutor()),
|
||||
() -> ddiTenant.createController(Controller.builder()
|
||||
.controllerId(controllerId)
|
||||
.securityToken(securityTargetToken)
|
||||
.build(),updateHandler)
|
||||
.controllerId(controllerId)
|
||||
.securityToken(securityTargetToken)
|
||||
.build(), updateHandler)
|
||||
.setOverridePollMillis(10_000)
|
||||
.start(Executors.newSingleThreadScheduledExecutor())
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
@ShellMethod(key = "stop-one")
|
||||
|
||||
@@ -10,31 +10,31 @@
|
||||
|
||||
-->
|
||||
<project
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-sdk</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-sdk</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hawkbit-sdk-device</artifactId>
|
||||
<name>hawkBit :: SDK :: Device SDK</name>
|
||||
<description>Device SDK that could be used for development of devices on JVM based languages</description>
|
||||
<artifactId>hawkbit-sdk-device</artifactId>
|
||||
<name>hawkBit :: SDK :: Device SDK</name>
|
||||
<description>Device SDK that could be used for development of devices on JVM based languages</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-sdk-commons</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-sdk-commons</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-ddi-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-ddi-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -9,6 +9,17 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.sdk.device;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
@@ -28,17 +39,6 @@ import org.springframework.hateoas.Link;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Class representing DDI device connecting directly to hawkBit.
|
||||
*/
|
||||
@@ -81,10 +81,10 @@ public class DdiController {
|
||||
* @param tenant the tenant of the device belongs to
|
||||
* @param controller the controller
|
||||
* @param hawkbitClient a factory for creating to {@link DdiRootControllerRestApi} (and used)
|
||||
* for communication to hawkBit
|
||||
* for communication to hawkBit
|
||||
*/
|
||||
public DdiController(final Tenant tenant, final Controller controller,
|
||||
final UpdateHandler updateHandler, final HawkbitClient hawkbitClient) {
|
||||
final UpdateHandler updateHandler, final HawkbitClient hawkbitClient) {
|
||||
this.tenantId = tenant.getTenantId();
|
||||
gatewayToken = tenant.getGatewayToken();
|
||||
downloadAuthenticationEnabled = tenant.isDownloadAuthenticationEnabled();
|
||||
@@ -112,58 +112,88 @@ public class DdiController {
|
||||
currentActionId = null;
|
||||
}
|
||||
|
||||
public void updateAttribute(final String mode, final String key, final String value) {
|
||||
final DdiUpdateMode updateMode = switch (mode.toLowerCase()) {
|
||||
case "replace" -> DdiUpdateMode.REPLACE;
|
||||
case "remove" -> DdiUpdateMode.REMOVE;
|
||||
default -> DdiUpdateMode.MERGE;
|
||||
};
|
||||
|
||||
final DdiConfigData configData = new DdiConfigData(Collections.singletonMap(key, value), updateMode);
|
||||
|
||||
getDdiApi().putConfigData(configData, getTenantId(), getControllerId());
|
||||
}
|
||||
|
||||
void sendFeedback(final UpdateStatus updateStatus) {
|
||||
log.debug(LOG_PREFIX + "Send feedback {} -> {}", getTenantId(), getControllerId(), currentActionId, updateStatus);
|
||||
try {
|
||||
getDdiApi().postDeploymentBaseActionFeedback(updateStatus.feedback(), getTenantId(), getControllerId(),
|
||||
currentActionId);
|
||||
} catch (final RuntimeException e) {
|
||||
log.error(LOG_PREFIX + "Failed to send feedback {} -> {}", getTenantId(), getControllerId(),
|
||||
currentActionId, updateStatus, e);
|
||||
}
|
||||
|
||||
if (updateStatus.status() == UpdateStatus.Status.SUCCESSFUL ||
|
||||
updateStatus.status() == UpdateStatus.Status.FAILURE) {
|
||||
lastActionId = currentActionId;
|
||||
currentActionId = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void poll() {
|
||||
log.debug(LOG_PREFIX + " Polling ...", tenantId, controllerId);
|
||||
Optional.ofNullable(executorService).ifPresent(executor ->
|
||||
getControllerBase().ifPresentOrElse(
|
||||
controllerBase -> {
|
||||
final Optional<Link> confirmationBaseLink = getRequiredLink(controllerBase, CONFIRMATION_BASE_LINK);
|
||||
if (confirmationBaseLink.isPresent()) {
|
||||
final long actionId = getActionId(confirmationBaseLink.get());
|
||||
log.info(LOG_PREFIX + "Confirmation is required for action {}!", getTenantId(),
|
||||
getControllerId(), actionId);
|
||||
// TODO - confirmation handler
|
||||
sendConfirmationFeedback(actionId);
|
||||
executor.schedule(this::poll, IMMEDIATE_MS, TimeUnit.MILLISECONDS);
|
||||
} else {
|
||||
getRequiredLink(controllerBase, DEPLOYMENT_BASE_LINK).flatMap(this::getActionWithDeployment).ifPresentOrElse(actionWithDeployment -> {
|
||||
final long actionId = actionWithDeployment.getKey();
|
||||
if (currentActionId == null) {
|
||||
if (lastActionId != null && lastActionId == actionId) {
|
||||
log.info(LOG_PREFIX + "Still receive the last action {}",
|
||||
getTenantId(), getControllerId(), actionId);
|
||||
return;
|
||||
}
|
||||
getControllerBase().ifPresentOrElse(
|
||||
controllerBase -> {
|
||||
final Optional<Link> confirmationBaseLink = getRequiredLink(controllerBase, CONFIRMATION_BASE_LINK);
|
||||
if (confirmationBaseLink.isPresent()) {
|
||||
final long actionId = getActionId(confirmationBaseLink.get());
|
||||
log.info(LOG_PREFIX + "Confirmation is required for action {}!", getTenantId(),
|
||||
getControllerId(), actionId);
|
||||
// TODO - confirmation handler
|
||||
sendConfirmationFeedback(actionId);
|
||||
executor.schedule(this::poll, IMMEDIATE_MS, TimeUnit.MILLISECONDS);
|
||||
} else {
|
||||
getRequiredLink(controllerBase, DEPLOYMENT_BASE_LINK).flatMap(this::getActionWithDeployment)
|
||||
.ifPresentOrElse(actionWithDeployment -> {
|
||||
final long actionId = actionWithDeployment.getKey();
|
||||
if (currentActionId == null) {
|
||||
if (lastActionId != null && lastActionId == actionId) {
|
||||
log.info(LOG_PREFIX + "Still receive the last action {}",
|
||||
getTenantId(), getControllerId(), actionId);
|
||||
return;
|
||||
}
|
||||
|
||||
log.info(LOG_PREFIX + "Process action {}", getTenantId(), getControllerId(),
|
||||
actionId);
|
||||
final DdiDeployment deployment = actionWithDeployment.getValue().getDeployment();
|
||||
final DdiDeployment.HandlingType updateType = deployment.getUpdate();
|
||||
final List<DdiChunk> modules = deployment.getChunks();
|
||||
log.info(LOG_PREFIX + "Process action {}", getTenantId(), getControllerId(),
|
||||
actionId);
|
||||
final DdiDeployment deployment = actionWithDeployment.getValue().getDeployment();
|
||||
final DdiDeployment.HandlingType updateType = deployment.getUpdate();
|
||||
final List<DdiChunk> modules = deployment.getChunks();
|
||||
|
||||
currentActionId = actionId;
|
||||
executor.submit(
|
||||
updateHandler.getUpdateProcessor(this, updateType, modules));
|
||||
} else if (currentActionId != actionId) {
|
||||
// TODO - cancel and start new one?
|
||||
log.info(LOG_PREFIX + "Action {} is canceled while in process (new {})!", getTenantId(),
|
||||
getControllerId(), currentActionId, actionId);
|
||||
} // else same action - already processing
|
||||
}, () -> {
|
||||
if (currentActionId != null) {
|
||||
// TODO - cancel current?
|
||||
log.info(LOG_PREFIX + "Action {} is canceled while in process (not returned)!", getTenantId(),
|
||||
getControllerId(), getCurrentActionId());
|
||||
}
|
||||
});
|
||||
executor.schedule(this::poll, getPollMillis(controllerBase), TimeUnit.MILLISECONDS);
|
||||
currentActionId = actionId;
|
||||
executor.submit(
|
||||
updateHandler.getUpdateProcessor(this, updateType, modules));
|
||||
} else if (currentActionId != actionId) {
|
||||
// TODO - cancel and start new one?
|
||||
log.info(LOG_PREFIX + "Action {} is canceled while in process (new {})!", getTenantId(),
|
||||
getControllerId(), currentActionId, actionId);
|
||||
} // else same action - already processing
|
||||
}, () -> {
|
||||
if (currentActionId != null) {
|
||||
// TODO - cancel current?
|
||||
log.info(LOG_PREFIX + "Action {} is canceled while in process (not returned)!", getTenantId(),
|
||||
getControllerId(), getCurrentActionId());
|
||||
}
|
||||
});
|
||||
executor.schedule(this::poll, getPollMillis(controllerBase), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
},
|
||||
() -> {
|
||||
// error has occurred or no controller base hasn't been acquired
|
||||
executor.schedule(this::poll, DEFAULT_POLL_MS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
},
|
||||
() -> {
|
||||
// error has occurred or no controller base hasn't been acquired
|
||||
executor.schedule(this::poll, DEFAULT_POLL_MS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
));
|
||||
));
|
||||
}
|
||||
|
||||
private Optional<DdiControllerBase> getControllerBase() {
|
||||
@@ -211,42 +241,14 @@ public class DdiController {
|
||||
final ResponseEntity<DdiDeploymentBase> action = getDdiApi()
|
||||
.getControllerDeploymentBaseAction(getTenantId(), getControllerId(), actionId, -1, null);
|
||||
if (action.getStatusCode() != HttpStatus.OK) {
|
||||
log.warn(LOG_PREFIX + "Fail to get deployment action: {} -> {}", getTenantId(), getControllerId(), actionId, action.getStatusCode());
|
||||
log.warn(LOG_PREFIX + "Fail to get deployment action: {} -> {}", getTenantId(), getControllerId(), actionId,
|
||||
action.getStatusCode());
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.ofNullable(action.getBody() == null ? null : new AbstractMap.SimpleEntry<>(actionId, action.getBody()));
|
||||
}
|
||||
|
||||
public void updateAttribute(final String mode, final String key, final String value) {
|
||||
final DdiUpdateMode updateMode = switch (mode.toLowerCase()) {
|
||||
case "replace" -> DdiUpdateMode.REPLACE;
|
||||
case "remove" -> DdiUpdateMode.REMOVE;
|
||||
default -> DdiUpdateMode.MERGE;
|
||||
};
|
||||
|
||||
final DdiConfigData configData = new DdiConfigData(Collections.singletonMap(key, value), updateMode);
|
||||
|
||||
getDdiApi().putConfigData(configData, getTenantId(), getControllerId());
|
||||
}
|
||||
|
||||
void sendFeedback(final UpdateStatus updateStatus) {
|
||||
log.debug(LOG_PREFIX + "Send feedback {} -> {}", getTenantId(), getControllerId(), currentActionId, updateStatus);
|
||||
try {
|
||||
getDdiApi().postDeploymentBaseActionFeedback(updateStatus.feedback(), getTenantId(), getControllerId(),
|
||||
currentActionId);
|
||||
} catch (final RuntimeException e) {
|
||||
log.error(LOG_PREFIX + "Failed to send feedback {} -> {}", getTenantId(), getControllerId(),
|
||||
currentActionId, updateStatus, e);
|
||||
}
|
||||
|
||||
if (updateStatus.status() == UpdateStatus.Status.SUCCESSFUL ||
|
||||
updateStatus.status() == UpdateStatus.Status.FAILURE) {
|
||||
lastActionId = currentActionId;
|
||||
currentActionId = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void sendConfirmationFeedback(final long actionId) {
|
||||
final DdiConfirmationFeedback ddiConfirmationFeedback = new DdiConfirmationFeedback(
|
||||
DdiConfirmationFeedback.Confirmation.CONFIRMED, 0, Collections.singletonList(
|
||||
|
||||
@@ -9,15 +9,15 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.sdk.device;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.eclipse.hawkbit.sdk.Controller;
|
||||
import org.eclipse.hawkbit.sdk.HawkbitClient;
|
||||
import org.eclipse.hawkbit.sdk.Tenant;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* An in-memory simulated DDI Tenant to hold the controller twins in
|
||||
* memory and be able to retrieve them again.
|
||||
|
||||
@@ -9,6 +9,22 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.sdk.device;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HexFormat;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpGet;
|
||||
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
|
||||
@@ -28,22 +44,6 @@ import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HexFormat;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Update handler provide plug-in endpoint allowing for customization of the update processing.
|
||||
*/
|
||||
@@ -67,20 +67,16 @@ public interface UpdateHandler {
|
||||
@Slf4j
|
||||
class UpdateProcessor implements Runnable {
|
||||
|
||||
protected final Map<String, Path> downloads = new HashMap<>();
|
||||
private static final String LOG_PREFIX = "[{}:{}] ";
|
||||
|
||||
private static final String DOWNLOAD_LOG_MESSAGE = "Download ";
|
||||
private static final String EXPECTED = "(Expected: ";
|
||||
private static final String BUT_GOT_LOG_MESSAGE = " but got: ";
|
||||
private static final int MINIMUM_TOKEN_LENGTH_FOR_HINT = 6;
|
||||
|
||||
private final DdiController ddiController;
|
||||
|
||||
private final DdiDeployment.HandlingType updateType;
|
||||
private final List<DdiChunk> modules;
|
||||
|
||||
private final ArtifactHandler artifactHandler;
|
||||
protected final Map<String, Path> downloads = new HashMap<>();
|
||||
|
||||
public UpdateProcessor(
|
||||
final DdiController ddiController,
|
||||
@@ -186,101 +182,6 @@ public interface UpdateHandler {
|
||||
log.debug(LOG_PREFIX + "Cleaned up", ddiController.getTenantId(), ddiController.getControllerId());
|
||||
}
|
||||
|
||||
private void handleArtifact(
|
||||
final String targetToken, final String gatewayToken,
|
||||
final List<UpdateStatus> status, final DdiArtifact artifact) {
|
||||
artifact.getLink("download").ifPresentOrElse(
|
||||
// HTTPS
|
||||
link -> status.add(downloadUrl(link.getHref(), gatewayToken, targetToken,
|
||||
artifact.getHashes(), artifact.getSize())),
|
||||
// HTTP
|
||||
() -> status.add(downloadUrl(
|
||||
artifact.getLink("download-http")
|
||||
.map(Link::getHref)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Nor https nor http found!")),
|
||||
gatewayToken, targetToken,
|
||||
artifact.getHashes(), artifact.getSize()))
|
||||
);
|
||||
}
|
||||
|
||||
private UpdateStatus downloadUrl(
|
||||
final String url, final String gatewayToken, final String targetToken,
|
||||
final DdiArtifactHash hash, final long size) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(LOG_PREFIX + "Downloading {} with token {}, expected hash {} and size {}",
|
||||
ddiController.getTenantId(), ddiController.getControllerId(), url,
|
||||
hideTokenDetails(targetToken), hash, size);
|
||||
}
|
||||
|
||||
try {
|
||||
return readAndCheckDownloadUrl(url, gatewayToken, targetToken, hash, size);
|
||||
} catch (final IOException | KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
|
||||
log.error(LOG_PREFIX + "Failed to download {}",
|
||||
ddiController.getTenantId(), ddiController.getControllerId(), url, e);
|
||||
return new UpdateStatus(
|
||||
UpdateStatus.Status.FAILURE,
|
||||
List.of("Failed to download " + url + ": " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private UpdateStatus readAndCheckDownloadUrl(final String url, final String gatewayToken,
|
||||
final String targetToken, final DdiArtifactHash hash, final long size)
|
||||
throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException {
|
||||
final Validator sizeValidator = sizeValidator(size);
|
||||
final Validator hashValidator = hashValidator(hash);
|
||||
final ArtifactHandler.DownloadHandler downloadHandler = artifactHandler.getDownloadHandler(url);
|
||||
|
||||
try (final CloseableHttpClient httpclient = createHttpClientThatAcceptsAllServerCerts()) {
|
||||
final HttpGet request = new HttpGet(url);
|
||||
if (StringUtils.hasLength(targetToken)) {
|
||||
request.addHeader(HttpHeaders.AUTHORIZATION, "TargetToken " + targetToken);
|
||||
} else if (StringUtils.hasLength(gatewayToken)) {
|
||||
request.addHeader(HttpHeaders.AUTHORIZATION, "GatewayToken " + gatewayToken);
|
||||
}
|
||||
|
||||
return httpclient.execute(request, response -> {
|
||||
try {
|
||||
if (response.getCode() != HttpStatus.OK.value()) {
|
||||
throw new IllegalStateException("Unexpected status code: " + response.getCode());
|
||||
}
|
||||
|
||||
if (response.getEntity().getContentLength() != size) {
|
||||
throw new IllegalArgumentException("Wrong content length " + EXPECTED + size + BUT_GOT_LOG_MESSAGE + response.getEntity()
|
||||
.getContentLength() + ")!");
|
||||
}
|
||||
|
||||
final byte[] buff = new byte[32 * 1024];
|
||||
try (final InputStream is = response.getEntity().getContent()) {
|
||||
for (int read; (read = is.read(buff)) != -1; ) {
|
||||
sizeValidator.read(buff, read);
|
||||
hashValidator.read(buff, read);
|
||||
downloadHandler.read(buff, 0, read);
|
||||
}
|
||||
}
|
||||
sizeValidator.validate();
|
||||
hashValidator.validate();
|
||||
|
||||
final String message = "Downloaded " + url + " (" + size + " bytes)";
|
||||
log.debug(LOG_PREFIX + message, ddiController.getTenantId(), ddiController.getControllerId());
|
||||
downloadHandler.finished(ArtifactHandler.DownloadHandler.Status.SUCCESS);
|
||||
downloadHandler.download().ifPresent(path -> downloads.put(url, path));
|
||||
return new UpdateStatus(UpdateStatus.Status.SUCCESSFUL, List.of(message));
|
||||
} catch (final Exception e) {
|
||||
final String message = e.getMessage();
|
||||
if (log.isTraceEnabled()) {
|
||||
log.error(LOG_PREFIX + DOWNLOAD_LOG_MESSAGE + url + " failed: " + message,
|
||||
ddiController.getTenantId(), ddiController.getControllerId(), e);
|
||||
} else {
|
||||
log.error(LOG_PREFIX + DOWNLOAD_LOG_MESSAGE + url + " failed: " + message,
|
||||
ddiController.getTenantId(), ddiController.getControllerId());
|
||||
}
|
||||
downloadHandler.finished(ArtifactHandler.DownloadHandler.Status.ERROR);
|
||||
return new UpdateStatus(UpdateStatus.Status.FAILURE, List.of(message));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static String hideTokenDetails(final String targetToken) {
|
||||
if (targetToken == null) {
|
||||
return "<NULL!>";
|
||||
@@ -314,14 +215,6 @@ public interface UpdateHandler {
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
private interface Validator {
|
||||
|
||||
void read(final byte[] buff, final int len);
|
||||
|
||||
void validate();
|
||||
}
|
||||
|
||||
private static Validator sizeValidator(final long size) {
|
||||
return new Validator() {
|
||||
|
||||
@@ -384,6 +277,7 @@ public interface UpdateHandler {
|
||||
}
|
||||
|
||||
return new Validator() {
|
||||
|
||||
@Override
|
||||
public void read(final byte[] buff, final int len) {
|
||||
hashValidators.forEach(hashValidator -> hashValidator.update(buff, len));
|
||||
@@ -395,5 +289,108 @@ public interface UpdateHandler {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void handleArtifact(
|
||||
final String targetToken, final String gatewayToken,
|
||||
final List<UpdateStatus> status, final DdiArtifact artifact) {
|
||||
artifact.getLink("download").ifPresentOrElse(
|
||||
// HTTPS
|
||||
link -> status.add(downloadUrl(link.getHref(), gatewayToken, targetToken,
|
||||
artifact.getHashes(), artifact.getSize())),
|
||||
// HTTP
|
||||
() -> status.add(downloadUrl(
|
||||
artifact.getLink("download-http")
|
||||
.map(Link::getHref)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Nor https nor http found!")),
|
||||
gatewayToken, targetToken,
|
||||
artifact.getHashes(), artifact.getSize()))
|
||||
);
|
||||
}
|
||||
|
||||
private UpdateStatus downloadUrl(
|
||||
final String url, final String gatewayToken, final String targetToken,
|
||||
final DdiArtifactHash hash, final long size) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(LOG_PREFIX + "Downloading {} with token {}, expected hash {} and size {}",
|
||||
ddiController.getTenantId(), ddiController.getControllerId(), url,
|
||||
hideTokenDetails(targetToken), hash, size);
|
||||
}
|
||||
|
||||
try {
|
||||
return readAndCheckDownloadUrl(url, gatewayToken, targetToken, hash, size);
|
||||
} catch (final IOException | KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
|
||||
log.error(LOG_PREFIX + "Failed to download {}",
|
||||
ddiController.getTenantId(), ddiController.getControllerId(), url, e);
|
||||
return new UpdateStatus(
|
||||
UpdateStatus.Status.FAILURE,
|
||||
List.of("Failed to download " + url + ": " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private UpdateStatus readAndCheckDownloadUrl(final String url, final String gatewayToken,
|
||||
final String targetToken, final DdiArtifactHash hash, final long size)
|
||||
throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException {
|
||||
final Validator sizeValidator = sizeValidator(size);
|
||||
final Validator hashValidator = hashValidator(hash);
|
||||
final ArtifactHandler.DownloadHandler downloadHandler = artifactHandler.getDownloadHandler(url);
|
||||
|
||||
try (final CloseableHttpClient httpclient = createHttpClientThatAcceptsAllServerCerts()) {
|
||||
final HttpGet request = new HttpGet(url);
|
||||
if (StringUtils.hasLength(targetToken)) {
|
||||
request.addHeader(HttpHeaders.AUTHORIZATION, "TargetToken " + targetToken);
|
||||
} else if (StringUtils.hasLength(gatewayToken)) {
|
||||
request.addHeader(HttpHeaders.AUTHORIZATION, "GatewayToken " + gatewayToken);
|
||||
}
|
||||
|
||||
return httpclient.execute(request, response -> {
|
||||
try {
|
||||
if (response.getCode() != HttpStatus.OK.value()) {
|
||||
throw new IllegalStateException("Unexpected status code: " + response.getCode());
|
||||
}
|
||||
|
||||
if (response.getEntity().getContentLength() != size) {
|
||||
throw new IllegalArgumentException(
|
||||
"Wrong content length " + EXPECTED + size + BUT_GOT_LOG_MESSAGE + response.getEntity()
|
||||
.getContentLength() + ")!");
|
||||
}
|
||||
|
||||
final byte[] buff = new byte[32 * 1024];
|
||||
try (final InputStream is = response.getEntity().getContent()) {
|
||||
for (int read; (read = is.read(buff)) != -1; ) {
|
||||
sizeValidator.read(buff, read);
|
||||
hashValidator.read(buff, read);
|
||||
downloadHandler.read(buff, 0, read);
|
||||
}
|
||||
}
|
||||
sizeValidator.validate();
|
||||
hashValidator.validate();
|
||||
|
||||
final String message = "Downloaded " + url + " (" + size + " bytes)";
|
||||
log.debug(LOG_PREFIX + message, ddiController.getTenantId(), ddiController.getControllerId());
|
||||
downloadHandler.finished(ArtifactHandler.DownloadHandler.Status.SUCCESS);
|
||||
downloadHandler.download().ifPresent(path -> downloads.put(url, path));
|
||||
return new UpdateStatus(UpdateStatus.Status.SUCCESSFUL, List.of(message));
|
||||
} catch (final Exception e) {
|
||||
final String message = e.getMessage();
|
||||
if (log.isTraceEnabled()) {
|
||||
log.error(LOG_PREFIX + DOWNLOAD_LOG_MESSAGE + url + " failed: " + message,
|
||||
ddiController.getTenantId(), ddiController.getControllerId(), e);
|
||||
} else {
|
||||
log.error(LOG_PREFIX + DOWNLOAD_LOG_MESSAGE + url + " failed: " + message,
|
||||
ddiController.getTenantId(), ddiController.getControllerId());
|
||||
}
|
||||
downloadHandler.finished(ArtifactHandler.DownloadHandler.Status.ERROR);
|
||||
return new UpdateStatus(UpdateStatus.Status.FAILURE, List.of(message));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private interface Validator {
|
||||
|
||||
void read(final byte[] buff, final int len);
|
||||
|
||||
void validate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,14 +9,19 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.sdk.device;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiActionFeedback;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiResult;
|
||||
import org.eclipse.hawkbit.ddi.json.model.DdiStatus;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record UpdateStatus(Status status, List<String> messages) {
|
||||
|
||||
DdiActionFeedback feedback() {
|
||||
return new DdiActionFeedback(null,
|
||||
new DdiStatus(status.executionStatus, new DdiResult(status.finalResult, null), status.code, messages));
|
||||
}
|
||||
|
||||
/**
|
||||
* The status to response to the hawkBit update server if an simulated update process should be respond with
|
||||
* successful or failure update.
|
||||
@@ -59,9 +64,4 @@ public record UpdateStatus(Status status, List<String> messages) {
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
DdiActionFeedback feedback() {
|
||||
return new DdiActionFeedback(null,
|
||||
new DdiStatus(status.executionStatus, new DdiResult(status.finalResult, null), status.code, messages));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,35 +10,35 @@
|
||||
|
||||
-->
|
||||
<project
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-sdk</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-sdk</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hawkbit-sdk-dmf</artifactId>
|
||||
<name>hawkBit :: SDK :: DMF SDK</name>
|
||||
<description>DMF SDK that could be used for development of DMF integrations on JVM based languages</description>
|
||||
<artifactId>hawkbit-sdk-dmf</artifactId>
|
||||
<name>hawkBit :: SDK :: DMF SDK</name>
|
||||
<description>DMF SDK that could be used for development of DMF integrations on JVM based languages</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-sdk-commons</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-sdk-commons</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-dmf-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
<artifactId>hawkbit-dmf-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.amqp</groupId>
|
||||
<artifactId>spring-rabbit</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.amqp</groupId>
|
||||
<artifactId>spring-rabbit</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -9,6 +9,10 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.sdk.dmf;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -19,10 +23,6 @@ import org.eclipse.hawkbit.sdk.Controller;
|
||||
import org.eclipse.hawkbit.sdk.Tenant;
|
||||
import org.eclipse.hawkbit.sdk.dmf.amqp.DmfSender;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
/**
|
||||
* Class representing DMF device twin connecting to hawkBit via DMF.
|
||||
*/
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.sdk.dmf;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.eclipse.hawkbit.sdk.Controller;
|
||||
import org.eclipse.hawkbit.sdk.Tenant;
|
||||
@@ -16,11 +21,6 @@ import org.eclipse.hawkbit.sdk.dmf.amqp.Amqp;
|
||||
import org.eclipse.hawkbit.sdk.dmf.amqp.VHost;
|
||||
import org.springframework.amqp.core.Message;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* An in-memory simulated DMF Tenant to hold the controller twins in
|
||||
* memory and be able to retrieve them again.
|
||||
|
||||
@@ -9,6 +9,22 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.sdk.dmf;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HexFormat;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpGet;
|
||||
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
|
||||
@@ -29,22 +45,6 @@ import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HexFormat;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Update handler provide plug-in endpoint allowing for customization of the update processing.
|
||||
*/
|
||||
@@ -68,20 +68,16 @@ public interface UpdateHandler {
|
||||
@Slf4j
|
||||
class UpdateProcessor implements Runnable {
|
||||
|
||||
protected final Map<String, Path> downloads = new HashMap<>();
|
||||
private static final String LOG_PREFIX = "[{}:{}] ";
|
||||
|
||||
private static final String DOWNLOAD_LOG_MESSAGE = "Download ";
|
||||
private static final String EXPECTED = "(Expected: ";
|
||||
private static final String BUT_GOT_LOG_MESSAGE = " but got: ";
|
||||
private static final int MINIMUM_TOKEN_LENGTH_FOR_HINT = 6;
|
||||
|
||||
private final DmfController dmfController;
|
||||
|
||||
private final DmfDownloadAndUpdateRequest updateRequest;
|
||||
private final EventTopic eventTopic;
|
||||
|
||||
private final ArtifactHandler artifactHandler;
|
||||
protected final Map<String, Path> downloads = new HashMap<>();
|
||||
|
||||
public UpdateProcessor(
|
||||
final DmfController dmfController,
|
||||
@@ -189,94 +185,6 @@ public interface UpdateHandler {
|
||||
log.debug(LOG_PREFIX + "Cleaned up", dmfController.getTenantId(), dmfController.getControllerId());
|
||||
}
|
||||
|
||||
private void handleArtifact(
|
||||
final String targetToken,
|
||||
final List<UpdateStatus> status, final DmfArtifact artifact) {
|
||||
if (artifact.getUrls().containsKey("HTTPS")) {
|
||||
status.add(downloadUrl(artifact.getUrls().get("HTTPS"), targetToken,
|
||||
artifact.getHashes(), artifact.getSize()));
|
||||
} else if (artifact.getUrls().containsKey("HTTP")) {
|
||||
status.add(downloadUrl(artifact.getUrls().get("HTTP"), targetToken,
|
||||
artifact.getHashes(), artifact.getSize()));
|
||||
}
|
||||
}
|
||||
|
||||
private UpdateStatus downloadUrl(
|
||||
final String url, final String targetToken,
|
||||
final DmfArtifactHash hash, final long size) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(LOG_PREFIX + "Downloading {} with token {}, expected hash {} and size {}",
|
||||
dmfController.getTenantId(), dmfController.getControllerId(), url,
|
||||
hideTokenDetails(targetToken), hash, size);
|
||||
}
|
||||
|
||||
try {
|
||||
return readAndCheckDownloadUrl(url, targetToken, hash, size);
|
||||
} catch (final IOException | KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
|
||||
log.error(LOG_PREFIX + "Failed to download {}",
|
||||
dmfController.getTenantId(), dmfController.getControllerId(), url, e);
|
||||
return new UpdateStatus(
|
||||
DmfActionStatus.ERROR,
|
||||
List.of("Failed to download " + url + ": " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private UpdateStatus readAndCheckDownloadUrl(final String url,
|
||||
final String targetToken, final DmfArtifactHash hash, final long size)
|
||||
throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException {
|
||||
final Validator sizeValidator = sizeValidator(size);
|
||||
final Validator hashValidator = hashValidator(hash);
|
||||
final ArtifactHandler.DownloadHandler downloadHandler = artifactHandler.getDownloadHandler(url);
|
||||
|
||||
try (final CloseableHttpClient httpclient = createHttpClientThatAcceptsAllServerCerts()) {
|
||||
final HttpGet request = new HttpGet(url);
|
||||
if (StringUtils.hasLength(targetToken)) {
|
||||
request.addHeader(HttpHeaders.AUTHORIZATION, "TargetToken " + targetToken);
|
||||
}
|
||||
|
||||
return httpclient.execute(request, response -> {
|
||||
try {
|
||||
if (response.getCode() != HttpStatus.OK.value()) {
|
||||
throw new IllegalStateException("Unexpected status code: " + response.getCode());
|
||||
}
|
||||
|
||||
if (response.getEntity().getContentLength() != size) {
|
||||
throw new IllegalArgumentException("Wrong content length " + EXPECTED + size + BUT_GOT_LOG_MESSAGE + response.getEntity()
|
||||
.getContentLength() + ")!");
|
||||
}
|
||||
|
||||
final byte[] buff = new byte[32 * 1024];
|
||||
try (final InputStream is = response.getEntity().getContent()) {
|
||||
for (int read; (read = is.read(buff)) != -1; ) {
|
||||
sizeValidator.read(buff, read);
|
||||
hashValidator.read(buff, read);
|
||||
downloadHandler.read(buff, 0, read);
|
||||
}
|
||||
}
|
||||
sizeValidator.validate();
|
||||
hashValidator.validate();
|
||||
|
||||
final String message = "Downloaded " + url + " (" + size + " bytes)";
|
||||
log.debug(LOG_PREFIX + message, dmfController.getTenantId(), dmfController.getControllerId());
|
||||
downloadHandler.finished(ArtifactHandler.DownloadHandler.Status.SUCCESS);
|
||||
downloadHandler.download().ifPresent(path -> downloads.put(url, path));
|
||||
return new UpdateStatus(DmfActionStatus.FINISHED, List.of(message));
|
||||
} catch (final Exception e) {
|
||||
final String message = e.getMessage();
|
||||
if (log.isTraceEnabled()) {
|
||||
log.error(LOG_PREFIX + DOWNLOAD_LOG_MESSAGE + url + " failed: " + message,
|
||||
dmfController.getTenantId(), dmfController.getControllerId(), e);
|
||||
} else {
|
||||
log.error(LOG_PREFIX + DOWNLOAD_LOG_MESSAGE + url + " failed: " + message,
|
||||
dmfController.getTenantId(), dmfController.getControllerId());
|
||||
}
|
||||
downloadHandler.finished(ArtifactHandler.DownloadHandler.Status.ERROR);
|
||||
return new UpdateStatus(DmfActionStatus.ERROR, List.of(message));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static String hideTokenDetails(final String targetToken) {
|
||||
if (targetToken == null) {
|
||||
return "<NULL!>";
|
||||
@@ -310,14 +218,6 @@ public interface UpdateHandler {
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
private interface Validator {
|
||||
|
||||
void read(final byte[] buff, final int len);
|
||||
|
||||
void validate();
|
||||
}
|
||||
|
||||
private static Validator sizeValidator(final long size) {
|
||||
return new Validator() {
|
||||
|
||||
@@ -377,6 +277,7 @@ public interface UpdateHandler {
|
||||
}
|
||||
|
||||
return new Validator() {
|
||||
|
||||
@Override
|
||||
public void read(final byte[] buff, final int len) {
|
||||
hashValidators.forEach(hashValidator -> hashValidator.update(buff, len));
|
||||
@@ -388,5 +289,101 @@ public interface UpdateHandler {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void handleArtifact(
|
||||
final String targetToken,
|
||||
final List<UpdateStatus> status, final DmfArtifact artifact) {
|
||||
if (artifact.getUrls().containsKey("HTTPS")) {
|
||||
status.add(downloadUrl(artifact.getUrls().get("HTTPS"), targetToken,
|
||||
artifact.getHashes(), artifact.getSize()));
|
||||
} else if (artifact.getUrls().containsKey("HTTP")) {
|
||||
status.add(downloadUrl(artifact.getUrls().get("HTTP"), targetToken,
|
||||
artifact.getHashes(), artifact.getSize()));
|
||||
}
|
||||
}
|
||||
|
||||
private UpdateStatus downloadUrl(
|
||||
final String url, final String targetToken,
|
||||
final DmfArtifactHash hash, final long size) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(LOG_PREFIX + "Downloading {} with token {}, expected hash {} and size {}",
|
||||
dmfController.getTenantId(), dmfController.getControllerId(), url,
|
||||
hideTokenDetails(targetToken), hash, size);
|
||||
}
|
||||
|
||||
try {
|
||||
return readAndCheckDownloadUrl(url, targetToken, hash, size);
|
||||
} catch (final IOException | KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
|
||||
log.error(LOG_PREFIX + "Failed to download {}",
|
||||
dmfController.getTenantId(), dmfController.getControllerId(), url, e);
|
||||
return new UpdateStatus(
|
||||
DmfActionStatus.ERROR,
|
||||
List.of("Failed to download " + url + ": " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private UpdateStatus readAndCheckDownloadUrl(final String url,
|
||||
final String targetToken, final DmfArtifactHash hash, final long size)
|
||||
throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException {
|
||||
final Validator sizeValidator = sizeValidator(size);
|
||||
final Validator hashValidator = hashValidator(hash);
|
||||
final ArtifactHandler.DownloadHandler downloadHandler = artifactHandler.getDownloadHandler(url);
|
||||
|
||||
try (final CloseableHttpClient httpclient = createHttpClientThatAcceptsAllServerCerts()) {
|
||||
final HttpGet request = new HttpGet(url);
|
||||
if (StringUtils.hasLength(targetToken)) {
|
||||
request.addHeader(HttpHeaders.AUTHORIZATION, "TargetToken " + targetToken);
|
||||
}
|
||||
|
||||
return httpclient.execute(request, response -> {
|
||||
try {
|
||||
if (response.getCode() != HttpStatus.OK.value()) {
|
||||
throw new IllegalStateException("Unexpected status code: " + response.getCode());
|
||||
}
|
||||
|
||||
if (response.getEntity().getContentLength() != size) {
|
||||
throw new IllegalArgumentException(
|
||||
"Wrong content length " + EXPECTED + size + BUT_GOT_LOG_MESSAGE + response.getEntity()
|
||||
.getContentLength() + ")!");
|
||||
}
|
||||
|
||||
final byte[] buff = new byte[32 * 1024];
|
||||
try (final InputStream is = response.getEntity().getContent()) {
|
||||
for (int read; (read = is.read(buff)) != -1; ) {
|
||||
sizeValidator.read(buff, read);
|
||||
hashValidator.read(buff, read);
|
||||
downloadHandler.read(buff, 0, read);
|
||||
}
|
||||
}
|
||||
sizeValidator.validate();
|
||||
hashValidator.validate();
|
||||
|
||||
final String message = "Downloaded " + url + " (" + size + " bytes)";
|
||||
log.debug(LOG_PREFIX + message, dmfController.getTenantId(), dmfController.getControllerId());
|
||||
downloadHandler.finished(ArtifactHandler.DownloadHandler.Status.SUCCESS);
|
||||
downloadHandler.download().ifPresent(path -> downloads.put(url, path));
|
||||
return new UpdateStatus(DmfActionStatus.FINISHED, List.of(message));
|
||||
} catch (final Exception e) {
|
||||
final String message = e.getMessage();
|
||||
if (log.isTraceEnabled()) {
|
||||
log.error(LOG_PREFIX + DOWNLOAD_LOG_MESSAGE + url + " failed: " + message,
|
||||
dmfController.getTenantId(), dmfController.getControllerId(), e);
|
||||
} else {
|
||||
log.error(LOG_PREFIX + DOWNLOAD_LOG_MESSAGE + url + " failed: " + message,
|
||||
dmfController.getTenantId(), dmfController.getControllerId());
|
||||
}
|
||||
downloadHandler.finished(ArtifactHandler.DownloadHandler.Status.ERROR);
|
||||
return new UpdateStatus(DmfActionStatus.ERROR, List.of(message));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private interface Validator {
|
||||
|
||||
void read(final byte[] buff, final int len);
|
||||
|
||||
void validate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,8 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.sdk.dmf;
|
||||
|
||||
import org.eclipse.hawkbit.dmf.json.model.DmfActionStatus;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.hawkbit.dmf.json.model.DmfActionStatus;
|
||||
|
||||
public record UpdateStatus(DmfActionStatus status, List<String> messages) {}
|
||||
@@ -9,6 +9,10 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.sdk.dmf.amqp;
|
||||
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.hawkbit.sdk.Tenant.DMF;
|
||||
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
|
||||
@@ -16,10 +20,6 @@ import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Abstract class for connecting to AMQP host.
|
||||
*/
|
||||
@@ -41,7 +41,7 @@ public class Amqp {
|
||||
|
||||
public VHost getVhost(final DMF dmf, final boolean initVHost) {
|
||||
final String vHost = dmf == null || ObjectUtils.isEmpty(dmf.getVirtualHost()) ?
|
||||
(rabbitProperties.getVirtualHost() == null ? "/" :rabbitProperties.getVirtualHost()) :
|
||||
(rabbitProperties.getVirtualHost() == null ? "/" : rabbitProperties.getVirtualHost()) :
|
||||
dmf.getVirtualHost();
|
||||
return vHosts.computeIfAbsent(vHost, vh -> new VHost(getConnectionFactory(dmf, vHost), amqpProperties, initVHost));
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ package org.eclipse.hawkbit.sdk.dmf.amqp;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Bean which holds the necessary properties for configuring the AMQP connection.
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.sdk.dmf.amqp;
|
||||
|
||||
import static org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings.DMF_EXCHANGE;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
@@ -31,17 +33,14 @@ import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.amqp.support.converter.AbstractJavaTypeMapper;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import static org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings.DMF_EXCHANGE;
|
||||
|
||||
/**
|
||||
* Sender service to send messages to update server.
|
||||
*/
|
||||
@Slf4j
|
||||
public class DmfSender {
|
||||
|
||||
private static final byte[] EMPTY_BODY = new byte[0];
|
||||
|
||||
protected final RabbitTemplate rabbitTemplate;
|
||||
private static final byte[] EMPTY_BODY = new byte[0];
|
||||
private final AmqpProperties amqpProperties;
|
||||
private final ConcurrentHashMap<String, BiConsumer<String, Message>> pingListeners = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
@@ -9,6 +9,15 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.sdk.dmf.amqp;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.EventTopic;
|
||||
import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey;
|
||||
@@ -33,24 +42,15 @@ import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
import org.springframework.amqp.support.converter.AbstractJavaTypeMapper;
|
||||
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Abstract class for sender and receiver service.
|
||||
*/
|
||||
@Slf4j
|
||||
public class VHost extends DmfSender implements MessageListener {
|
||||
|
||||
private static final String REGEX_EXTRACT_ACTION_ID = "[^0-9]";
|
||||
private final SimpleMessageListenerContainer container;
|
||||
private final ConcurrentHashMap<String, DmfTenant> dmfTenants = new ConcurrentHashMap<>();
|
||||
|
||||
private final Set<Long> openActions = Collections.synchronizedSet(new HashSet<>());
|
||||
|
||||
public VHost(final ConnectionFactory connectionFactory, final AmqpProperties amqpProperties) {
|
||||
@@ -80,7 +80,7 @@ public class VHost extends DmfSender implements MessageListener {
|
||||
rabbitAdmin.declareExchange(exchange);
|
||||
rabbitAdmin.declareBinding(BindingBuilder.bind(queue).to(exchange));
|
||||
}
|
||||
|
||||
|
||||
container = new SimpleMessageListenerContainer();
|
||||
container.setConnectionFactory(connectionFactory);
|
||||
container.setQueueNames(amqpProperties.getReceiverConnectorQueueFromSp());
|
||||
@@ -88,11 +88,6 @@ public class VHost extends DmfSender implements MessageListener {
|
||||
container.start();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
container.stop();
|
||||
rabbitTemplate.destroy();
|
||||
}
|
||||
|
||||
public void register(final DmfTenant dmfTenant) {
|
||||
dmfTenants.put(dmfTenant.getTenant().getTenantId(), dmfTenant);
|
||||
}
|
||||
@@ -100,8 +95,8 @@ public class VHost extends DmfSender implements MessageListener {
|
||||
@Override
|
||||
public void onMessage(final Message message) {
|
||||
final String tenantId = getTenant(message);
|
||||
final String controllerId = (String)message.getMessageProperties().getHeaders().get(MessageHeaderKey.THING_ID);
|
||||
final String type = (String)message.getMessageProperties().getHeaders().get(MessageHeaderKey.TYPE);
|
||||
final String controllerId = (String) message.getMessageProperties().getHeaders().get(MessageHeaderKey.THING_ID);
|
||||
final String type = (String) message.getMessageProperties().getHeaders().get(MessageHeaderKey.TYPE);
|
||||
|
||||
log.info("Message received for target {}, value : {}", controllerId, message);
|
||||
switch (MessageType.valueOf(type)) {
|
||||
@@ -130,6 +125,62 @@ public class VHost extends DmfSender implements MessageListener {
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleAttributeUpdateRequest(final Message message, final String controllerId) {
|
||||
final String tenantId = getTenant(message);
|
||||
Optional.ofNullable(dmfTenants.get(tenantId))
|
||||
.flatMap(dmfTenant -> dmfTenant.getController(controllerId))
|
||||
.ifPresent(controller ->
|
||||
updateAttributes(tenantId, controllerId, DmfUpdateMode.MERGE, controller.getAttributes()));
|
||||
}
|
||||
|
||||
protected void handleCancelDownloadAction(final Message message, final String thingId) {
|
||||
final String tenant = getTenant(message);
|
||||
final Long actionId = extractActionIdFrom(message);
|
||||
|
||||
processCancelDownloadAction(thingId, tenant, actionId);
|
||||
}
|
||||
|
||||
protected void handleUpdateProcess(final Message message, final String controllerId, final EventTopic actionType) {
|
||||
final String tenant = getTenant(message);
|
||||
final DmfDownloadAndUpdateRequest downloadAndUpdateRequest = convertMessage(message,
|
||||
DmfDownloadAndUpdateRequest.class);
|
||||
dmfTenants.get(tenant).getController(controllerId)
|
||||
.ifPresent(controller -> controller.setCurrentActionId(downloadAndUpdateRequest.getActionId()));
|
||||
processUpdate(tenant, controllerId, actionType, downloadAndUpdateRequest);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
container.stop();
|
||||
rabbitTemplate.destroy();
|
||||
}
|
||||
|
||||
private static String getTenant(final Message message) {
|
||||
final MessageProperties messageProperties = message.getMessageProperties();
|
||||
final Map<String, Object> headers = messageProperties.getHeaders();
|
||||
return (String) headers.get(MessageHeaderKey.TENANT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to validate if content type is set in the message properties.
|
||||
*
|
||||
* @param message the message to get validated
|
||||
*/
|
||||
private static void checkContentTypeJson(final Message message) {
|
||||
if (message.getBody().length == 0) {
|
||||
return;
|
||||
}
|
||||
final MessageProperties messageProperties = message.getMessageProperties();
|
||||
final String headerContentType = (String) messageProperties.getHeaders().get("content-type");
|
||||
if (null != headerContentType) {
|
||||
messageProperties.setContentType(headerContentType);
|
||||
}
|
||||
final String contentType = messageProperties.getContentType();
|
||||
if (contentType != null && contentType.contains("json")) {
|
||||
return;
|
||||
}
|
||||
throw new AmqpRejectAndDontRequeueException("Content-Type is not JSON compatible");
|
||||
}
|
||||
|
||||
private void handleEventMessage(final Message message, final String thingId) {
|
||||
final Object eventHeader = message.getMessageProperties().getHeaders().get(MessageHeaderKey.TOPIC);
|
||||
if (eventHeader == null) {
|
||||
@@ -138,27 +189,26 @@ public class VHost extends DmfSender implements MessageListener {
|
||||
}
|
||||
|
||||
// Exception squid:S2259 - Checked before
|
||||
@SuppressWarnings({ "squid:S2259" })
|
||||
final EventTopic eventTopic = EventTopic.valueOf(eventHeader.toString());
|
||||
@SuppressWarnings({ "squid:S2259" }) final EventTopic eventTopic = EventTopic.valueOf(eventHeader.toString());
|
||||
switch (eventTopic) {
|
||||
case CONFIRM:
|
||||
handleConfirmation(message, thingId);
|
||||
break;
|
||||
case DOWNLOAD_AND_INSTALL, DOWNLOAD:
|
||||
handleUpdateProcess(message, thingId, eventTopic);
|
||||
break;
|
||||
case CANCEL_DOWNLOAD:
|
||||
handleCancelDownloadAction(message, thingId);
|
||||
break;
|
||||
case REQUEST_ATTRIBUTES_UPDATE:
|
||||
handleAttributeUpdateRequest(message, thingId);
|
||||
break;
|
||||
case MULTI_ACTION:
|
||||
handleMultiActionRequest(message, thingId);
|
||||
break;
|
||||
default:
|
||||
log.info("No valid event property: {}", eventTopic);
|
||||
break;
|
||||
case CONFIRM:
|
||||
handleConfirmation(message, thingId);
|
||||
break;
|
||||
case DOWNLOAD_AND_INSTALL, DOWNLOAD:
|
||||
handleUpdateProcess(message, thingId, eventTopic);
|
||||
break;
|
||||
case CANCEL_DOWNLOAD:
|
||||
handleCancelDownloadAction(message, thingId);
|
||||
break;
|
||||
case REQUEST_ATTRIBUTES_UPDATE:
|
||||
handleAttributeUpdateRequest(message, thingId);
|
||||
break;
|
||||
case MULTI_ACTION:
|
||||
handleMultiActionRequest(message, thingId);
|
||||
break;
|
||||
default:
|
||||
log.info("No valid event property: {}", eventTopic);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +216,6 @@ public class VHost extends DmfSender implements MessageListener {
|
||||
log.warn("Handle confirmed received for {}! Skip it!", controllerId);
|
||||
}
|
||||
|
||||
private static final String REGEX_EXTRACT_ACTION_ID = "[^0-9]";
|
||||
private long extractActionIdFrom(final Message message) {
|
||||
final String messageAsString = message.toString();
|
||||
final String requiredMessageContent = messageAsString
|
||||
@@ -191,57 +240,29 @@ public class VHost extends DmfSender implements MessageListener {
|
||||
openActions.add(actionId);
|
||||
|
||||
switch (eventTopic) {
|
||||
case DOWNLOAD:
|
||||
case DOWNLOAD_AND_INSTALL:
|
||||
if (action instanceof DmfDownloadAndUpdateRequest) {
|
||||
processUpdate(tenant, controllerId, eventTopic, (DmfDownloadAndUpdateRequest) action);
|
||||
}
|
||||
break;
|
||||
case CANCEL_DOWNLOAD:
|
||||
processCancelDownloadAction(controllerId, tenant, action.getActionId());
|
||||
break;
|
||||
default:
|
||||
openActions.remove(actionId);
|
||||
log.info("No valid event property in MULTI_ACTION.");
|
||||
break;
|
||||
case DOWNLOAD:
|
||||
case DOWNLOAD_AND_INSTALL:
|
||||
if (action instanceof DmfDownloadAndUpdateRequest) {
|
||||
processUpdate(tenant, controllerId, eventTopic, (DmfDownloadAndUpdateRequest) action);
|
||||
}
|
||||
break;
|
||||
case CANCEL_DOWNLOAD:
|
||||
processCancelDownloadAction(controllerId, tenant, action.getActionId());
|
||||
break;
|
||||
default:
|
||||
openActions.remove(actionId);
|
||||
log.info("No valid event property in MULTI_ACTION.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleAttributeUpdateRequest(final Message message, final String controllerId) {
|
||||
final String tenantId = getTenant(message);
|
||||
Optional.ofNullable(dmfTenants.get(tenantId))
|
||||
.flatMap(dmfTenant -> dmfTenant.getController(controllerId))
|
||||
.ifPresent(controller ->
|
||||
updateAttributes(tenantId, controllerId, DmfUpdateMode.MERGE, controller.getAttributes()));
|
||||
}
|
||||
|
||||
private static String getTenant(final Message message) {
|
||||
final MessageProperties messageProperties = message.getMessageProperties();
|
||||
final Map<String, Object> headers = messageProperties.getHeaders();
|
||||
return (String) headers.get(MessageHeaderKey.TENANT);
|
||||
}
|
||||
|
||||
protected void handleCancelDownloadAction(final Message message, final String thingId) {
|
||||
final String tenant = getTenant(message);
|
||||
final Long actionId = extractActionIdFrom(message);
|
||||
|
||||
processCancelDownloadAction(thingId, tenant, actionId);
|
||||
}
|
||||
|
||||
private void processCancelDownloadAction(final String thingId, final String tenant, final Long actionId) {
|
||||
finishUpdateProcess(thingId, actionId, Collections.singletonList("Simulation canceled"));
|
||||
openActions.remove(actionId);
|
||||
}
|
||||
|
||||
protected void handleUpdateProcess(final Message message, final String controllerId, final EventTopic actionType) {
|
||||
final String tenant = getTenant(message);
|
||||
final DmfDownloadAndUpdateRequest downloadAndUpdateRequest = convertMessage(message,
|
||||
DmfDownloadAndUpdateRequest.class);
|
||||
dmfTenants.get(tenant).getController(controllerId).ifPresent(controller -> controller.setCurrentActionId(downloadAndUpdateRequest.getActionId()));
|
||||
processUpdate(tenant, controllerId, actionType, downloadAndUpdateRequest);
|
||||
}
|
||||
|
||||
private void processUpdate(final String tenantId, final String controllerId, final EventTopic actionType, final DmfDownloadAndUpdateRequest updateRequest) {
|
||||
private void processUpdate(final String tenantId, final String controllerId, final EventTopic actionType,
|
||||
final DmfDownloadAndUpdateRequest updateRequest) {
|
||||
Optional.ofNullable(dmfTenants.get(tenantId))
|
||||
.flatMap(dmfTenant -> dmfTenant.getController(controllerId))
|
||||
.ifPresent(controller -> controller.processUpdate(actionType, updateRequest));
|
||||
@@ -257,26 +278,4 @@ public class VHost extends DmfSender implements MessageListener {
|
||||
clazz.getTypeName());
|
||||
return (T) rabbitTemplate.getMessageConverter().fromMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to validate if content type is set in the message properties.
|
||||
*
|
||||
* @param message
|
||||
* the message to get validated
|
||||
*/
|
||||
private static void checkContentTypeJson(final Message message) {
|
||||
if (message.getBody().length == 0) {
|
||||
return;
|
||||
}
|
||||
final MessageProperties messageProperties = message.getMessageProperties();
|
||||
final String headerContentType = (String) messageProperties.getHeaders().get("content-type");
|
||||
if (null != headerContentType) {
|
||||
messageProperties.setContentType(headerContentType);
|
||||
}
|
||||
final String contentType = messageProperties.getContentType();
|
||||
if (contentType != null && contentType.contains("json")) {
|
||||
return;
|
||||
}
|
||||
throw new AmqpRejectAndDontRequeueException("Content-Type is not JSON compatible");
|
||||
}
|
||||
}
|
||||
@@ -9,18 +9,18 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.sdk.dmf.health;
|
||||
|
||||
import org.eclipse.hawkbit.sdk.dmf.DmfTenant;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.eclipse.hawkbit.sdk.dmf.DmfTenant;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
/**
|
||||
* Handle all incoming Messages from hawkBit update server.
|
||||
*/
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
|
||||
-->
|
||||
<project
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
|
||||
@@ -9,6 +9,12 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.sdk.mgmt;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
|
||||
import feign.FeignException;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -22,13 +28,6 @@ import org.eclipse.hawkbit.sdk.HawkbitClient;
|
||||
import org.eclipse.hawkbit.sdk.Tenant;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Management Api Interface
|
||||
*/
|
||||
@@ -39,13 +38,17 @@ public class MgmtApi {
|
||||
private static final String AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY = "authentication.gatewaytoken.key";
|
||||
private static final String AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_ENABLED = "authentication.gatewaytoken.enabled";
|
||||
private static final String AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED = "authentication.targettoken.enabled";
|
||||
|
||||
|
||||
private static final Random RND = new SecureRandom();
|
||||
@NonNull
|
||||
private final Tenant tenant;
|
||||
@NonNull
|
||||
private final HawkbitClient hawkbitClient;
|
||||
|
||||
public static String randomToken() {
|
||||
final byte[] rnd = new byte[24];
|
||||
RND.nextBytes(rnd);
|
||||
return Base64.getEncoder().encodeToString(rnd);
|
||||
}
|
||||
|
||||
// if gateway toke is configured then the gateway auth is enabled key is set
|
||||
// so all devices use gateway token authentication
|
||||
@@ -73,8 +76,8 @@ public class MgmtApi {
|
||||
}
|
||||
if (!gatewayToken.equals(
|
||||
Objects.requireNonNull(mgmtTenantManagementRestApi
|
||||
.getTenantConfigurationValue(AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY)
|
||||
.getBody()).getValue())) {
|
||||
.getTenantConfigurationValue(AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY)
|
||||
.getBody()).getValue())) {
|
||||
mgmtTenantManagementRestApi.updateTenantConfiguration(
|
||||
Map.of(AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY, gatewayToken)
|
||||
);
|
||||
@@ -121,11 +124,4 @@ public class MgmtApi {
|
||||
public void deleteController(final String controllerId) {
|
||||
hawkbitClient.mgmtService(MgmtTargetRestApi.class, tenant).deleteTarget(controllerId);
|
||||
}
|
||||
|
||||
private static final Random RND = new SecureRandom();
|
||||
public static String randomToken() {
|
||||
final byte[] rnd = new byte[24];
|
||||
RND.nextBytes(rnd);
|
||||
return Base64.getEncoder().encodeToString(rnd);
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,8 @@
|
||||
SPDX-License-Identifier: EPL-2.0
|
||||
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
|
||||
Reference in New Issue
Block a user