Added integration option with device simulator.

Signed-off-by: Kai Zimmermann <kai.zimmermann@bosch-si.com>
This commit is contained in:
Kai Zimmermann
2016-06-13 09:03:01 +02:00
parent d5e591a4a6
commit a9c5dcef69
16 changed files with 459 additions and 76 deletions

View File

@@ -9,8 +9,12 @@
package org.eclipse.hawkbit.mgmt.client;
import org.eclipse.hawkbit.feign.core.client.FeignClientConfiguration;
import org.eclipse.hawkbit.feign.core.client.IgnoreMultipleConsumersProducersSpringMvcContract;
import org.eclipse.hawkbit.mgmt.client.resource.MgmtSoftwareModuleClientResource;
import org.eclipse.hawkbit.mgmt.client.scenarios.ConfigurableScenario;
import org.eclipse.hawkbit.mgmt.client.scenarios.CreateStartedRolloutExample;
import org.eclipse.hawkbit.mgmt.client.scenarios.upload.FeignMultipartEncoder;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
@@ -18,11 +22,19 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.feign.support.ResponseEntityDecoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.hateoas.hal.Jackson2HalModule;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import feign.Feign;
import feign.Logger;
import feign.auth.BasicAuthRequestInterceptor;
import feign.jackson.JacksonDecoder;
@SpringBootApplication
@EnableFeignClients
@@ -72,6 +84,21 @@ public class Application implements CommandLineRunner {
return new CreateStartedRolloutExample();
}
@Bean
public MgmtSoftwareModuleClientResource uploadSoftwareModule() {
final ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerModule(new Jackson2HalModule());
return Feign.builder().contract(new IgnoreMultipleConsumersProducersSpringMvcContract())
.requestInterceptor(
new BasicAuthRequestInterceptor(configuration.getUsername(), configuration.getPassword()))
.logger(new Logger.ErrorLogger()).encoder(new FeignMultipartEncoder())
.decoder(new ResponseEntityDecoder(new JacksonDecoder(mapper)))
.target(MgmtSoftwareModuleClientResource.class,
configuration.getUrl() + MgmtRestConstants.SOFTWAREMODULE_V1_REQUEST_MAPPING);
}
private boolean containsArg(final String containsArg, final String... args) {
for (final String arg : args) {
if (arg.equalsIgnoreCase(containsArg)) {

View File

@@ -40,6 +40,7 @@ public class ClientConfigurationProperties {
private final List<Scenario> scenarios = new ArrayList<>();
public static class Scenario {
private String tenant = "DEFAULT";
private int targets = 100;
private int distributionSets = 10;
private int appModulesPerDistributionSet = 2;
@@ -47,6 +48,46 @@ public class ClientConfigurationProperties {
private String smSwName = "Application";
private String smFwName = "Firmware";
private String targetName = "Device";
private int artifactsPerSM = 1;
private String targetAddress = "amqp:/simulator.replyTo";
/**
* Artifact size. Values can use the suffixed "MB" or "KB" to indicate a
* Megabyte or Kilobyte size.
*/
private String artifactSize = "1MB";
public String getTargetAddress() {
return targetAddress;
}
public void setTargetAddress(final String targetAddress) {
this.targetAddress = targetAddress;
}
public String getTenant() {
return tenant;
}
public void setTenant(final String tenant) {
this.tenant = tenant;
}
public int getArtifactsPerSM() {
return artifactsPerSM;
}
public void setArtifactsPerSM(final int artifactsPerSM) {
this.artifactsPerSM = artifactsPerSM;
}
public String getArtifactSize() {
return artifactSize;
}
public void setArtifactSize(final String artifactSize) {
this.artifactSize = artifactSize;
}
public String getTargetName() {
return targetName;

View File

@@ -9,20 +9,25 @@
package org.eclipse.hawkbit.mgmt.client.scenarios;
import java.util.List;
import java.util.Random;
import org.eclipse.hawkbit.mgmt.client.ClientConfigurationProperties;
import org.eclipse.hawkbit.mgmt.client.ClientConfigurationProperties.Scenario;
import org.eclipse.hawkbit.mgmt.client.resource.MgmtDistributionSetClientResource;
import org.eclipse.hawkbit.mgmt.client.resource.MgmtSoftwareModuleClientResource;
import org.eclipse.hawkbit.mgmt.client.resource.MgmtSystemManagementClientResource;
import org.eclipse.hawkbit.mgmt.client.resource.MgmtTargetClientResource;
import org.eclipse.hawkbit.mgmt.client.resource.builder.DistributionSetBuilder;
import org.eclipse.hawkbit.mgmt.client.resource.builder.SoftwareModuleAssigmentBuilder;
import org.eclipse.hawkbit.mgmt.client.resource.builder.SoftwareModuleBuilder;
import org.eclipse.hawkbit.mgmt.client.resource.builder.TargetBuilder;
import org.eclipse.hawkbit.mgmt.client.scenarios.upload.ArtifactFile;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtDistributionSet;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
/**
*
@@ -37,11 +42,19 @@ public class ConfigurableScenario {
private MgmtDistributionSetClientResource distributionSetResource;
@Autowired
@Qualifier("mgmtSoftwareModuleClientResource")
private MgmtSoftwareModuleClientResource softwareModuleResource;
@Autowired
@Qualifier("uploadSoftwareModule")
private MgmtSoftwareModuleClientResource uploadSoftwareModule;
@Autowired
private MgmtTargetClientResource targetResource;
@Autowired
private MgmtSystemManagementClientResource systemManagementResource;
@Autowired
private ClientConfigurationProperties clientConfigurationProperties;
@@ -56,37 +69,76 @@ public class ConfigurableScenario {
}
private void createScenario(final Scenario scenario) {
systemManagementResource.deleteTenant(scenario.getTenant());
createTargets(scenario);
createDistributionSets(scenario);
}
private void createDistributionSets(final Scenario scenario) {
final byte[] artifact = generateArtifact(scenario);
distributionSetResource
.createDistributionSets(new DistributionSetBuilder().name(scenario.getDsName()).type("os_app")
.version("1.0.").buildAsList(scenario.getDistributionSets()))
.getBody().parallelStream().forEach(dsSet -> {
final List<MgmtSoftwareModule> modules = softwareModuleResource
.createSoftwareModules(new SoftwareModuleBuilder().name(scenario.getSmFwName())
.version(dsSet.getVersion()).type("os").build())
.getBody();
modules.addAll(
softwareModuleResource
.createSoftwareModules(new SoftwareModuleBuilder().name(scenario.getSmSwName())
.version(dsSet.getVersion() + ".").type("application")
.buildAsList(scenario.getAppModulesPerDistributionSet()))
.getBody());
final List<MgmtSoftwareModule> modules = addModules(scenario, dsSet, artifact);
final SoftwareModuleAssigmentBuilder assign = new SoftwareModuleAssigmentBuilder();
modules.forEach(module -> assign.id(module.getModuleId()));
distributionSetResource.assignSoftwareModules(dsSet.getDsId(), assign.build());
});
}
private List<MgmtSoftwareModule> addModules(final Scenario scenario, final MgmtDistributionSet dsSet,
final byte[] artifact) {
final List<MgmtSoftwareModule> modules = softwareModuleResource.createSoftwareModules(
new SoftwareModuleBuilder().name(scenario.getSmFwName()).version(dsSet.getVersion()).type("os").build())
.getBody();
modules.addAll(softwareModuleResource
.createSoftwareModules(
new SoftwareModuleBuilder().name(scenario.getSmSwName()).version(dsSet.getVersion() + ".")
.type("application").buildAsList(scenario.getAppModulesPerDistributionSet()))
.getBody());
for (int x = 0; x < scenario.getArtifactsPerSM(); x++) {
modules.forEach(module -> {
final ArtifactFile file = new ArtifactFile("dummyfile.dummy", null, "multipart/form-data", artifact);
uploadSoftwareModule.uploadArtifact(module.getModuleId(), file, null, null, null);
});
}
return modules;
}
private byte[] generateArtifact(final Scenario scenario) {
// create random object
final Random random = new Random();
// create byte array
final byte[] nbyte = new byte[parseSize(scenario.getArtifactSize())];
// put the next byte in the array
random.nextBytes(nbyte);
return nbyte;
}
private void createTargets(final Scenario scenario) {
for (int i = 0; i < scenario.getTargets() / 100; i++) {
targetResource.createTargets(new TargetBuilder().controllerId(scenario.getTargetName()).buildAsList(i * 100,
(i + 1) * 100 > scenario.getTargets() ? scenario.getTargets() : (i + 1) * 100));
targetResource.createTargets(new TargetBuilder().controllerId(scenario.getTargetName())
.address(scenario.getTargetAddress()).buildAsList(i * 100,
(i + 1) * 100 > scenario.getTargets() ? scenario.getTargets() - i * 100 : 100));
}
}
private int parseSize(final String s) {
final String size = s.toUpperCase();
if (size.endsWith("KB")) {
return Integer.valueOf(size.substring(0, size.length() - 2)) * 1024;
}
if (size.endsWith("MB")) {
return Integer.valueOf(size.substring(0, size.length() - 2)) * 1024 * 1024;
}
return Integer.valueOf(size);
}
}

View File

@@ -28,6 +28,7 @@ import org.eclipse.hawkbit.mgmt.json.model.rollout.MgmtRolloutResponseBody;
import org.eclipse.hawkbit.mgmt.json.model.softwaremodule.MgmtSoftwareModule;
import org.eclipse.hawkbit.mgmt.json.model.softwaremoduletype.MgmtSoftwareModuleType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
/**
* Example for creating and starting a Rollout.
@@ -45,6 +46,7 @@ public class CreateStartedRolloutExample {
private MgmtDistributionSetClientResource distributionSetResource;
@Autowired
@Qualifier("mgmtSoftwareModuleClientResource")
private MgmtSoftwareModuleClientResource softwareModuleResource;
@Autowired

View File

@@ -0,0 +1,103 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.mgmt.client.scenarios.upload;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;
public class ArtifactFile implements MultipartFile {
private final String name;
private final String originalFilename;
private final String contentType;
private final byte[] content;
/**
* Create a new ArtifactFile with the given content.
*
* @param name
* the name of the file
* @param content
* the content of the file
*/
public ArtifactFile(final String name, final byte[] content) {
this(name, "", null, content);
}
/**
* Create a new ArtifactFile with the given content.
*
* @param name
* of the file
* @param originalFilename
* the original filename (as on the client's machine)
* @param contentType
* the content type
* @param content
* of the file
*/
public ArtifactFile(final String name, final String originalFilename, final String contentType,
final byte[] content) {
Assert.hasLength(name, "Name must not be null");
this.name = name;
this.originalFilename = originalFilename != null ? originalFilename : "";
this.contentType = contentType;
this.content = content != null ? content : new byte[0];
}
@Override
public String getName() {
return this.name;
}
@Override
public String getOriginalFilename() {
return this.originalFilename;
}
@Override
public String getContentType() {
return this.contentType;
}
@Override
public boolean isEmpty() {
return this.content.length == 0;
}
@Override
public long getSize() {
return this.content.length;
}
@Override
public byte[] getBytes() throws IOException {
return this.content;
}
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(this.content);
}
@Override
public void transferTo(final File dest) throws IOException {
FileCopyUtils.copy(this.content, dest);
}
}

View File

@@ -0,0 +1,160 @@
/**
* Copyright (c) 2011-2015 Bosch Software Innovations GmbH, Germany. All rights reserved.
*/
package org.eclipse.hawkbit.mgmt.client.scenarios.upload;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map.Entry;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import feign.RequestTemplate;
import feign.codec.EncodeException;
import feign.codec.Encoder;
/**
* A feign encoder implementation which handles {@link MultipartFile} body.
*/
public class FeignMultipartEncoder implements Encoder {
private final List<HttpMessageConverter<?>> converters = new RestTemplate().getMessageConverters();
private final HttpHeaders multipartHeaders = new HttpHeaders();
private final HttpHeaders jsonHeaders = new HttpHeaders();
public static final Charset UTF_8 = Charset.forName("UTF-8");
public FeignMultipartEncoder() {
multipartHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
jsonHeaders.setContentType(MediaType.APPLICATION_JSON);
}
@Override
public void encode(final Object object, final Type bodyType, final RequestTemplate template)
throws EncodeException {
encodeMultipartFormRequest(object, template);
}
private void encodeMultipartFormRequest(final Object value, final RequestTemplate template) {
if (value == null) {
throw new EncodeException("Cannot encode request with null value.");
}
if (!isMultipartFile(value)) {
throw new EncodeException("Only multipart can be handled by this encoder");
}
encodeRequest(encodeMultipartFile((MultipartFile) value), multipartHeaders, template);
}
@SuppressWarnings("unchecked")
private void encodeRequest(final Object value, final HttpHeaders requestHeaders, final RequestTemplate template)
throws EncodeException {
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
final HttpOutputMessage dummyRequest = new HttpOutputMessageImpl(outputStream, requestHeaders);
try {
final Class<?> requestType = value.getClass();
final MediaType requestContentType = requestHeaders.getContentType();
for (final HttpMessageConverter<?> messageConverter : converters) {
if (messageConverter.canWrite(requestType, requestContentType)) {
((HttpMessageConverter<Object>) messageConverter).write(value, requestContentType, dummyRequest);
break;
}
}
} catch (final IOException ex) {
throw new EncodeException("Cannot encode request.", ex);
}
final HttpHeaders headers = dummyRequest.getHeaders();
if (headers != null) {
for (final Entry<String, List<String>> entry : headers.entrySet()) {
template.header(entry.getKey(), entry.getValue());
}
}
/*
* we should use a template output stream... this will cause issues if
* files are too big, since the whole request will be in memory.
*/
template.body(outputStream.toByteArray(), UTF_8);
}
private MultiValueMap<String, Object> encodeMultipartFile(final MultipartFile file) {
try {
final MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
multiValueMap.add("file", new MultipartFileResource(file.getName(), file.getSize(), file.getInputStream()));
return multiValueMap;
} catch (final IOException ex) {
throw new EncodeException("Cannot encode request.", ex);
}
}
private boolean isMultipartFile(final Object object) {
return object instanceof MultipartFile;
}
private class HttpOutputMessageImpl implements HttpOutputMessage {
private final OutputStream body;
private final HttpHeaders headers;
public HttpOutputMessageImpl(final OutputStream body, final HttpHeaders headers) {
this.body = body;
this.headers = headers;
}
@Override
public OutputStream getBody() throws IOException {
return body;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
}
/**
* Dummy resource class. Wraps file content and its original name.
*/
static class MultipartFileResource extends InputStreamResource {
private final String filename;
private final long size;
public MultipartFileResource(final String filename, final long size, final InputStream inputStream) {
super(inputStream);
this.size = size;
this.filename = filename;
}
@Override
public String getFilename() {
return this.filename;
}
@Override
public InputStream getInputStream() throws IOException, IllegalStateException {
return super.getInputStream(); // To change body of generated
// methods, choose Tools | Templates.
}
@Override
public long contentLength() throws IOException {
return size;
}
}
}

View File

@@ -7,7 +7,7 @@
# http://www.eclipse.org/legal/epl-v10.html
#
hawkbit.url=localhost:8080
hawkbit.url=http://localhost:8080
hawkbit.username=admin
hawkbit.password=admin
@@ -20,4 +20,5 @@ spring.main.show-banner=false
#hawkbit.scenarios.[0].sm-sw-name=gettingstarted-example
hawkbit.scenarios.[0].targets=10000
hawkbit.scenarios.[0].distribution-sets=100
hawkbit.scenarios.[0].distribution-sets=100
hawkbit.scenarios.[0].artifactsPerSM=0